r/w updates

r/w updates

--- grovepi.coffee
+++ grovepi.coffee
@@ -37,8 +37,8 @@
 		if args[1]
 			data = args[0]
 			callback = args[1]
-			writeArgs.push port
-			writeArgs.push length
+			writeArgs.push cmd
+			writeArgs.push data
 			writeCmd = 'writeBytes'
 		else
 			callback = args[0]


Update wunderground.coffee

Update wunderground.coffee

removed heroku specific comment

--- src/scripts/wunderground.coffee
+++ src/scripts/wunderground.coffee
@@ -6,7 +6,7 @@
 #
 # Configuration:
 #   HUBOT_WUNDERGROUND_API_KEY Sign up at http://www.wunderground.com/weather/api/.
-#   HUBOT_WUNDERGROUND_USE_METRIC (heroku config:unset HUBOT_WUNDERGROUND_USE_METRIC to disable)
+#   HUBOT_WUNDERGROUND_USE_METRIC Set to arbitrary value to use forecasts with metric system units
 #
 # Commands:
 #   hubot weather me <location> - short-term forecast
@@ -124,4 +124,4 @@

 alternative_place = (item) ->
   return '' if item.country != 'US' || item.state == "" || item.city == ""
-  return "#{item.state}/#{item.city.replace(/\s/g, '_')}"+  return "#{item.state}/#{item.city.replace(/\s/g, '_')}"


Fix missing import in `os configure`

Fix missing import in `os configure`

Fixes #1028

Change-type: patch

--- lib/actions/os.coffee
+++ lib/actions/os.coffee
@@ -148,6 +148,7 @@
 		.nodeify(done)

 buildConfig = (image, deviceType, advanced = false) ->
+	Promise = require('bluebird')
 	form = require('resin-cli-form')
 	helpers = require('../utils/helpers')


Removed unnecessary source map generation during `require()` and made stack line number patching on by default.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -14,6 +14,8 @@

 # The current CoffeeScript version number.
 exports.VERSION = '1.6.3'
+
+extensions = ['.coffee', '.litcoffee', '.coffee.md']

 # Expose helpers for testing.
 exports.helpers = helpers
@@ -84,7 +86,7 @@
 # setting `__filename`, `__dirname`, and relative `require()`.
 exports.run = (code, options = {}) ->
   mainModule = require.main
-  options.sourceMap ?= true
+
   # Set the filename.
   mainModule.filename = process.argv[1] =
     if options.filename then fs.realpathSync(options.filename) else '.'
@@ -97,14 +99,10 @@

   # Compile.
   if not helpers.isCoffee(mainModule.filename) or require.extensions
-    answer = compile(code, options)
-    # Attach sourceMap object to sourceMaps[options.filename] so that
-    # it is accessible by Error.prepareStackTrace.
-    do patchStackTrace
-    sourceMaps[mainModule.filename] = answer.sourceMap
-    mainModule._compile answer.js, mainModule.filename
-  else
-    mainModule._compile code, mainModule.filename
+    answer = compile code, options
+    code = answer.js? and answer.js or answer
+
+  mainModule._compile code, mainModule.filename

 # Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
 # The CoffeeScript REPL uses this to run the input.
@@ -142,12 +140,12 @@
   else
     vm.runInContext js, sandbox

-# Load and run a CoffeeScript file for Node, stripping any `BOM`s.
-loadFile = (module, filename) ->
+compileFile = (filename, sourceMap) ->
   raw = fs.readFileSync filename, 'utf8'
   stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw
+
   try
-    answer = compile(stripped, {filename, sourceMap: true, literate: helpers.isLiterate filename})
+    answer = compile(stripped, {filename, sourceMap, literate: helpers.isLiterate filename})
   catch err
     # As the filename and code of a dynamically loaded file will be different
     # from the original file compiled with CoffeeScript.run, add that
@@ -155,13 +153,18 @@
     err.filename = filename
     err.code = stripped
     throw err
-  sourceMaps[filename] = answer.sourceMap
-  module._compile answer.js, filename
+
+  answer
+
+# Load and run a CoffeeScript file for Node, stripping any `BOM`s.
+loadFile = (module, filename) ->
+  answer = compileFile filename, false
+  module._compile answer, filename

 # If the installed version of Node supports `require.extensions`, register
 # CoffeeScript as an extension.
 if require.extensions
-  for ext in ['.coffee', '.litcoffee', '.coffee.md']
+  for ext in extensions
     require.extensions[ext] = loadFile

   # Patch Node's module loader to be able to handle mult-dot extensions.
@@ -219,7 +222,6 @@
     @pos = 0
   upcomingInput: ->
     ""
-
 # Make all the AST nodes visible to the parser.
 parser.yy = require './nodes'

@@ -233,38 +235,6 @@
   # from the lexer.
   helpers.throwSyntaxError message, parser.lexer.yylloc

-# Based on [michaelficarra/CoffeeScriptRedux](http://goo.gl/ZTx1p)
-# NodeJS / V8 have no support for transforming positions in stack traces using
-# sourceMap, so we must monkey-patch Error to display CoffeeScript source
-# positions.
-
-patched = false
-
-# Map of filenames -> sourceMap object.
-sourceMaps = {}
-
-patchStackTrace = ->
-  return if patched
-  patched = true
-
-  # (Assigning to a property of the Module object in the normal module cache is
-  # unsuitable, because node deletes those objects from the cache if an
-  # exception is thrown in the module body.)
-
-  Error.prepareStackTrace = (err, stack) ->
-    sourceFiles = {}
-
-    getSourceMapping = (filename, line, column) ->
-      sourceMap = sourceMaps[filename]
-      answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap
-      if answer then [answer[0] + 1, answer[1] + 1] else null
-
-    frames = for frame in stack
-      break if frame.getFunction() is exports.run
-      "  at #{formatSourcePosition frame, getSourceMapping}"
-
-    "#{err.name}: #{err.message ? ''}\n#{frames.join '\n'}\n"
-
 # Based on http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.js
 # Modified to handle sourceMap
 formatSourcePosition = (frame, getSourceMapping) ->
@@ -293,7 +263,6 @@
       else
         "#{fileName}:#{line}:#{column}"

-
   functionName = frame.getFunctionName()
   isConstructor = frame.isConstructor()
   isMethodCall = not (frame.isToplevel() or isConstructor)
@@ -319,3 +288,29 @@
   else
     fileLocation

+# Map of filenames -> sourceMap object.
+sourceMaps = {}
+
+# Generates the source map for a coffee file and stores it in the local cache variable.
+getSourceMap = (filename) ->
+  return sourceMaps[filename] if sourceMaps[filename]
+  return unless path.extname(filename) in extensions
+  answer = compileFile filename, true
+  sourceMaps[filename] = answer.sourceMap
+
+# Based on [michaelficarra/CoffeeScriptRedux](http://goo.gl/ZTx1p)
+# NodeJS / V8 have no support for transforming positions in stack traces using
+# sourceMap, so we must monkey-patch Error to display CoffeeScript source
+# positions.
+Error.prepareStackTrace = (err, stack) ->
+  getSourceMapping = (filename, line, column) ->
+    sourceMap = getSourceMap filename
+    answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap
+    if answer then [answer[0] + 1, answer[1] + 1] else null
+
+  frames = for frame in stack
+    break if frame.getFunction() is exports.run
+    "  at #{formatSourcePosition frame, getSourceMapping}"
+
+  "#{err.name}: #{err.message ? ''}\n#{frames.join '\n'}\n"
+


fix node v0.10 path

fix node v0.10 path

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -105,7 +105,7 @@
   mainModule.moduleCache and= {}

   # Assign paths for node_modules loading
-  mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename
+  mainModule.paths = require('module')._nodeModulePaths path.dirname mainModule.filename

   # Compile.
   if not helpers.isCoffee(mainModule.filename) or require.extensions
@@ -277,4 +277,4 @@
   else if functionName
     "#{functionName} (#{fileLocation})"
   else
-    fileLocation+    fileLocation


merging @TrevorBurnham's pull request #1314, enhancing `CS.eval`; closes #1314

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -87,7 +87,9 @@
     sandbox.global.global = sandbox.global.root = sandbox.global.GLOBAL = sandbox
   sandbox.__filename = options.filename || 'eval'
   sandbox.__dirname  = path.dirname sandbox.__filename
-  js = compile "_=(#{code.trim()})", options
+  o = {}; o[k] = v for k, v of options
+  o.bare = on # ensure return value
+  js = compile "_=(#{code.trim()})", o
   vm.runInNewContext js, sandbox, sandbox.__filename

 # Instantiate a Lexer for our use here.


Adding the text widget to the toolbar by default

--- coffee/jquery.coffee
+++ coffee/jquery.coffee
@@ -23,7 +23,7 @@
   unless 'toolClasses' of opts
     opts.toolClasses = [
         LC.PencilWidget, LC.EraserWidget, LC.LineWidget, LC.RectangleWidget,
-        LC.PanWidget, LC.EyeDropperWidget
+        LC.PanWidget, LC.EyeDropperWidget, LC.TextWidget
     ]

   $el = $(el)


Add API key for TurApp

Add API key for TurApp

--- coffee/server.coffee
+++ coffee/server.coffee
@@ -48,6 +48,7 @@
 apiKeys =
   dnt: 'DNT'
   nrk: 'NRK'
+  '30ad3a3a1d2c7c63102e09e6fe4bb253': 'TurApp'

 app.get '/', (req, res) ->
   res.json message: 'Here be dragons'


Fixed white space

Fixed white space

--- coffee/shapes.coffee
+++ coffee/shapes.coffee
@@ -18,7 +18,8 @@
     ctx.lineWidth = @strokeWidth
     ctx.strokeStyle = @color
     ctx.strokeRect(@x, @y, @width, @height)
-
+
+
 class LC.LinePathShape extends LC.Shape
   constructor: ->
     @points = []


rm forgotten "return" in the loop that does setAlwaysOnTop

--- init-nw.coffee
+++ init-nw.coffee
@@ -47,8 +47,8 @@
         restoreWindow nww, fwi[urlParams.win]
     else
       D.floatingWindows = []; D.editorsOnTop = 0
-      nww.on 'focus', -> (for x in D.floatingWindows then x.setAlwaysOnTop !!D.editorsOnTop; return); return
-      nww.on 'blur',  -> (for x in D.floatingWindows then x.setAlwaysOnTop false;            return); return
+      nww.on 'focus', -> (for x in D.floatingWindows then x.setAlwaysOnTop !!D.editorsOnTop); return
+      nww.on 'blur',  -> (for x in D.floatingWindows then x.setAlwaysOnTop false           ); return
       if localStorage.winInfo then try restoreWindow nww, JSON.parse localStorage.winInfo
     return
   nww.show(); nww.focus() # focus() is needed for the Mac


fix https://github.com/sagemathinc/smc/issues/1715 -- "sagews: codemirror fails displaying a docstring"

--- src/smc-webapp/misc_page.coffee
+++ src/smc-webapp/misc_page.coffee
@@ -866,6 +866,10 @@
             content   : required
             type      : required   # 'docstring', 'source-code' -- FUTURE:
             target    : required
+        if typeof(opts.content) != 'string'
+            # If for some reason the content isn't a string (e.g., undefined or an object or something else),
+            # convert it a string, which will display fine.
+            opts.content = "#{JSON.stringify(opts.content)}"
         element = templates.find(".salvus-codemirror-introspect")
         element.find(".salvus-codemirror-introspect-title").text(opts.target)
         element.modal()


Adds some predicates for display logic

Adds some predicates for display logic

--- models/customer.coffee
+++ models/customer.coffee
@@ -10,6 +10,13 @@

   url: "#{API_URL}/account/customer"

+  requiresPayment: ->
+    ((not @get('is_premium')) or @get('is_canceled')) and
+      @has('plan_id')
+
+  requiresPaymentMethod: ->
+    @requiresPayment() and @related().sources.length is 0
+
   related: ->
     return @__related__ if @__related__?


Nodejs: metrics: check if method properties exist

--- workers/datadog.coffee
+++ workers/datadog.coffee
@@ -1,6 +1,5 @@
 { argv }        = require 'optimist'
 KONFIG          = require('koding-config-manager').load("main.#{argv.c}")
