make just one Refresh button, remove unnecessary code, and create beginnings of a drop-down for switching branches

--- src/smc-webapp/editor_git.cjsx
+++ src/smc-webapp/editor_git.cjsx
@@ -395,10 +395,6 @@

                     Select the changed tracked files to commit
-                    <Space/> <Space/>
-                    <Button onClick={=>@props.actions.get_changed_tracked_files()}>
-                        Refresh
-                    </Button>

                 {<FileListing
@@ -429,51 +425,16 @@
         head =

                 git log
-                <Space/> <Space/>
-                <Button onClick={=>@props.actions.update_log()}>
-                    Refresh
-                </Button>
-                <Button onClick={=>@props.actions.setState(git_log:'')}>
-                    Clear
-                </Button>

         <Panel header={head}>
             {
{@props.git_log}
 if @props.git_log}
         </Panel>

-    render_changed_tracked_files : ->
-        head =
-
-                Changed tracked files
-                <Space/> <Space/>
-                <Button onClick={=>@props.actions.get_changed_tracked_files()}>
-                    Refresh
-                </Button>
-
-        <Panel header={head}>
-
-                Changed tracked files
-                <Space/> <Space/>
-                <Button onClick={=>@props.actions.get_changed_tracked_files()}>
-                    Refresh
-                </Button>
-
-            {<FileListing
-                listing             = {@props.git_changed_tracked_files}
-                listing_type        = 'tracked'
-                checked_files       = {@props.checked_files}
-                actions             = {@props.actions} /> if @props.git_changed_tracked_files}
-        </Panel>
-
     render_changed_untracked_files : ->
         head =

                 Changed untracked files not covered by .gitignore
-                <Space/> <Space/>
-                <Button onClick={=>@props.actions.get_changed_untracked_files()}>
-                    Refresh
-                </Button>
                 <Button onClick={=>@props.actions.git_add_selected()}>
                     Add selected
                 </Button>
@@ -500,12 +461,7 @@
                     {@render_diff_files()}
                 </DropdownButton>
                 <Space/> <Space/>
-                <Button onClick={=>@props.actions.update_diff()}>
-                    Refresh
-                </Button>
-                <Button onClick={=>@props.actions.setState(git_diff:'')}>
-                    Clear
-                </Button>
+

         <Panel header={head}>
             {
{@props.git_diff}
 if @props.git_diff}
@@ -576,7 +532,17 @@

     render : ->

-
Git Repository at {@props.git_repo_root}

+

+
Git Repository at {@props.git_repo_root}

+                <Space/> <Space/>
+                <Button onClick={=>@props.actions.set_tab(@props.tab)}>
+                    Refresh
+                </Button>
+                <Space/> <Space/>
+                <DropdownButton title={'Switch branch from '+@props.current_branch} id='switch_branches'>
+                    {@render_diff_files()}
+                </DropdownButton>
+

             <Tabs animation={false} activeKey={@props.tab} onSelect={(key)=>@props.actions.set_tab(key)}>
                 {@render_tabs()}
             </Tabs>


Add goal script

Add goal script

--- scripts/goal.coffee
+++ scripts/goal.coffee
@@ -5,9 +5,8 @@
 #   hubot 今日の目標は〜 - 今日の目標を設定します。期限は同日午前3時です。
 #   hubot 今日の目標は？ - 今日の目標を表示します。
 #   hubot 目標達成 - 目標を達成したことを hubot に知らせます。
+#   hubot 達成度 - 目標の達成度を hubot に聞きます。

-
-Util = require "util"
 moment = require "moment"
 _ = require "underscore"

@@ -31,6 +30,15 @@
     "あ、あなたが神か。。 :innocent:" #=100
   ]

+  colors = [
+    "1abc9c"
+    "2ecc71"
+    "3498db"
+    "9b59b6"
+    "e67e22"
+    "e74c3c"
+  ]
+
   robot.respond /今日の目標は(?!？)(.+)/i, (msg) ->
     goal = msg.match[1]
     user = msg.envelope.user.name
@@ -51,7 +59,7 @@

     msg.send "OK。#{user}さんの今日の目標は「#{goal}」ですね。"

-  robot.respond /今日の目標は？/i, (msg) ->
+  robot.respond /今日の目標は(？)?$/i, (msg) ->
     user = msg.envelope.user.name
     data = robot.brain.data.goals[user] or null

@@ -75,17 +83,70 @@
   robot.respond /.*達成度.*/i, (msg) ->
     user = msg.envelope.user.name
     data = robot.brain.data.goals[user] or null
+    embed_user = user

     unless data and data.attempted
-      msg.send "#{user}さんのデータはありません。"
-      return
+      embed_user = null
+      msg.send "#{user}さんのデータはありませんが。。"

-    attempted = data.attempted
-    achieved = data.achieved
-    rate = Math.round achieved / attempted * 100
+    user_goals_data = robot.brain.data.goals

-    review = reviews[Math.floor rate / 20]
+    user_rates = {}
+    for _user, _data of user_goals_data
+      attempted = _data.attempted
+      achieved = _data.achieved
+      rate = getRate achieved, attempted
+      user_rates[_user] = rate

-    msg.send "#{user}さんの目標達成度は、" +
-      "#{rate}%（#{achieved} / #{attempted}）です！\n" +
-      review
+    user_rates["test"] = 87
+    user_rates["test2"] = 33
+
+    chart_url = getBarCharUrl user_rates, embed_user
+
+    unless data and data.attempted
+      msg.send "皆さんの目標達成度のチャートです。\n" +
+        chart_url
+    else
+      rate = getRate data.achieved, data.attempted
+      review = reviews[Math.floor rate / 20]
+      msg.send "#{user}さんの目標達成度は、" +
+        "#{rate}%" +
+        "（#{data.achieved} / #{data.attempted}）です！\n" +
+        review + "\n" +
+        chart_url
+
+  getRate = (achieved, attempted) ->
+    Math.round achieved / attempted * 100
+
+  getBarCharUrl = (user_rates, embed_user) ->
+    Quiche = require "quiche"
+    bar = new Quiche "bar"
+    bar.setWidth 400
+    bar.setHeight 50 + 20 * _.keys(user_rates).length
+    bar.setTitle "目標達成度"
+    bar.setBarHorizontal()
+    bar.setBarWidth 10
+    bar.setBarSpacing 5
+    bar.setLegendLeft()
+    bar.setTransparentBackground()
+
+    if embed_user?
+      index = parseInt Math.random() * colors.length, 10
+      embed_color = colors[index]
+
+    for user, rate of user_rates
+      if embed_user?
+        if user is embed_user
+          color = embed_color
+        else
+          color = "95a5a6"
+      else
+        index = parseInt Math.random() * colors.length, 10
+        color = colors[index]
+      bar.addData [rate], user, color
+
+    bar.addData [100], "神", "95a5a6"
+
+    bar.addAxisLabels('x', [0, 25, 50, 75, 100])
+
+    bar.getUrl true


So I hope indention puts it in scope of res.

<_<

--- scripts/loud.coffee
+++ scripts/loud.coffee
@@ -72,5 +72,5 @@
     if not robot.brain.get('louds')?
       robot.brain.set('louds', [])

-getRandomLoud = ->
-  res.random(robot.brain.get('louds'))
+  getRandomLoud = ->
+    res.random(robot.brain.get('louds'))


fix cursor size calculation bug

fix cursor size calculation bug

--- src/modules/cursor.coffee
+++ src/modules/cursor.coffee
@@ -48,9 +48,9 @@
       [leftText, rightText, didSplit] = Scribe.DOM.splitNode(position.leafNode.firstChild, position.offset)
       if rightText?
         span = @container.ownerDocument.createElement('span')
-        Scribe.DOM.wrap(span, rightText)
+        rightText.parentNode.insertBefore(span, rightText)
         _moveCursor.call(this, cursor, span)
-        Scribe.DOM.unwrap(span)
+        span.parentNode.removeChild(span)
       else if leftText?
         span = @container.ownerDocument.createElement('span')
         leftText.parentNode.parentNode.appendChild(span)


Removed now-unused '_matches' method

Removed now-unused '_matches' method

--- src/tb/lib/ti/traversal.coffee
+++ src/tb/lib/ti/traversal.coffee
@@ -6,17 +6,6 @@
 slice = [].slice

 module.exports = ($) ->
-
-  _matches = (element, selector) ->
-
-    return false unless element
-
-    if match = selector.match classSelectorRE
-      element._class is match[1]
-    else if match = selector.match idSelectorRE
-      element.id is match[1]
-    else
-      element._viewName is selector

   each: (callback) ->


resize a big size image for ios

resize a big size image for ios

--- src/ngPreviewCover.coffee
+++ src/ngPreviewCover.coffee
@@ -18,14 +18,26 @@
       canvas.height = h
       canvas

+    getMinScale:( width, height, resizeW, resizeH )->
+      scaleW = resizeW / width
+      scaleH = resizeH / height
+      if scaleW < scaleH then scaleW else scaleH
+
     cut:( image, fromYRate, heightRate, resizeWidth, resizeHeight )->
+      isIos = /(ipad)|(iphone)/i.test navigator.userAgent
+      iosMaxSize = 1924
       deferred = $q.defer()
       promise = @loadImageSize image
       promise.then ( size )=>
-        canvas = @createCanvas size.width, (resizeHeight / image.height) * size.height
+        scale = 1
+        if isIos and (iosMaxSize < size.width or iosMaxSize < size.height)
+          scale = @getMinScale size.width, size.height, iosMaxSize, iosMaxSize
+          size.width = Math.ceil size.width * scale
+          size.height = Math.ceil size.height * scale
+
+        canvas = @createCanvas size.width - ( if isIos then 1 else 0 ), (resizeHeight / image.height) * size.height
         ctx = canvas.getContext "2d"
-        #ctx.drawImage image, 0,  fromYRate * size.height, size.width, size.height, 0, 0, size.width, size.height
-        ctx.drawImage image, 0, 0, size.width, size.height, 0, (-fromYRate * size.height), size.width, size.height
+        ctx.drawImage image, 0, 0, size.width, size.height , 0, parseInt(-fromYRate * size.height), canvas.width , size.height
         deferred.resolve canvas.toDataURL()
       deferred.promise


fix watch bug when node warns: "possible EventEmitter memory leak detected. 11 listeners added." Just check if watcher was create

--- src/command.coffee
+++ src/command.coffee
@@ -216,8 +216,13 @@

     deepWatch source, watchList, ->
       for filename in watchList
+        continue if filename of watchers
+
         do (filename) ->
-          watchers[filename] = (fs.watchFile or fs.watch) filename, -> compile filename
+          watchers[filename] = try
+              fs.watchFile filename, -> compile filename
+            catch ex
+              fs.watch filename, -> compile filename

   deepWatch = (filename, watchList, callback) ->
     return no if filename in watchList


Fix up CoffeeScript version of _significantContents() to match behavior of original plain JavaScript version.

--- src/control.coffee
+++ src/control.coffee
@@ -325,10 +325,13 @@
       if typeof content is "string" and $.trim( content ).length > 0
         return content  # Found significant text

-      # Content is an array
+      # Content is an array
       for node in content
-        if typeof node is "string" and $.trim( node ).length > 0
-          return content # Found significant text
+        if typeof node is "string"
+          if $.trim( node ).length > 0
+            return content # Found significant text
+          else
+            return null # Empty string, not significant
         else if node.nodeType isnt 8 # Comment node
           return content # Found some real element


createAt(), if properties parameter is undefined, doesn't attempt to pass that undefined value to the control's content() setter.

--- src/control.coffee
+++ src/control.coffee
@@ -109,7 +109,7 @@
         # Tags don't match; replace with elements with the right tag.
         $controls = replaceElements $controls, new @ defaultTarget

-    if !$.isPlainObject( properties )
+    if properties isnt undefined and !$.isPlainObject( properties )
       # Property value implicitly handed to content() property.
       properties = content: properties


Fix number check

Fix number check

--- src/podroller/core.coffee
+++ src/podroller/core.coffee
@@ -174,11 +174,11 @@

             console.log "requested start, end is", requestStart, requestEnd