-os              = require 'os'
 { MetricsBase } = require 'koding-datadog'

 module.exports = class Metrics extends MetricsBase
@@ -18,10 +17,14 @@

   @generateName : (opts) ->

-    { method } = opts
-    { constructorName, method, type } = method
+    notDefined      = 'notDefined'
+    method          = opts.method           ? {}

-    return "#{@prefix}.#{constructorName}.#{method}.#{type}"
+    type            = method.type           ? notDefined
+    methodName      = method.method         ? notDefined
+    constructorName = method.contructorName ? notDefined
+
+    return "#{@prefix}.#{constructorName}.#{methodName}.#{type}"

   @methodMetrics : (opts) ->


Watching also the vendors for changes.

Watching also the vendors for changes.

--- src/toaster/core/builder.coffee
+++ src/toaster/core/builder.coffee
@@ -83,18 +83,19 @@

       # and watch them entirely
       @watchers.push (watcher = fsu.watch dir, /.coffee$/m)
-      watcher.on 'create', (FnUtil.proxy @on_fs_change, dir, 'create')
-      watcher.on 'change', (FnUtil.proxy @on_fs_change, dir, 'change')
-      watcher.on 'delete', (FnUtil.proxy @on_fs_change, dir, 'delete')
+      watcher.on 'create', (FnUtil.proxy @on_fs_change, false, dir, 'create')
+      watcher.on 'change', (FnUtil.proxy @on_fs_change, false, dir, 'change')
+      watcher.on 'delete', (FnUtil.proxy @on_fs_change, false, dir, 'delete')

     # watching vendors for changes
-    # for vendor in @vendors
-    #   temp = fsu.watch vendor
-    #   temp.on 'create', (FnUtil.proxy @on_fs_change, src, 'create')
-    #   temp.on 'change', (FnUtil.proxy @on_fs_change, src, 'change')
-    #   temp.on 'delete', (FnUtil.proxy @on_fs_change, src, 'delete')
-
-  on_fs_change:(dir, ev, f)=>
+    for vname, vpath of @config.vendors
+      @watchers.push (watcher = fsu.watch vpath)
+      dir = path.join (path.dirname vpath), '..'
+      watcher.on 'create', (FnUtil.proxy @on_fs_change, true, dir, 'create')
+      watcher.on 'change', (FnUtil.proxy @on_fs_change, true, dir, 'change')
+      watcher.on 'delete', (FnUtil.proxy @on_fs_change, true, dir, 'delete')
+
+  on_fs_change:(is_vendor, dir, ev, f)=>
     # skip all folder creation
     return if f.type == "dir" and ev == "create"

@@ -153,15 +154,20 @@
         # updates file information
         file = ArrayUtil.find @files, 'filepath': relative_path

-        if file is null
-          warn "CHANGED FILE IS APPARENTLY NULL..."
+        if file is null and is_vendor is false
+          warn "Change file is apparently null, it shouldn't happened.\n"+
+              "Please report this at the repo issues section."
         else
+
           # cli msg
           msg = "• #{type} changed".bold
           log "[#{now}] #{msg} #{relative_path}"

-          file.item.getinfo()
-          file.item.compile_to_disk()
+          if is_vendor
+            @copy_vendors_to_release false, location
+          else
+            file.item.getinfo()
+            file.item.compile_to_disk @config

   clear:->
     # clear release folder


Bail early when there's no block for a given index

--- src/trix/models/document.coffee
+++ src/trix/models/document.coffee
@@ -366,17 +366,15 @@
       callback(block, textRange, locationRange.index)
     else
       locationRange.eachIndex (index) =>
-        block = @getBlockAtIndex(index)
-
-        textRange = switch index
-          when locationRange.start.index
-            [locationRange.start.offset, block.text.getLength()]
-          when locationRange.end.index
-            [0, locationRange.end.offset]
-          else
-            [0, block.text.getLength()]
-
-        callback(block, textRange, index)
+        if block = @getBlockAtIndex(index)
+          textRange = switch index
+            when locationRange.start.index
+              [locationRange.start.offset, block.text.getLength()]
+            when locationRange.end.index
+              [0, locationRange.end.offset]
+            else
+              [0, block.text.getLength()]
+          callback(block, textRange, index)

   getCommonAttributesAtLocationRange: (locationRange) ->
     if locationRange.isCollapsed()


Pass attribute to newer setter method

Pass attribute to newer setter method

--- src/trix/text_controller.coffee
+++ src/trix/text_controller.coffee
@@ -66,18 +66,8 @@
   # Current attributes

   toggleCurrentAttribute: (attributeName) ->
-    if selectedRange = @getSelectedRange()
-      if @currentAttributes[attributeName]
-        @text.removeAttributeAtRange(attributeName, selectedRange)
-      else
-        @text.addAttributeAtRange(attributeName, true, selectedRange)
-    else
-      if @currentAttributes[attributeName]
-        delete @currentAttributes[attributeName]
-      else
-        @currentAttributes[attributeName] = true
-
-    @notifyDelegateOfCurrentAttributesChange()
+    value = not @currentAttributes[attributeName]
+    @updateCurrentAttribute(attributeName, value)

   updateCurrentAttribute: (attributeName, value) ->
     if selectedRange = @getSelectedRange()


new file: fix bug when pressing enter rather than clicking a button

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -1306,12 +1306,13 @@
         click_new_file_button = (evt) =>
             if evt?
                 ext = $(evt.target).closest('a').data('ext')
+            else
+                ext = undefined
             target = @new_file_tab_input.val()
             if target.indexOf("://") != -1 or misc.startswith(target, "git@github.com:")
                 download_button.icon_spin(start:true, delay:500)
                 new_file_from_web target, () =>
                     download_button.icon_spin(false)
-
             else
                 create_file(ext)
             return false


Don't respond when nothing is in redis

Don't respond when nothing is in redis

Log an error when it happens

--- scripts/poo-tracker.coffee
+++ scripts/poo-tracker.coffee
@@ -25,4 +25,6 @@

   robot.respond /poo tracker( me)?/i, (res) ->
     redis_client.get POO_LATEST_KEY -> (err, reply)
+      return console.error("Failed get with key '#{POO_TRACKER_KEY}': #{err}") if err
+      return if reply == null
       return res.send "Latest poo: #{reply}"


removed ajax logging

removed ajax logging

--- static/coffee-script/app.coffee
+++ static/coffee-script/app.coffee
@@ -12,7 +12,6 @@

   refresh: (->
     $.getJSON baseurl+"/groups", (data) =>
-      console.log data
       @set 'options', data
       #if we have only one value, auto-select it
       if data.length is 1
@@ -32,7 +31,6 @@
       @set 'options', []
     else
       $.getJSON baseurl+"/"+group+"/maps", (data) =>
-        console.log data
         @set 'options', data
         #if we have only one value, auto-select it
         if data.length is 1
@@ -54,7 +52,6 @@
       @set 'options', []
     else
       $.getJSON baseurl+"/"+group+"/"+map+"/dates", (data) =>
-        console.log data
         data.sort()
         data.reverse()
         @set 'options', data
@@ -80,7 +77,6 @@
       @set 'options', []
     else
       $.getJSON baseurl+"/"+group+"/"+map+"/"+date+"/times", (data) =>
-        console.log data
         data.sort()
         data.reverse()
         @set 'options', data


:lipstick: Cleanup.

:lipstick: Cleanup.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -191,13 +191,14 @@
             pane = atom.workspace.paneForItem(currentEditor)
             pane.activate()

-            # TODO: switch to declarative function selection...
             isVisualMode = editorView.classList.contains 'visual-mode'
-            if isVisualMode || (currentEditor.getSelections().length == 1 &&
+            isSelected = (currentEditor.getSelections().length == 1 &&
                 currentEditor.getSelectedText() != '')
-                    currentEditor.selectToScreenPosition(location.position)
+            if (isVisualMode || isSelected)
+                currentEditor.selectToScreenPosition location.position
             else
                 currentEditor.setCursorScreenPosition location.position
+
             # useHomingBeacon = atom.config.get 'jumpy.useHomingBeaconEffectOnJumps'
             # if useHomingBeacon
             #     debugger


:memo: Comment

:memo: Comment

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -200,6 +200,7 @@
                 console.timeEnd 'drawLabels'

             [firstVisibleRow, lastVisibleRow] = editorView.getVisibleRowRange()
+            # TODO: Right now there are issues with lastVisbleRow
             for lineNumber in [firstVisibleRow...lastVisibleRow]
                 lineContents = editor.lineTextForScreenRow(lineNumber)
                 if editor.isFoldedAtScreenRow(lineNumber)


Displaying of labels inside overlay with regex.

- Using old regex by word methodology.
- Better in a lot of respects, gets everything.
- Putting labels in Overlay I think is much better / cleaner.
- Much less code, properly leverages pixelPositionForBufferPosition.
- On way to handling various editorViews.
- Working jumps with current editor hard coded.
- Keys continued across views.
- Closes #7.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -10,11 +10,11 @@

 module.exports =
 class JumpyView extends View
+
   @content: ->
     @div ''

   initialize: (serializeState) ->
-    @pixels = @getAllPixelLocations()
     atom.workspaceView.command "jumpy:toggle", => @toggle()
     atom.workspaceView.command "jumpy:clear", => @clear()
     that = this
@@ -39,8 +39,9 @@
   clearJumpMode: ->
       @clearKeys()
       $('#status-bar-jumpy').html("")
-      atom.workspaceView.find('.jumpy').remove()
-      atom.workspaceView.eachEditorView (e) -> e.removeClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
+      atom.workspaceView.eachEditorView (e) ->
+          e.find('.jumpy').remove()
+          e.removeClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
       @detach()

   jump: ->
@@ -53,44 +54,9 @@
       console.log "Jumpy jumped to: #{@firstChar}#{@secondChar} at (#{location})"

   findLocation: ->
-      nearestMultiple = (val, base) ->
-          Math.round(val / base) * base
-
-      labelElement = atom.workspaceView.find(".jumpy.#{@firstChar}#{@secondChar}").get(0)
-      return null unless labelElement
-
-      labelLocation = labelElement.getBoundingClientRect()
-      cursor = $('.editor .scroll-view .overlayer .cursor').get(0)
-      nearestCursor =
-          left: nearestMultiple(labelLocation.left, cursor.clientWidth)
-          top: nearestMultiple(labelLocation.bottom - labelLocation.height, cursor.clientHeight)
-
-      lines = atom.workspaceView.find('.lines')
-      offsetTop = lines.get(0).offsetTop
-      offsetLeft = $('.scroll-view').scrollLeft()
-      scrollViewOffset = $('.editor .scroll-view').offset()
-      for line, lineIndex in @pixels
-          line = _.compact line
-          for char, charIndex in line
-              isAtLeft = (nearestCursor.left ==
-                  nearestMultiple(char.left + scrollViewOffset.left - offsetLeft, cursor.clientWidth))
-              isAtTop = (nearestCursor.top ==
-                  nearestMultiple(char.top + scrollViewOffset.top + offsetTop, cursor.clientHeight))
-
-              if isAtLeft && isAtTop
-                  return [lineIndex, charIndex]
-
-      return null
-
-  getAllPixelLocations: ->
-      pixels = []
-      for line, lineIndex in atom.workspace.getActivePaneItem().buffer.lines
-          pixels.push([])
-          for char, charIndex in line
-              pixelPosition = atom.workspaceView.getActiveView().pixelPositionForBufferPosition([lineIndex, charIndex])
-              pixels[lineIndex][charIndex] = pixelPosition unless pixelPosition.left == 0 && pixelPosition.top == 0
-
-      return pixels
+      bufferPosition = @allPositions[0]["#{@firstChar}#{@secondChar}"]
+      return null unless bufferPosition
+      return bufferPosition

   # Returns an object that can be retrieved when package is activated
   serialize: ->
@@ -103,18 +69,26 @@

   toggle: ->
     $('#status-bar-jumpy').html("Jumpy: Jump Mode!")
-    atom.workspaceView.eachEditorView (e) -> e.addClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
+    @allPositions = {}
+    that = this
+    editorCount = 0
+    nextKeys = _.clone keys
+    atom.workspaceView.eachEditorView (e) ->
+        e.addClass 'jumpy-specificity-1 jumpy-specificity-2 jumpy-jump-mode'
+        e.find('.scroll-view .overlayer').append('

')
+        positions = {}
+        that.allPositions[editorCount++] = positions

-    relevantClasses = ['variable', 'keyword', 'method', 'string.quoted']
-    atom.workspaceView.find((".line .source .#{c}" for c in relevantClasses).join()).prepend('

')
-
-    nextKeys = _.clone keys
-
-    for label in atom.workspaceView.find(".jumpy.label")
-        key = nextKeys.shift()
-        $(label)
-            .html(key)
-            .addClass(key)
+        wordsPattern = /([\w]){2,}/g
+        for line, lineNumber in atom.workspace.getActivePaneItem().buffer.lines
+            if line != ''
+                while ((word = wordsPattern.exec(line)) != null)
+                    keyLabel = nextKeys.shift()
+                    positions[keyLabel] = {row: lineNumber, column: word.index}
+                    pixelPosition = e.pixelPositionForBufferPosition([lineNumber, word.index])
+                    labelElement = $("
#{keyLabel}
")
+                        .css({left: pixelPosition.left, top: pixelPosition.top})
+                    e.find(".jumpy.labels").append(labelElement)

   clear: ->
       @clearJumpMode()


Update doc comment of tagsNotClosedInFragment


Move test of last char so it is independant of its type

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -101,11 +101,10 @@
   #   update the position of our cursor to the new one using a character
   #   @param character [Character] the new character
   updateCursorPosition : (obj) => @locker.try ()=>
-    if typeof obj is "number"
-      if obj == misc.LAST_CHAR
-        @selfCursor = misc.LAST_CHAR
-      else
-        @selfCursor = @_model.getContent("characters").ref(obj)
+    if obj is misc.LAST_CHAR
+      @selfCursor = misc.LAST_CHAR
+    else if typeof obj is "number"
+      @selfCursor = @_model.getContent("characters").ref(obj)
     else
       @selfCursor = obj
     @_model.getContent("cursors").val(@_model.HB.getUserId(), @selfCursor)


ZMQKernel: Add missing @

ZMQKernel: Add missing @

--- lib/zmq-kernel.coffee
+++ lib/zmq-kernel.coffee
@@ -50,7 +50,7 @@
                     atom.notifications.addError @kernelSpec.display_name,
                         detail: data, dismissable: true
         else
-            console.log 'ZMQKernel: connectionFile:', connectionFile
+            console.log 'ZMQKernel: connectionFile:', @connectionFile
             atom.notifications.addInfo 'Using an existing kernel connection'


yeah, son

yeah, son

--- public/app.coffee
+++ public/app.coffee
@@ -50,6 +50,12 @@
   $chatPage.on 'click', (event) ->
     $input.focus()

+  $input.on 'input', ->
+    return unless connected
+    updateTyping()
+
+
+
   addSystemMessage = (message, options) ->
     $messageBodyEl = $('<span class="message-body" />')
       .text(message)
@@ -62,6 +68,8 @@
     if options.prepend then $messages.prepend($el) else $messages.append($el)
     # scroll to the newest message
     $messages[0].scrollTop = $messages[0].scrollHeight
+
+  # socket.io listeners

   socket.on 'used name', ->
     username = null


Fixed: tags are not saved in uppercase anymore. Close #14

--- client/app/utils/TaskUtil.coffee
+++ client/app/utils/TaskUtil.coffee
@@ -20,7 +20,7 @@
 # helper function to extract tag from description
 module.exports.extractTags = (desc) ->
     tags = desc.match regex
-    tags = _.map tags, (tag) -> tag.trim().replace '#', ''
+    tags = _.map tags, (tag) -> tag.trim().replace('#', '').toLowerCase()
     tags = _.uniq tags
     return tags


ide: worspace readme text change

ide: worspace readme text change

--- client/ide/lib/idehelpers.coffee
+++ client/ide/lib/idehelpers.coffee
@@ -5,6 +5,26 @@
 FSHelper = require 'app/util/fs/fshelper'
 FilePermissionsModal = require './views/modals/filepermissionsmodal'

+WORKSPACE_WELCOME_TXT = '''
+  # Awesome, you\'ve just made a new workspace!
+  Workspaces are fantastic for organizing your work. This new workspace, which lives inside the 'Workspaces' folder of your home
+  directory, is a place where you can store all relevant and related files to a particular project. For example, if you
+  are working on multiple projects, its nice to have them separated as follows:
+  -|home dir
+     -|Workspaces
+      -| Project 1
+      -| Project 2
+      -| Project 3
+
+  Workspaces help keep your projects organized. You can create any number of workspaces. There are no limits.
+
+  Note: A workspace folder is just a regular folder so you can create any number of files and folders inside it.
+
+  As you move back and forth between your workspaces, Koding will remember everything about each workspace. This includes things
+  like IDE settings, files open, Terminals open, etc.
+
+  Enjoy and Happy Koding!
+'''

 module.exports = helpers =

@@ -44,7 +64,7 @@
         filePath   = "#{workspace.rootPath}/README.md"
         readMeFile = FSHelper.createFileInstance { path: filePath, machine }

-        readMeFile.save globals.WORKSPACE_WELCOME_TXT, (err) =>
+        readMeFile.save WORKSPACE_WELCOME_TXT, (err) =>
           return helpers.handleWorkspaceCreateError_ eventObj, err  if err

           eventObj.emit 'WorkspaceCreated', workspace


Fixed resetYear

Fixed resetYear

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -276,8 +276,8 @@
             return
         adminUser = await InformaticsUser.findAdmin()

-        runForUser = (userId) ->
-            await groups.moveUserToGroup(adminUser, userId, "unknown")
+        runForUser = (user) ->
+            await groups.moveUserToGroup(adminUser, user._id, "unknown")
             await user.setUserList("unknown")
             logger.info("Moved user #{user._id} to unknown group")

@@ -285,7 +285,7 @@
         for user in users
             if user.userList == "stud" or user.userList == "unknown"
                 continue
-            runForUser(user._id)
+            runForUser(user)
         res.send('OK')

     app.get '/api/updateResults/:user', ensureLoggedIn, wrap (req, res) ->


Allow pass an array of roles to user in Acctouns.createUser

--- server/lib/accounts.coffee
+++ server/lib/accounts.coffee
@@ -69,16 +69,25 @@
 	return user

 # Wrap insertUserDoc to allow executing code after Accounts.insertUserDoc is run
-Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc) ->
-	options = arguments[1]
-	user = arguments[2]
+Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc, options, user) ->
+	roles = []
+	if Match.test(user.globalRoles, [String]) and user.globalRoles.length > 0
+		roles = roles.concat user.globalRoles
+
+	delete user.globalRoles
+
 	_id = insertUserDoc.call(Accounts, options, user)

-	# when inserting first user give them admin privileges otherwise make a regular user
-	firstUser = RocketChat.models.Users.findOne({ _id: { $ne: 'rocket.cat' }}, { sort: { createdAt: 1 }})
-	roleName = if firstUser?._id is _id then 'admin' else 'user'
+	if roles.length is 0
+		# when inserting first user give them admin privileges otherwise make a regular user
+		firstUser = RocketChat.models.Users.findOne({ _id: { $ne: 'rocket.cat' }}, { sort: { createdAt: 1 }})
+		if firstUser?._id is _id
+			roles.push 'admin'
+		else
+			roles.push 'user'

-	RocketChat.authz.addUsersToRoles(_id, roleName)
+	RocketChat.authz.addUsersToRoles(_id, roles)
+
 	RocketChat.callbacks.run 'afterCreateUser', options, user
 	return _id


payment: make sure options is passed again after the first invoke of planSelected

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -60,6 +60,7 @@

   initEvents: ->
+
     @plans.on 'PlanSelected', @bound 'planSelected'

     @on 'IntervalToggleChanged', @bound 'handleToggleChanged'
@@ -131,6 +132,9 @@
     { paymentController } = KD.singletons

     paymentController.subscriptions (err, subscription) =>
+
+      return KD.showError err  if err?
+
       { planTitle } = subscription

       @state.currentPlan = planTitle
@@ -148,7 +152,7 @@
       .router
       .handleRoute '/Login'  unless KD.isLoggedIn()

-    return @loadPlan @bound 'planSelected'  unless @state.currentPlan?
+    return @loadPlan @lazyBound 'planSelected', options  unless @state.currentPlan?

     @setState options


pricing : remove storage

pricing : remove storage

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -102,12 +102,6 @@
         title    : "Go to your environment"
         callback : ->
           KD.singleton("router").handleRoute "/Environments"
-    else
-      storageController = KD.singletons.appStorageController
-      paymentApp = storageController.storage "PaymentDetails", "1.0"
-      token = paymentApp.getValue "UserToken"
-      KD.singletons.appManager.open 'Login', (app) ->
-        app.prepareFinishRegistrationForm token

   showGroupCreated: (group, subscription) ->


update album view to handle clearance

update album view to handle clearance

--- client/app/views/album.coffee
+++ client/app/views/album.coffee
@@ -6,10 +6,16 @@
 {editable} = require 'lib/helpers'

 thProcessor = require 'models/thumbprocessor'
+CozyClearanceModal = require 'cozy-clearance/modal_share_view'
 contactModel = require 'models/contact'

 Contact = new contactModel()
 clipboard = new Clipboard()
+
+class ShareModal extends CozyClearanceModal
+    initialize: ->
+        super
+        @refresh()

 module.exports = class AlbumView extends BaseView
     template: require 'templates/album'
@@ -19,19 +25,12 @@

     events: =>
         'click a.delete': @destroyModel
-        'click a.changeclearance': @changeClearance
-        'click a.addcontact': @addcontact
+        'click a.clearance': @changeClearance
         'click a.sendmail': @sendMail
-        'click a.add': @prepareContact
         'click a#rebuild-th-btn': @rebuildThumbs
-        'keyup #mails': @onKeyUpMails

     getRenderData: ->
-        clearance = @model.get 'clearance'
-        clearance = 'private' unless clearance?
-        clearanceHelpers = @clearanceHelpers clearance
         res = _.extend
-            clearanceHelpers: clearanceHelpers
             photosNumber: @model.photos.length
         , @model.attributes
         res
@@ -42,6 +41,12 @@
             editable: @options.editable
             collection: @model.photos
             beforeUpload: @beforePhotoUpload
+
+        # Do not run afterRender again when model changed.
+        @model.on 'change', =>
+            data = _.extend {}, @options, @getRenderData()
+            @$el.html @template(data)
+            @$el.find("#photos").append @galery.$el

         @galery.album = @model
         @galery.render()
@@ -57,8 +62,6 @@
     # make the divs editable
     makeEditable: =>
         @$el.addClass 'editing'
-
-        @refreshPopOver @model.get 'clearance'

         editable @$('#title'),
             placeholder: t 'Title ...'
@@ -78,75 +81,13 @@
                 app.router.navigate 'albums', true

     # Change sharing state of the album.
-    changeClearance: (event) ->
-        newclearance = event.target.id.replace 'change', ''
-        id = event.target.id
+    changeClearance: (event) =>
+        @model.set 'clearance', [] unless @model.get('clearance')?
+        @model.set 'type', 'album'

-        @$("##{id}").spin 'tiny'
-        @$("##{id}").css 'color', 'transparent'
-        @saveModel(clearance: newclearance).then =>
-            @$("##{id}").spin()
-            @$("##{id}").css 'color', 'white'
-            @refreshPopOver newclearance
+        console.log @model
+        new ShareModal model: @model

-    # Change content of clearance modal accordingly with current album.
-    refreshPopOver: (clearance) ->
-        help =  @clearanceHelpers clearance
-        modal = @$('#clearance-modal')
-
-        @$('.clearance').find('span').text clearance
-        modal.find('h3').text help?.title
-        modal.find('.modal-body').html help?.content
-        modal.find('.changeclearance').removeClass 'active'
-        modal.find('#change' + clearance).addClass 'active'
-        if clearance is "hidden"
-            modal.find('.share').show()
-            clipboard.set @getPublicUrl()
-        else
-            modal.find('.share').hide()
-            clipboard.set ""
-
-    # Add a contact to the list of contacts with who the album is shared.
-    addcontact: () ->
-        # Initialize user's contacts
-        modal = @$('#add-contact-modal')
-        @options.contacts = []
-        Contact.list
-            success: (body) =>
-                for contact in body
-                    for item in contact.datapoints
-                        if item.name is "email"
-                            # Contact.index is used to identify checkbox
-                            contact.index = (contact.fn).split(' ').join('_')
-                            contact.index = (contact.index).split("'").join('_')
-                            contact.index = (contact.index).split("/").join('_')
-                            contact.index = (contact.index).split("*").join('_')
-                            # Sort contacts alphabetically
-                            n = 0
-                            while @options.contacts[n]? and @options.contacts[n].fn < contact.fn
-                                n++
-                            @options.contacts.splice(n, 0, contact)
-                            break
-                if @options.contacts.length is 0
-                    @options.contacts = "No contacts found"
-                @$('#add-contact-modal').modal('hide')
-                @render modal
-                @$('#add-contact-modal').modal('show')
-            error: (err) ->
-                console.log err
-
-    # Prepare contact fields by adding emails that are already there.
-    prepareContact: (event) ->
-        # Recover mails of selected contacts
-        modal = @$('#add-contact-modal')
-        mails = []
-        for contact in @options.contacts
-            if @$("##{contact.index}").is(':checked')
-                for item in contact.datapoints
-                    if item.name is "email"
-                        mails.push ' ' + item.value
-                        break
-        @$('#mails').val(mails)

     # Temporary tool to allow people to rebuild the thumbnails with size
     # set recently. New size is larger, so older thumbs looks blurry.
@@ -164,33 +105,6 @@
                 , 500
         recFunc()

-    # Check if enter key is pressed.
-    onKeyUpMails: (event) ->
-        if event.which is 13 or event.keyCode is 13
-            @sendMail()
-
-    # Send mail via server to contacts selected for the sharing.
-    sendMail: (event) ->
-        mails = @$('#mails').val()
-        if mails.length is 0
-            alert "Please enter an email."
-        else
-            @$("a.sendmail").spin 'tiny'
-            @$("a.sendmail").css 'color', 'transparent'
-            @model.sendMail @getPublicUrl(), mails,
-                success: =>
-                    @$("a.sendmail").spin()
-                    @$("a.sendmail").css 'color', 'white'
-                    # TODO: translation
-                    msg = "Mail was successfully sent to: \n"
-                    mails = mails.split(',')
-                    for mail in mails
-                        msg = msg + "\n" + mail
-                    alert msg
-                error: (err) =>
-                    @$("a.sendmail").spin()
-                    @$("a.sendmail").css 'color', 'white'
-                    alert JSON.parse(err.responseText).error

     saveModel: (hash) ->
         promise = @model.save(hash)
@@ -207,17 +121,3 @@
         path = '/public/' if path is '/'
         hash = window.location.hash.replace '/edit', ''
         return origin + path + hash
-
-    # Give displayed text depending on clearance state.
-    clearanceHelpers: (clearance) ->
-        if clearance is 'public'
-            title: t 'This album is public'
-            content: t 'It will appears on your homepage.'
-        else if clearance is 'hidden'
-            title: t 'This album is hidden'
-            content: t("hidden-description") + " #{@getPublicUrl()}" +
-                        "<p>If you want to copy url in your clipboard: "+
-                        "just press Ctrl+C </p>"
-        else if clearance is 'private'
-            title: t 'This album is private'
-            content: t 'It cannot be accessed from the public side'


Remove extra log

Remove extra log

--- client/codemirror/math.coffee
+++ client/codemirror/math.coffee
@@ -2,7 +2,6 @@

 mathMode =
   token: (stream, state) ->
-    console.log 'hi', stream.peek()
     if stream.eat '\\'
       stream.next()  ## escaped character
       null


- [grunt] added `loadPath` configuration for Sass task

--- tasks/grunt-contrib-sass.coffee
+++ tasks/grunt-contrib-sass.coffee
@@ -8,6 +8,7 @@
     build:
       options:
         style: 'nested'
+        loadPath: '.'
       files: [
         src: '<%= file.source.style %>'
         dest: '<%= file.build.style.compiled %>'


removes trace and download button of archived tracings in explorative tracing view

--- app/assets/javascripts/dashboard/views/explorative_tracing_list_item_view.coffee
+++ app/assets/javascripts/dashboard/views/explorative_tracing_list_item_view.coffee
@@ -39,16 +39,16 @@
     <%= contentType + " - " + typ %>
     <%= created %>

-      <a href="<%= jsRoutes.controllers.AnnotationController.trace(typ, id).url %>">
-
-        trace
-

-      <a href="<%= jsRoutes.controllers.AnnotationController.download(typ, id).url %>">
-
-        download
-

       <% if (typ == "Explorational"){ %>
         <% if (!state.isFinished) {%>
+          <a href="<%= jsRoutes.controllers.AnnotationController.trace(typ, id).url %>">
+
+            trace
+

+          <a href="<%= jsRoutes.controllers.AnnotationController.download(typ, id).url %>">
+
+            download
+

           <a href="<%= jsRoutes.controllers.AnnotationController.finish(typ, id).url %>"
              id="finish-tracing">


refactored middle calculation #347

refactored middle calculation #347

--- app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
+++ app/assets/javascripts/oxalis/view/skeletontracing/abstract_tree_renderer.coffee
@@ -88,7 +88,7 @@

     # get the decision point
     decision = @getNextDecision(tree)
-    middle = @calculateTreeMiddle(decision.node, left, right)
+    middle = @calculateTreeMiddle(decision, left, right)
     rootX = middle # if decisionPoint is leaf, there's not much to do

     # if the decision point has 2 children, draw them and remember their position
@@ -136,20 +136,19 @@

   ###*
    * calculate m (middle that divides the left and right tree, if any)
-   * @param  {Object} decisionPoint  a point which definitely has to be drawn
+   * @param  {Object} decision       a point which definitely has to be drawn
    * @param  {Number} left           left border in pixels
    * @param  {Number} right          right border in pixels
    * @return {Number}                middle in pixels
   ###
-  calculateTreeMiddle : (decisionPoint, left, right) ->
-
-    if decisionPoint.children.length < 2
-      return (left + right) / 2
-    else
-      child1 = decisionPoint.children[0]
-      child2 = decisionPoint.children[1]
-      return (right - left) * child1.width / (child1.width + child2.width) + left
-
+  calculateTreeMiddle : (decision, left, right) ->
+
+    if decision.isBranch
+      leftChild = decision.node.children[0]
+      rightChild = decision.node.children[1]
+      return (right - left) * leftChild.width / (leftChild.width + rightChild.width) + left
+
+    return (left + right) / 2

   # Calculate the top of the children
   calculateChildTreeTop : (mode, chainCount, top) ->


Add several setter shims to ReactEditorView

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -164,6 +164,18 @@
   setWidthInChars: (widthInChars) ->
     @component.getDOMNode().style.width = (@editor.getDefaultCharWidth() * widthInChars) + 'px'

+  setLineHeight: (lineHeight) ->
+    @component.setLineHeight(lineHeight)
+
+  setShowIndentGuide: (showIndentGuide) ->
+    @component.setShowIndentGuide(showIndentGuide)
+
+  setSoftWrap: (softWrap) ->
+    @editor.setSoftWrap(softWrap)
+
+  setShowInvisibles: (showInvisibles) ->
+    @component.setShowInvisibles(showInvisibles)
+
   getText: ->
     @editor.getText()


Backbone is better than Backend

Backbone is better than Backend

--- bokehjs/src/coffee/common/has_properties.coffee
+++ bokehjs/src/coffee/common/has_properties.coffee
@@ -1,6 +1,6 @@
 $ = require "jquery"
 _ = require "underscore"
-Backend = require "backbone"
+Backbone = require "backbone"
 base = require "./base"
 {logger} = require "./logging"


Bans work again

Bans work again

--- packages/shrub-villiany/index.coffee
+++ packages/shrub-villiany/index.coffee
@@ -8,6 +8,8 @@

 orm = null

+{AuthorizationFailure} = require 'shrub-socket/manager'
+
 Fingerprint = require 'fingerprint'
 logger = null
 villianyLimiter = null
@@ -44,9 +46,7 @@
     Fingerprint = require 'fingerprint'

     # Bans.
-    #
-    # ###### TODO: Bans don't actually work at the moment.
-    Ban = attributes: expires: 'date'
+    Ban = attributes: expires: 'dateTime'

     # The structure of a ban is dictated by the fingerprint structure.
     Fingerprint.keys().forEach (key) ->
@@ -132,8 +132,6 @@

   # #### Implements hook `shrubSocketConnectionMiddleware`.
   registrar.registerHook 'shrubSocketConnectionMiddleware', ->
-
-    {AuthorizationFailure} = require 'shrub-socket/manager'

     label: 'Provide villiany management'
     middleware: socketMiddleware()
@@ -225,7 +223,7 @@
     'You are banned.'

   message += " The ban will be lifted #{
-    moment().add('seconds', ttl).fromNow()
+    moment().add(ttl, 'seconds').fromNow()
   }." if ttl?

   message
@@ -245,4 +243,4 @@

   enforcementMiddleware

-]+]


Fix command documentation to be in the proper format.

The current implementation causes the help message to be near unreadable.

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -2,36 +2,35 @@
 #   Interact with PagerDuty services, schedules, and incidents with Hubot.
 #
 # Commands:
-#
-#   hubot pager me as <email>                                                          remember your pager email is <email>
-#   hubot pager forget me                                                              forget your pager email
-#   hubot who's on call                                                                return a list of services and who is on call for them
-#   hubot who's on call for <schedule>                                                 return the username of who's on call for any schedule matching <search>
-#   hubot pager trigger <user> <msg>                                                   create a new incident with <msg> and assign it to <user>
-#   hubot pager trigger <schedule> <msg>                                               create a new incident with <msg> and assign it the user currently on call for <schedule>
-#   hubot pager incidents                                                              return the current incidents
-#   hubot pager sup                                                                    return the current incidents
-#   hubot pager incident <incident>                                                    return the incident NNN
-#   hubot pager note <incident> <content>                                              add note to incident #<incident> with <content>
-#   hubot pager notes <incident>                                                       show notes for incident #<incident>
-#   hubot pager problems                                                               return all open incidents
-#   hubot pager ack <incident>                                                         ack incident #<incident>
-#   hubot pager ack                                                                    ack triggered incidents assigned to you
-#   hubot pager ack!                                                                   ack all triggered incidents, not just yours
-#   hubot pager ack <incident1> <incident2> ... <incidentN>                            ack all specified incidents
-#   hubot pager resolve <incident>                                                     resolve incident #<incident>
-#   hubot pager resolve <incident1> <incident2> ... <incidentN>                        resolve all specified incidents
-#   hubot pager resolve                                                                resolve acknowledged incidents assigned to you
-#   hubot pager resolve!                                                               resolve all acknowledged, not just yours
-#   hubot pager schedules                                                              list schedules
-#   hubot pager schedules <search>                                                     list schedules matching <search>
-#   hubot pager schedule <schedule>                                                    show <schedule>'s shifts for the upcoming month
-#   hubot pager me <schedule> <minutes>                                                take the pager for <minutes> minutes
-#   hubot pager override <schedule> <start> - <end> [username]                         Create an schedule override from <start> until <end>. If [username] is left off, defaults to you. start and end should date-parsable dates, like 2014-06-24T09:06:45-07:00, see http://momentjs.com/docs/#/parsing/string/ for examples.
-#   hubot pager overrides <schedule>                                                   show upcoming overrides for the next month
-#   hubot pager override <schedule> delete <id>                                        delete an override by its ID
-#   hubot pager services                                                               list services
-#   hubot pager maintenance <minutes> <service_id1> <service_id2> ... <service_idN>    schedule a maintenance window for <minutes> for specified services
+#   hubot pager me as <email> - remember your pager email is <email>
+#   hubot pager forget me - forget your pager email
+#   hubot who's on call - return a list of services and who is on call for them
+#   hubot who's on call for <schedule> - return the username of who's on call for any schedule matching <search>
+#   hubot pager trigger <user> <msg> - create a new incident with <msg> and assign it to <user>
+#   hubot pager trigger <schedule> <msg> - create a new incident with <msg> and assign it the user currently on call for <schedule>
+#   hubot pager incidents - return the current incidents
+#   hubot pager sup - return the current incidents
+#   hubot pager incident <incident> - return the incident NNN
+#   hubot pager note <incident> <content> - add note to incident #<incident> with <content>
+#   hubot pager notes <incident> - show notes for incident #<incident>
+#   hubot pager problems - return all open incidents
+#   hubot pager ack <incident> - ack incident #<incident>
+#   hubot pager ack - ack triggered incidents assigned to you
+#   hubot pager ack! - ack all triggered incidents, not just yours
+#   hubot pager ack <incident1> <incident2> ... <incidentN> - ack all specified incidents
+#   hubot pager resolve <incident> - resolve incident #<incident>
+#   hubot pager resolve <incident1> <incident2> ... <incidentN> - resolve all specified incidents
+#   hubot pager resolve - resolve acknowledged incidents assigned to you
+#   hubot pager resolve! - resolve all acknowledged, not just yours
+#   hubot pager schedules - list schedules
+#   hubot pager schedules <search> - list schedules matching <search>
+#   hubot pager schedule <schedule> - show <schedule>'s shifts for the upcoming month
+#   hubot pager me <schedule> <minutes> - take the pager for <minutes> minutes
+#   hubot pager override <schedule> <start> - <end> [username] - Create an schedule override from <start> until <end>. If [username] is left off, defaults to you. start and end should date-parsable dates, like 2014-06-24T09:06:45-07:00, see http://momentjs.com/docs/#/parsing/string/ for examples.
+#   hubot pager overrides <schedule> - show upcoming overrides for the next month
+#   hubot pager override <schedule> delete <id> - delete an override by its ID
+#   hubot pager services - list services
+#   hubot pager maintenance <minutes> <service_id1> <service_id2> ... <service_idN> - schedule a maintenance window for <minutes> for specified services
 #
 # Authors:
 #   Jesse Newland, Josh Nicols, Jacob Bednarz, Chris Lundquist, Chris Streeter, Joseph Pierri, Greg Hoin, Michael Warkentin


better help message

better help message

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -108,6 +108,10 @@
     userOrSchedule = msg.match[3]
     reason         = msg.match[4]
     description    = "#{reason} - @#{fromUserName}"
+
+    unless reason
+      msg.reply "Please include a user or schedule to page, like 'hubot pager ops everything is on fire'."
+      return

     # Figure out who we are
     campfireUserToPagerDutyUser msg, msg.message.user, (triggerdByPagerDutyUser) ->


Add a missing quote so copy/paste works

Add a missing quote so copy/paste works

--- src/scripts/wikipedia.coffee
+++ src/scripts/wikipedia.coffee
@@ -3,7 +3,7 @@
 #
 # Dependencies:
 #   "htmlparser": "1.7.6"
-#   "soupselect: "0.2.0"
+#   "soupselect": "0.2.0"
 #   "underscore": "1.3.3"
 #   "underscore.string": "2.3.0"
 #


reverttt

reverttt

--- coach/src/exercise/index.cjsx
+++ coach/src/exercise/index.cjsx
@@ -45,7 +45,6 @@
     if not sections?.length then return

     section = _.first(sections)
-
     <div key='task-help-links' className='task-help-links'>
       Comes from {@sectionFormat(section.chapter_section)} - {section.title}
     </div>


Fix text of Mist Hunter.

Fix text of Mist Hunter.

fixes #314

--- coffeescripts/cards-en.coffee
+++ coffeescripts/cards-en.coffee
@@ -864,7 +864,7 @@
         "TIE/v1":
             text: """TIE Advanced Prototype only.%LINEBREAK%After you acquire a target lock, you may perform a free evade action."""
         "Mist Hunter":
-            text: """G-1A starfighter only.%LINEBREAK%Your upgrade bar gains the %BARRELROLL% Upgrade icon.%LINEBREAK%You must equip 1 "Tractor Beam" Upgrade card (paying its squad point cost as normal)."""
+            text: """G-1A starfighter only.%LINEBREAK%Your action bar gains the %BARRELROLL% Upgrade icon.%LINEBREAK%You must equip 1 "Tractor Beam" Upgrade card (paying its squad point cost as normal)."""
         "Punishing One":
             text: """JumpMaster 5000 only.%LINEBREAK%Increase your primary weapon value by 1."""
         "Assailer":


exec function can now take success and error callbacks as arguments, for those that dislike jQuery deferred chaining

--- shared/code/custard.coffee
+++ shared/code/custard.coffee
@@ -1,14 +1,19 @@
 boxName = window.location.pathname.split('/')[1]
 baseUrl = window.location.origin

-exec = (cmd) ->
+exec = (cmd, success, error) ->
   settings = readSettings()
-  $.ajax
+  options =
     url: "#{baseUrl}/#{boxName}/exec"
     type: "POST"
     data:
       apikey: settings.source.apikey
       cmd: cmd
+  if success?
+    options.success = success
+  if error?
+    options.error = error
+  $.ajax options

 readSettings = ->
   return null if window.location.hash is ''


Recursively serialize nested models.

Recursively serialize nested models.

--- coffee/chaplin/views/view.coffee
+++ coffee/chaplin/views/view.coffee
@@ -3,8 +3,9 @@
   'underscore',
   'backbone',
   'chaplin/lib/utils',
-  'chaplin/lib/subscriber'
-], ($, _, Backbone, utils, Subscriber) ->
+  'chaplin/lib/subscriber',
+  'chaplin/models/model'
+], ($, _, Backbone, utils, Subscriber, Model) ->
   'use strict'

   class View extends Backbone.View
@@ -256,13 +257,21 @@

     # Get attributes from model or collection
     getTemplateData: ->
+      serialize = (object) ->
+        result = {}
+        for key, value of object
+          result[key] = if value instanceof Model
+            serialize value.getAttributes()
+          else
+            value
+        result

       modelAttributes = @model and @model.getAttributes()
       templateData = if modelAttributes
         # Return an object which delegates to the returned attributes
         # object so a custom getTemplateData might safely add and alter
         # properties (at least primitive values).
-        utils.beget modelAttributes
+        utils.beget serialize modelAttributes
       else
         {}


commenting out faketimes

commenting out faketimes

--- test/models/partner_show.coffee
+++ test/models/partner_show.coffee
@@ -156,7 +156,7 @@
       @partnerShow.posterImageUrl()
       Backbone.sync.args[0][2].success []

-  describe '#isEndingSoon', ->
+  xdescribe '#isEndingSoon', ->
     beforeEach ->
       @ending = '2013-08-23T04:00:00+00:00'
       @partnerShow.set 'end_at', @ending
@@ -181,7 +181,7 @@
       @partnerShow.isEndingSoon(3).should.be.true
       @clock.restore()

-  describe '#endingIn', ->
+  xdescribe '#endingIn', ->
     beforeEach ->
       @ending = '2013-08-23T04:00:00+00:00'
       @partnerShow.set 'end_at', @ending
@@ -197,7 +197,7 @@
       @partnerShow.endingIn().should.equal 'today'
       @clock.restore()

-  describe '#isOpeningToday', ->
+  xdescribe '#isOpeningToday', ->
     beforeEach ->
       @starting = '2013-07-12T04:00:00+00:00'
       @partnerShow.set 'start_at', @starting


Add: unit test for product type update.

Add: unit test for product type update.

--- test/product-import.spec.coffee
+++ test/product-import.spec.coffee
@@ -4,6 +4,8 @@
 ClientConfig = require '../config'
 Promise = require 'bluebird'
 path = require 'path'
+{ExtendedLogger} = require 'sphere-node-utils'
+package_json = require '../package.json'

 frozenTimeStamp = new Date().getTime()

@@ -157,15 +159,25 @@

   beforeEach ->

+    @logger = new ExtendedLogger
+      additionalFields:
+        project_key: 'productImporterTests'
+      logConfig:
+        name: "#{package_json.name}-#{package_json.version}"
+        streams: [
+          { level: 'info', stream: process.stdout }
+        ]
+
     errorDir = path.join(__dirname, '../errors')

     Config =
       clientConfig: ClientConfig
       errorDir: errorDir
       errorLimit: 0
+      ensureEnums: true
       blackList: ['prices']

-    @import = new ProductImport null, Config
+    @import = new ProductImport @logger, Config

   it 'should initialize', ->
     expect(@import).toBeDefined()
@@ -296,7 +308,7 @@
         done()
       .catch done

-  describe '::_generateSlug', ->
+  xdescribe '::_generateSlug', ->

     it 'should generate valid slug', ->
       sampleName =
@@ -428,6 +440,8 @@
           prices: []
         ],
         version: 1