-            # If rangeVals has two numbers, then check if they
+            # If rangeVals has two numbers > 0, then check if they
             # are valid and within the file size
-            if _u.isNumber(requestStart) and _u.isNumber(requestEnd)
-                rangeStart = if (requestStart >= fstart && requestStart < fend) then requestStart else fstart
-                rangeEnd = if (requestEnd > fstart && requestEnd <= fend) then requestEnd else fend
+            if (requestStart >= fstart) and (requestEnd > fstart)
+                rangeStart = if (requestStart < fend) then requestStart else fstart
+                rangeEnd = if (requestEnd <= fend) then requestEnd else fend

                 # Request is for a range
                 rangeRequest = true


Headers to first camel case, just for follow conventions

--- src/portals/portal.coffee
+++ src/portals/portal.coffee
@@ -20,12 +20,12 @@
       response.setHeader 'Server', "stubby/#{CLI.version()} node/#{process.version} (#{process.platform} #{process.arch})"

       if request.headers['origin']?
-        response.setHeader 'access-control-allow-origin', request.headers['origin']
-        response.setHeader 'access-control-allow-credentials', true
+        response.setHeader 'Access-Control-Allow-Origin', request.headers['origin']
+        response.setHeader 'Access-Control-Allow-Credentials', true
         if request.headers['access-control-request-headers']?
-          response.setHeader 'access-control-allow-headers', request.headers['access-control-request-headers']
+          response.setHeader 'Access-Control-Allow-Headers', request.headers['access-control-request-headers']
         if request.headers['access-control-request-method']?
-          response.setHeader 'access-control-allow-methods', request.headers['access-control-request-method']
+          response.setHeader 'Access-Control-Allow-Methods', request.headers['access-control-request-method']

       return response


refactor Element

refactor Element

--- src/element.coffee
+++ src/element.coffee
@@ -1,15 +1,14 @@
 {Node} = require './node'

 class Element extends Node
-  constructor: (options) ->
-    super options
-    @type = 'element'
-    @name = options.name
+  constructor: ({ level, @name }) ->
+    super { level }

   @parse: (s) ->
     { level, node } = Node.parseBasic s
-    if node.match(/^<\S+$/)
-      new Element level: level, name: node.substring 1
+    m = node.match /^<(\S+)$/
+    if m?
+      new Element { level, name: m[1] }

   append: (prev) ->
     if @level > prev.level
@@ -24,7 +23,7 @@

   write: ->
     attributes = (" #{k}=\"#{v}\"" for k, v of @attributes).join ''
-    children = @children.map((i) -> i.write()).join('')
+    children = @children.map((i) -> i.write()).join ''
     '<' + @name + attributes + '>' + children + '</' + @name + '>'

 module.exports.Element = Element


Fix crashing bug when directory doesn't have _includes

--- src/enfield.coffee
+++ src/enfield.coffee
@@ -363,7 +363,7 @@
   includesDir = path.join config.source, '_includes'

   unless fs.existsSync includesDir
-    return {}
+    return callback null, {}

   # Grab the plain contents of all files, including within subdirectories
   contents = {}


add undefined validation on data-bgcolor

--- widgets/text/text.coffee
+++ widgets/text/text.coffee
@@ -1,7 +1,8 @@
 class Dashing.Text extends Dashing.Widget

   ready: ->
-    $(@node).css("background-color",$(@node).attr("data-bgcolor"))
+
+    $(@node).css("background-color",$(@node).attr("data-bgcolor")) if $(@node).attr("data-bgcolor")?

   onData: (data) ->


Improve regexp of destiny script.

Improve regexp of destiny script.

Before it only mached when having a space at the end, e.g. "is it pizza day ?",
now the space is optional and it also matches "is it pizza day?".

--- src/scripts/destiny.coffee
+++ src/scripts/destiny.coffee
@@ -14,7 +14,7 @@
 #   KuiKui

 module.exports = (robot) ->
-  robot.respond /is it (\w+) day \?/i, (msg) ->
+  robot.respond /is it (\w+) day ?\?/i, (msg) ->
     action = msg.match[1]
     nbDay = Math.floor(new Date().getTime() / 1000 / 86400)
     actionHash = action.length + action.charCodeAt(0) + action.charCodeAt(action.length - 1)


fix al-attr

fix al-attr


Changed regex

Changed regex


Ensure new <script>s are loaded in order when navigating

Fixes #282

--- src/turbolinks/renderer.coffee
+++ src/turbolinks/renderer.coffee
@@ -19,6 +19,7 @@
     else
       createdScriptElement = document.createElement("script")
       createdScriptElement.textContent = element.textContent
+      createdScriptElement.async = false
       copyElementAttributes(createdScriptElement, element)
       createdScriptElement


same fix as before, but in typing info

same fix as before, but in typing info

--- src/ui/views/typinginfo.coffee
+++ src/ui/views/typinginfo.coffee
@@ -6,9 +6,9 @@

     conv_id = viewstate?.selectedConv
     c = conv[conv_id]
-    return unless c

     div class:'typing '+('typingnow' if c?.typing?.length), ->
+        return unless c
         span class:'material-icons', 'more_horiz' if c?.typing?.length
         for t, i in (c.typing ? [])
             name = nameof entity[t.user_id.chat_id]


fix mobible for history push state

fix mobible for history push state

--- documentation/app/static/scripts/modules/process-documentation.coffee
+++ documentation/app/static/scripts/modules/process-documentation.coffee
@@ -12,6 +12,9 @@
   class ProcessDocumentation
     constructor: (@options) ->
       {@elements} = options
+
+    isMobile: ->
+      return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)

     initPrettyPrint: ->
       $('pre').addClass 'prettyprint'
@@ -71,7 +74,7 @@

     bindHeaderNavEvents: ->
       $(@elements.header).find('a').on 'click', (evt) =>
-        if history.pushState
+        if history.pushState or @isMobile isnt true
           evt.preventDefault()

           $ct = $(evt.currentTarget)
@@ -100,8 +103,6 @@
       history.pushState(url: url, title, url)

       $(window).on 'popstate', (evt) =>
-        url = evt.originalEvent.state.url
-
         $(@elements.header).find('li').removeClass 'active'
         $('body').removeClass 'api-page'
         $(@elements.nav).empty()


Set default cursor pointer for TextButton

--- src/view/text_button.js.coffee
+++ src/view/text_button.js.coffee
@@ -4,8 +4,15 @@
     @_labelView = new CB.LabelView()
     @_labelView.frame = new CB.Rect(0, 0, 0, 0)
     this.addSubview(@_labelView)
+    @cursor = "pointer"

   @property "readonly", "labelView"
+
+  @property "cursor",
+    set: (newPointer) ->
+      @_pointer = newPointer
+      @css("cursor", newPointer)
+      return

   sizeThatFits: (size) ->
     return @labelView.sizeThatFits(size)


I AM AN IDIOT

I AM AN IDIOT

--- scripts/tell.coffee
+++ scripts/tell.coffee
@@ -26,8 +26,9 @@
        localstorage[room][username] += tellmessage
      else
        localstorage[room][username] = tellmessage
+     robot.reply "Ok, I'll tell #{username}"
      return
-    robot.reply "Ok, I'll tell #{username}"
+

    # When a user enters, check if someone left them a message
    robot.enter (msg) ->


Fix brunch test.

Fix brunch test.

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -279,7 +279,7 @@
       "Function('exports, require, module', #{data})"
     else
       "function(exports, require, module) {\n  #{indent data}\n}"
-    "require.register(#{path}, #{definition});\n"
+    "window.require.register(#{path}, #{definition});\n"

 normalizeWrapper = (typeOrFunction, addSourceURLs) ->
   switch typeOrFunction


Fix bug cloning packages installed with https url

--- src/install.coffee
+++ src/install.coffee
@@ -547,7 +547,8 @@
       url = packageInfo.toString()
       develop.cloneRepository url, cloneDir, {}, callback
     else if packageInfo.default is 'https'
-      develop.cloneRepository name, cloneDir, {}, callback
+      url = packageInfo.https().replace(/^git\+https:/, "https:")
+      develop.cloneRepository url, cloneDir, {}, callback
     else if packageInfo.default is 'shortcut'
       url = packageInfo.https().replace(/^git\+https:/, "https:")
       develop.cloneRepository url, cloneDir, {}, (error) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -128,7 +128,7 @@
   @param {Number} radius Border radius of the meter
   @param {Boolean} showName Boolean to toggle whether or not to show the attribute associated with the meter
   @param {String} text A String to display over the meter. Overrides default name attribute
-    width - How wide the meter is
+  width - How wide the meter is
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


readBytes should return -1 at EOF.

readBytes should return -1 at EOF.