+
+      @import._cache.productType[sampleProductTypeResponse.body.results[0].id] = sampleProductTypeResponse.body.results[0]

       spyOn(@import, "_prepareNewProduct").andCallFake (prepareProduct) -> Promise.resolve(prepareProduct)
       spyOn(@import, "_prepareUpdateProduct").andCallFake (prepareProduct) -> Promise.resolve(prepareProduct)
@@ -463,6 +477,7 @@
           created: 1
           updated: 1
           failed: 0
+          productTypeUpdated: 0
           errorDir: path.join(__dirname,'../errors')
         done()
       .catch done
@@ -652,7 +667,72 @@
       .catch (err) ->
         done(err)

-
-
-
-
+  describe 'update product type', ->
+
+    it ' :: should do nothing for empty update actions', (done) ->
+      @import._updateProductType([])
+      .then =>
+        expect(@import._summary.productTypeUpdated).toBe 0
+        done()
+      .catch (err) ->
+        done(err)
+
+    it ' :: should update product type correctly', (done) ->
+      sampleProductType = _.deepClone sampleProductTypeResponse.body.results[0]
+      sampleEnumAttribute =
+        name: 'sample-enum-attribute'
+        label:
+          en: 'Sample Enum Attribute'
+        type:
+          name: 'enum'
+          values: [
+            key: 'enum-1-key'
+            label: 'enum-1-label'
+          ,
+            key: 'enum-2-key'
+            label: 'enum-2-label'
+          ]
+
+      sampleProductType.attributes.push sampleEnumAttribute
+
+      sampleUpdateAction =
+        action: 'addPlainEnumValue'
+        attributeName: 'sample-enum-attribute'
+        value:
+          key: 'enum-3-key'
+          label: 'enum-3-key'
+
+      sampleInput =
+        product_type_internal_id: [
+          sampleUpdateAction
+        ]
+
+      expectedPayload =
+        version: sampleProductType.version
+        actions: [
+          sampleUpdateAction
+        ]
+
+      sampleUpdatedProductType = _.deepClone sampleProductType
+
+      sampleUpdatedProductType.version++
+      sampleUpdatedProductType.attributes[0].type.values.push sampleUpdateAction.value
+
+      @import._cache.productType[sampleProductType.id] = sampleProductType
+      spyOn(@import.client._rest, 'POST').andCallFake (endpoint, payload, callback) ->
+        callback(null, {statusCode: 200}, sampleUpdatedProductType)
+      @import._updateProductType(sampleInput)
+      .then =>
+        updatedProductType = @import._cache.productType[sampleProductType.id]
+        expect(updatedProductType.version).toBe 2
+        expect(updatedProductType.attributes[0].type.values[2]).toEqual(sampleUpdateAction.value)
+        expect(@import._summary.productTypeUpdated).toBe 1
+        expect(@import.client._rest.POST.calls[0].args[1]).toEqual expectedPayload
+        done()
+      .catch (err) ->
+        done(err)
+
+
+
+
+


CLI/uninstall making it follow the new install command sematics

--- cli/commands/uninstall.coffee
+++ cli/commands/uninstall.coffee
@@ -3,20 +3,31 @@
 #
 # Copyright (C) 2011-2012 Nikolay Nemshilov
 #
+lovelyrc = require('../lovelyrc')

-exports.init = (args) ->
-  lovelyrc = require('../lovelyrc')
+uninstall = (names)->
+  if args = names[0].match(/^(.+)\-(\d+\.\d+\.\d+)$/)
+    args  = [args[1], args[2]]
+  else
+    args  = [names[0], undefined]

-  if args.length == 0
-    print_error "You should specify the package name"
+  names.shift()

-  location = lovelyrc.base
-  location[location.length - 1] == '/' || (location += '/')
-  location += "packages/#{args[0]}/#{args[1] || ''}"
+  location = lovelyrc.base + "packages/#{args[0]}/#{args[1] || ''}"

   sout "» Uninstalling ~/.lovely/packages/#{args[0]}/#{args[1] || ''}".ljust(61)
   system "rm -rf #{location}", ->
     sout "Done\n".green
+    uninstall names if names.length > 0
+
+
+
+
+exports.init = (args) ->
+  if args.length == 0
+    print_error "You should specify the package name"
+  else
+    uninstall args

 exports.help = (args) ->