Completely undocumented in the JDK comments :(

--- src/methods.coffee
+++ src/methods.coffee
@@ -452,7 +452,7 @@
               # this is a real file that we've already opened
               data = fs.readSync(_this.fields.$file, n_bytes)[0]
               byte_arr.array[offset...offset+data.length] = (data.charCodeAt(i) for i in [0...data.length])
-              return data.length
+              return if data.length == 0 and n_bytes isnt 0 then -1 else data.length
             # reading from System.in, do it async
             console.log '>>> reading from Stdin now!'
             result = null # will be filled in after the yield


flow cleanup

flow cleanup

--- src/methods.coffee
+++ src/methods.coffee
@@ -188,12 +188,11 @@
       if UNSAFE?
         return ms.pop() # assume that we have implemented all the necessary natives
       else
-        if sig.indexOf('::registerNatives()V',1) >= 0 or sig.indexOf('::initIDs()V',1) >= 0
-          return ms.pop() # these are all just NOPs
-        try
+        ms.pop()
+        unless sig.indexOf('::registerNatives()V',1) >= 0 or sig.indexOf('::initIDs()V',1) >= 0
           java_throw runtime_state, 'java/lang/Error', "native method NYI: #{sig}"
-        finally
-          ms.pop()
+        return
+
     if @access_flags.abstract
       java_throw runtime_state, 'java/lang/Error', "called abstract method: #{sig}"


cleaning up sysprops

cleaning up sysprops

--- src/natives.coffee
+++ src/natives.coffee
@@ -17,7 +17,14 @@
 # things assigned to root will be available outside this module
 root = exports ? this.natives = {}

+if node?  # node is only defined if we're in the browser
+  vendor_path ='/home/doppio/vendor'
+else
+  vendor_path = path.resolve __dirname, '../vendor'
+
 system_properties = {
+  'java.home': "#{vendor_path}/java_home",
+  'sun.boot.class.path': "#{vendor_path}/classes",
   'file.encoding':'US_ASCII','java.vendor':'DoppioVM',
   'java.version': '1.6', 'java.vendor.url': 'https://github.com/int3/doppio',
   'java.class.version': '50.0',
@@ -27,12 +34,6 @@
   'java.awt.headless': 'true',
   'useJavaUtilZip': 'true'  # hack for sun6javac, avoid ZipFileIndex shenanigans
 }
-if node?  # node is only defined if we're in the browser
-  system_properties['java.home'] ='/home/doppio/vendor/java_home'
-  system_properties['sun.boot.class.path'] = '/home/doppio/vendor/classes'
-else
-  system_properties['java.home'] = path.resolve __dirname, '../vendor/java_home'
-  system_properties['sun.boot.class.path'] = path.resolve __dirname, '../vendor/classes'

 get_property = (rs, jvm_key, _default = null) ->
   key = jvm_key.jvm2js_str()


Re-trap non-native method for speed.

Re-trap non-native method for speed.

Running it natively is about 4x slower.

--- src/natives.coffee
+++ src/natives.coffee
@@ -69,6 +69,11 @@
       Bits: [
         o 'byteOrder()L!/!/ByteOrder;', (rs) -> rs.static_get {class:'java/nio/ByteOrder',name:'LITTLE_ENDIAN'}
       ]
+      charset:
+        Charset$3: [
+          # this is trapped and NOP'ed for speed
+          o 'run()L!/lang/Object;', (rs) -> null
+        ]

 doPrivileged = (rs) ->
   oref = rs.curr_frame().locals[0]


Much better

Much better

--- src/request.coffee
+++ src/request.coffee
@@ -7,6 +7,10 @@
     format: "json"
     async: true
     data: null
+
+  # @POST = (options) ->
+  #   options.method = "POST"
+  #   new Request(options)

   constructor: (options = {}) ->
     @_emitter = new Emitter
@@ -65,8 +69,3 @@
       return JSON.stringify(@data)

     @data
-
-# class @Request.POST extends Request
-#   constructor: (options) ->
-#     super(options)
-#     @method = "POST"


Add first-child selector to first tab. /cc @procload

--- app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
+++ app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
@@ -159,6 +159,7 @@
   get_pages: (tpl = 'page') ->
     html = ''
     $.each @options.pages, (idx,el) =>
+      el['classes'] = 'first-child' if idx == 0
       html += Mustache.to_html @tpl(tpl), el
     html

@@ -245,7 +246,6 @@
     link

   modal_open: (data) ->
-    data = @modal_data(data) unless typeof data == 'object'
     @modal_active = true
     if data.document == 'true'
       html = Mustache.to_html(@tpl('pdf'),{ filename: data.uid })
@@ -261,11 +261,6 @@
       html = Mustache.to_html(@tpl('show'),{ filename: data.uid, src: url, orientation: data.orientation })
       $.facebox("
#{html}
")
     @touch(data)
-
-  modal_data: (id) ->
-    @log "modal_data(#{id})"
-    #touch_url = Mustache.to_html @options.touch_url, { id: el.id }
-    #$.post "#{@options.base_url}#{touch_url}"

   load_img: (el,src) ->
     img = new Image()
@@ -429,7 +424,7 @@

'
     handle: '{{ title }}'
-    tab: '{{ title }}'
+    tab: '{{ title }}'
     page: '


891: Use small icon for non-clustered responses

--- app/assets/javascripts/backbone/views/dashboard_map_view.js.coffee
+++ app/assets/javascripts/backbone/views/dashboard_map_view.js.coffee
@@ -34,6 +34,7 @@
       m = new google.maps.Marker({
         position: p,
         title: I18n.t('activerecord.models.response.one') + ' #' + l.r_id,
+        icon: 'https://storage.googleapis.com/support-kms-prod/SNP_2752125_en_v0',
         r_id: l.r_id
       })


updating clientes

updating clientes

--- app/assets/javascripts/views/clientes/clientes_edit_view.js.coffee
+++ app/assets/javascripts/views/clientes/clientes_edit_view.js.coffee
@@ -7,9 +7,20 @@

   attributes: ["tipoId", "identificacion", "nombres", "direccion", "telefono", "email"]

+  success: (cliente) ->
+    alertify.log "Cliente actualizado"
+    self.get("controller").transitionToRoute "clientes.show", cliente.get("id")
+
+  failure: (response) ->
+    self.setErrors response.errors
+    self.set "submitting", false
+
   submit: ->
     self = @
     @emptyErrors()
     data = @getFormData()
-    console.log data
+    cliente = @get("model")
+    cliente.setProperties data
+    cliente.save().then @success, @failure
+    @set "submitting", true
     false


fix https://github.com/sagemathinc/smc/issues/1242 -- Resizing browser window with terminal should trigger terminal refresh

--- src/smc-webapp/console.coffee
+++ src/smc-webapp/console.coffee
@@ -190,7 +190,8 @@
             @set_session(opts.session)

     append_to_value: (data) =>
-        # this @value is used for copy/paste of the session history.
+        # this @value is used for copy/paste of the session history and @value_orig for resize/refresh
+        @value_orig += data
         @value += data.replace(/\x1b\[.{1,5}m|\x1b\].*0;|\x1b\[.*~|\x1b\[?.*l/g,'')

     init_mesg: () =>
@@ -354,7 +355,7 @@
     reset: () =>
         # reset the terminal to clean; need to do this on connect or reconnect.
         #$(@terminal.element).css('opacity':'0.5').animate(opacity:1, duration:500)
-        @value = ''
+        @value = @value_orig = ''
         @scrollbar_nlines = 0
         @scrollbar.empty()
         @terminal.reset()
@@ -781,6 +782,7 @@

     refresh: () =>
         @terminal.refresh(0, @opts.rows-1)
+        @terminal.showCursor()

     # Determine the current size (rows and columns) of the DOM
@@ -811,8 +813,15 @@
         @_needs_resize = false
         @session.write_data(resize_code(@opts.cols, @opts.rows))

+        @full_rerender()
+
         # Refresh depends on correct @opts being set!
         @refresh()
+
+    full_rerender: =>
+        value = @value_orig
+        @reset()
+        @render(value)

     resize_terminal: () =>
         # Determine size of container DOM.


fix some mobile/touch terminal issues involving the box at the top

--- src/smc-webapp/console.coffee
+++ src/smc-webapp/console.coffee
@@ -75,7 +75,6 @@
 focused_console = undefined
 client_keydown = (ev) ->
     focused_console?.client_keydown(ev)
-

 class Console extends EventEmitter
     constructor: (opts={}) ->
@@ -455,11 +454,7 @@
         redux.getActions('file_use').mark_file(@project_id, @path, 'edit')

     client_keydown: (ev) =>
-        #console.log("client_keydown", ev)
-        if @_input_line_is_focused and ev.keyCode == 32
-            # on mobile ipad we avoid sending space twice (see the hack in
-            # term.js involving the smart keyboard)
-            return false
+        #console.log("client_keydown")
         @mark_file_use()
         if ev.ctrlKey and ev.shiftKey
             switch ev.keyCode
@@ -659,6 +654,7 @@

         input_line.on 'focus', =>
             @_input_line_is_focused = true
+            @terminal.blur()
         input_line.on 'blur', =>
             @_input_line_is_focused = false

@@ -666,7 +662,7 @@
             @session?.write_data(input_line.val())
             input_line.val('')

-        input_line.on 'keyup', (e) =>
+        input_line.on 'keydown', (e) =>
             if e.which == 13
                 e.preventDefault()
                 submit_line()


Experiment to improve the charts

Experiment to improve the charts

--- app/assets/javascripts/components/moving_records/interactive_chart.js.coffee
+++ app/assets/javascripts/components/moving_records/interactive_chart.js.coffee
@@ -7,16 +7,23 @@
       chartInstance: null

     render: ->
+      console.log "render"
       React.DOM.canvas
         ref:   @props.name
         style: { height: @props.height, width: @props.width }

     componentDidMount: ->
+      console.log "componentDidMount"
       @initializeChart()

-    componentDidUpdate:  ->
-      @state.chartInstance.destroy()
-      @initializeChart()
+    # componentDidUpdate:  ->
+    #   console.log "componentDidUpdate"
+    #   @state.chartInstance.destroy() if @state.chartInstance
+    #   @initializeChart()
+
+    componentWillUnmount: ->
+      console.log "componentWillUnmount"
+      @state.chartInstance.destroy() if @state.chartInstance

     initializeChart: ->
       canvas = React.findDOMNode(@refs[@props.name])


fix return

fix return

--- MediaAdminBundle/Resources/public/coffee/mediatheque/orchestraMediaType.coffee
+++ MediaAdminBundle/Resources/public/coffee/mediatheque/orchestraMediaType.coffee
@@ -11,4 +11,4 @@
   if $('.clear-media', view.$el).length > 0
     $.extend true, view, extendView['orchestraMediaType']
     return view.delegateEvents()
-  return+  return


remove console.log

remove console.log

--- src/tourney/pods/index.coffee
+++ src/tourney/pods/index.coffee
@@ -40,7 +40,7 @@
   podSchedule = generatePodSchedule pods
   divisionSchedule = generateDivisionSchedule divisions

-  console.log divisionSchedule
+  # console.log divisionSchedule

   # console.log _(podSchedule).reduce(((memo, pod) -> memo + pod.games), 0)
   # console.log teamsInDivision


Correctly apply sort when updating

Correctly apply sort when updating

--- annotran/static/scripts/widget-controller.coffee
+++ annotran/static/scripts/widget-controller.coffee
@@ -160,7 +160,7 @@
       $scope.$root.allPageAnnotations.push annot

       # now update the interface
-      $scope.$root.updateUserList(0)
+      $scope.$root.updateUserList($scope.$root.direction)

       # now fire an event that can be hooked since we've updated the lists in the background
       $scope.$root.$broadcast(eventsa.ROOTSCOPE_LISTS_UPDATED);
@@ -193,7 +193,7 @@
       $scope.$root.allPageAnnotations = array

       # now update the interface
-      $scope.$root.updateUserList(0)
+      $scope.$root.updateUserList($scope.$root.direction)

       # now fire an event that can be hooked since we've updated the lists in the background
       $scope.$root.$broadcast(eventsa.ROOTSCOPE_LISTS_UPDATED);


Make option.problemContext optional

Make option.problemContext optional

--- src/validators/options.coffee
+++ src/validators/options.coffee
@@ -41,7 +41,7 @@
       problems:
         type: ['object', 'undefined']
       problemContext:
-        type: 'object'
+        type: ['object', 'undefined']
       includeFlow:
         type: 'boolean'
         default: true


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -75,6 +75,13 @@

 ###*

+

+4.5.round()
+# => 5
+
+4.4.round()
+# => 4
+

 @name circularPoint
 @methodOf Number#


Remove redundant scope variables.

Remove redundant scope variables.

--- app/assets/javascripts/app/controllers.js.coffee
+++ app/assets/javascripts/app/controllers.js.coffee
@@ -1,4 +1,4 @@
-@DatabasesCtrl = ($scope, $element, $log, tableFilterFactory, confirmationDialog, doAction) ->
+@DatabasesCtrl = ($scope, $element, tableFilterFactory, confirmationDialog, doAction) ->
   $scope.databases = $element.data("databases")
   $scope.tableFilter = tableFilterFactory($scope, "databases")

@@ -13,7 +13,7 @@
         doAction(url, "delete")

 @CollectionsCtrl = ($scope, $element, tableFilterFactory, confirmationDialog, doAction) ->
-  $scope.dbName = $element.data("db-name")
+  dbName = $element.data("db-name")
   $scope.collections = $element.data("collections")
   $scope.tableFilter = tableFilterFactory($scope, "collections")

@@ -24,16 +24,16 @@
     confirmationDialog
       message: "Deleting #{collection.name}. Are you sure?"
       onOk: ->
-        url = "/databases/#{$scope.dbName}/collections/#{collection.name}"
+        url = "/databases/#{dbName}/collections/#{collection.name}"
         doAction(url, "delete")

 @DocumentsCtrl = ($scope, $element, confirmationDialog, doAction) ->
-  $scope.dbName = $element.data("db-name")
-  $scope.collectionName = $element.data("collection-name")
+  dbName = $element.data("db-name")
+  collectionName = $element.data("collection-name")

   $scope.delete = (id) ->
     confirmationDialog
       message: "Are you sure?"
       onOk: ->
-        url = "/databases/#{$scope.dbName}/collections/#{$scope.collectionName}/#{id}"
+        url = "/databases/#{dbName}/collections/#{collectionName}/#{id}"
         doAction(url, "delete")


Does not override udefine values + udefine.node is now called udefine.commonjs

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

 # Root object hook
 do (root = module?.exports ? this) ->
-  root.udefine = (name, deps, factory) ->
+  root.udefine or= (name, deps, factory) ->
     if Array.isArray name
       [name, deps, factory] = [undefined, [], deps]
     else
@@ -31,5 +31,7 @@
         result = factory.apply @, globalsArr
     result

-  root.udefine.globals = {}
-  root.udefine.node = {}
+  root.udefine.globals or= {}
+  root.udefine.commonjs or= {}
+
+  null


Not using global object in Node.js any more

--- src/udefine.coffee
+++ src/udefine.coffee
@@ -4,10 +4,7 @@
 hasModule = module? and module.exports

 # Root object hook
-# TODO: Binding to global on Node.js is not the best idea (I know it's bad
-#  practice), but what are the alternatives? (Putting it into a requirable
-#  module is not really an option for small-scale projects)
-do (root = if hasModule then global else this) ->
+do (root = if hasModule then module.exports else this) ->
   root.udefine or= (name, deps, factory) ->
     throw new Error 'A udefine module needs to have a name' unless name?

@@ -72,7 +69,4 @@

   # Configuration helper function
   root.udefine.configure = (configFunc) ->
-    configFunc.apply root.udefine, [if hasModule then {} else root]
-
-
-  module.exports = root.udefine+    configFunc.apply root.udefine, [if hasModule then {} else root]


add comment

add comment

--- app/assets/javascripts/components/light-box.cjsx
+++ app/assets/javascripts/components/light-box.cjsx
@@ -1,6 +1,6 @@
 React               = require 'react'
 SVGImage            = require './svg-image'
-ActionButton                  = require './action-button'
+ActionButton        = require './action-button'

 module.exports = React.createClass
   displayName: 'LightBox'
@@ -23,6 +23,7 @@
     # console.log 'LightBox::componentWillReceiveProps(), PROPS = ', @props
     # console.log 'LightBox::compo....................(), SUBJECT_INDEX = ', @props.subject_index

+    # STI: This was causing some of the weird behavior when clicking to view a subject
     # @setState
     #   first: @props.subject_set.subjects[@props.subject_index]


form controls to add a new list item

form controls to add a new list item

--- app/assets/javascripts/components/list.js.coffee
+++ app/assets/javascripts/components/list.js.coffee
@@ -1,4 +1,8 @@
 @List = React.createClass
+  getInitialState: ->
+    title: ''
+  handleChange: (e) ->
+    @setState title: e.target.value
   handleDelete: (e) ->
     e.preventDefault()
     $.ajax
@@ -31,3 +35,31 @@
         className: 'panel-body'
         for item in @props.list.list_items
           React.createElement ListItem, key: item.id, item: item
+        React.DOM.form
+          className: 'form-inline'
+          React.DOM.div
+            className: 'form-group'
+            React.DOM.input
+              type: 'text'
+              className: 'form-control input-sm'
+              placeholder: 'Title'
+              name: 'title'
+              onChange: @handleChange
+            React.DOM.input
+              type: 'text'
+              className: 'form-control input-sm'
+              placeholder: 'Description'
+              name: 'description'
+            React.DOM.select
+              className: 'form-control input-sm'
+              name: 'state'
+              React.DOM.option null, 'Not started'
+              React.DOM.option null, 'In progress'
+              React.DOM.option null, 'Completed'
+            React.DOM.button
+              type: 'submit'
+              disabled: !@valid()
+              className: 'btn btn-primary btn-sm'
+              "Add"
+  valid: ->
+    @state.title


fix popover

fix popover

--- app/assets/javascripts/pt/tags/popover.js.coffee
+++ app/assets/javascripts/pt/tags/popover.js.coffee
@@ -19,13 +19,13 @@
               content: html
               html: true
               trigger: 'manual'
-              container: attr.popoverContainer or 'body'
+              container: $el.parent()
               placement: attr.popoverPlacement or 'right'
             $el.data 'ptPopoverLoaded', false
             $el.popover 'show' if $el.data 'ptPopoverFocused'
-
       else
         $el.popover 'show'
+
 pt.tags.push
   popover:
     event: 'mouseleave'
@@ -33,7 +33,4 @@
       $el = $ this
       attr = $el.ptAttr()
       $el.data 'ptPopoverFocused', false
-      if not attr.popoverContainer
-        $el.popover 'hide'
-      else
-        $el.closest(attr.popoverContainer).find('.popover').remove()
+      $el.parent().find('.popover').remove()


Simplified Sugar.RichTextArea

Simplified Sugar.RichTextArea

--- app/assets/javascripts/sugar/rich_text.js.coffee
+++ app/assets/javascripts/sugar/rich_text.js.coffee
@@ -1,137 +1,99 @@
-Sugar.RichTextArea = (textArea, options) ->
-  @textArea = textArea
+Sugar.RichTextArea = (textarea, options) ->
+
+  return this if textarea.richtext
+
+  markdownDecorator =
+    bold: (str)           -> ["**", str, "**"]
+    emphasis: (str)       -> ["_", str, "_"]
+    link: (url, name)     -> ["[", name, "](#{url})"]
+    image: (url)          -> ["![](", url, ")"]
+    mp3: (url, name)      -> ["", name, ""]
+    blockquote: (str)     -> ["", ("> " + line for line in str.split("\n")).join("\n"), ""]
+    code: (str, language) -> ["```#{language}\n", str, "\n```"]
+    spoiler: (str)        -> ["
", str, "
"]
+
+  htmlDecorator =
+    bold: (str)           -> ["", str, ""]
+    emphasis: (str)       -> ["", str, ""]
+    link: (url, name)     -> ["", name, ""]
+    image: (url)          -> [""]
+    mp3: (url, name)      -> ["", name, ""]
+    blockquote: (str)     -> ["
", str, "
"]
+    code: (str, language) -> ["
", str, "
"]
+    spoiler: (str)        -> ["
", str, "
"]
+
   settings = jQuery.extend(
     className: "richTextToolbar"
   , options)

-  @toolbar =
-    settings: settings
-    textArea: textArea
-    listElement: false
-    buttons: []
-    addButton: (name, className, callback, options) ->
-      li = document.createElement("li")
-      a = document.createElement("a")
-      i = document.createElement("i")
-      a.title = name
-      a.textArea = @textArea
+  decorator = markdownDecorator

-      jQuery(a).click ->
-        selection = textArea.selectedText()
+  formatButton = $("
Markdown
")
+  toolbar = $("

").append(formatButton).insertBefore(textarea)

-        if typeof textArea.selectionStart != "undefined"
-          selectionStart = textArea.selectionStart
-          selectionEnd = textArea.selectionEnd
+  addButton = (name, className, callback) ->
+    link = $("")

-        [prefix, replacement, postfix] = callback(selection)
-        textArea.replaceSelection(prefix + replacement + postfix)
-        $(textArea).focus()
+    link.click ->
+      selection = $(textarea).getSelection().text

-        if typeof textArea.setSelectionRange != "undefined"
-          textArea.setSelectionRange(
-            (selectionStart + prefix.length),
-            (selectionEnd + (replacement.length - selection.length) + prefix.length)
-          )
+      if typeof textarea.selectionStart != "undefined"
+        selectionStart = textarea.selectionStart
+        selectionEnd = textarea.selectionEnd

-      jQuery(a).addClass className
-      jQuery(i).addClass "icon-#{className}"
-      jQuery(a).append(i)
-      jQuery(li).addClass("button")
-      jQuery(li).append(a).insertBefore jQuery(@listElement).find('.formatting')
-      @buttons.push li
-      this
+      [prefix, replacement, postfix] = callback(selection)
+      $(textarea).replaceSelection(prefix + replacement + postfix)
+      $(textarea).focus()

-    create: ->
-      unless @listElement
-        @listElement = document.createElement("ul")
-        jQuery(@listElement).addClass @settings.className
-        jQuery(@listElement).append("
Markdown
")
-        jQuery(@listElement).insertBefore @textArea
+      if typeof textarea.setSelectionRange != "undefined"
+        textarea.setSelectionRange(
+          (selectionStart + prefix.length),
+          (selectionEnd + (replacement.length - selection.length) + prefix.length)
+        )

-  @textArea.selectedText = ->
-    jQuery(this).getSelection().text
+    $("

").append(link).insertBefore(formatButton)

-  @textArea.replaceSelection = (replacement) ->
-    jQuery(this).replaceSelection replacement
+  # Bold button
+  addButton "Bold", "bold", (selection) -> decorator.bold(selection)

-  @textArea.wrapSelection = ->
-    prepend = arguments[0]
-    append = (if (arguments.length > 1) then arguments[1] else prepend)
-    @replaceSelection prepend + @selectedText() + append
+  # Italic button
+  addButton "Italics", "italic", (selection) -> decorator.emphasis(selection)

-  @textArea.toolbar = @toolbar
-  @toolbar.create()
-  this
+  # Link button
+  addButton "Link", "link", (selection) ->
+    url = prompt("Enter link URL", "")
+    name = if selection.length > 0 then selection else "Link text"
+    url = if url.length > 0 then url else "http://example.com/"
+    url = url.replace(/^(?!(f|ht)tps?:\/\/)/, 'http://')
+    decorator.link(url, name)

-markdownDecorator =
-  bold: (str)           -> ["**", str, "**"]
-  emphasis: (str)       -> ["_", str, "_"]
-  link: (url, name)     -> ["[", name, "](#{url})"]
-  image: (url)          -> ["![](", url, ")"]
-  mp3: (url, name)      -> ["", name, ""]
-  blockquote: (str)     -> ["", ("> " + line for line in str.split("\n")).join("\n"), ""]
-  code: (str, language) -> ["```#{language}\n", str, "\n```"]
-  spoiler: (str)        -> ["
", str, "
"]
+  # Image tag
+  addButton "Image", "picture", (selection) ->
+    url = if selection.length > 0 then selection else prompt("Enter image URL", "")
+    decorator.image(url)

-htmlDecorator =
-  bold: (str)           -> ["", str, ""]
-  emphasis: (str)       -> ["", str, ""]
-  link: (url, name)     -> ["", name, ""]
-  image: (url)          -> [""]
-  mp3: (url, name)      -> ["", name, ""]
-  blockquote: (str)     -> ["
", str, "
"]
-  code: (str, language) -> ["
", str, "
"]
-  spoiler: (str)        -> ["
", str, "
"]
+  # MP3 button
+  addButton "MP3", "music", (selection) ->
+    url = prompt("Enter MP3 URL", "")
+    name = if selection.length > 0 then selection else prompt("Enter track title", "")
+    url = if url.length > 0 then url else "http://example.com/example.mp3"
+    url = url.replace(/^(?!(f|ht)tps?:\/\/)/, 'http://')
+    decorator.mp3(url, name)
+
+  # Block Quote
+  addButton "Block Quote", "quote-left", (selection) -> decorator.blockquote(selection)
+
+  # Code button
+  addButton "Code", "code", (selection) ->
+    lang = prompt("Enter language (leave blank for no syntax highlighting)", "")
+    decorator.code(selection, lang)
+
+  # Spoiler button
+  addButton "Spoiler", "warning-sign", (selection) -> decorator.spoiler(selection)
+
+  textarea.richtext = true
+

 $(Sugar).bind 'ready modified', ->
+  $('textarea.rich').each -> new Sugar.RichTextArea(this)

-  $('textarea.rich').each ->
-
-    unless this.toolbar
-      ta = new Sugar.RichTextArea(this)
-      decorator = markdownDecorator
-
-      # Setup the buttons
-      ta.toolbar
-
-        # Bold
-        .addButton "Bold", "bold", (selection) ->
-          decorator.bold(selection)
-
-        # Italic
-        .addButton "Italics", "italic", (selection) ->
-          decorator.emphasis(selection)
-
-        # Link
-        .addButton "Link", "link", (selection) ->
-          url = prompt("Enter link URL", "")
-          name = if selection.length > 0 then selection else "Link text"
-          url = if url.length > 0 then url else "http://example.com/"
-          url = url.replace(/^(?!(f|ht)tps?:\/\/)/, 'http://')
-          decorator.link(url, name)
-
-        # Image tag
-        .addButton "Image", "picture", (selection) ->
-          url = if selection.length > 0 then selection else prompt("Enter image URL", "")
-          decorator.image(url)
-
-        # MP3 Player
-        .addButton "MP3", "music", (selection) ->
-          url = prompt("Enter MP3 URL", "")
-          name = if selection.length > 0 then selection else prompt("Enter track title", "")
-          url = if url.length > 0 then url else "http://example.com/example.mp3"
-          url = url.replace(/^(?!(f|ht)tps?:\/\/)/, 'http://')
-          decorator.mp3(url, name)
-
-        # Block Quote
-        .addButton "Block Quote", "quote-left", (selection) ->
-          decorator.blockquote(selection)
-
-        # Escape HTML
-        .addButton "Code", "code", (selection) ->
-          lang = prompt("Enter language (leave blank for no syntax highlighting)", "")
-          decorator.code(selection, lang)
-
-        # Spoiler
-        .addButton "Spoiler", "warning-sign", (selection) ->
-          decorator.spoiler(selection)


fix max members restrictions

fix max members restrictions

--- app/modules/services/current-user.service.coffee
+++ app/modules/services/current-user.service.coffee
@@ -129,7 +129,7 @@

         user = @.getUser()

-        if user.get('max_members_private_projects') != null && project.get('members').size >= user.get('max_members_private_projects')
+        if user.get('max_members_private_projects') != null && project.get('members').size > user.get('max_members_private_projects')
             return {valid: false, reason: 'max_members_private_projects', type: 'private_project'}

         return {valid: true}
@@ -145,7 +145,7 @@

         user = @.getUser()

-        if user.get('max_members_public_projects') != null && project.get('members').size >= user.get('max_members_public_projects')
+        if user.get('max_members_public_projects') != null && project.get('members').size > user.get('max_members_public_projects')
             return {valid: false, reason: 'max_members_public_projects', type: 'public_project'}

         return {valid: true}


Fixed uninformative "spawn OK" error message on windows on

--- src/wrapper.coffee
+++ src/wrapper.coffee
@@ -22,7 +22,8 @@
       'pipe' # stderr
     ]
     proc = spawn @bin, args, {stdio}
-    proc.once 'error', reject
+    proc.once 'error', (err) ->
+      reject err unless err.code is 'OK' is err.errno
     proc.once 'close', onClose = (code, signal) ->
       if code isnt 0 or signal isnt null
         err = new Error "Command failed: #{stderr}"


magic overrides are last in regex pref order

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -83,37 +83,6 @@
     email = msg.match[1]
     msg.message.user.pagerdutyEmail = email
     msg.send "Okay, I'll remember your PagerDuty email is #{email}"
-
-  # Assumes your Campfire usernames and PagerDuty names are identical
-  robot.respond /pager( me)? (.+) (\d+)$/i, (msg) ->
-    campfireUserToPagerDutyUser msg, msg.message.user, (user) ->
-
-      userId = user.id
-      return unless userId
-
-      if !msg.match[2] || msg.match[2] == 'me'
-        msg.reply "Please specify a schedule with 'pager me ops 60.'' Use 'pager schedules' to list all schedules."
-        return
-
-      withScheduleMatching msg, msg.match[2], (matchingSchedule) ->
-
-        return unless matchingSchedule.id
-
-        start     = moment().format()
-        minutes   = parseInt msg.match[3]
-        end       = moment().add('minutes', minutes).format()
-        override  = {
-          'start':     start,
-          'end':       end,
-          'user_id':   userId
-        }
-        withCurrentOncall msg, matchingSchedule, (old_username, schedule) ->
-          data = { 'override': override }
-          pagerDutyPost msg, "/schedules/#{schedule.id}/overrides", data, (json) ->
-            if json.override
-              start = moment(json.override.start)
-              end = moment(json.override.end)
-              msg.send "Rejoice, #{old_username}! #{json.override.user.name} has the pager on #{schedule.name} until #{end.format()}"

   robot.respond /(pager|major)( me)? incident (.*)$/, (msg) ->
     pagerDutyIncident msg, msg.match[3], (incident) ->
@@ -181,6 +150,7 @@
             , 5000

   robot.respond /(?:pager|major)(?: me)? ack(?:nowledge)? (.+)$/i, (msg) ->
+    msg.finish()
     incidentNumbers = parseIncidentNumbers(msg.match[1])

     # only acknowledge triggered things, since it doesn't make sense to re-acknowledge if it's already in re-acknowledge
@@ -211,6 +181,7 @@
       updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'acknowledged')

   robot.respond /(?:pager|major)(?: me)? res(?:olve)?(?:d)? (.+)$/i, (msg) ->
+    msg.finish()
     incidentNumbers = parseIncidentNumbers(msg.match[1])

     # allow resolving of triggered and acknowedlge, since being explicit
@@ -238,6 +209,7 @@
       updateIncidents(msg, incidentNumbers, 'acknowledged', 'resolved')

   robot.respond /(pager|major)( me)? notes (.+)$/i, (msg) ->
+    msg.finish()
     incidentId = msg.match[3]
     pagerDutyGet msg, "/incidents/#{incidentId}/notes", {}, (json) ->
       buffer = ""
@@ -246,6 +218,8 @@
       msg.send buffer

   robot.respond /(pager|major)( me)? note ([\d\w]+) (.+)$/i, (msg) ->
+    msg.finish()
+
     incidentId = msg.match[3]
     content = msg.match[4]

@@ -365,6 +339,38 @@
           msg.send ":boom:"
         else
           msg.send "Something went weird."
+
+  robot.respond /pager( me)? (.+) (\d+)$/i, (msg) ->
+    msg.finish()
+    campfireUserToPagerDutyUser msg, msg.message.user, (user) ->
+
+      userId = user.id
+      return unless userId
+
+      if !msg.match[2] || msg.match[2] == 'me'
+        msg.reply "Please specify a schedule with 'pager me ops 60.'' Use 'pager schedules' to list all schedules."
+        return
+
+      withScheduleMatching msg, msg.match[2], (matchingSchedule) ->
+
+        return unless matchingSchedule.id
+
+        start     = moment().format()
+        minutes   = parseInt msg.match[3]
+        end       = moment().add('minutes', minutes).format()
+        override  = {
+          'start':     start,
+          'end':       end,
+          'user_id':   userId
+        }
+        withCurrentOncall msg, matchingSchedule, (old_username, schedule) ->
+          data = { 'override': override }
+          pagerDutyPost msg, "/schedules/#{schedule.id}/overrides", data, (json) ->
+            if json.override
+              start = moment(json.override.start)
+              end = moment(json.override.end)
+              msg.send "Rejoice, #{old_username}! #{json.override.user.name} has the pager on #{schedule.name} until #{end.format()}"
+

   # who is on call?
   robot.respond /who('s|s| is)? (on call|oncall)( for (.+))?/i, (msg) ->
@@ -516,11 +522,11 @@
     pagerDutyGet msg, "/schedules", query, (json) ->
       schedule = null
       # Single result returned
-      if json.schedules and json.schedules.length == 1
+      if json?.schedules?.length == 1
         schedule = json.schedules[0]

       # Multiple results returned and one is exact
-      if json.schedules and json.schedules.length > 1
+      if json?.schedules?.length > 1
         matchingExactly = json.schedules.filter (s) ->
           s.name == q
         if matchingExactly.length == 1


Update ui-scroll.coffee

Update ui-scroll.coffee

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -15,7 +15,7 @@

 	.directive( 'ngScrollViewport'
 		[ '$log'
-			 ->
+			 () ->
 				controller:
 					[ '$scope', '$element'
 						(scope, element) -> element


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -132,6 +132,7 @@
     this.toString()

 String::startsWith = () ->
+

 ###*
 Returns a new string in Title Case.


Remove testing method

Remove testing method

--- src/scripts/ambush.coffee
+++ src/scripts/ambush.coffee
@@ -23,9 +23,6 @@
     else
       msg.send "#{msg.match[1]}? Never heard of 'em"

-  robot.hear /test/, (msg) ->
-    robot.testAmbush(msg)
-
   robot.hear /./i, (msg) ->
     if (ambushes = robot.brain.data.ambushes[msg.message.user.name])
       msg.send "Hey, " + msg.message.user.name + ", while you were out:"


Make sure its exists before calling it

Make sure its exists before calling it

--- client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
+++ client/app/Applications/Activity.kdapplication/activitylistcontroller.coffee
@@ -104,7 +104,7 @@

     if @_state is 'private'
       view = @addHiddenItem activity, 0
-      @activityHeader.newActivityArrived()
+      @activityHeader?.newActivityArrived()

   newActivityArrived:(activity)->

@@ -117,7 +117,7 @@
         @updateNewMemberBucket activity
       else
         view = @addHiddenItem activity, 0
-        @activityHeader.newActivityArrived()
+        @activityHeader?.newActivityArrived()

   updateNewMemberBucket:(activity)->


Fix automatic cancel at period end

Fix automatic cancel at period end

--- src/smc-hub/client.coffee
+++ src/smc-hub/client.coffee
@@ -2187,7 +2187,7 @@
                 (cb) =>
                     if schema.cancel_at_period_end
                         dbg("Setting subscription to cancel at period end")
-                        @_stripe.customers.cancelSubscription(customer_id, subscription.id, {at_period_end:true}, cb)
+                        @_stripe.subscriptions.update(subscription.id, {cancel_at_period_end:true}, cb)
                     else
                         cb()
                 (cb) =>
@@ -2231,7 +2231,7 @@
                     dbg("cancel the subscription at stripe")
                     # This also returns the subscription, which lets
                     # us easily get the metadata of all projects associated to this subscription.
-                    @_stripe.customers.cancelSubscription(customer_id, subscription_id, {at_period_end:mesg.at_period_end}, cb)
+                    @_stripe.subscriptions.update(subscription_id, {cancel_at_period_end:mesg.at_period_end}, cb)
                 (cb) =>
                     @database.stripe_update_customer(account_id : @account_id, stripe : @_stripe, customer_id : customer_id, cb: cb)
             ], (err) =>


CLI resources parser better message

CLI resources parser better message

--- cli/tasks/build/parse/resources/parser.coffee
+++ cli/tasks/build/parse/resources/parser.coffee
@@ -104,7 +104,7 @@
                 return

     parseResourcesFolder = (path) ->
-        throw new Error "Resources folder not implemented"
+        throw new Error "Resources folder `#{path}` is not implemented"

     parseResourcesFile = (path, config) ->
         assert.isString path


Client: change permission names

Client: change permission names

--- client/Dashboard/views/permissionsform.coffee
+++ client/Dashboard/views/permissionsform.coffee
@@ -70,18 +70,15 @@

   readableText = (text)->
     dictionary =
-      "JTag"        : "Tags"
-      "JNewApp"     : "Apps"
-      "JGroup"      : "Groups"
-      "JPost"       : "Social API"
-      "JVM"         : "Compute"
-      "CActivity"   : "Activity"
-      "JGroupBundle": "Group Bundles"
-      "JDomain"     : "Domains"
-      "JProxyFilter": "Proxy Filters"
-      "JInvitation" : "Invitations"
-      "JComment"    : "Comments"
-      "JStack"      : "Stacks"
+      "JNewApp"        : "Apps"
+      "JGroup"         : "Groups"
+      "SocialMessage"  : "Social API"
+      "JVM"            : "Compute"
+      "JGroupBundle"   : "Group Bundles"
+      "JDomain"        : "Domains"
+      "JProxyFilter"   : "Proxy Filters"
+      "JInvitation"    : "Invitations"
+      "JStack"         : "Stacks"

     return dictionary[text] or text.charAt(0).toUpperCase()+text.slice(1)


Remote-Extension: code tidy up better implementation

--- client/app/lib/remote-extensions/index.coffee
+++ client/app/lib/remote-extensions/index.coffee
@@ -3,35 +3,38 @@

 module.exports = RemoteExtensions =

-  getInstance: (instanceId)     -> globals.__remoteCache[instanceId]
-
-  setInstance: (data, instance) -> globals.__remoteCache[data._id] = instance

   initialize: (remote) ->

-    (Object.keys remote.api).forEach (model) ->
-      remote.api[model]::init = (data) ->
-        super
-
-        instance = RemoteExtensions.getInstance data._id
-
-        unless instance
-          RemoteExtensions.setInstance data, this
-        else
-          for own key of data when instance[key]?
-            instance[key] = data[key]
+    @injectCacheOnAPI remote

     remote.api.JComputeStack = require './computestack'
     remote.api.JMachine      = require './machine'

-  updateInstance: (instanceId) -> # , data) ->
+  injectCacheOnAPI: (remote) ->

-    instance = globals.__remoteCache[instanceId]
-    return  unless instance
+    (Object.keys remote.api).forEach (model) ->

-    instance.emit 'update' # , data
+      remote.api[model]::init = (data) ->
+        super
+
+        RemoteExtensions.addInstance data._id, this

   getCache: -> globals.__remoteCache

+
+  updateInstance: (instanceId) -> # , data) ->
+  getInstances: (instanceId) -> @getCache()[instanceId] ? []
+
+
+  hasInstances: (instanceId) -> (@getInstances instanceId).length > 0
+
+
+  addInstance: (instanceId, instance) ->
+
+    @getCache()[instanceId] ?= []
+    @getCache()[instanceId].push instance
+
+


Don't use include when values are retured raw array

--- client/lanes/models/query/SyncedResult.coffee
+++ client/lanes/models/query/SyncedResult.coffee
@@ -13,7 +13,7 @@
             fields: _.pluck( @result.query.fields.where(query: true), 'id')
         }

-        _.extend(options, Lanes.u.invokeOrReturn(@result.query.syncOptions))
+        _.extend(options, _.omit(@result.query.syncOptions, 'include'))

         @pendingLoad = Lanes.Models.Sync.perform('GET', options).then (resp) =>
             @result.total = resp.total


remove unused templates stuff from paths

--- src/steroids/paths.coffee
+++ src/steroids/paths.coffee
@@ -7,14 +7,6 @@

   @npm: path.join __dirname, "..", ".."
   @applicationDir: process.cwd()
-
-  @templatesDir: path.join @npm, "templates"
-  @templates:
-    applications: path.join @templatesDir, "applications"
-    resources: path.join @templatesDir, "resources"
-    scaffolds: path.join @templatesDir, "scaffolds"
-    gruntfile: path.join @templatesDir, "applications", "default", "Gruntfile.js"
-    packageJson: path.join @templatesDir, "applications", "default", "package.json"

   @grunt:
     library: path.join @npm, "node_modules", "grunt", "lib", "grunt"


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -216,14 +216,14 @@
 Returns the the nearest grid resolution less than or equal to the number.

-    7.snap(8)
-=> 0
-
-    4.snap(8)
-=> 0
-
-   12.snap(8)
-=> 8
+7.snap(8)
+# => 0
+
+4.snap(8)
+# => 0
+
+12.snap(8)
+# => 8

 @name snap


number_extensions

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -66,8 +66,12 @@
 @type Number
 @returns The number rounded to the nearest integer.

-(4.5).round() # => 5
-(4.4).round() # => 4
+

+   (4.5).round()
+=> 5
+   (4.4).round()
+=> 4
+

 ###
 Number::round = ->
   Math.round(this)


Teamwork: Fixed avatars on tab. Basically forced to redraw avatars again when event come from Firebase.

--- client/Teamwork/Views/teamworktabview.coffee
+++ client/Teamwork/Views/teamworktabview.coffee
@@ -37,30 +37,32 @@
   listenPaneDidShow: ->
     @tabView.on "PaneDidShow", (pane) =>
       @stateRef.child(KD.nick()).set pane.getOptions().indexKey
-
-      # need to delay fetching state ref.
-      # otherwise clients might be in different tabs.
-      KD.utils.wait 600, =>
-        @stateRef.once "value", (snapshot) =>
-          map = snapshot.val()
-          return unless map
-
-          data = {}
-
-          for username, indexKey of map
-            data[indexKey] = []  unless data[indexKey]
-            jAccount       = @workspace.users[username]
-            data[indexKey].push jAccount  if jAccount
-
-          for pane in @tabView.panes
-            {indexKey} = pane.getOptions()
-            if data[indexKey]
-              avatars = data[indexKey].filter (jAccount) ->
-                return if jAccount?.profile.nickname is KD.nick() then no else yes
-              pane.tabHandle.avatarView?.avatar?.destroy()
-              pane.tabHandle.setAccounts avatars
-            else
-              pane.tabHandle.avatarView?.avatar?.destroy()
+      @updateTabAvatars()
+
+  updateTabAvatars: ->
+    # need to delay fetching state ref.
+    # otherwise clients might be in different tabs.
+    KD.utils.wait 600, =>
+      @stateRef.once "value", (snapshot) =>
+        map = snapshot.val()
+        return unless map
+
+        data = {}
+
+        for username, indexKey of map
+          data[indexKey] = []  unless data[indexKey]
+          jAccount       = @workspace.users[username]
+          data[indexKey].push jAccount  if jAccount
+
+        for pane in @tabView.panes
+          {indexKey} = pane.getOptions()
+          if data[indexKey]
+            avatars = data[indexKey].filter (jAccount) ->
+              return if jAccount?.profile.nickname is KD.nick() then no else yes
+            pane.tabHandle.avatarView?.avatar?.destroy()
+            pane.tabHandle.setAccounts avatars
+          else
+            pane.tabHandle.avatarView?.avatar?.destroy()

   listenChildRemovedOnKeysRef: ->
     @keysRef.on "child_removed", (snapshot) =>
@@ -111,6 +113,8 @@
               terminal.setFocused yes  if document.activeElement is document.body
             else if pane.editor
               pane.editor.codeMirrorEditor.refresh()
+
+      @updateTabAvatars()

   createElements: ->
     @tabHandleHolder = new ApplicationTabHandleHolder delegate: this


TMS-2698 Fix typo

TMS-2698 Fix typo

https://koding.atlassian.net/browse/TMS-2698

--- client/app/lib/notificationcontroller.coffee
+++ client/app/lib/notificationcontroller.coffee
@@ -180,7 +180,7 @@
         content       :
           """

-            @#{adminNick} made you #{articlize role} #{role}, please refresh your browser to changes take effect.
+            @#{adminNick} made you #{articlize role} #{role}, please refresh your browser for changes to take effect.

           """
         buttons       :


client: wait for compute ctrl to create test vm

--- client/app/lib/util/createTestMachine.coffee
+++ client/app/lib/util/createTestMachine.coffee
@@ -5,10 +5,10 @@
 _machine = null
 _workspaces = null

-module.exports = createTestMachine = ->
+module.exports = createTestMachine = -> new Promise (resolve, reject) ->

   if _machine and _workspaces
-    return Promise.resolve { machine: _machine, workspaces: _workspaces }
+    return resolve { machine: _machine, workspaces: _workspaces }

   machine = require('mocks/mockmanagedmachine')()
   { reactor, computeController } = kd.singletons
@@ -16,13 +16,14 @@
   { workspaces } = machine
   delete machine.workspaces

-  managedHelper.ensureManagedStack (err) ->
-    computeController.create { machine }, (err, machine) ->
-      return Promise.reject(err)  if err
-      dataProvider.addTestMachine machine, workspaces
-      reactor.dispatch 'LOAD_USER_ENVIRONMENT_SUCCESS', dataProvider.get()
-      reactor.dispatch 'ADD_TEST_MACHINE', { machine, workspaces }
-      _machine = machine
-      _workspaces = workspaces
-      Promise.resolve { machine, workspaces }
+  computeController.ready ->
+    managedHelper.ensureManagedStack (err) ->
+      computeController.create { machine }, (err, machine) ->
+        return reject(err)  if err
+        dataProvider.addTestMachine machine, workspaces
+        reactor.dispatch 'LOAD_USER_ENVIRONMENT_SUCCESS', dataProvider.get()
+        reactor.dispatch 'ADD_TEST_MACHINE', { machine, workspaces }
+        _machine = machine
+        _workspaces = workspaces
+        resolve { machine, workspaces }


object_extensions

--- src/object_extensions.coffee
+++ src/object_extensions.coffee
@@ -17,7 +17,6 @@
 ###
 Object.isArray = (object)->
   Object::toString.call(object) == '[object Array]'
-
 ###*
 Merges properties from objects into target without overiding.
 First come, first served.


And that is why we write tests, folks.

And that is why we write tests, folks.

--- client/views/signUp/extraSignUpFields.coffee
+++ client/views/signUp/extraSignUpFields.coffee
@@ -4,7 +4,7 @@

 Template._entryExtraSignUpField.helpers
   isTextField: ->
-    @type is "text_box"
+    @type isnt "check_box"

   isCheckbox: ->
     @type is "check_box"


Store game in localStorage based on location

--- storyturtle.coffee
+++ storyturtle.coffee
@@ -151,6 +151,24 @@

     next()

+  storage =
+    set: (text) ->
+      name = @nameFromLocation()
+      localStorage?.setItem "storyturtle_#{name}", text
+
+    get: ->
+      name = @nameFromLocation()
+      return localStorage?.getItem "storyturtle_#{name}"
+
+    nameFromLocation: ->
+      name = "#{document.location}"
+      name.replace(/.*\//, '') # Strip all but last path component
+          .replace(/\..*/, '') # Strip extension
+
+  if storedGame = storage.get()
+    # If we previously stored game, load it
+    editor.val(storedGame)
+
   play.click ->
     board.show()
     editor.hide()
@@ -162,6 +180,9 @@
     parse gameText, ->
       speaker.text ""
       controls.show()
+
+    storage.set gameText
+
     false

   edit.click ->


update tests to work with new `$wye()` implementation


Do not exclude *.d.ts from Atom fuzzy-finder or tree-view

--- atom/config.cson
+++ atom/config.cson
@@ -67,7 +67,6 @@
       ".Trashes"
       "node_modules"
       "Thumbs.db"
-      "*.d.ts"
       "__pycache__"
     ]
     packagesWithKeymapsDisabled: [


Hide ignored files in Atom tree-view

Hide ignored files in Atom tree-view

--- atom/config.cson
+++ atom/config.cson
@@ -25,6 +25,10 @@
       "welcome"
     ]
     openEmptyEditorOnStart: false
+    themes: [
+      "one-light-ui"
+      "one-light-syntax"
+    ]
   editor:
     fontSize: 15
     invisibles:
@@ -52,7 +56,9 @@
   "theme-switcher":
     syntaxThemeOne: "one-dark-syntax"
     syntaxThemeTwo: "one-light-syntax"
-  "tree-view": {}
+  "tree-view":
+    hideIgnoredNames: true
+    hideVcsIgnoredFiles: true
   welcome:
     showOnStartup: false
 ".css.source":


Latest Atom configuration

Latest Atom configuration

--- atom/config.cson
+++ atom/config.cson
@@ -72,10 +72,8 @@
     tabType: "soft"
   "exception-reporting":
     userId: "ee24cf00-f793-9081-be2a-2cdae5536b7f"
-  "file-icons":
-    coloured: false
   "find-and-replace":
-    openProjectFindResultsInRightPane: false
+    scrollToResultOnLiveSearch: true
   "fortune-background-tips":
     fortuneCommand: "fortune -s Computers"
   "fuzzy-finder":
@@ -115,6 +113,7 @@
   "tree-view":
     hideIgnoredNames: true
     hideVcsIgnoredFiles: true
+    squashDirectoryNames: true
   welcome:
     showOnStartup: false
 ".asciidoc.source":


allow *hear* on engage!

allow *hear* on engage!


Ace: Renamed method name to reuse.

Ace: Renamed method name to reuse.

--- client/landing/app/Applications/Ace.kdapplication/acefindandreplaceview.coffee
+++ client/landing/app/Applications/Ace.kdapplication/acefindandreplaceview.coffee
@@ -70,7 +70,7 @@

   close: ->
     @hide()
-    @resizeAceEditor 0
+    @resizeEditor 0
     @findInput.setValue    ""
     @replaceInput.setValue ""
     @emit "FindAndReplaceViewClosed"
@@ -78,10 +78,10 @@
   setViewHeight: (isReplaceMode) ->
     height = if isReplaceMode then 60 else 32
     @$().css { height }
-    @resizeAceEditor height
+    @resizeEditor height
     @show()

-  resizeAceEditor: (height) ->
+  resizeEditor: (height) ->
     {ace} = @getDelegate()
     ace.setHeight ace.getHeight() + @lastHeightTakenFromAce - height
     ace.editor.resize yes


Expanded propertycontainer test in webadmin to be more specific about what goes wrong

--- community/server/src/main/coffeescript/test/neo4j/webadmin/modules/databrowser/views/TestPropertyContainerView.coffee
+++ community/server/src/main/coffeescript/test/neo4j/webadmin/modules/databrowser/views/TestPropertyContainerView.coffee
@@ -2,23 +2,21 @@
 define ['lib/amd/Backbone','neo4j/webadmin/modules/databrowser/views/PropertyContainerView'], (Backbone, PropertyContainerView) ->

   # TODO: Refactor out the "shouldBeConvertedToString" into it's own class
-  describe "PropertyContainerView", ->
+  describe "PropertyContainerView", ->
+    pcv = new PropertyContainerView(template:null)
+
     it "recognizes ascii characters as strings", ->
-      pcv = new PropertyContainerView(template:null)
-
       expect(pcv.shouldBeConvertedToString "a").toBe(true)
       expect(pcv.shouldBeConvertedToString "abcd123 ").toBe(true)

-    it "recognizes odd characters as strings", ->
-      pcv = new PropertyContainerView(template:null)
-
+    it "recognizes swedish characters as strings", ->
       expect(pcv.shouldBeConvertedToString "åäö").toBe(true)
       expect(pcv.shouldBeConvertedToString "åäö #$ asd  ").toBe(true)
+
+    it "recognizes strings containing odd characters as strings", ->
       expect(pcv.shouldBeConvertedToString ";åäö #$ asd  ").toBe(true)

     it "recognizes valid JSON values as not being strings", ->
-      pcv = new PropertyContainerView(template:null)
-
       expect(pcv.shouldBeConvertedToString "1").toBe(false)
       expect(pcv.shouldBeConvertedToString "12").toBe(false)
       expect(pcv.shouldBeConvertedToString "12.523").toBe(false)


AccountSshKeyListController: No need to use a customView since its provided by parent class and we shouldn’t use ‘console.log’ directly, otherwise we can’t control the logs

--- client/app/Applications/Account.kdapplication/views/sshkeys.coffee
+++ client/app/Applications/Account.kdapplication/views/sshkeys.coffee
@@ -9,12 +9,8 @@
     @getListView().on "UpdatedItems", =>
       @newItem = no
       newKeys = @getItemsOrdered().map (item)-> item.getData()
-      if newKeys.length is 0
-        @addCustomItem "<cite>You have no SSH keys.</cite>"
-      else
-        @customItem?.destroy()
-      KD.remote.api.JUser.setSSHKeys newKeys, ->
-        console.log "Saved keys."
+      unless newKeys.length is 0 then @customItem?.destroy()
+      KD.remote.api.JUser.setSSHKeys newKeys, -> log "Saved keys."

     @getListView().on "RemoveItem", (item)=>
       @newItem = no


Let WebTerm works

Let WebTerm works

--- client/app/Applications/WebTerm.kdapplication/AppController.coffee
+++ client/app/Applications/WebTerm.kdapplication/AppController.coffee
@@ -8,7 +8,7 @@
     behavior     : "application"
     preCondition :
       condition  : (cb)->
-        KD.singletons.vmController.info (err, info)=>
+        KD.singletons.vmController.info (err, vm, info)=>
           cb  if info?.state is 'RUNNING' then yes else no
       failure    : (cb)->
         KD.singletons.vmController.askToTurnOn 'WebTerm', cb


landing: blind fix: welcome page should never be visible

This is an attempt to fix, since I can't reproduce locally what is reported here: https://koding.atlassian.net/browse/TMS-2199

--- client/landing/site.landing/coffee/team/tabs/teamwelcometab.coffee
+++ client/landing/site.landing/coffee/team/tabs/teamwelcometab.coffee
@@ -23,8 +23,6 @@
     super options, data

     { mainController } = kd.singletons
-    teamData           = utils.getTeamData()
-    name               = @getOption 'name'

     @header = new MainHeaderView
       cssClass : 'team'
@@ -33,21 +31,7 @@
     @correct = new kd.ButtonView
       title      : 'Yes, that\'s the right address'
       style      : 'TeamsModal-button TeamsModal-button--green correct'
-      callback   : ->
-        go = ->
-          utils.storeNewTeamData 'signup', formData
-          utils.storeNewTeamData name, yes
-          kd.singletons.router.handleRoute '/Join'
-
-        formData          = {}
-        { email }         = teamData.invitation
-        formData.join     = yes
-        formData.username = email
-        formData.slug     = kd.config.group.slug
-
-        utils.validateEmail { email },
-          success : -> formData.alreadyMember = no; go()
-          error   : -> formData.alreadyMember = yes; go()
+      callback   : @bound 'next'

     @membersDesc = new kd.CustomHTMLView
       tagName: 'span'
@@ -55,7 +39,32 @@
     @decorateTeamMembers()

     # temp
-    @correct.click()
+    @next()
+
+
+  next: ->
+
+    teamData = utils.getTeamData()
+    name     = @getOption 'name'
+
+    go = ->
+      utils.storeNewTeamData 'signup', formData
+      utils.storeNewTeamData name, yes
+      kd.singletons.router.handleRoute '/Join'
+
+    formData          = {}
+    { email }         = teamData.invitation
+    formData.join     = yes
+    formData.username = email
+    formData.slug     = kd.config.group.slug
+
+    utils.validateEmail { email },
+      success : ->
+        formData.alreadyMember = no
+        go()
+      error   : ->
+        formData.alreadyMember = yes
+        go()

   decorateTeamMembers: ->


Add test for a capture without passing ignore to constructor

--- test/test-environmental.coffee
+++ test/test-environmental.coffee
@@ -67,3 +67,18 @@
           NODE_ENV        : "production"
           SUBDOMAIN       : "mycompany-myapp"
         done()
+
+    it "should capture environemtn without defining an ignore", (done) ->
+      env = new Environmental
+      capture = env.capture "#{__dirname}/../envs/production.sh", (err, flat) ->
+        expect(err).to.be.null
+        expect(flat).to.deep.equal
+          DEBUG           : ""
+          DEPLOY_ENV      : "production"
+          MYAPP_REDIS_HOST: "127.0.0.1"
+          MYAPP_REDIS_PORT: "6379"
+          MYAPP_REDIS_PASS: ""
+          NODE_APP_PREFIX : "MYAPP"
+          NODE_ENV        : "production"
+          SUBDOMAIN       : "mycompany-myapp"
+        done()


CSS Text size measuring performance improved

--- impl/css/level0/text.coffee
+++ impl/css/level0/text.coffee
@@ -14,107 +14,6 @@

 	textImpl = {}
 	signal.create textImpl, 'onFontReady'
-
-	sizeUpdatePending = false
-
-	updateSize = do ->
-		MAX_CHECKS = 40
-		CHECKS_AFTER_CHANGE = 35
-
-		queue = []
-		windowLoadQueue = []
-		pending = false
-		intervalPending = false
-		intervalId = 0
-
-		textImpl.onFontReady ->
-			while elem = windowLoadQueue.pop()
-				updateSize elem
-			return
-
-		updateAll = ->
-			pending = false
-			sizeUpdatePending = true
-			i = 0
-			n = queue.length
-			while i < n
-				item = queue[i]
-				updateSizeNow item
-				if ++item._impl.sizeChecks > MAX_CHECKS
-					if i is n-1
-						queue.pop()
-						n--
-					else
-						queue[i] = queue.pop()
-						i--
-						n--
-					item._impl.pendingSize = false
-				i++
-			sizeUpdatePending = false
-			return
-
-		updateAllInInterval = ->
-			updateAll()
-			unless queue.length
-				clearInterval intervalId
-				intervalPending = false
-			return
-
-		updateSizeNow = (item) ->
-			{innerElem} = item._impl
-
-			if item._impl.autoWidth
-				width = innerElem.offsetWidth
-				if item.width isnt width + 1
-					item._impl.sizeChecks = CHECKS_AFTER_CHANGE
-				if width > 0
-					item.width = width + 1
-
-			if item._impl.autoHeight
-				height = innerElem.offsetHeight
-				if item.height isnt height
-					item._impl.sizeChecks = CHECKS_AFTER_CHANGE
-				if height > 0
-					item.height = height
-
-			if (item._height > 0 and item._impl.elem.offsetParent) or item._impl.sizeChecks is MAX_CHECKS
-				if item._impl.innerElem.parentNode is hatchery
-					implUtils.prependElement item._impl.elem, item._impl.innerElem
-
-			return
-
-		(item) ->
-			data = item._impl
-
-			data.sizeChecks = 0
-
-			if data.pendingSize or (not data.autoWidth and not data.autoHeight)
-				return
-
-			if not isFontReady
-				windowLoadQueue.push item
-				return
-
-			sizeUpdatePending = true
-			updateSizeNow item
-			sizeUpdatePending = false
-			if data.sizeChecks is MAX_CHECKS
-				return
-
-			data.pendingSize = true
-			queue.push item
-
-			if item._height is 0 or not data.elem.offsetParent
-				hatchery.appendChild data.innerElem
-
-			unless pending
-				setTimeout updateAll, 10
-				pending = true
-
-			unless intervalPending
-				intervalId = setInterval updateAllInInterval, 50
-				intervalPending = true
-			return

 	reloadFontFamilyQueue = []
 	isFontReady = false
@@ -137,38 +36,74 @@
 			@_impl.innerElemStyle.fontFamily = @_impl.innerElemStyle.fontFamily
 		return

+	sizeUpdatePending = false
+
+	updateSize = do ->
+		windowLoadQueue = []
+
+		textImpl.onFontReady ->
+			while elem = windowLoadQueue.pop()
+				updateSize elem
+			return
+
+		(item) ->
+			data = item._impl
+			{innerElem} = data
+
+			if not data.autoWidth and not data.autoHeight
+				return
+
+			if not isFontReady
+				windowLoadQueue.push item
+
+			sizeUpdatePending = true
+
+			if data.autoWidth
+				item.width = innerElem.offsetWidth
+			if data.autoHeight
+				item.height = innerElem.offsetHeight
+
+			sizeUpdatePending = false
+
+			if innerElem.parentNode is hatchery
+				implUtils.prependElement data.elem, innerElem
+			else
+				if item._height is 0
+					hatchery.appendChild data.innerElem
+					updateSize item
+			return
+
 	updateContent = do ->
+		pending = false
 		queue = []
-		pending = false
-
-		updateItems = ->
+
+		updateItem = (item) ->
+			data = item._impl
+			if data.containsHTML
+				data.innerElem.innerHTML = item._text
+			else
+				data.innerElem.textContent = item._text
+			updateSize item
+			return
+
+		updateAll = ->
+			while item = queue.pop()
+				item._impl.contentUpdatePending = false
+				updateItem item
 			pending = false
-
-			# update
-			for item in queue
-				val = item._text
-				if val.indexOf('<') isnt -1 or val.indexOf('&#x') isnt -1
-					item._impl.innerElem.innerHTML = val
-				else
-					item._impl.innerElem.textContent = val
-
-			while queue.length
-				item = queue.pop()
-				item._impl.pendingContent = false
-				updateSize item
-
 			return

 		(item) ->
-			if item._impl.pendingContent
+			data = item._impl
+			if data.contentUpdatePending
 				return

-			item._impl.pendingContent = true
 			queue.push item
+			data.contentUpdatePending = true

 			unless pending
-				setImmediate updateItems
-				pending = true
+				setImmediate updateAll
+				pending = false
 			return

 	onWidthChange = ->
@@ -179,7 +114,7 @@
 			innerElemStyle.whiteSpace = if auto then 'pre' else 'pre-wrap'
 			innerElemStyle.width = if auto then 'auto' else "#{width}px"
 			if @_impl.autoWidth or @_impl.autoHeight
-				updateSize @
+				updateContent @
 		return

 	onHeightChange = ->
@@ -189,7 +124,7 @@
 			auto = @_impl.autoHeight = height is 0
 			innerElemStyle.height = if auto then 'auto' else "#{height}px"
 			if @_impl.autoWidth or @_impl.autoHeight
-				updateSize @
+				updateContent @
 		return

 	SHEET = """
@@ -217,16 +152,17 @@
 	COLOR_RESOURCE_REQUEST =
 		property: 'color'

+	CONTAINS_HTML_RE = /<|&#x/
+
 	DATA =
 		stylesheet: null
 		autoWidth: true
 		autoHeight: true
 		innerElem: null
 		innerElemStyle: null
-		sizeChecks: 0
-		pendingSize: false
-		pendingContent: false
 		uid: 0
+		contentUpdatePending: false
+		containsHTML: false

 	exports =
 	DATA: DATA
@@ -257,6 +193,7 @@
 			impl.utils.onFontLoaded reloadFontFamily, @

 	setText: (val) ->
+		@_impl.containsHTML = CONTAINS_HTML_RE.test(val)
 		updateContent @
 		return

@@ -282,7 +219,7 @@

 	setTextLineHeight: (val) ->
 		@_impl.innerElemStyle.lineHeight = val
-		updateSize @
+		updateContent @
 		return

 	setTextFontFamily: (val) ->
@@ -294,28 +231,28 @@
 		else
 			val = "'#{val}'"
 		@_impl.innerElemStyle.fontFamily = val
-		updateSize @
+		updateContent @
 		return

 	setTextFontPixelSize: (val) ->
 		val = round val
 		@_impl.innerElemStyle.fontSize = "#{val}px"
-		updateSize @
+		updateContent @
 		return

 	setTextFontWeight: (val) ->
 		@_impl.innerElemStyle.fontWeight = implUtils.getFontWeight val
-		updateSize @
+		updateContent @
 		return

 	setTextFontWordSpacing: (val) ->
 		@_impl.innerElemStyle.wordSpacing = "#{val}px"
-		updateSize @
+		updateContent @
 		return

 	setTextFontLetterSpacing: (val) ->
 		@_impl.innerElemStyle.letterSpacing = "#{val}px"
-		updateSize @
+		updateContent @
 		return

 	setTextFontItalic: (val) ->


adds unit tests

adds unit tests

--- js/test/tree/TreeLoader-test.coffee
+++ js/test/tree/TreeLoader-test.coffee
@@ -22,17 +22,27 @@

   NODE_1   = '1'
   NODE_1_0 = '1~0'
+
   NODE_2   = '2'
   NODE_2_0 = '2~0'
   NODE_2_2 = '2~2'
+
   NODE_3   = '3'

-  MOCK_METADATA_BY_MOCK_ID           = { }
-  MOCK_METADATA_BY_MOCK_ID[NODE_1]   = { childObjectCount: 1 }
-  MOCK_METADATA_BY_MOCK_ID[NODE_1_0] = { childObjectCount: 0 }
-  MOCK_METADATA_BY_MOCK_ID[NODE_2]   = { childObjectCount: 3 }
-  MOCK_METADATA_BY_MOCK_ID[NODE_2_0] = { childObjectCount: 0 }
-  MOCK_METADATA_BY_MOCK_ID[NODE_2_2] = { childObjectCount: 0 }
+  NODE_4     = '4'
+  NODE_4_0   = '4~0'
+  NODE_4_0_0 = '4~0~0'
+
+  MOCK_METADATA_BY_MOCK_ID             = { }
+  MOCK_METADATA_BY_MOCK_ID[NODE_1]     = { childObjectCount: 1 }
+  MOCK_METADATA_BY_MOCK_ID[NODE_1_0]   = { childObjectCount: 0 }
+  MOCK_METADATA_BY_MOCK_ID[NODE_2]     = { childObjectCount: 3 }
+  MOCK_METADATA_BY_MOCK_ID[NODE_2_0]   = { childObjectCount: 0 }
+  MOCK_METADATA_BY_MOCK_ID[NODE_2_2]   = { childObjectCount: 0 }
+  MOCK_METADATA_BY_MOCK_ID[NODE_4]     = { childObjectCount: 1 }
+  MOCK_METADATA_BY_MOCK_ID[NODE_4_0]   = { childObjectCount: 1 }
+  MOCK_METADATA_BY_MOCK_ID[NODE_4_0_0] = { childObjectCount: 0}
+

   # setup
   # =====
@@ -86,3 +96,24 @@
         .then (tree) ->
           expect(tree).toBeUndefined()
           done()
+
+      it 'should load a tree with depth constraint 1', (done) ->
+        treeLoader.load(NODE_4, { depth: 1 })
+        .then (tree) ->
+          expect(tree).toBeDefined()
+          expect(tree.id).toBe(NODE_4)
+          expect(tree.children.length).toBe(1)
+          expect(_.first(tree.children).id).toBe(NODE_4_0)
+          expect(_.first(tree.children).children.length).toBe(0)
+          done()
+
+      it 'should load a tree with depth constraint 2', (done) ->
+        treeLoader.load(NODE_4, { depth: 2 })
+        .then (tree) ->
+          expect(tree).toBeDefined()
+          expect(tree.id).toBe(NODE_4)
+          expect(tree.children.length).toBe(1)
+          expect(_.first(tree.children).id).toBe(NODE_4_0)
+          expect(_.first(tree.children).children.length).toBe(1)
+          expect(_.first(_.first(tree.children).children).id).toBe(NODE_4_0_0)
+          done()


work on displaying parameters inputs

work on displaying parameters inputs

--- jsapp/xlform/src/view.params.coffee
+++ jsapp/xlform/src/view.params.coffee
@@ -13,6 +13,34 @@
       @$el = @rowView.$('.params-view')

     render: ->
+      for param, value in @parameters
+        new ParamOption(@attributes.type.attributes.typeId, param, value).render().$el.appendTo(@$el)
       return @

+  class ParamOption extends $baseView
+    className: "param-option"
+    events:
+      "input": "oninput"
+      "click .js-clear-option": "onclear"
+
+    initialize: (@rowType, @paramName, @paramValue) ->
+
+    render: ->
+      @inputEl = $('<input>')
+      @container = $('<div>')
+
+      @container.append(@inputEl)
+
+      @$el.html(@d)
+      return @
+
+    oninput: (evt) ->
+      console.log('oninput', evt)
+
+    clear: () ->
+      console.log('clear')
+
+    saveValue: (newValue)->
+      console.log('saveValue', newValue)
+
   ParamsView: ParamsView


cleanup

cleanup

--- src/views/internal.coffee
+++ src/views/internal.coffee
@@ -38,9 +38,6 @@

     super data
     @init()
-    #@_init()
-    #console.log "IN"
-

   ###
   ###
@@ -68,8 +65,6 @@

     path.join "."

-
-
   ###
   ###

@@ -87,8 +82,9 @@

   $: (search) ->

-
-    el = $(@section.getChildNodes())
+    # a little overhead, but we need to re-scan the elements
+    # each time $() is called
+    el = $ @section.getChildNodes()

     if arguments.length
       return el.find search
@@ -151,8 +147,6 @@
       remove    : @_onRemove
       removed   : @_onRemoved

-      # stateChange: @_init
-
     @decorators.once "stateChange", @_init

   ###
@@ -170,8 +164,6 @@
   ###

   _onLoad      : () =>
-    #@_init()
-
   _onLoaded    : () =>

   ###
@@ -193,6 +185,4 @@
   _onRemoved   : () => @dispose()

-
-
 module.exports = InternalView


Refactoring of error handling to save LoC

--- src/controllers/admin.coffee
+++ src/controllers/admin.coffee
@@ -18,6 +18,13 @@
 exports.category_edit_post = (req, res) ->
   oldname = req.body.oldname
   name = req.body.name
+  error_func = (req, res, msg, oldname) ->
+    req.flash 'error', msg
+    if oldname
+      res.redirect '/admin/category/' + encodeURIComponent(oldname) + '/edit'
+    else
+      res.redirect '/admin/category/new'
+
   if oldname and !Categories.categories[oldname]?
     req.flash 'error', 'Invalid category'
     res.redirect '/admin/categories'
@@ -25,25 +32,13 @@

   if !req.body.icon? or !req.body.icon
-    req.flash 'error','You must specify an icon'
-    if oldname
-      res.redirect '/admin/category/' + encodeURIComponent(oldname) + '/edit'
-    else
-      res.redirect '/admin/category/new'
+    error_func req, res, 'You must specify an icon', oldname
     return
   if !name? or !name
-    req.flash 'error','You must specify an name'
-    if oldname
-      res.redirect '/admin/category/' + encodeURIComponent(oldname) + '/edit'
-    else
-      res.redirect '/admin/category/new'
+    error_func req, res, 'You must specify a name', oldname
     return
   if name != oldname and (Categories.categories[name]? or Categories.meta_categories[name]?)
-    req.flash 'error','A category named ' + name + ' already exists'
-    if oldname
-      res.redirect '/admin/category/' + encodeURIComponent(oldname) + '/edit'
-    else
-      res.redirect '/admin/category/new'
+    error_func req, res, 'A category named ' + name + ' already exists', oldname
     return

   if oldname
@@ -72,44 +67,36 @@
 exports.meta_category_edit_post = (req, res) ->
   oldname = req.body.oldname
   name = req.body.name
+
+  error_func = (req, res, msg, oldname) ->
+    req.flash 'error', msg
+    if oldname
+      res.redirect '/admin/meta-category/' + encodeURIComponent(oldname) + '/edit'
+    else
+      res.redirect '/admin/meta-category/new'
+
   if oldname and !Categories.meta_categories[oldname]?
     req.flash 'error', 'Invalid meta-category'
     res.redirect '/admin/categories'
     return

   if !name? or !name
-    req.flash 'error','You must specify an name'
-    if oldname
-      res.redirect '/admin/meta-category/' + encodeURIComponent(oldname) + '/edit'
-    else
-      res.redirect '/admin/meta-category/new'
+    error_func req, res, 'You must specify a name', oldname
     return
   if name != oldname and (Categories.categories[name]? or Categories.meta_categories[name]?)
-    req.flash 'error','A category named ' + name + ' already exists'
-    if oldname
-      res.redirect '/admin/meta-category/' + encodeURIComponent(oldname) + '/edit'
-    else
-      res.redirect '/admin/meta-category/new'
+    error_func req, res, 'A category called ' + name + ' already exists', oldname
     return

   cats = req.body.categories
   if !cats
-    req.flash 'error','You must select one category'
-    if oldname
-      res.redirect '/admin/meta-category/' + encodeURIComponent(oldname) + '/edit'
-    else
-      res.redirect '/admin/meta-category/new'
+    error_func req, res, 'You must specify one category', oldname
     return

   if !Array.isArray(cats)
     cats = [cats]
   for i in cats
     if !Categories.categories[i]?
-      req.flash 'error','Form error'
-      if oldname
-        res.redirect '/admin/meta-category/' + encodeURIComponent(oldname) + '/edit'
-      else
-        res.redirect '/admin/meta-category/new'
+      error_func req, res, 'Form error (invalid category: ' + i +')' , oldname
       return

   if oldname


Frontpage updated

Frontpage updated

--- src/controllers/index.coffee
+++ src/controllers/index.coffee
@@ -4,5 +4,5 @@
   db = req.app.get 'models'
   db.Order.findAll()
     .success (result) ->
-      res.send result
-#    res.render 'index'+      res.render 'index'
+#      res.send result


changed path to file with picture

changed path to file with picture

--- src/controllers/index.coffee
+++ src/controllers/index.coffee
@@ -47,16 +47,16 @@
     form_data = new FormData()
     #form_data.append 'photo', body

-    exec "ls -la", (error, stdout, stderr) ->
-      sys.puts stdout
+    #exec "ls -la", (error, stdout, stderr) ->
+    #  sys.puts stdout
       #console.log 'Error: ' + error
       #console.log 'Stdout: ' + stdout
       #console.log 'Stderr: ' + stderr

-    read_stream = fs.createReadStream(__dirname + '/life_is_random.jpg')
+    read_stream = fs.createReadStream('.app/controllers/life_is_random.jpg')
     read_stream.on 'open', ->
       #console.log read_stream
-      #form_data.append 'photo', read_stream
+      form_data.append 'photo', read_stream
       #console.log form_data.getHeaders()
       #console.log form_data
       form_data.submit server_url, (err, resp)->