@@ -24,6 +35,6 @@
   Uninstalls a package

   Usage:
-      lovely uninstall <package-name>[ <version>]
+      lovely uninstall <package-name>[-<version>] ...

   """


pricing: provide a way to start workflow programmatically from a certain state

--- client/Pricing/AppView.coffee
+++ client/Pricing/AppView.coffee
@@ -114,6 +114,7 @@

   selectIntervalToggle: (planInterval) ->
+
     button = @intervalToggle.buttons[planInterval]
     @intervalToggle.buttonReceivedClick button

@@ -172,6 +173,14 @@
         .handleRoute '/'

+  continueFrom: (planTitle, planInterval) ->
+
+    @emit 'IntervalToggleChanged', {planInterval}
+
+    plan = @plans.planViews[planTitle]
+    plan.select()
+
+
   setState: (obj) -> @state = KD.utils.extend @state, obj


Adjust layout direction based on user's language

This fixes the issue https://github.com/RocketChat/Rocket.Chat/issues/1623

So basically if a user login from a new machine and the language loaded from their profile is different from the local one then set the correct language and if it is an RTL language set the RTL class immediately.

--- client/startup/startup.coffee
+++ client/startup/startup.coffee
@@ -39,8 +39,11 @@
 		if Meteor.user()?.language?
 			c.stop()

-			localStorage.setItem("userLanguage", Meteor.user().language)
-			setLanguage Meteor.user().language
+			if localStorage.getItem('userLanguage') isnt Meteor.user().language
+				localStorage.setItem("userLanguage", Meteor.user().language)
+				setLanguage Meteor.user().language
+				if isRtl localStorage.getItem "userLanguage"
+					$('html').addClass "rtl"

 	userLanguage = localStorage.getItem("userLanguage")
 	userLanguage ?= defaultUserLanguage()


Don't send socket to client

Don't send socket to client

--- src/RemoteSubGraph.coffee
+++ src/RemoteSubGraph.coffee
@@ -115,6 +115,7 @@
     name = definition.id
     # Send data across to remote graph
     @inPorts.add name, @normalizePort(definition), (event, packet) =>
+      packet = null if event in ['connect', 'disconnect']
       @runtime.sendRuntime 'packet',
         port: name
         event: event


Use timeout logic with iframe runtimes

Use timeout logic with iframe runtimes

--- components/ConnectRuntime.coffee
+++ components/ConnectRuntime.coffee
@@ -97,20 +97,25 @@
     rt.setParentElement @element if @element
     rt.once 'capabilities', onCapabilities
     rt.once 'error', onError
-    timeout = setTimeout onTimeout, @timeout unless definition.protocol is 'iframe'
+    timeout = setTimeout onTimeout, @timeout
     @outPorts.runtime.beginGroup definition.id
     @outPorts.runtime.send rt
     @outPorts.runtime.endGroup()
     @outPorts.runtime.disconnect()
-    rt.on 'connected', ->
+    rt.on 'connected', =>
       unless definition.protocol is 'iframe'
         # We can assume regular runtimes to be ready immediately
         rt.sendRuntime 'getruntime', {}
         return
       # Iframe runtimes sometimes take a while to self-initialize
+      clearTimeout timeout if timeout
+      timeout = setTimeout onTimeout, @timeout
       setTimeout ->
         rt.sendRuntime 'getruntime', {}
       , 1000
+      setTimeout ->
+        rt.sendRuntime 'getruntime', {}
+      , 2000
     rt.connect()

 exports.getComponent = -> new ConnectRuntime


Simplify by porting to Process API

Simplify by porting to Process API

--- components/DispatchAction.coffee
+++ components/DispatchAction.coffee
@@ -28,32 +28,21 @@
     addressable: true
   c.outPorts.add 'handling',
     datatype: 'integer'
-
-  routes = ''
-  c.inPorts.routes.on 'data', (data) ->
-    routes = data
-
-  sentTo = null
-  sentToIdx = null
-  c.inPorts.in.on 'data', (data) ->
+  c.process (input, output) ->
+    return unless input.hasData 'routes', 'in'
+    [routes, data] = input.getData 'routes', 'in'
+    unless data?.action
+      output.done new Error 'No action provided in payload'
+      return
     handled = routes.split ','
     handler = findHandler data.action.split(':'), handled
     if handler is -1
-      sentTo = c.outPorts.pass
-    else
-      c.outPorts.handling.send handler
-      sentTo = c.outPorts.handle
-      sentToIdx = handler
-    sentTo.send data, sentToIdx
-  c.inPorts.in.on 'disconnect', ->
-    return unless sentTo
-    sentTo.disconnect sentToIdx
-    sentTo = null
-    sentToIdx = null
-
-  c.shutdown = ->
-    sentTo = null
-    sentToIdx = null
-    routes = ''
-
-  return c
+      output.sendDone
+        pass: data
+      return
+    output.send
+      handling: handler
+    output.send
+      handle: new noflo.IP 'data', data,
+        index: handler
+    output.done()


whitespace tweaks

whitespace tweaks

--- lib/GOM.coffee
+++ lib/GOM.coffee
@@ -23,9 +23,9 @@
   $.notAttr = ['children','data']
   $.emptyTags = ['br','hr','meta','link','base','img','embed','param','area','col','input']

-  $ = require('./mixins/helpers')($)
-  $ = require('./mixins/render')($)
-  $ = require('./mixins/transform')($)
+  require('./mixins/helpers')($)
+  require('./mixins/render')($)
+  require('./mixins/transform')($)

   class Node

@@ -33,26 +33,20 @@
       tag or tag='div'
       #attributes or attributes={}
       #children or children=[]
-
       @tag = tag

       if attributes
-
         @attributes = attributes
         #@attributes.class or @attributes.class = []

       if attributes?.children
         children = attributes.children
         delete attributes.children
-
       else if children? and !(children instanceof Array)
         children = [children]

       @children = children if children

       @
-
-
-  # ---------------------------

   return $


Fixed test (broken when updated added to backend)

sry @tenedor!

--- coffee/test/model.spec.coffee
+++ coffee/test/model.spec.coffee
@@ -192,15 +192,15 @@
       m1 = new Model acornid: acornid
       m2 = new Model acornid: acornid
       spy = new EventSpy m1, 'sync'
-      date = new Date().toString()
+      testval = 'test'

       runs -> m1.fetch()

       waitsFor (-> spy.triggered), 'fetch should complete',
         acorn.config.test.timeout
       runs ->
-        expect(m1.get 'updated').not.toEqual date
-        m1.set({updated: date})
+        expect(m1.get 'test').not.toEqual testval
+        m1.set({test: testval})
         m1.synced = false
         m1.save {},
           success: => m1.synced = true
@@ -211,9 +211,9 @@
       waitsFor (=> m1.synced), 'sync should complete',
         acorn.config.test.timeout
       runs ->
-        expect(m1.get 'updated').toEqual date
-        expect(m2.get 'updated').not.toEqual date
-        expect(m2.get 'updated').not.toEqual m1.get 'updated'
+        expect(m1.get 'test').toEqual testval
+        expect(m2.get 'test').not.toEqual testval
+        expect(m2.get 'test').not.toEqual m1.get 'test'

         spy = new EventSpy m2, 'change'
         m2.fetch()
@@ -222,9 +222,9 @@
         acorn.config.test.timeout

       runs ->
-        expect(m1.get 'updated').toEqual date
-        expect(m2.get 'updated').toEqual date
-        expect(m2.get 'updated').toEqual m1.get 'updated'
+        expect(m1.get 'test').toEqual testval
+        expect(m2.get 'test').toEqual testval
+        expect(m2.get 'test').toEqual m1.get 'test'

     it 'should be able to delete', ->


isBeforeで同日別時間をfalseにする方法を追加

isBeforeで同日別時間をfalseにする方法を追加


Add missing ship tags to titles.

Add missing ship tags to titles.

fixes #516

--- coffeescripts/cards-de.coffee
+++ coffeescripts/cards-de.coffee
@@ -2077,8 +2077,10 @@
         'Havoc':
             text: '''Nur für Scurrg H-6 Bomber.%LINEBREAK%Füge deiner Aufwertungsleiste die %SYSTEM%- und %SALVAGEDASTROMECH%-Symbole hinzu und entferne das %CREW%-Symbol.%LINEBREAK%Du kannst keine nicht-einzigartigen %SALVAGEDASTROMECH%-Aufwertungen ausrüsten.'''
         'Vaksai':
+            ship: "Kihraxz-Jäger"
             text: '''Kihraxz Fighter only.%LINEBREAK%The squad point cost of each of your equipped upgrades is reduced by 1 (to a minimum of 0).%LINEBREAK%You may equip up to 3 different Modification upgrades.'''
         'StarViper Mk. II':
+            ship: "SternenViper"
             text: '''StarViper only.%LINEBREAK%You may equip up to 2 different title Upgrades.%LINEBREAK%When performing a barrel roll action, you must use the (%BANKLEFT% 1) or (%BANKRIGHT% 1) template instead of the (%STRAIGHT% 1) template.'''
         'XG-1 Assault Configuration':
             text: '''Alpha-class Star Wing only.%LINEBREAK%Your upgrade bar gains 2 %CANNON% icons.%LINEBREAK%You may perform attacks with %CANNON% secondary weapons that cost 2 or fewer points even while you have a weapons disabled token.'''


add click action for close button

add click action for close button

--- lib/app-list.coffee
+++ lib/app-list.coffee
@@ -94,6 +94,8 @@
     @endButton = $ '<button />', {} =
       text: 'Beenden'
       id: 'end'
+    @endButton.click ->
+      window.close() #TODO: Nur die Seite ist geschlossen?
     @dom.append @endButton

   add: (url) ->


Switch around watch and watchFile

Switch around watch and watchFile

--- lib/beantest.coffee
+++ lib/beantest.coffee
@@ -43,9 +43,9 @@
     for fileName in fileNames
       if fileName not of seen
         seen[fileName] = true
-        if fs.watchFile?
-          fs.watchFile fileName, { interval: REFRESH_RATE }, runTest
-        else fs.watch fileName, { interval: REFRESH_RATE }, runTest
+        if "watch" in fs
+          fs.watch fileName, { interval: REFRESH_RATE }, runTest
+        else fs.watchFile fileName, { interval: REFRESH_RATE }, runTest

     setTimeout beantest, REFRESH_RATE if fileNames.length > 0


split, vsplit

split, vsplit

--- lib/commands.coffee
+++ lib/commands.coffee
@@ -11,7 +11,14 @@
 wq = ({editor, editorElement}) ->
   editor.save()
   dispatch(editorElement, 'core:close') # FIXME
-  # atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow()
+
+split = ({editor, editorElement}) ->
+  editor.save()
+  dispatch(editorElement, 'pane:split-down')
+
+vsplit = ({editor, editorElement}) ->
+  editor.save()
+  dispatch(editorElement, 'pane:split-right')

 # Configuration switch
 # -------------------------
@@ -45,7 +52,7 @@

 module.exports = {
   normalCommands: {
-    w, wq,
+    w, wq, split, vsplit
   }
   toggleCommands: {
     showInvisible


Provide filename for any failed graph parsing

--- components/BlobToEntry.coffee
+++ components/BlobToEntry.coffee
@@ -9,7 +9,9 @@
   method = 'loadFBP' if entry.remote.language is 'fbp'
   content = JSON.parse content if entry.remote.language is 'json'
   noflo.graph[method] content, (err, graph) ->
-    return callback err if err
+    if err
+      callback new Error "Failed to load #{entry.remote.name}: #{err.message}"
+      return
     # Properties that need to be changed for both cases
     graph.properties = {} unless graph.properties
     graph.properties.sha = sha


Hotfix #3

Hotfix #3

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -139,23 +139,24 @@
                                 process.exit 1
                             else
                                 duplicateEntryIds = _.union results
-                                toRemoveCount = duplicateEntryIds.length
+                                logger.info duplicateEntryIds
+                                # toRemoveCount = duplicateEntryIds.length

-                                removeDuplicateEntry = (entryId, next) ->
-                                    TeamTaskProgress.remove _id: entryId, (err) ->
-                                        if err?
-                                            logger.error err
-                                            next err, null
-                                        else
-                                            next null, null
+                                # removeDuplicateEntry = (entryId, next) ->
+                                #     TeamTaskProgress.remove _id: entryId, (err) ->
+                                #         if err?
+                                #             logger.error err
+                                #             next err, null
+                                #         else
+                                #             next null, null

-                                async.mapLimit toRemoveCount, 5, removeDuplicateEntry, (err, results) ->
-                                    if err?
-                                        logger.error err
-                                        process.exit 1
-                                    else
-                                        logger.info "Removed #{toRemoveCount} entries"
-                                        process.exit 0
+                                # async.mapLimit duplicateEntryIds, 5, removeDuplicateEntry, (err, results) ->
+                                #     if err?
+                                #         logger.error err
+                                #         process.exit 1
+                                #     else
+                                #         logger.info "Removed #{toRemoveCount} entries"
+                                #         process.exit 0

 module.exports.run = ->


major rewrite

major rewrite

especially we now provide extended events by default (esl_event is gone)
res.command() is synchronous while res.execute() is asynchronous.

--- lib/esl.coffee
+++ lib/esl.coffee
@@ -75,35 +75,27 @@
 #
 # If you'd like to get realtime channel variables and synchronous commands, do
 #
-#     server = esl.createCallServer true
+#     server = esl.createCallServer()
 #
 #     server.on 'CONNECT', (req,res) ->
-#       res.execute 'verbose_events', null, (req,res) ->
-#         # You may now access realtime variables as
+#       # "verbose_events" will send us channel data after each "command".
+#       res.command 'verbose_events', null, (req,res) ->
+#         # You may now access realtime variables from req.body
 #         foo = req.body.variable_foo
-#         res.execute 'play-file', 'voicemail/vm-hello', (req,res) ->
-#           # This call with wait for the command to finish
-#           # Your application continues here
+#         res.command 'play-file', 'voicemail/vm-hello', (req,res) ->
+#           # Wait for the command to finish.
+#           # The application continues here
 #
 #     server.listen 7000
 #
-# Note that although the socket command was called (in the dialplan above) with
-# the "async" parameter, createCallServer is processing the commands synchronously.
-# Of course you can work synchronously without using verbose_events; you may still
-# obtain realtime values by using the API (lookup the "eval" command).
-# However you cannot use verbose_events asynchronously, unless you capture the
-# CHANNEL_EXECUTE_COMPLETE event.
+# The asynchronous version of "command" is "execute".
 #
 # An asynchronous server will look this way:
 #
 #     server = esl.createCallServer()
 #
 #     server.on 'CONNECT', (req,res) ->
-#       # Start processing the call
-#       # Channel data is available as req.channel_data
-#       # Channel UUID is available as req.unique_id
-#       # For example:
-#       uri = req.channel_data.variable_sip_req_uri
+#       uri = req.body.variable_sip_req_uri
 #
 #     # Other FreeSwitch channel events are available as well:
 #     server.on 'CHANNEL_ANSWER', (req,res) ->
@@ -233,41 +225,38 @@
 class eslResponse
   constructor: (@socket) ->

-  # If a callback is provided we attempt to trigger it
-  # when the operation completes.
-  # By default, the trigger is done as soon as the ESL command
-  # finishes.
-  intercept_response: (command,args,cb) ->
-    # Make sure we are the only one receiving command replies
-    @socket.removeAllListeners('esl_command_reply')
-    @socket.removeAllListeners('esl_api_response')
-    # Register the callback for the proper event types.
-    @socket.on 'esl_command_reply', cb
-    @socket.on 'esl_api_response', cb
-
-  # A generic way of sending commands back to FreeSwitch.
+  register_callback: (event,cb) ->
+    @socket.on event, (req,res) =>
+      @socket.removeAllListeners event
+      cb req,res
+
+  # A generic way of sending commands to FreeSwitch.
   #
-  #      send (string,hash,function(req,res))
+  #      send (string,array,function(req,res){})
   #
-  # is normally not used directly.
+  # This is normally not used directly.
+  # The array and callback are both optional.

   send: (command,args,cb) ->
-      if exports.debug
-        util.log util.inspect command: command, args: args
-
-      if cb?
-        @intercept_response command, args, cb
-
-      # Send the command out.
-      try
-        # write will throw if the socket is closed
-        @socket.write "#{command}\n"
-        if args?
-          for key, value of args
-            @socket.write "#{key}: #{value}\n"
-        @socket.write "\n"
-      catch e
-        cb e
+
+    # The arguments parameter is optional.
+    if typeof args is 'function' and not cb?
+      [cb,args] = [args,null]
+
+    if exports.debug
+      util.log util.inspect command: command, args: args
+
+    if cb? then @register_callback 'esl_command_reply', cb
+
+    # Send the command out.
+    try
+      @socket.write "#{command}\n"
+      if args?
+        for key, value of args
+          @socket.write "#{key}: #{value}\n"
+      @socket.write "\n"
+    catch e
+      @socket.emit 'esl_error', error:e

   on: (event,listener) -> @socket.on(event,listener)

@@ -277,15 +266,16 @@

   # Send an API command, see [Mod commands](http://wiki.freeswitch.org/wiki/Mod_commands)
   api: (command,cb) ->
-    @send "api #{command}", null, cb
+    @register_callback 'esl_api_response', cb
+    @send "api #{command}"

   # Send an API command in the background.
   # The callback will receive the Job UUID (instead of the usual request/response pair).
   bgapi: (command,cb) ->
-    @send "bgapi #{command}", null, (req,res) ->
-      if cb?
-        r = res.header['Reply-Text']?.match /\+OK Job-UUID: (.+)$/
-        cb r[1]
+    @register_callback 'esl_command_reply', (req,res) ->
+      r = res.header['Reply-Text']?.match /\+OK Job-UUID: (.+)$/
+      cb? r[1]
+    @send "bgapi #{command}"

   #### Event reception and filtering

@@ -297,25 +287,25 @@
   #     res.event_json 'HEARTBEAT'
   #
   event_json: (events...,cb) ->
-    @send "event json #{events.join(' ')}", null, cb
+    @send "event json #{events.join(' ')}", cb

   # Remove the given event types from the events ACL.
   nixevent: (events...,cb) ->
-    @send "nixevent #{events.join(' ')}", null, cb
+    @send "nixevent #{events.join(' ')}", cb

   # Remove all events types.
   noevents: (cb) ->
-    @send "noevents", null, cb
+    @send "noevents", cb

   # Generic event filtering
   filter: (header,value,cb) ->
-    @send "filter #{header} #{value}", null, cb
+    @send "filter #{header} #{value}", cb

   filter_delete: (header,value,cb) ->
     if value?
-      @send "filter #{header} #{value}", null, cb
+      @send "filter #{header} #{value}", cb
     else
-      @send "filter #{header}", null, cb
+      @send "filter #{header}", cb

   # Send an event into the FreeSwitch event queue.
   sendevent: (event_name,args,cb) ->
@@ -329,25 +319,25 @@
   #         # Start sending other commands here.
   #     client.connect ...
   #
-  auth: (password,cb)       -> @send "auth #{password}", null, cb
+  auth: (password,cb)       -> @send "auth #{password}", cb

   # connect() and linger() are used in server mode.
-  connect: (cb)             -> @send "connect", null, cb    # Outbound mode
-
-  linger: (cb)              -> @send "linger", null, cb     # Outbound mode
+  connect: (cb)             -> @send "connect", cb    # Outbound mode
+
+  linger: (cb)              -> @send "linger", cb     # Outbound mode

   # Send the exit command to the FreeSwitch socket.
-  exit: (cb)                -> @send "exit", null, cb
+  exit: (cb)                -> @send "exit", cb

   #### Event logging commands
   log: (level,cb) ->
     [level,cb] = [null,level] if typeof(level) is 'function'
     if level?
-      @send "log #{level}", null, cb
+      @send "log #{level}", cb
     else
-      @send "log", null, cb
-
-  nolog: (cb)                 -> @send "nolog", null, cb
+      @send "log", cb
+
+  nolog: (cb)                 -> @send "nolog", cb

   #### Message sending
   # Send Message (to a UUID)
@@ -373,6 +363,12 @@
       'execute-app-arg':  app_arg
     @sendmsg_uuid uuid, 'execute', options, cb

+  # Execute an application synchronously.
+  # The callback is only called when the command has completed.
+  command_uuid: (uuid,app_name,app_arg,cb) ->
+    @socket.on "CHANNEL_EXECUTE_COMPLETE #{app_name} #{app_arg}", cb
+    @execute_uuid uuid,app_name,app_arg
+
   # Hangup a call

   hangup_uuid: (uuid,hangup_cause,cb) ->
@@ -392,24 +388,49 @@
   # Execute an application for the current UUID (in server/outbound mode)

   execute: (app_name,app_arg,cb)  -> @execute_uuid null, app_name, app_arg, cb
+  command: (app_name,app_arg,cb)  -> @command_uuid null, app_name, app_arg, cb

   hangup: (hangup_cause,cb)       -> @hangup_uuid  null, hangup_cause, cb

   unicast: (args,cb)              -> @unicast_uuid null, args, cb

   # nomedia: TODO
+
+  # Clean-up at the end of the connection.
+  auto_cleanup: ->
+    @on 'esl_disconnect_notice', (req,res) ->
+      if exports.debug
+        util.log "Received ESL disconnection notice"
+      switch req.headers['Content-Disposition']
+        when 'linger'      then @exit()
+        when 'disconnect'  then @end()
+
+  start_call_server: ->
+    @connect (req,res) ->
+      unique_id = req.body['Unique-ID']
+      @auto_cleanup
+      # Handle the incoming connection
+      @linger (req,res) ->
+        @filter Unique_ID, unique_id, (req,res) ->
+          @event_json 'ALL', (req,res) ->
+            @emit 'CONNECT', req, res

 #### Connection Listener (socket events handler)
 # This is modelled after Node.js' http.js

-connectionListener= (socket,response) ->
-
-  response ?= eslResponse
+connectionListener= (socket) ->

   socket.setEncoding('ascii')
   parser = new eslParser socket
   socket.on 'data', (data) ->  parser.on_data(data)
   socket.on 'end',  ()     ->  parser.on_end()
+
+  # Make the command responses somewhat unique.
+  socket.on 'CHANNEL_EXECUTE_COMPLETE', ->
+    application = req.body['Application']
+    application_data = req.body['Application-Data']
+    socket.emit "#{event_name} #{application} #{application_data}", req, res
+
   parser.process = (headers,body) ->
     if exports.debug
       util.log util.inspect headers: headers, body: body
@@ -419,8 +440,10 @@
     # and assign unified event IDs to the ESL Content-Types.

     switch headers['Content-Type']
+
       when 'auth/request'
         event = 'esl_auth_request'
+
       when 'command/reply'
         event = 'esl_command_reply'
         # Apparently a bug in the response to "connect"
@@ -430,124 +453,64 @@
           for n in ['Content-Type','Reply-Text','Socket-Mode','Control']
             headers[n] = body[n]
             delete body[n]
+
       when 'text/event-json'
         try
           body = JSON.parse(body)
         catch error
           util.log "JSON #{error} in #{body}"
           return
-        event = 'esl_event'
+        event = req.body['Event-Name']
+
       when 'text/event-plain'
         body = parse_header_text(body)
-        event = 'esl_event'
+        event = req.body['Event-Name']
+
       when 'log/data'
         event = 'esl_log_data'
+
       when 'text/disconnect-notice'
         event = 'esl_disconnect_notice'
+
       when 'api/response'
         event = 'esl_api_response'
+
       else
         event = headers['Content-Type']
+
     # Build request and response and send them out.
     req = new eslRequest headers,body
-    res = new response socket
+    res = new eslResponse socket
     if exports.debug
       util.log util.inspect event:event, req:req, res:res
     socket.emit event, req, res
+
   # Get things started
-  socket.emit 'esl_connect', new response socket
+  socket.emit 'esl_connect', new eslResponse socket

 #### ESL Server

+
 class eslServer extends net.Server
-  constructor: (requestListener,response) ->
+  constructor: (requestListener) ->
     @on 'connection', (socket) ->
       socket.on 'esl_connect', requestListener
-      connectionListener socket, response
+      connectionListener socket
+
     super()

 # You can use createServer(callback) from your code.
+# The callback will receive an eslResponse object.
 exports.createServer = (requestListener) -> return new eslServer(requestListener)
+
+exports.createCallServer = -> return new eslServer (res) -> res.start_call_server()

 #### ESL client
 class eslClient extends net.Socket
-  constructor: (response) ->
+  constructor: () ->
     @on 'connect', ->
-      connectionListener @, response
+      connectionListener @
+
     super()

 exports.createClient = -> return new eslClient()
-
-#### CallServer: a higher-level interface
-#
-# This interface is based on my
-# [prepaid code](http://stephane.shimaore.net/git/?p=ccnq3.git;a=blob;f=applications/prepaid/)
-# and handles the nitty-gritty of setting up the server properly.
-
-exports.createCallServer = (synchronous) ->
-
-    Unique_ID = 'Unique-ID'
-
-    listener = (res) ->
-      if exports.debug
-        util.log "Incoming connection"
-        util.log util.inspect res
-
-      res.connect (req,res) =>
-
-        # Channel data
-        channel_data = req.body
-        # UUID
-        unique_id = channel_data[Unique_ID]
-
-        if exports.debug
-          util.log "Incoming call UUID = #{unique_id}"
-
-        # Clean-up at the end of the connection.
-        res.on 'esl_disconnect_notice', (req,res) ->
-          if exports.debug
-            util.log "Received ESL disconnection notice"
-          switch req.headers['Content-Disposition']
-            when 'linger'      then res.exit()
-            when 'disconnect'  then res.end()
-
-        # Use this from your code to force a disconnection.
-        #
-        #     res.emit 'force_disconnect'
-        #
-        res.on 'force_disconnect', ->
-          util.log 'Hangup call'
-          @bgapi "uuid_kill #{unique_id}"
-
-        # Translate channel events into server events.
-        res.on 'esl_event', (req,res) ->
-          req.channel_data = channel_data
-          req.unique_id = unique_id
-          server.emit req.body['Event-Name'], req, res
-
-        # Handle the incoming connection
-        res.linger (req,res) ->
-          res.filter Unique_ID, unique_id, (req,res) ->
-            res.event_json 'ALL', (req,res) ->
-              req.channel_data = channel_data
-              req.unique_id = unique_id
-              server.emit 'CONNECT', req, res
-
-    class synchronousResponse extends eslResponse
-      intercept_response: (command,args,cb) ->
-        @socket.removeAllListeners('esl_command_reply')
-        @socket.removeAllListeners('esl_api_response')
-        server.removeAllListeners('CHANNEL_EXECUTE_COMPLETE')
-        if command is 'sendmsg' and args['call-command'] is 'execute'
-          server.on 'CHANNEL_EXECUTE_COMPLETE', cb
-        else
-          # Make sure we are the only one receiving command replies
-          # Register the callback for the proper event types.
-          @socket.on 'esl_command_reply', cb
-          @socket.on 'esl_api_response', cb
-
-    response = null
-    if synchronous
-      response = synchronousResponse
-
-    server = new eslServer listener, response


make the "file doesn't save" error a little better

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1526,7 +1526,7 @@
             # WARNING: As far as I can tell, this doesn't call FileEditor.save
             if err
                 if redux.getProjectStore(@project_id).is_file_open(@filename)  # only show error if file actually opened
-                    alert_message(type:"error", message:"Error saving #{@filename} -- #{err}; please try later")
+                    alert_message(type:"error", message:"Error saving #{@filename} -- #{err}; please try again or restart your project.")
             else
                 @emit('saved')
             @save_button.icon_spin(false)


polish PR https://github.com/sagemathinc/smc/pull/797

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -1265,7 +1265,7 @@
         whose title, description or users contain {query}
         <Space/><Space/>
         <Button onClick={@clear_and_focus_input}>
-            CLEAR SEARCH
+            Cancel
         </Button>

     render_alert_message : ->
@@ -1275,7 +1275,7 @@
         query += hashtags_string

         if query isnt '' or @props.deleted or @props.hidden
-            <Alert bsStyle='warning' style={'fontSize':'1.2em'}>
+            <Alert bsStyle='warning' style={'fontSize':'1.3em'}>
                 Only showing<Space/>
                 {"#{if @props.deleted then 'deleted ' else ''}#{if @props.hidden then 'hidden ' else ''}"}
                 projects<Space/>


Dia: Clear highlights when clicked to blank area

--- client/Framework/components/dia/kddiascene.coffee
+++ client/Framework/components/dia/kddiascene.coffee
@@ -51,6 +51,10 @@
     @fakeContext.strokeStyle = @getOption "lineColorHelper"

     @fakeContext.stroke()
+
+  click:(e)->
+    return if e.target isnt e.currentTarget
+    @setActiveDia()

   mouseMove:(e)->
     return  unless @_trackJoint


sidebar: set end of search result list

sidebar: set end of search result list

--- client/Social/Activity/sidebar/searchmodal.coffee
+++ client/Social/Activity/sidebar/searchmodal.coffee
@@ -125,6 +125,8 @@

       return  if err

+      @reachedEndOfTheList = yes  unless items.length
+
       callback items


OutputParser: ignore if there is no content or content is empty string

--- client/admin/lib/views/stacks/outputparser.coffee
+++ client/admin/lib/views/stacks/outputparser.coffee
@@ -13,6 +13,7 @@

   showUserFriendlyError: (content) ->

+    return  if not content or content.trim?() is ''
     return  if content.indexOf('error') is -1

     for errorString, niceMessage of HANDLED_ERRORS


Update campfire to work with the new request method

--- plugins/campfire/index.coffee
+++ plugins/campfire/index.coffee
@@ -13,8 +13,13 @@
       message:
         body: message
         type: "TextMessage"
+
+    url = "https://#{config.authToken}:X@#{config.domain}.campfirenow.com/room/#{config.roomId}/speak.xml"

     # Send the request to campfire
-    @httpPostJson "https://#{config.authToken}:X@#{config.domain}.campfirenow.com/room/#{config.roomId}/speak.xml", payload
+    @request
+      .post(url)
+      .send(payload)
+      .end();

 module.exports = Campfire


Fix test about color selector

Fix test about color selector

--- app/modules/components/color-selector/color-selector.controller.spec.coffee
+++ app/modules/components/color-selector/color-selector.controller.spec.coffee
@@ -36,19 +36,14 @@
         inject ($controller) ->
             controller = $controller

-        colorSelectorCtrl = controller "ColorSelectorCtrl"
-        colorSelectorCtrl.colorList = [
-            '#fce94f',
-            '#edd400',
-            '#c4a000',
-        ]
-        colorSelectorCtrl.displaycolorList = false

     it "display Color Selector", () ->
+        colorSelectorCtrl = controller "ColorSelectorCtrl"
         colorSelectorCtrl.toggleColorList()
-        expect(colorSelectorCtrl.displaycolorList).to.be.true
+        expect(colorSelectorCtrl.displayColorList).to.be.true

     it "on select Color", () ->
+        colorSelectorCtrl = controller "ColorSelectorCtrl"
         colorSelectorCtrl.toggleColorList = sinon.stub()

         color = '#FFFFFF'
@@ -58,3 +53,17 @@
         colorSelectorCtrl.onSelectDropdownColor(color)
         expect(colorSelectorCtrl.toggleColorList).have.been.called
         expect(colorSelectorCtrl.onSelectColor).to.have.been.calledWith({color: color})
+
+    it "save on keydown Enter", () ->
+        colorSelectorCtrl = controller "ColorSelectorCtrl"
+        colorSelectorCtrl.onSelectDropdownColor = sinon.stub()
+
+        event = {which: 13, stopPropagation: sinon.stub()}
+        color = "#fabada"
+
+        colorSelectorCtrl.color = color
+
+        colorSelectorCtrl.onKeyDown(event)
+        expect(event.stopPropagation).have.been.called
+        expect(colorSelectorCtrl.onSelectDropdownColor).have.been.called
+        expect(colorSelectorCtrl.onSelectDropdownColor).have.been.calledWith(color)


Support Stata

Support Stata

Signed-off-by: Nikita Gryzlov <8aec7c938bcc12a94c1b8a9a7df4f3785bc450dd@gmail.com>

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -591,6 +591,14 @@
       command: "sml"
       args: (context) -> [context.filepath]

+  Stata:
+    "Selection Based":
+      command: "xstata-se"
+      args: (context)  -> ['do', context.getCode()]
+    "File Based":
+      command: "xstata-se"
+      args: (context) -> ['do', context.filepath]
+
   Swift:
     "File Based":
       command: "swift"


Updated for the new Swift compiler.

Updated for the new Swift compiler.

'swift' command is available on both OS X and Linux platforms.

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -489,8 +489,8 @@

   Swift:
     "File Based":
-      command: "xcrun"
-      args: (context) -> ['swift', context.filepath]
+      command: "swift"
+      args: (context) -> [context.filepath]

   TypeScript:
     "Selection Based":


Whoops. Need that quote

Whoops. Need that quote

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -69,7 +69,7 @@
         command: "bash"
         args: (context) -> ["-c", "g++ -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]
     else if GrammarUtils.OperatingSystem.isWindows() and GrammarUtils.OperatingSystem.release().indexOf("14399") >= 0
-      "File Based:
+      "File Based":
         command: "bash"
         args: (context) -> ["-c", "g++ -Wall -include stdio.h -include iostream '" + context.filepath + "' -o /tmp/cpp.out && /tmp/cpp.out"]


browserify rebuild should wait for build to finish before outputting done msg

--- gulp/utils/build-browserify-js.coffee
+++ gulp/utils/build-browserify-js.coffee
@@ -39,8 +39,8 @@
       .on 'update', ->
         gutil.log 'Rerunning browserify...'
         updateStart = Date.now()
-        _process(b)
-        gutil.log '...Done (' + (Date.now() - updateStart) + 'ms)'
+        _process(b).on 'end', ->
+          gutil.log '...Done (' + (Date.now() - updateStart) + 'ms)'

   # kick-off
   _process(b)


:art: refactoring

:art: refactoring

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -698,13 +698,16 @@
   for change in changes when change.newRange?
     {oldRange, oldText, newRange, newText} = change
     if range?
+      # shrink range
       if oldText.length and range.containsRange(oldRange)
         extent = oldRange.getExtent()
-        extent.column = 0 if (oldRange.end.row isnt range.end.row)
+        extent.column = 0 unless (range.end.row is oldRange.end.row)
         range.end = range.end.translate(extent.negate())
+
+      # expand range
       if newText.length and range.containsPoint(newRange.start)
         extent = newRange.getExtent()
-        extent.column = 0 if (newRange.start.row isnt range.end.row)
+        extent.column = 0 unless (range.end.row is newRange.start.row)
         range.end = range.end.translate(extent)
     else
       # Since newRange is freezed, we need to copy() to un-freeze range.


:memo:

:memo:

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -637,12 +637,8 @@

     @activate('normal')

-# Input
+# Insert entering operation
 # -------------------------
-# The operation for text entered in insert-mode. Broadly speaking, input
-# operators manage an undo transaction and set a @typingCompleted variable when
-# it's done. When the input operation is completed, the typingCompleted variable
-# tells the operation to repeat itself instead of enter insert mode.
 class ActivateInsertMode extends Operator
   @extend()
   complete: true


Make pigments:reload also reloading paths

--- lib/pigments.coffee
+++ lib/pigments.coffee
@@ -176,4 +176,4 @@

   reloadProjectVariables: ->
     @project.initialize().then =>
-      @project.reloadVariablesForPaths(@project.getPaths())
+      @project.loadPathsAndVariables()