Fix a server crash when a design document has no views, refs #202

--- server/lib/request.coffee
+++ server/lib/request.coffee
@@ -270,7 +270,7 @@
 ## Initialize request
 module.exports.init = (callback) ->
     removeEmptyView = (doc, callback) ->
-        if Object.keys(doc.views).length is 0 or not doc?.views?
+        if not doc?.views? or Object.keys(doc.views).length is 0
             db.remove doc._id, doc._rev, (err, response) ->
                 if err
                     log.error "[Definition] err: " + err.message
@@ -316,13 +316,18 @@
                 recoverDesignDocs (err, docs) ->
                     return callback err if err?
                     async.forEach docs, (doc, cb) ->
-                        async.forEach Object.keys(doc.views), (view, cb) ->
-                            body = doc.views[view]
-                            storeAppView apps, doc, view, body, cb
-                        , (err) ->
-                            removeEmptyView doc, (err) ->
-                                log.error err if err?
-                                cb()
+                        if not doc?.views?
+                            log.warn "Document has no view"
+                            log.warn doc
+                            cb()
+                        else
+                            async.forEach Object.keys(doc.views), (view, cb) ->
+                                body = doc.views[view]
+                                storeAppView apps, doc, view, body, cb
+                            , (err) ->
+                                removeEmptyView doc, (err) ->
+                                    log.error err if err?
+                                    cb()
                     , (err) ->
                         log.error err if err?
                         callback()


Fix for https://github.com/josephg/ShareJS/issues/51

--- src/client/textarea.coffee
+++ src/client/textarea.coffee
@@ -38,8 +38,9 @@
         cursor + text.length
       else
         cursor
-
-    replaceText elem.value[...pos] + text + elem.value[pos..], transformCursor
+    #for IE8 and Opera that replace \n with \r\n.
+    prevvalue = elem.value.replace /\r\n/g, '\n'
+    replaceText prevvalue[...pos] + text + prevvalue[pos..], transformCursor

   @on 'delete', (pos, text) ->
     transformCursor = (cursor) ->
@@ -47,8 +48,9 @@
         cursor - Math.min(text.length, cursor - pos)
       else
         cursor
-
-    replaceText elem.value[...pos] + elem.value[pos + text.length..], transformCursor
+    #for IE8 and Opera that replace \n with \r\n.
+    prevvalue = elem.value.replace /\r\n/g, '\n'
+    replaceText prevvalue[...pos] + prevvalue[pos + text.length..], transformCursor

   genOp = (event) ->
     onNextTick = (fn) -> setTimeout fn, 0


WIP - ScrollElement events filled out

WIP - ScrollElement events filled out

--- source/Art/Engine/Elements/Widgets/ScrollElement.coffee
+++ source/Art/Engine/Elements/Widgets/ScrollElement.coffee
@@ -358,26 +358,38 @@
       preprocess: (v) -> legalTrackingValues[v]
     tracking:           default: null

-    scrollPosition:     default: 0
+    scrollPosition:
+      default: 0
+      postSetter: (position) -> @_scrollPositionChanged()

   defaultChildrenLayout:  "column"
   defaultChildArea:       "logicalArea"

   constructor: ->
     super
-    @_maxScrollPosition = 0
+    @_childrenOffset = 0
+    @_childrenSize = 0
+    @_windowSize = 0
+    @_firstOnScreenChildIndex = -1
+    @_lastOnScreenChildIndex = -1
+    @_focusedChildIndex = -1
     @_initGestureProps()
+    @onNextReady =>
+      # @jumpToEnd() if @startAtEnd
+      @_scrollPositionChanged()
+
+  @getter "firstOnScreenChildIndex lastOnScreenChildIndex focusedChildIndex childrenSize windowSize"

   postFlexLayout: (mainCoordinate, inFlowChildren, mainChildrenSize, mainElementSizeForChildren, mainChildrenAlignedOffset) ->
     {_focusedChild, _focusedChildAxis, _scrollPos, _scrollPosition, _tracking} = @getState true
-    # log postFlexLayout: {mainChildrenSize, mainElementSizeForChildren}
-    @_maxScrollPosition = max 0, mainChildrenSize - mainElementSizeForChildren
+    @_windowSize = mainElementSizeForChildren
+    @_childrenSize = mainChildrenSize

     offsetDelta = if mainChildrenSize <= mainElementSizeForChildren
-      _scrollPosition
+      _scrollPosition / 2
     else switch _tracking
-      when "start", null then _scrollPosition - mainChildrenAlignedOffset
-      when "end"         then _scrollPosition + mainElementSizeForChildren - mainChildrenSize - mainChildrenAlignedOffset
+      when "start", null then _scrollPosition / 2 - mainChildrenAlignedOffset
+      when "end"         then _scrollPosition / 2 + mainElementSizeForChildren - mainChildrenSize - mainChildrenAlignedOffset
       when "child"
         _scrollPosition - if mainCoordinate == "x"
           _focusedChild.getCurrentLocationX true, point0
@@ -391,7 +403,7 @@
       else
         child._translateLocationXY 0, offsetDelta for child in inFlowChildren

-    @_updateTracking mainCoordinate, inFlowChildren, mainChildrenSize, mainElementSizeForChildren, mainChildrenAlignedOffset + offsetDelta
+    @_updateTracking mainCoordinate, inFlowChildren, mainChildrenSize, mainElementSizeForChildren, @_childrenOffset = mainChildrenAlignedOffset + offsetDelta

   _updateTracking: (mainCoordinate, inFlowChildren, mainChildrenSize, mainElementSizeForChildren, mainChildrenOffset) ->
     {_scrollPosition, _tracking, _track} = @getPendingState()
@@ -415,16 +427,64 @@
     @_scrollPositionManuallySet = false

     @_pendingState._scrollPosition = switch _tracking
-      when null, "start" then (if !scrolled then 0 else mainChildrenOffset)
-      when "end"         then (if !scrolled then 0 else mainChildrenOffset - mainElementSizeForChildren + mainChildrenSize)
+      when null, "start" then 2 * (if !scrolled then 0 else mainChildrenOffset)
+      when "end"         then 2 * (if !scrolled then 0 else mainChildrenOffset - mainElementSizeForChildren + mainChildrenSize)
       when "child"       then @_findFocusedChild mainCoordinate, inFlowChildren, mainChildrenSize, mainElementSizeForChildren

     if _tracking != "child"
       @_pendingState._focusedChild = null
-    if @_focusedChild != @_pendingState._focusedChild
-      @queueEvent "focusedChildChanged", focusedChild: @_pendingState._focusedChild
+
+    @_updateOnScreenInfo()

     log "tracking: #{@_pendingState._tracking} #{_scrollPosition}"
+
+  _updateOnScreenInfo: (inFlowChildren) ->
+    {isVertical, childrenSize, windowSize} = @
+
+    focusedChild = @_pendingState._focusedChild
+
+    children = @_pendingState._children
+    firstOnScreenChildIndex = children.length
+    focusedChildIndex = lastOnScreenChildIndex = -1
+
+    for child, i in children
+      if child.getPendingInFlow()
+        if isVertical
+          pos = child.getCurrentLocationY false, point0
+          size = child.getCurrentSize().y
+        else
+          pos = child.getCurrentLocationX false, point0
+          size = child.getCurrentSize().x
+
+        if pos < windowSize && pos + size > 0
+          firstOnScreenChildIndex = min i, firstOnScreenChildIndex
+          lastOnScreenChildIndex  = max i, lastOnScreenChildIndex
+          if child == focusedChild
+            focusedChildIndex = i
+
+    firstOnScreenChildIndex = -1 if firstOnScreenChildIndex == children.length
+
+    if (
+        firstOnScreenChildIndex != @_firstOnScreenChildIndex ||
+        lastOnScreenChildIndex  != @_lastOnScreenChildIndex ||
+        focusedChildIndex       != @_focusedChildIndex ||
+        focusedChild            != @_focusedChild
+      )
+      @queueEvent "scrollUpdate", =>
+        {
+          childrenSize
+          windowSize
+          focusedChild
+          firstOnScreenChildIndex
+          lastOnScreenChildIndex
+          focusedChildIndex
+        }
+
+    @_firstOnScreenChildIndex = firstOnScreenChildIndex
+    @_lastOnScreenChildIndex  = lastOnScreenChildIndex
+    @_focusedChildIndex       = focusedChildIndex
+
+    null

   # OUT: child's position relative to this, it's parent
   _findFocusedChild: (mainCoordinate, inFlowChildren, mainChildrenSize, mainElementSizeForChildren) ->
@@ -470,9 +530,16 @@
   animateToValidScrollPosition: ->
     pendingScrollPosition = @getScrollPosition true

-    @scrollPosition = switch @getTracking true
-      when "start"  then bound -@_maxScrollPosition, pendingScrollPosition, 0
-      when "end"    then bound 0, pendingScrollPosition, @_maxScrollPosition
+    {childrenSize, windowSize} = @
+
+    offscreenChildrenSize = childrenSize - windowSize
+
+    pendingTracking = @getTracking true
+    pendingTracking = null if childrenSize <= windowSize
+
+    @scrollPosition = switch pendingTracking
+      when "start"  then bound -offscreenChildrenSize, pendingScrollPosition, 0
+      when "end"    then bound 0, pendingScrollPosition, offscreenChildrenSize
       when null     then 0
       when "child"  then pendingScrollPosition

@@ -626,16 +693,16 @@
   #       @_scrollContents.setLocation @newPoint position
   # # maxCount = 5

-  # _scrollPositionChanged: ->
-  #   unless @_activelyScrolling
-  #     @queueEvent "scrollingActive"
-  #     @_activelyScrolling = true
-
-  #   @_lastScrollUpdatedAt = thisScrollUpdateWasAt = currentSecond()
-  #   timeout 250, =>
-  #     if @_lastScrollUpdatedAt == thisScrollUpdateWasAt
-  #       @_activelyScrolling = false
-  #       @queueEvent "scrollingIdle"
+  _scrollPositionChanged: ->
+    unless @_activelyScrolling
+      @queueEvent "scrollingActive"
+      @_activelyScrolling = true
+
+    @_lastScrollUpdatedAt = thisScrollUpdateWasAt = currentSecond()
+    timeout 250, =>
+      if @_lastScrollUpdatedAt == thisScrollUpdateWasAt
+        @_activelyScrolling = false
+        @queueEvent "scrollingIdle"

   # _updatePagesSplit: (pages = @getPendingPages(), referenceFrame = @getPendingReferenceFrame())->

@@ -751,8 +818,23 @@

   # ######################################
   @getter
-    windowSize:                -> @getMainCoordinate @getCurrentSize()
     isVertical:                -> @_childrenLayout == "column"
+
+    numChildrenOnScreen: ->
+      {isVertical, windowSize} = @
+      numChildrenOnScreen = 0
+
+      for child in @children when child.inFlow
+        if isVertical
+          pos = child.getCurrentLocationY false, point0
+          size = child.getCurrentSize().y
+        else
+          pos = child.getCurrentLocationX false, point0
+          size = child.getCurrentSize().x
+        numChildrenOnScreen++ if pos < windowSize && pos + size > 0
+
+      numChildrenOnScreen
+
   #   atEnd:                     -> @_atEnd
   #   atStart:                   -> @_atStart
   #   inMiddle:                  -> !@_atEnd && !@_atStart
@@ -1046,17 +1128,6 @@
   #   if referenceFrame.page != newCurrentPage || referenceFrame.atEndEdge != atEndEdge
   #     @setReferenceFrame page: newCurrentPage, atEndEdge: atEndEdge

-  # _queueUpdateEvent: (newReferenceFrame, previousReferenceFrame)->
-  #   @onNextReady =>
-  #     referenceFrame = @getReferenceFrame()
-  #     @queueEvent "scrollUpdate",
-  #       previousReferenceFrame:previousReferenceFrame
-  #       referenceFrame:       referenceFrame
-  #       focusedPage:          referenceFrame.page
-  #       currentGeometry:      @getCurrentGeometry()
-  #       pagesBeforeBaseline:  @getPagesBeforeBaseline()
-  #       pagesAfterBaseline:   @getPagesAfterBaseline()
-
   # _sizeChanged: (newSize, oldSize) ->
   #   @_queueUpdateEvent()
   #   super


label current

label current

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -18,7 +18,7 @@
   }
   _AUTHORITY: {
     i: 0
-    j: (world) -> world.get('_INDEX')
+    j: (world) -> world.get('_INDEX') / 1.5
     action_key: (world) -> world.get('_LABEL')
     action: (world) -> world.get('signals')[ world.get('action_key') ]
     name: (world) ->
@@ -38,21 +38,21 @@
     {
       _LABEL: "turtle_controls"
       _AUTHORITY: (world) -> world.get('BUTTON')
-      _CHILDREN: [{_LABEL: "left"}, {_LABEL: "front"}, {_LABEL: "right"}]
+      _CHILDREN: [
+        {
+          _LABEL: "current_selection"
+          stroke: "white"
+          fill: "white"
+          name: (world, args) -> world.get('current').label()
+        }
+        {_LABEL: "left"}, {_LABEL: "front"}, {_LABEL: "right"}
+      ]
     }
     {
       _LABEL: "active_program"
       _AUTHORITY: (world) -> world.get('BUTTON')
       _CHILDREN: [{_LABEL: "step"}, {_LABEL: "run"}, {_LABEL: "stop"}, {_LABEL: "reset"}]
     }
-    {
-      _LABEL: "current_selection"
-      stroke: "white"
-      fill: "white"
-      name: (world, args) ->
-        current = world.get('current')
-        "#{current} #{current.get('i')}x#{current.get('j')} -> #{current.get('v_i')}x#{current.get('v_j')}"
-    }
     {_LABEL: "program_selector"}
   ]
 }


layout gci

layout gci

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -28,16 +28,38 @@
   }
   _CHILDREN: [
     {
-      _LABEL: "current"
+      _LABEL: "run_mode"
+      _CHILDREN: [
+        {_LABEL: "stop"}
+        {_LABEL: "wait"}
+        {_LABEL: "loop"}
+      ]
+    }
+    {
+      _LABEL: "game_actions"
+      _CHILDREN: [
+        {_LABEL: "init"}
+        {_LABEL: "restart"}
+        {_LABEL: "help"}
+      ]
+    }
+    {
+      _LABEL: "current_selection"
       stroke: "white"
       fill: "white"
       name: (world, args) ->
         current = world.get('current')
         "#{current} #{current.get('i')}x#{current.get('j')} -> #{current.get('v_i')}x#{current.get('v_j')}"
     }
-    {_LABEL: "left", row: 1, col: 0}
-    {_LABEL: "front", row: 1, col: 1}
-    {_LABEL: "right", row: 1, col: 2}
-    {_LABEL: "step", row: 2, col: 0}
+    {
+      _LABEL: "available_commands"
+      _CHILDREN: [
+        {_LABEL: "left", col: 0}
+        {_LABEL: "front", col: 1}
+        {_LABEL: "right", col: 2}
+      ]
+    }
+    {_LABEL: "active_program", row: 3}
+    {_LABEL: "program_selector", row: 4}
   ]
 }


support field label property

support field label property

--- app/assets/javascripts/hub_client.coffee
+++ app/assets/javascripts/hub_client.coffee
@@ -54,9 +54,11 @@

 class MemberField
-  constructor: (@_parent, @_name, @_type) ->
+  constructor: (@_parent, @_name, @_type, @_def) ->
   name: ->
     @_name
+  label: ->
+    @_def.label || @name()
   type: ->
     @_type
   isStruct: ->
@@ -65,19 +67,19 @@
     (if @_parent then @_parent.path() else []).concat(@_name)

 class ValueMemberField extends MemberField
-  constructor: (parent, name, type) ->
-    super(parent, name, type)
+  constructor: (parent, name, type, def) ->
+    super(parent, name, type, def)
   visit: (callback) ->
     callback(@)

 class StructMemberField extends MemberField
-  constructor: (parent, name, @_isOpen) ->
-    super(parent, name, 'struct')
+  constructor: (parent, name, def) ->
+    super(parent, name, 'struct', def)
     @_fields = [] # Array of MemberField
   fields: ->
     @_fields
   isOpen: ->
-    @_isOpen
+    @_def.type.open
   visit: (callback) ->
     res = {}
     for field in @fields()
@@ -106,11 +108,11 @@
   _buildMemberField: (parent, name, def) ->
     type = def.type?.kind || def.type
     if type == 'struct'
-      res = new StructMemberField(parent, name, def.type.open)
-      @_appendFields(res, res.fields(), def.type.members)
+      res = new StructMemberField(parent, name, def)
+      @_appendFields(res, res.fields(), def.type.members, def)
       res
     else
-      new ValueMemberField(parent, name, type)
+      new ValueMemberField(parent, name, type, def)
   _appendFields: (parent, target, members) ->
     for name, def of members
       target.push @_buildMemberField(parent, name, def)


Cleaning

Cleaning

--- app/assets/javascripts/profile.js.coffee
+++ app/assets/javascripts/profile.js.coffee
@@ -1,10 +1,7 @@

 jQuery ->
   email = $('#user_email').val()
-  console.log email
   $('#user_email').keyup ->
-    console.log $('#user_email').val()
-    console.log email
     if $('#user_email').val() == email
       $('#edit_user.profile .btn').attr('disabled', 'disabled')
     else


Actually say what the error was when upload fails

--- app/assets/javascripts/uploads.js.coffee
+++ app/assets/javascripts/uploads.js.coffee
@@ -28,7 +28,6 @@
             else
               data.orig_name = 'image'
             data.submit().error (e) ->
-              console.log(e)
               handle_file_error(data, 'upload_failed', e.status + " " + e.statusText)
     progress: (e, data) ->
       percent = parseInt(data.loaded / data.total * 100, 10)


Modified x/y to h/v

Modified x/y to h/v

--- bokehjs/src/coffee/renderer/glyph/glyph_renderer.coffee
+++ bokehjs/src/coffee/renderer/glyph/glyph_renderer.coffee
@@ -62,8 +62,8 @@

       #TODO: Perhaps pass 'plot_view' through in the request instead of these fractions carved off
-      plot_x_range = @plot_view.frame.get('h_range')
-      plot_y_range = @plot_view.frame.get('v_range')
+      plot_h_range = @plot_view.frame.get('h_range')
+      plot_v_range = @plot_view.frame.get('v_range')
       data_x_range = @plot_view.x_range
       data_y_range = @plot_view.y_range

@@ -73,9 +73,9 @@
         if domain == 'x'
           serversource.listen_for_line1d_updates(
             @mget('data_source'),
-            plot_x_range, plot_y_range,
+            plot_h_range, plot_v_range,
             data_x_range, data_y_range,
-            plot_x_range,
+            plot_h_range,
             # XXX: @glyph.x.field (etc.) indicates this be moved to Glyph
             @glyph.glyph.y.field,
             @glyph.glyph.x.field,
@@ -87,7 +87,7 @@
       else if (resample_op == 'heatmap')
         serversource.listen_for_heatmap_updates(
            @mget('data_source'),
-           plot_x_range, plot_y_range,
+           plot_h_range, plot_v_range,
            data_x_range, data_y_range,
            transform_params
         )
@@ -96,7 +96,7 @@
            @plot_view
            @mget('data_source'),
              #TODO: Joseph -- Get rid of the next four params because we're passing in the plot_view
-           plot_x_range, plot_y_range,
+           plot_h_range, plot_v_range,
            data_x_range, data_y_range,
            transform_params)
       else


Reuse generic methods of saving selection+scroll

--- app/js/jail_iframe/util/highlight.coffee
+++ app/js/jail_iframe/util/highlight.coffee
@@ -52,20 +52,19 @@

   results

+
+searchAlgorithm =
+  if window.find
+    searchWithWindowFind
+  else
+    searchWithFindText
+
 # Function to search the page
 search = (searchString) ->
-  return false unless window.find?
+  ensureScrollSaved ->
+    ensureSelectionSaved ->
+      searchAlgorithm(searchString.trim())

-  # Store scroll settings to reset to afterwards
-  scrollTop = window.document.body.scrollTop
-  scrollLeft = window.document.body.scrollLeft
-
-  results = searchWithWindowFind(searchString)
-
-  # Scroll back to previous location
-  window.scroll scrollLeft, scrollTop
-
-  results

 # Adapted from https://github.com/okfn/annotator/blob/master/src/annotator.coffee
 highlightNode = (node, id) ->


Use explicit indexing in model lookup in multi-select change handler

--- src/coffee/cilantro/ui/controls/select.coffee
+++ src/coffee/cilantro/ui/controls/select.coffee
@@ -103,10 +103,8 @@
             @render()

         onSelectionChange: (event) ->
-            values = (option.value for option in event.target.options when option.selected)
-
-            @collection.each (model) ->
-                model.set('selected', model.get('value') in values)
+            @ui.items.children().each (i, el) =>
+                @collection.models[i].set('selected', el.selected)

             @change()


Use explicit indexing to change selected status of collection models

The reason for this change is that integer values in the parsed
collection were found to return undefined from the findWhere call
because of the evaluation used by Backbone here:

    http://backbonejs.org/docs/backbone.html#section-107

For that reason, we avoid any lookups(and thus comparison nuances) and
just use explicit indexes into the collection as the DOM order and
collection order are known to be the same.

--- src/coffee/cilantro/ui/controls/select.coffee
+++ src/coffee/cilantro/ui/controls/select.coffee
@@ -70,8 +70,8 @@

         onSelectionChange: (event) ->
             @ui.items.children().each (i, el) =>
-                if i is 0 then return
-                @collection.findWhere(value: el.value).set('selected', el.selected)
+                if i > 0
+                    @collection.models[i].set('selected', el.selected)

             @change()


Canned text needs a percentage also.

Canned text needs a percentage also.

--- shared/canned_text.coffee
+++ shared/canned_text.coffee
@@ -76,7 +76,7 @@
       POISON_CONTINUE: "$p was hurt by poison!"
       SLEEP_CONTINUE: "$p is fast asleep."
       BURN_CONTINUE: "$p was hurt by its burn!"
-      RECOVER_HP: "$p recovered $1 HP!"
+      RECOVER_HP: "$p recovered $1% HP!"
       TRICK_START: "$p switched items with its target!"
       TRICK_END: "$p obtained one $i!"
       THIEF_START: "$p stole $p's $i!"


Correct indentation

Correct indentation

--- src/components/performance/homework-cell.cjsx
+++ src/components/performance/homework-cell.cjsx
@@ -9,8 +9,6 @@
     message = if @isLate()
       'Incomplete'
     else
-    message =  "#{@props.task.correct_exercise_count}/#{@props.task.exercise_count}"
-    unless @props.task.status is 'completed'
-      message += if @isLate() then ' (Late)' else ' (Incomplete)'
+      "#{@props.task.correct_exercise_count}/#{@props.task.exercise_count}"

     @renderLink({message})


Treeview: making sure that data is always an array and present

--- src/components/tree/treeviewcontroller.coffee
+++ src/components/tree/treeviewcontroller.coffee
@@ -24,7 +24,7 @@
     dragHelper.src   = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAG4AAAAYCAMAAAAs/jgVAAAA0lBMVEX///+It9YAAAD///8AAACIt9aIt9aIt9aIt9aIt9aIt9YAAAD///+It9aIt9aIt9aIt9aIt9aIt9aIt9bT09OIt9aIt9aIt9b///+It9bv9fr+/v79/f2QvNn4+PioyuHA2enP4u/09PS41Obf7PTn8ff6+vr29vb3+vygxd78/Pz19fX7+/vs7OzY2NjR0dGwz+Tv7+/T09Pz8/PX19fQ0NCYwdvx8fHLy8vq6urZ2dnX5/H5+fny8vLOzs739/fPz8/W1tbu7u7w8PDH3ezd3d0P0SzzAAAAGHRSTlMAACZqGJkG2/k2rkZV4bG0V9gDaYBabJYxtX/TAAACLElEQVR4Xu3V127bMBQG4NT1SOw4s0kP59Ce3iN7tu//Sj2i0iiKE8AB7Juiv0SIFAR9OAQJ7mwy/9NsvElz61yDA4cEgGWQZA0Evm0xyAFAYkVMr/nCtU8PjsnGcnxw2n7lGMsSnmSMAX9slNxRh2w4naMXjrGEByzgCWPsqtdE7qT7ARDrOuCqL4LdE8tlyCCHWMZmjw3kWuSDuJ5V4K9iKl7BWl7LcgHnXOLFecAHV8jtkU841wqrHGbhreHtWS6QN5nMbiR2BjPkfhaAoh7QEQVfKFqMkNMAoEAp3wNwC05FQGNbXQwAeuEX7z2DXVP0NIAv6uUhl8gslFKGmUzkbIBcx3L4/XUUC+q+crYg5NDSkTBaRIpo33J2qj1NYhCeL2IwRFMBC6Lqy8VWJ8MwxSuUQTgpuN2SI0Tbf6xyKETKaAVFKs4OlWc/Kt4ZX5NadgvuPrxPp+E0xWc4mX2Bi4FgKi6ytVQcESZafMAtnWWKDdnhBLlOnYshJteW+2Vqk0ko8qOSG1FCDJKjihMuIXS0Mpnj6e341sE2HTuWa9U5YgCM5eJyqUQeYCHIxRRAl5ygoIUP4FVcOdSUeAbv16WSPz/l+WWePz3nl/PhpxthjV22zkZo9i4e7i7u5tgeLn4P7TbfDme3+f4PJ0wdZ+o4aegs5wXX7m6D67aRe18ecpizPtlw+mf2RHhfnuWwwPPDDYr9w/PyAFo9z7d8vGJ5399kf+cfyh+807YxJJdmLQAAAABJRU5ErkJggg=="
     dragHelper.width = 110

-  constructor:(options = {},data)->
+  constructor:(options = {}, data = [])->

     o = options
     o.view                      or= (new KDScrollView cssClass : "jtreeview-wrapper")
@@ -33,8 +33,8 @@
     o.listViewClass             or= JTreeView
     o.itemChildClass            or= null
     o.itemChildOptions          or= {}
-    o.nodeIdPath                or= "id"
-    o.nodeParentIdPath          or= "parentId"
+    o.nodeIdPath                or= 'id'
+    o.nodeParentIdPath          or= 'parentId'
     o.contextMenu                ?= no
     o.multipleSelection          ?= no
     o.addListsCollapsed          ?= no
@@ -109,7 +109,7 @@
     @nodes[@getNodeId nodeData] = {}

     if options.putDepthInfo
-      if @nodes[nodeData[pIdPath]]
+      if @nodes[nodeData[pIdPath]]?.getData
         nodeData.depth = @nodes[nodeData[pIdPath]].getData().depth + 1
       else
         nodeData.depth = 0


add unit test for property

add unit test for property

--- test/unit/property.coffee
+++ test/unit/property.coffee
@@ -56,4 +56,37 @@
 		body {
 			width: auto !important;
 		}
+	'''
+
+test 'with trailing ;', ->
+	assert.compileTo '''
+		body
+			margin: 0; padding: 0;
+	''', '''
+		body {
+			margin: 0;
+			padding: 0;
+		}
+	'''
+
+test 'with multiple trailing ;', ->
+	assert.compileTo '''
+		body
+			margin: 0;; padding: 0
+	''', '''
+		body {
+			margin: 0;
+			padding: 0;
+		}
+	'''
+
+test 'with trailing ; and !important', ->
+	assert.compileTo '''
+		body
+			margin: 0 !important;; padding: 0;
+	''', '''
+		body {
+			margin: 0 !important;
+			padding: 0;
+		}
 	'''


standardise google analytics event names

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -481,10 +481,10 @@
                 code = if isNew then 201 else 200
                 if isNew
                   code = 201
-                  action = 'created'
+                  action = 'create'
                 else
                   code = 200
-                  action = 'updated'
+                  action = 'update'
                 na.trackEvent 'tools', action, body.name
                 return resp.send code, tool


immediate fix which allows surveys to be saved

fixes issue preventing select-multiple questions from loading
fixes issue preventing surveys from saving
creates new issue where certain 'onchange' behavior will not be triggerd

Prompted by a "model.previousAttributes" is not defined error which
coincided with certain updates to the build environment (node version,
perhaps?)

--- jsapp/xlform/src/model.base.coffee
+++ jsapp/xlform/src/model.base.coffee
@@ -138,11 +138,11 @@
           @hidden = @get("value") is @_oValue

       @on "change:value", (rd, val, ctxt)=>
-        @_parent.trigger "change", @key, val, ctxt
+        # @_parent.trigger "change", @key, val, ctxt
         @_parent.trigger "detail-change", @key, val, ctxt
         @getSurvey().trigger "row-detail-change", @_parent, @key, val, ctxt
-      if @key is "type"
-        @on "change:list", (rd, val, ctxt)=>
-          @_parent.trigger "change", @key, val, ctxt
+      # if @key is "type"
+      #   @on "change:list", (rd, val, ctxt)=>
+      #     @_parent.trigger "change", @key, val, ctxt

   base


Add Events.DragAnimationDidEnd aliases

Add Events.DragAnimationDidEnd aliases

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -17,7 +17,12 @@
 Events.DragEnd               = "dragend"
 Events.DragAnimationStart 	 = "draganimationstart"
 Events.DragAnimationEnd   	 = "draganimationend"
-Events.DirectionLockStart = "directionlockstart"
+Events.DirectionLockStart    = "directionlockstart"
+
+# Add deprecated aliases
+Events.DragAnimationDidStart = Events.DragAnimationStart
+Events.DragAnimationDidEnd = Events.DragAnimationEnd
+

 """


Moved suggested_topics to variable

Moved suggested_topics to variable

--- core/app/backbone/views/notification_view.coffee
+++ core/app/backbone/views/notification_view.coffee
@@ -56,9 +56,10 @@
   onRender: ->
     super()

+    suggested_topics = new SuggestedTopics [@channel_as_topic(), @other_channel_as_topic()]
     @addBackRegion.show new AddChannelToChannelsButtonView
                                 model: @other_channel()
-                                suggested_topics: new SuggestedTopics [@channel_as_topic(), @other_channel_as_topic()]
+                                suggested_topics: suggested_topics


Only submit status on first pass heartbeat.

--- ext/user_heartbeat.coffee
+++ ext/user_heartbeat.coffee
@@ -18,26 +18,26 @@

 startHeartbeat = (userId,clientId) ->
   setTimeout ->
-    refreshPresence(userId,clientId)
+    refreshPresence(userId,clientId,undefined,true)
   , 2000
   setInterval ->
-    refreshPresence(userId,clientId,this)
+    refreshPresence(userId,clientId,this,false)
   , 30000

-refreshPresence = (userId,clientId,hearbeatInterval) ->
+refreshPresence = (userId,clientId,hearbeatInterval,firstPass) ->
   mongodb.collection 'users', (err, collection) ->
     console.log err if err
     collection.findOne { _id: new mongo.ObjectID(userId) }, (err,user) ->
       console.log err if err
-      checkStatusAndBroadcast(user,clientId,hearbeatInterval) if user != null
+      checkStatusAndBroadcast(user,clientId,hearbeatInterval,firstPass) if user != null

-checkStatusAndBroadcast = (user,clientId,hearbeatInterval) ->
+checkStatusAndBroadcast = (user,clientId,hearbeatInterval,firstPass) ->
   setUserHeartbeat(user,Date.now())

   fayengine.clientExists clientId, (connected,score) ->
     if connected
       status = user.preferred_status
-      broadcastStatus(user,status) unless status == "offline"
+      broadcastStatus(user,status) if (status != "offline") and (firstPass == true)
     else
       rediscli.get "cloudsdale/users/#{user._id.toString()}", (err, lastSeen) ->
         lastSeen ||= 0


Change escape sequence to unicode instead of octal. Fixes #204

--- vendor/assets/javascripts/mercury/regions/editable.js.coffee
+++ vendor/assets/javascripts/mercury/regions/editable.js.coffee
@@ -509,14 +509,14 @@

     if @range
       if @commonAncestor(true).closest('.mercury-snippet').length
-        lastChild = @context.createTextNode('\00')
+        lastChild = @context.createTextNode("\x00")
         element.appendChild(lastChild)
     else
       if element.lastChild && element.lastChild.nodeType == 3 && element.lastChild.textContent.replace(/^[\s+|\n+]|[\s+|\n+]$/, '') == ''
         lastChild = element.lastChild
-        element.lastChild.textContent = '\00'
+        element.lastChild.textContent = "\x00"
       else
-        lastChild = @context.createTextNode('\00')
+        lastChild = @context.createTextNode("\x00")
         element.appendChild(lastChild)

     if lastChild


Use Riot version live on Production (NA)

--- functions/championify.coffee
+++ functions/championify.coffee
@@ -53,9 +53,9 @@
 ###
 getRiotVer = (step) ->
   cl 'Getting LoL Version'
-  hlp.ajaxRequest 'http://ddragon.leagueoflegends.com/api/versions.json', (err, body) ->
+  hlp.ajaxRequest 'https://ddragon.leagueoflegends.com/realms/na.json', (err, body) ->
     hlp.updateProgressBar(1.5)
-    step null, body[0]
+    step null, body.v

 ###*


Make CoffeeScript work with jison 0.4.14+

Since zaach/jison commit 3548861b, `parser.lexer` is never modified anymore (a
copy of it is made, and that copy is modified instead). CoffeeScript itself
modifies `parser.lexer` and then accesses those modifications in the custom
`parser.yy.parseError` function, but that of course does not work anymore. This
commit puts the data that `parser.yy.parseError` needs directly on the `parser`
so that it is not lost.

Supersedes #3603. Fixes #3608 and zaach/jison#243.

--- coffee-script.coffee
+++ coffee-script.coffee
@@ -202,16 +202,17 @@
 # directly as a "Jison lexer".
 parser.lexer =
   lex: ->
-    token = @tokens[@pos++]
+    token = parser.tokens[@pos++]
     if token
       [tag, @yytext, @yylloc] = token
-      @errorToken = token.origin or token
+      parser.errorToken = token.origin or token
       @yylineno = @yylloc.first_line
     else
       tag = ''

     tag
-  setInput: (@tokens) ->
+  setInput: (tokens) ->
+    parser.tokens = tokens
     @pos = 0
   upcomingInput: ->
     ""
@@ -223,7 +224,7 @@
   # Disregard Jison's message, it contains redundant line numer information.
   # Disregard the token, we take its value directly from the lexer in case
   # the error is caused by a generated token which might refer to its origin.
-  {errorToken, tokens} = parser.lexer
+  {errorToken, tokens} = parser
   [errorTag, errorText, errorLoc] = errorToken

   errorText = if errorToken is tokens[tokens.length - 1]


disabled config button if node published

--- BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
+++ BackofficeBundle/Resources/public/coffee/page/nodeView.coffee
@@ -120,12 +120,12 @@
       @renderWidgetStatus()
       @addVersionToView()
       @addPreviewLink()
+      @addConfigurationButton()
       if @node.attributes.status.published
         $('.ui-model *', @el).unbind()
         $('.js-widget-blockpanel', @$el).hide()
-        $('span.action', @el).hide()
+        $('span.' + @node.cid, @el).addClass('disabled')
       else
-        @addConfigurationButton()
         $("ul.ui-model-areas, ul.ui-model-blocks", @$el).each ->
           refreshUl $(this)
     return


CalloutPanel manages columns, fixed up misc stats and pve stats

--- src/javascript/main.coffee
+++ src/javascript/main.coffee
@@ -286,7 +286,7 @@
   return eveIconUrl(STYLES[key].iconId, width)

 styleIconId = (key) ->
-  return STYLES[key].iconId
+  return STYLES[key]?.iconId

 eveIconUrl = (id, width = 32) ->
   return "https://image.eveonline.com/Type/#{id}_#{width}.png"
@@ -448,9 +448,7 @@

       pveStats = React.createElement(PvePanel, {stats: @state.stats})

-      miscModules1 = React.createElement(MiscModuleOnePanel, {stats: @state.stats})
-      miscModules2 = React.createElement(MiscModuleTwoPanel, {stats: @state.stats})
-      miscModules3 = React.createElement(MiscModuleThreePanel, {stats: @state.stats})
+      miscModules = React.createElement(MiscModulePanel, {stats: @state.stats})

       industryJobs = React.createElement(IndustryJobsPanel, {stats: @state.stats})
       blueprints = React.createElement(IndustryBlueprintPanel, {stats: @state.stats})
@@ -515,13 +513,9 @@

         # PVE Stats

-        dom.div {className: 'row'},
-          dom.div {className: 'col-md-4'}, miscModules1
-          dom.div {className: 'col-md-4'}, miscModules3
-          dom.div {className: 'col-md-4'}, miscModules2
-
-        dom.div {className: 'row'}
-          dom.div {className: 'col-md-6'}, pveStats
+        miscModules
+
+        pveStats

         # ISK / Markets
         dom.div {className: 'row'},
@@ -593,10 +587,10 @@
       dom.div {className: 'col-md-4'},
         React.createElement(CharacterAvatar, {id: @props.character.id})
       dom.div {className: 'col-md-8'},
-        React.createElement CalloutStat, {value: humanizeMinutes(@props.stats.characterMinutes), description: 'Time Played'}
-        React.createElement CalloutStat, {value: numFmt(@props.stats.daysOfActivity), description: 'Active Days'}
-        React.createElement CalloutStat, {value: numFmt(@props.stats.characterSessionsStarted), description: 'Logins'}
-        React.createElement CalloutStat, {value: humanizeMinutes(avgSession), description: 'Avereage Session Length'}
+        React.createElement CalloutStat, {value: @props.stats.characterMinutes, description: 'Time Played', formatter: humanizeMinutes}
+        React.createElement CalloutStat, {value: @props.stats.daysOfActivity, description: 'Active Days'}
+        React.createElement CalloutStat, {value: @props.stats.characterSessionsStarted, description: 'Logins'}
+        React.createElement CalloutStat, {value: avgSession, description: 'Avereage Session Length', formatter: humanizeMinutes}
 )

 CharacterAvatar = React.createClass(
@@ -613,6 +607,10 @@

 CalloutStat = React.createClass(
   displayName: 'CalloutStat'
+  getDefaultProps: ->
+    return {
+      formatter: numFmt
+    }
   render: ->
     icon = null
     iconWrap = null
@@ -622,8 +620,14 @@
       icon = dom.img {src: STYLES[@props.icon].icon}
     if icon
       iconWrap = dom.span {className: 'iconWrap'}, icon
+
+    if @props.formatter
+      value = @props.formatter(@props.value)
+    else
+      value = @props.value
+
     dom.div {className: 'callout'},
-      dom.div {className: 'value'}, iconWrap, @props.value
+      dom.div {className: 'value'}, iconWrap, value
       dom.div {className: 'description'}, @props.description
 )

@@ -713,14 +717,17 @@
       speed: 7700

   render: ->
-    vehicle = _.sample @vehicles
-    speedKmPerS = vehicle.speed / 1000
-    speedOfLightAu = speedKmPerS / 149597871
-    totalAu = @props.distance
-    travelSeconds = totalAu / speedOfLightAu
-    lightSpeedYears = numFmt(d3.round(travelSeconds / 60 / 60 / 24/ 365, 1))
-    distanceText = "#{vehicle.name} would take #{lightSpeedYears} years to cover your total distance travelled."
-    dom.h4 {className: 'pull-right'}, dom.em(null,distanceText)
+    if @props.distance > 0
+      vehicle = _.sample @vehicles
+      speedKmPerS = vehicle.speed / 1000
+      speedOfLightAu = speedKmPerS / 149597871
+      totalAu = @props.distance
+      travelSeconds = totalAu / speedOfLightAu
+      lightSpeedYears = numFmt(d3.round(travelSeconds / 60 / 60 / 24/ 365, 1))
+      distanceText = "#{vehicle.name} would take #{lightSpeedYears} years to cover your total distance travelled."
+      return dom.h4 {className: 'pull-right'}, dom.em(null,distanceText)
+    else
+      return null
 )

 KillsPanel = React.createClass(
@@ -903,28 +910,45 @@
     #   ehp: 22600000
     #   fit: 'https://o.smium.org/loadout/private/23322/7459269642280763392'
   render: ->
-    ship = _.sample @ships
-    numShips = d3.round(@props.damage / ship.ehp, 1)
-    stamps = []
-    for i in [0...Math.floor(numShips)]
-      stamps.push dom.img {key: i, src: eveIconUrl(ship.id), width: 32, height: 32, className: 'stamp pull-left'}
-
-    dom.div {className: 'row'},
-      dom.h4 {className: 'pull-left'},
-        dom.em null,"You have dealt enough damage to kill #{numShips} "
-          dom.a {href: ship.fit}, ship.pluralName
-      dom.div {className: 'clearfix'}, ''
-      dom.div {className: ''}, stamps
+    if @props.damage == 0
+      return null
+    else
+      ship = _.sample @ships
+      numShips = d3.round(@props.damage / ship.ehp, 1)
+      stamps = []
+      for i in [0...Math.floor(numShips)]
+        stamps.push dom.img {key: i, src: eveIconUrl(ship.id), width: 32, height: 32, className: 'stamp pull-left'}
+
+      return dom.div {className: 'row'},
+        dom.h4 {className: 'pull-left'},
+          dom.em null,"You have dealt enough damage to kill #{numShips} "
+            dom.a {href: ship.fit}, ship.pluralName
+        dom.div {className: 'clearfix'}, ''
+        dom.div {className: ''}, stamps

 )

 CalloutPanel = React.createClass(
   displayName: 'CalloutPanel'
+  getDefaultProps: ->
+    return {
+      columns: 1 # 1, 2, 3, 4, 6, 12
+    }
   render: ->
     if @props.callouts
       calloutElements = @props.callouts.map (callout) ->
         return React.createElement(CalloutStat, callout)
-      dom.div null, calloutElements
+      itemsPerCol = Math.ceil(calloutElements.length / @props.columns)
+      columns = []
+      colSize = 12 / @props.columns
+      for i in [0...@props.columns]
+        col = dom.div {className: "col-md-#{colSize}"}, _.take(calloutElements, itemsPerCol)
+        calloutElements.splice(0, itemsPerCol)
+        columns.push col
+      dom.div null,
+        dom.h3 null, @props.title
+        dom.div {className: 'row'},
+          columns
     else
       null
 )
@@ -1044,7 +1068,7 @@
   render: ->
     dom.div {className: 'row'},
       dom.h3 null, 'Local Reps'
-        React.createElement(PieDataPanel,{data: @chartData(), chartType: 'shipHp', layout: 'vertical'})
+        React.createElement(PieDataPanel,{data: @chartData(), chartType: 'shipHp', layout: 'vertical', showZero: true})
 )

 RepsGivenPanel = React.createClass(
@@ -1068,7 +1092,7 @@
   render: ->
     dom.div {className: 'row'},
       dom.h3 null, 'Remote Reps Given'
-        React.createElement(PieDataPanel,{data: @chartData(), chartType: 'shipHp', layout: 'vertical'})
+        React.createElement(PieDataPanel,{data: @chartData(), chartType: 'shipHp', layout: 'vertical', showZero: true})
 )

 RepsReceivedPanel = React.createClass(
@@ -1092,27 +1116,61 @@
   render: ->
     dom.div {className: 'row'},
       dom.h3 null, 'Reps Received'
-        React.createElement(PieDataPanel,{data: @chartData(), chartType: 'shipHp', layout: 'vertical'})
+        React.createElement(PieDataPanel,{data: @chartData(), chartType: 'shipHp', layout: 'vertical', showZero: true})
 )

 PvePanel = React.createClass(
   displayName: 'PvePanel'
   render: ->
-    dom.div null,
-      dom.h3 null, 'PVE'
-      dom.ul null,
-        dom.li null, "Completed #{numFmt @props.stats.pveMissionsSucceeded} missions"
-        dom.li null, "Completed #{numFmt @props.stats.pveMissionsSucceededEpicArc} epic arcs"
-        # These might be broken
-        #dom.li null, "Hacked #{numFmt @props.stats.industryArcheologySuccesses} relic cans"
-        #dom.li null, "Hacked #{numFmt @props.stats.industryHackingSuccesses} data cans"
-        dom.li null, "NPC Combat flagged #{numFmt @props.stats.combatNpcFlagSet} times"
-        dom.li null, "Taken #{numFmt @props.stats.combatDamageFromNPCsAmount} damage from NPCs"
-        dom.li null, "Scrammed #{numFmt @props.stats.combatWarpScrambledbyNPC} times by NPCs"
-        dom.li null, "Webbed #{numFmt @props.stats.combatWebifiedbyNPC} times by NPCs"
-)
-
-MiscModuleOnePanel = React.createClass(
+    if @props.stats
+      callouts = [
+        {
+          value: @props.stats.combatNpcFlagSet
+          description: 'NPC Flagged'
+          iconId: styleIconId 'npcFlag'
+        }
+        {
+          value: @props.stats.combatDamageFromNPCsAmount
+          description: 'Damage Taken from NPCs'
+          iconId: styleIconId 'npcDamage'
+        }
+        {
+          value: @props.stats.combatWarpScrambledbyNPC
+          description: 'Scrammed by NPC'
+          iconId: styleIconId 'scram'
+        }
+        {
+          value: @props.stats.combatWebifiedbyNPC
+          description: 'Webbed by NPC'
+          iconId: styleIconId 'web'
+        }
+        {
+          value: @props.stats.pveMissionsSucceeded
+          description: 'Missions Completed'
+          iconId: styleIconId 'mission'
+        }
+        {
+          value: @props.stats.pveMissionsSucceededEpicArc
+          description: 'Epic Arcs Completed'
+          iconId: styleIconId 'epicArc'
+        }
+        {
+          value: @props.stats.industryArcheologySuccesses
+          description: 'Relic Cans Hacked'
+          iconId: styleIconId 'relicCan'
+        }
+        {
+          value: @props.stats.industryHackingSuccesses
+          description: 'Data Cans Hacked'
+          iconId: styleIconId 'dataCan'
+        }
+      ]
+      return React.createElement(CalloutPanel, {title: 'PVE', callouts: callouts, columns: 4})
+    else
+      return null
+)
+
+MiscModulePanel = React.createClass(
   displayName: 'MiscModulePanelOne'
   render: ->
     if @props.stats
@@ -1127,53 +1185,33 @@
           description: 'Cynos Lit'
           iconId: styleIconId 'cyno'
         }
-      ]
-      return React.createElement(CalloutPanel, {callouts: callouts})
-    else
-      return null
-)
-MiscModuleTwoPanel = React.createClass(
-  displayName: 'MiscModulePanelTwo'
-  render: ->
-    if @props.stats
-      callouts = [
         {
           value: @props.stats.moduleActivationsFleetAssist
           description: 'Activated Gang Link'
           iconId: styleIconId 'gangLink'
         }
         {
+          value: @props.stats.moduleActivationsEwarECM
+          description: 'Violated Space Bushido'
+          iconId: styleIconId 'ecm'
+        }
+        {
+          value: @props.stats.moduleActivationsEwarDampener
+          description: 'Activated Sensor Damp'
+          iconId: styleIconId 'damp'
+        }
+        {
+          value: @props.stats.moduleActivationsEwarTargetPainter
+          description: 'Activated Target Painter'
+          iconId: styleIconId 'painter'
+        }
+        {
           value: @props.stats.moduleActivationsEwarVampire
           description: 'Activated NOS'
           iconId: styleIconId 'nos'
         }
       ]
-      return React.createElement(CalloutPanel, {callouts: callouts})
-    else
-      return null
-)
-MiscModuleThreePanel = React.createClass(
-  displayName: 'MiscModulePanelThree'
-  render: ->
-    if @props.stats
-      callouts = [
-        {
-          value: @props.stats.moduleActivationsEwarECM
-          description: 'Violated Space Bushido'
-          iconId: styleIconId 'ecm'
-        }
-        {
-          value: @props.stats.moduleActivationsEwarDampener
-          description: 'Activated Sensor Damp'
-          iconId: styleIconId 'damp'
-        }
-        {
-          value: @props.stats.moduleActivationsEwarTargetPainter
-          description: 'Activated Target Painter'
-          iconId: styleIconId 'painter'
-        }
-      ]
-      return React.createElement(CalloutPanel, {callouts: callouts})
+      return React.createElement(CalloutPanel, {callouts: callouts, columns: 4})
     else
       return null
 )
@@ -1222,7 +1260,7 @@
     return data
   render: ->
     dom.div {className: 'row'},
-      dom.h3 null, 'Manufacturing Jobs'
+      dom.h3 null, 'Manufacturing Jobs', ' ', dom.small(null, 'Units Produced')
         React.createElement(PieDataPanel,{data: @chartData()})
 )

@@ -1333,9 +1371,9 @@
     ]
     return data
   render: ->
-    dom.div {className: 'row'},
+    dom.div {className: ''},
       dom.h3 null, 'Mining'
-        React.createElement(PieDataPanel,{data: @chartData()})
+      React.createElement(PieDataPanel,{data: @chartData()})
 )

 ISKPanel = React.createClass(
@@ -1399,10 +1437,10 @@
         value: @props.stats["social#{str}ContactHorrible"]
       }
     ]
-    dom.div {className: 'row'},
+    dom.div {className: ''},
       dom.h3 null,
         title
-      React.createElement(PieDataPanel, {data: data})
+      React.createElement(PieDataPanel, {data: data, showZero: true})

 )

@@ -1444,6 +1482,7 @@
     return {
       chartType: 'pie'
       layout: 'horizontal'
+      showZero: false
     }
   deriveData: (sortFn) ->
     tmp = _.clone @props.data
@@ -1466,8 +1505,11 @@
         sortFn = (a, b) -> return HP_BAR_ORDER.indexOf(a.key) - HP_BAR_ORDER.indexOf(b.key)
     data = @deriveData(sortFn)

+    if @total == 0
+      return dom.div null, "No activity this year"
+
     chart = React.createElement(chartElementType, {data: data, total: @total})
-    table = React.createElement(PieDataTable,{data: data, total: @total})
+    table = React.createElement(PieDataTable,{data: data, total: @total, showZero: @props.showZero})

     if @props.layout is 'horizontal'
       return dom.div {className: 'row'},
@@ -1481,9 +1523,15 @@

 PieDataTable = React.createClass(
   displayName: 'PieDataTable'
+  getDefaultProps: ->
+    return {
+      showZero: false
+    }
   render: ->
     colors = d3.scale.category20()
     rows = _.map @props.data, (d) =>
+      if d.value == 0 and not @props.showZero
+        return null
       color = STYLES[d.key].color ? colors(d.key)
       iconCell = dom.td null,
         dom.div {className: "circle", style: {backgroundColor: color}}, null


Made 'element' and 'id' properties available on Subscriber.

--- src/js/OTSubscriber.coffee
+++ src/js/OTSubscriber.coffee
@@ -2,6 +2,7 @@
 #   Properties:
 #     id (string) - dom id of the subscriber
 #     stream (Stream) - stream to which you are subscribing
+#     element (Element) - HTML DOM element containing the Subscriber
 #   Methods:
 #     getAudioVolume()
 #     getImgData() : String
@@ -35,6 +36,8 @@

   constructor: (stream, divName, properties) ->
     element = document.getElementById(divName)
+    @id = divName
+    @element = element
     pdebug "creating subscriber", properties
     @streamId = stream.streamId
     if(properties? && properties.width=="100%" && properties.height == "100%")


parameter check on project_id

parameter check on project_id

--- services/clsi/app.coffee
+++ services/clsi/app.coffee
@@ -35,6 +35,12 @@
 	req.setTimeout TIMEOUT
 	res.setTimeout TIMEOUT
 	next()
+
+app.param 'project_id', (req, res, next, project_id) ->
+	if project_id?.match /^[a-zA-Z0-9_-]+$/
+		next()
+	else
+		next new Error("invalid project id")

 app.post   "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile
 app.delete "/project/:project_id", CompileController.clearCache


less brackets in grammar cson

less brackets in grammar cson

--- grammars/language-idris.cson
+++ grammars/language-idris.cson
@@ -56,14 +56,12 @@
       name: 'string.quoted.idris'
       begin: '"'
       beginCaptures:
-        {
-          0: { name: 'punctuation.definition.string.begin.idris' }
-        }
+        0:
+          name: 'punctuation.definition.string.begin.idris'
       end: '"'
       endCaptures:
-        {
-          0: { name: 'punctuation.definition.string.end.idris' }
-        }
+        0:
+          name: 'punctuation.definition.string.end.idris'
       patterns:
         [
           {
@@ -76,14 +74,12 @@
       name: 'keyword.operator.function.infix.idris'
       begin: '`'
       beginCaptures:
-        {
-          0: { name: 'punctuation.definition.entity.idris' }
-        }
+        0:
+          name: 'punctuation.definition.entity.idris'
       end: '`'
       endCaptures:
-        {
-          0: { name: 'punctuation.definition.entity.idris' }
-        }
+        0:
+          name: 'punctuation.definition.entity.idris'
     }
   ]
 repository:


Exclude non-published docs from feeds or sitemap.xml

--- site/server/views.coffee
+++ site/server/views.coffee
@@ -86,7 +86,7 @@
       date = doc.updated_at
       date = doc.published_at unless date
       date = new Date().toISOString() unless date
-      if types.indexOf(doc.type) >= 0
+      if types.indexOf(doc.type) >= 0 and doc.published
         emit [doc.site, 'content', date, doc.type, doc.slug], null
       else if others.indexOf(doc.type) >= 0
         emit [doc.site, 'x-other', date, doc.type, doc.slug], null


Did Stuff!

Did Stuff!

--- src/live_controller.coffee
+++ src/live_controller.coffee
@@ -1,105 +1,109 @@
-zipObject = (k, v) ->
-  arr = _.zip(k, v)
-  red = (m, x)->
-    m[x[0]] = x[1]
-    return m
-  _.reduce(arr, red, {})
-
-merge = _.extend
-
-# from: https://github.com/senchalabs/connect/blob/master/lib/middleware/router.js
-
-normalizePath = (path, keys) ->
-
-  buildRegex  = (_, slash, format, key, capture, optional) ->
-    keys.push(key)
-    slash ?= ''
-    return '' +
-      (if optional then '' else slash) +
-      '(?:' +
-      (if optional then slash else '') +
-      (format || '') + (capture || '([^/]+?)') + ')' +
-      (optional || '')
-
-  path = path.concat('/?')
-             .replace(/\/\(/g, '(?:/')
-             .replace( /(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, buildRegex)
-             .replace(/([\/.])/g, '\\$1')
-             .replace(/\*/g, '(.+)')
-  return new RegExp('^' + path + '$', 'i')
-
-getHelper = (url) ->
-  helperClassName = url.replace /^[a-zA-Z]|\/[a-zA-Z]/g, (x) -> x.toUpperCase()
-  helperClassName = helperClassName.replace(/\//g, "")+ "Helper"
-  return window[helperClassName]
-
-window.onpopstate = (event) ->
-  event ?= {}
-  state = event.state || {}
-  method = state.method || "get"
-  params = state.params || {}
-  Router[method](document.location.pathname, params)
-
-routes = {}
+routes =
+  get: []
+  post: []
+  put: []
+  delete: []
+
 routeMethods = ["get", "post", "put", "delete"]

-matchRoute = (method, url, params, push) ->
-  params ?= {}
+define ["liveView", "jquery", "underscore"], (LiveView, $, _) ->

-  for i in [0...routes[method].length]
-    obj = routes[method][i]
-    matches = url.match obj.regex
-    if matches
-      new LiveView "/views" + url + ".html", {}, (view) ->
-        ctor = getHelper url
-        if ctor?
-          helper = new ctor(view)
-        else
-          helper = () ->
-        merge params, zipObject(obj.keys, matches.slice(1))
-        methods = {
-                    url: url,
-                    view: view,
-                    autoRender: true
-                    helper: helper
-                  }
-
-        stopRoute = false
-        _.each obj.filters.before, (filter) ->
-          if !stopRoute && filter.call(methods, params) == false
-            stopRoute = true
-        if stopRoute
-          return
+  zipObject = (k, v) ->
+    arr = _.zip(k, v)
+    red = (m, x)->
+      m[x[0]] = x[1]
+      return m
+    _.reduce(arr, red, {})

-        console.log(obj)
-        obj.callback.call methods, params
+  merge = _.extend

-        _.each obj.filters.after, (filter) ->
-          filter(params)
+  # from: https://github.com/senchalabs/connect/blob/master/lib/middleware/router.js

-        if push
-          $("body").attr("class", url.replace(/\//g, " ").trim())
-          window.history.pushState { method: method, params: params }, "", url
-        if(methods.autoRender)
-          $("#main").html(methods.view.context)
-      return
+  normalizePath = (path, keys) ->

-registerRoute = (method, route, filters, callback) ->
-  keys = []
-  routes[method].push { "regex": normalizePath(route, keys)
-                      , "keys": keys
-                      , "original": route
-                      , "filters": filters
-                      , "callback": callback }
+    buildRegex  = (_, slash, format, key, capture, optional) ->
+      keys.push(key)
+      slash ?= ''
+      return '' +
+        (if optional then '' else slash) +
+        '(?:' +
+        (if optional then slash else '') +
+        (format || '') + (capture || '([^/]+?)') + ')' +
+        (optional || '')

-class Controller
-  constructor: (base, scope) ->
+    path = path.concat('/?')
+               .replace(/\/\(/g, '(?:/')
+               .replace( /(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, buildRegex)
+               .replace(/([\/.])/g, '\\$1')
+               .replace(/\*/g, '(.+)')
+    return new RegExp('^' + path + '$', 'i')
+
+  getHelper = (url) ->
+    helperClassName = url.replace /^[a-zA-Z]|\/[a-zA-Z]/g, (x) -> x.toUpperCase()
+    helperClassName = helperClassName.replace(/\//g, "")+ "Helper"
+    return window[helperClassName]
+
+  window.onpopstate = (event) ->
+    event ?= {}
+    state = event.state || {}
+    method = state.method || "get"
+    params = state.params || {}
+    Router[method](document.location.pathname, params)
+
+  matchRoute = (method, url, params, push) ->
+    params ?= {}
+
+    console.log routes
+    for i in [0...routes[method].length]
+      obj = routes[method][i]
+      matches = url.match obj.regex
+      if matches
+        new LiveView "/views" + url + ".html", {}, (view) ->
+          ctor = getHelper url
+          if ctor?
+            helper = new ctor(view)
+          else
+            helper = () ->
+          merge params, zipObject(obj.keys, matches.slice(1))
+          methods =
+            url: url,
+            view: view,
+            autoRender: true
+            helper: helper
+
+          stopRoute = false
+          _.each obj.filters.before, (filter) ->
+            if !stopRoute && filter.call(methods, params) == false
+              stopRoute = true
+          if stopRoute
+            return
+
+          console.log(obj)
+          obj.callback.call methods, params
+
+          _.each obj.filters.after, (filter) ->
+            filter(params)
+
+          if push
+            $("body").attr("class", url.replace(/\//g, " ").trim())
+            window.history.pushState { method: method, params: params }, "", url
+          if(methods.autoRender)
+            $("#main").html(methods.view.context)
+        return
+
+  registerRoute = (method, route, filters, callback) ->
+    keys = []
+    routes[method].push { "regex": normalizePath(route, keys)
+                        , "keys": keys
+                        , "original": route
+                        , "filters": filters
+                        , "callback": callback }
+
+  Controller = (base, scope) ->
+
     router = {}
     filters = {before:[], after:[]}
     routeMethods.forEach (method) ->
-
-      if not _.isArray(routes[method])
-        routes[method] = []

       router[method] = (route, callback) ->
         if typeof route != "string"
@@ -114,29 +118,22 @@
       filters.after.push filter

     scope router
+    console.log routes

-    window.onPopState = (event) ->
-      params = event.state.params
-      method = event.state.method
-      url = document.location.pathname

-      if method == "get" || method == "delete"
-        this[method](url, false)
+  exports = {}
+  exports.Controller = Controller
+  exports.Router = Router =
+    get: (url, push) ->
+      matchRoute("get", url, {}, push)

-      if method == "put" || method == "post"
-        this[method](url, params, false)
+    post: (url, params, push) ->
+      matchRoute("post", url, params, push)

-window.Controller = Controller
-window.Router = {
-  get: (url, push) ->
-    matchRoute("get", url, {}, push)
+    put: (url, params, push) ->
+      matchRoute("put", url, params, push)

-  post: (url, params, push) ->
-    matchRoute("post", url, params, push)
+    delete: (url, push) ->
+      matchRoute("delete", url, {}, push)

-  put: (url, params, push) ->
-    matchRoute("put", url, params, push)
-
-  delete: (url, push) ->
-    matchRoute("delete", url, {}, push)
-}
+  return exports


Fixing typo

Fixing typo

--- source/core/module.coffee
+++ source/core/module.coffee
@@ -55,7 +55,7 @@
   @method template
   @param  {value}    String or Function
   ###
-  @template = (value) ->
+  @template: (value) ->
     if value? and typeof value is "string"
       @template = value
     else


link commits with bezier curve

link commits with bezier curve

--- content/gitoe-draw.coffee
+++ content/gitoe-draw.coffee
@@ -10,9 +10,10 @@
   #   - better looking
   #   want: referencing to position of parents
   # XXX Maybe re-layouting
+  # XXX Maybe layout in layer --- how to trigger?
   constructor: (@cb)->
     # @cb:
-    #   draw_node : ( id, layer, pos, height )
+    #   draw_node : ( id, layer, pos )

     # topo
     @children = {}  # { id: [ children ] }
@@ -153,20 +154,23 @@
     commit_box = @draw_commit_box(coord)
     text = @draw_commit_text(coord, sha1)
     paths = @draw_paths(coord, parents)
+    need_focus = false
     if coord.left > @canvas_size.width
       @canvas_inc_width()
-      @focus(coord)
+      need_focus = true
     if coord.top > @canvas_size.height
       @canvas_inc_height()
+      need_focus = true
+    if need_focus
       @focus(coord)
     @objs[sha1] = {
-      commit_box: commit_box
-      text: text
-      paths: paths
+      commit_box : commit_box
+      text       : text
+      paths      : paths
     }

   commit_coord: (layer,pos)->{
-    # left and top coord of cell
+    # left and top coord of commit-box
     left: (@constant.padding_left + @constant.outer_width  * pos)
     top : (@constant.padding_top  + @constant.outer_height * layer)
   }
@@ -196,14 +200,46 @@
     ].join ' '
     for p in parents_pos
       coord_p = @commit_coord(p.layer, p.pos)
-      path_command = start + [
+      path_command = @path_command(coord, coord_p)
+      if path_command
+        paths.push @canvas.path( start + path_command )
+    paths
+
+  path_command: ( coord, coord_p )->
+    # command string to draw path,
+    # ref http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
+    bottom_of_parent = {
+      x: coord_p.left + @constant.box.width / 2
+      y: coord_p.top  + @constant.box.height
+    }
+    top_of_highest_fake_node = {
+      x: coord.left  + @constant.box.width / 2
+      y: coord_p.top + @constant.outer_height
+    }
+    if coord.left == coord_p.left # same column
+      [
         'L'
-        coord_p.left + @constant.box.width/2
-        coord_p.top  + @constant.box.height
+        bottom_of_parent.x
+        bottom_of_parent.y
       ].join ' '
-      paths.push @canvas.path( path_command )
-    paths
-
+    else
+      vertical_distance = @constant.outer_height - @constant.box.height
+      ratio = 0.3
+      [
+        'L'
+        top_of_highest_fake_node.x
+        top_of_highest_fake_node.y
+        'C'
+        top_of_highest_fake_node.x
+        @mix( top_of_highest_fake_node.y, bottom_of_parent.y , ratio)
+        bottom_of_parent.x
+        @mix( top_of_highest_fake_node.y, bottom_of_parent.y , 1-ratio)
+        bottom_of_parent.x
+        bottom_of_parent.y
+      ].join ' '
+
+  mix: (a,b,ratio)->
+    a*ratio + b*(1-ratio)
   focus: (coord)->
     @div.scrollTo {
       left: coord.left - 500


cleanup

cleanup

--- content/serializer.coffee
+++ content/serializer.coffee
@@ -16,7 +16,7 @@

     debug('Serializer.init')
     mapping = yield Zotero.DB.queryAsync("""
-      SELECT bf.fieldName as baseName, it.typeName, f.fieldName
+      SELECT bf.fieldName, it.typeName, f.fieldName as fieldAlias
       FROM baseFieldMappingsCombined bfmc
       join fields bf on bf.fieldID = bfmc.baseFieldID
       join fields f on f.fieldID = bfmc.fieldID
@@ -25,14 +25,14 @@
     """)

     mapping = mapping.reduce((map, alias) ->
-      map[alias.baseName] ||= {}
-      map[alias.baseName]['item.' + alias.fieldName] = true
+      map[alias.fieldName] ||= {}
+      map[alias.fieldName]['item.' + alias.fieldAlias] = true
       return map
     , {})

     simplify = ''
-    for baseName, aliases of mapping
-      simplify += "if (item.#{baseName} == null) { item.#{baseName} = #{Object.keys(aliases).join(' || ')}; }\n"
+    for fieldName, aliases of mapping
+      simplify += "if (item.#{fieldName} == null) { item.#{fieldName} = #{Object.keys(aliases).join(' || ')}; }\n"
     simplify += 'item.tags = item.tags ? item.tags.map(function(tag) { return tag.tag }) : [];'
     simplify += 'return item;'


Remove marker count assertions

Remove marker count assertions

These are breaking on ns-editor-marker-layers and honestly we should
just be testing the behavior anyway.

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -456,7 +456,6 @@
         simulateTabKeyEvent()
         expect(editor.lineTextForBufferRow(0)).toBe "with placeholder test"
         expect(editor.lineTextForBufferRow(1)).toBe "without placeholder var quicksort = function () {"
-        expect(editor.getMarkerCount()).toBe 3

         expect(editor.getSelectedBufferRange()).toEqual [[0, 17], [0, 21]]

@@ -641,7 +640,6 @@
       Snippets.insert("hello ${1:world}", editor)

       expect(editor.lineTextForBufferRow(0)).toBe "var hello world = function () {"
-      expect(editor.getMarkerCount()).toBe 2
       expect(editor.getSelectedBufferRange()).toEqual [[0, 10], [0, 15]]

   describe "when the 'snippets:available' command is triggered", ->


Fix multi-caret spec

Fix multi-caret spec

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -91,7 +91,6 @@
             prefix: "t9"
             body: """
               with placeholder ${1:test}
-              and again ${1:test}
               without placeholder $1
             """

@@ -329,12 +328,10 @@
         editor.insertText('t9')
         editorView.trigger keydownEvent('tab', target: editorView[0])
         expect(buffer.lineForRow(0)).toBe "with placeholder test"
-        expect(buffer.lineForRow(1)).toBe "and again test"
-        expect(buffer.lineForRow(2)).toBe "without placeholder "
+        expect(buffer.lineForRow(1)).toBe "without placeholder var quicksort = function () {"
         editor.insertText('hello')
         expect(buffer.lineForRow(0)).toBe "with placeholder hello"
-        expect(buffer.lineForRow(1)).toBe "and again hello"
-        expect(buffer.lineForRow(2)).toBe "without placeholder hello"
+        expect(buffer.lineForRow(1)).toBe "without placeholder hello"
         expect(editor.getMarkerCount()).toBe 3

   describe "snippet loading", ->


Fix tests for proper tab stops

Fix tests for proper tab stops

--- spec/snippets-spec.coffee
+++ spec/snippets-spec.coffee
@@ -275,12 +275,12 @@

       describe "when multiple snippets match the prefix", ->
         it "expands the snippet that is the longest match for the prefix", ->
-          editor.insertText('t13')
-          expect(editor.getCursorScreenPosition()).toEqual [0, 3]
-
-          editorView.trigger keydownEvent('tab', target: editorView[0])
-          expect(buffer.lineForRow(0)).toBe "t13  var quicksort = function () {"
-          expect(editor.getCursorScreenPosition()).toEqual [0, 5]
+          editor.insertText('t113')
+          expect(editor.getCursorScreenPosition()).toEqual [0, 4]
+
+          editorView.trigger keydownEvent('tab', target: editorView[0])
+          expect(buffer.lineForRow(0)).toBe "t113  var quicksort = function () {"
+          expect(editor.getCursorScreenPosition()).toEqual [0, 6]

           editor.undo()
           editor.undo()
@@ -304,12 +304,12 @@

     describe "when the letters preceding the cursor don't match a snippet", ->
       it "inserts a tab as normal", ->
-        editor.insertText("xte")
-        expect(editor.getCursorScreenPosition()).toEqual [0, 3]
-
-        editorView.trigger keydownEvent('tab', target: editorView[0])
-        expect(buffer.lineForRow(0)).toBe "xte  var quicksort = function () {"
-        expect(editor.getCursorScreenPosition()).toEqual [0, 5]
+        editor.insertText("xxte")
+        expect(editor.getCursorScreenPosition()).toEqual [0, 4]
+
+        editorView.trigger keydownEvent('tab', target: editorView[0])
+        expect(buffer.lineForRow(0)).toBe "xxte  var quicksort = function () {"
+        expect(editor.getCursorScreenPosition()).toEqual [0, 6]

     describe "when text is selected", ->
       it "inserts a tab as normal", ->


correct register value in S keybinding spec

the newline should be in the register

--- spec/vim-mode-spec.coffee
+++ spec/vim-mode-spec.coffee
@@ -160,7 +160,7 @@
         expect(editor).toHaveClass 'insert-mode'
         expect(editor.getText()).toBe '12345\n\nABCDE'
         expect(editor.getCursorScreenPosition()).toEqual [1, 0]
-        expect(vimState.getRegister('"').text).toBe 'abcde'
+        expect(vimState.getRegister('"').text).toBe "abcde\n"

     describe "the d keybinding", ->
       describe "when followed by a d", ->


Update uk.coffee (#5793)

Update uk.coffee (#5793)

* Updated uk.coffee

* Update uk.coffee

* Update uk.coffee

Co-authored-by: Matias Forbord <7d4dc3970a879d64548b3a155f22f7990dc8fc0c@users.noreply.github.com>
Co-authored-by: Valentin Bryukhanov <53545457af642b0f8712bb2cc004f120ce4436f4@gmail.com>


Export base models

Export base models


Compute selectors only when needed

Compute selectors only when needed


thanks for the style help atom

thanks for the style help atom

--- app/graph_drawing.coffee
+++ app/graph_drawing.coffee
@@ -78,49 +78,49 @@
   mouse_moves = new Bacon.Bus

   area = d3.svg.area()
-  .x((d) -> d.x)
-  .y0((d) -> y d.y0 ? 0)
-  .y1((d) -> y d.value + (d.y0 ? 0))
-  .defined((d) -> d.value?)
+    .x((d) -> d.x)
+    .y0((d) -> y d.y0 ? 0)
+    .y1((d) -> y d.value + (d.y0 ? 0))
+    .defined((d) -> d.value?)

   line = d3.svg.line()
-  .x((d) -> d.x)
-  .y((d) -> y d.value)
-  .defined((d) -> d.value?)
+    .x((d) -> d.x)
+    .y((d) -> y d.value)
+    .defined((d) -> d.value?)

   stack = d3.layout.stack()
-  .values((d) -> d.values)
-  .x((d) -> d.time)
-  .y((d) -> d.value)
-  .out((d, y0, y) ->
-    d.y0 = y0
-    d.value = y)
+    .values((d) -> d.values)
+    .x((d) -> d.time)
+    .y((d) -> d.value)
+    .out((d, y0, y) ->
+      d.y0 = y0
+      d.value = y)

   svg = d3.select(container).append('svg')
-  .on('mousemove', (d, i) -> mouse_moves.push d3.mouse g.node())
+    .on('mousemove', (d, i) -> mouse_moves.push d3.mouse g.node())

   g = svg
-  .append("g")
+    .append("g")

   xAxisG = g.append('g')
-  .attr('class', 'x axis')
+    .attr('class', 'x axis')

   yXaxisG = g.append('g')
-  .attr('class', 'y axis')
+    .attr('class', 'y axis')

   vertical_crosshair = g.append('line')
-  .attr('class', 'crosshair')
-  .attr('y1', 0)
+    .attr('class', 'crosshair')
+    .attr('y1', 0)

   crosshair_time = g.append('text')
-  .attr('class', 'crosshair-time')
-  .attr('y', -6)
+    .attr('class', 'crosshair-time')
+    .attr('y', -6)

   brushG = g.append("g")
-  .attr("class", "x brush")
+    .attr("class", "x brush")

   legend = d3.select(container).append('ul')
-  .attr('class', 'legend')
+    .attr('class', 'legend')

   currentCrosshairTime = null
   allNames = []
@@ -194,8 +194,8 @@
         circles.attr('r', 4)
       selection.classed 'hovered', true
     unhovers = hover_selections.merge(mouse_out)
-    .withStateMachine([], (previous, event) -> [[event], previous])
-    .filter (e) -> e.selection?
+      .withStateMachine([], (previous, event) -> [[event], previous])
+      .filter (e) -> e.selection?
     unhovers.onValue ({selection, index}) ->
       selection.classed 'hovered', false
       if type == 'line'
@@ -304,22 +304,22 @@
       target.scatterValues = filter_scatter_values values

     svg
-    .attr('width', width + margin.left + margin.right)
-    .attr('height', height + margin.top + margin.bottom)
-    .style('background-color', params.bgcolor)
+      .attr('width', width + margin.left + margin.right)
+      .attr('height', height + margin.top + margin.bottom)
+      .style('background-color', params.bgcolor)

     g
-    .attr("transform", "translate(#{margin.left},#{margin.top})")
+      .attr("transform", "translate(#{margin.left},#{margin.top})")

     xAxisG
-    .attr('class', 'x axis')
-    .attr('transform', "translate(0, #{height})")
-    .call(x_axis)
+      .attr('class', 'x axis')
+      .attr('transform', "translate(0, #{height})")
+      .call(x_axis)

     yXaxisG.call(y_axis)

     vertical_crosshair
-    .attr('y2', height)
+      .attr('y2', height)

     brushed = ->
       brushBus.push if brush.empty() then clearExtent else (v) -> _.extend {}, v, {extent: brush.extent()}
@@ -327,10 +327,10 @@
     brushend = -> brushBus.push setNotBrushing

     brush = d3.svg.brush()
-    .x(x)
-    .on("brush", brushed)
-    .on('brushstart', brushstart)
-    .on('brushend', brushend)
+      .x(x)
+      .on("brush", brushed)
+      .on('brushstart', brushstart)
+      .on('brushend', brushend)

     brushG.call(brush)

@@ -343,19 +343,19 @@
       brush brushG

     brushG.selectAll("rect")
-    .attr("y", 0)
-    .attr("height", height)
-    .attr('fill', '#efefef')
+      .attr("y", 0)
+      .attr("height", height)
+      .attr('fill', '#efefef')

     positionCrosshair = (x, time) ->
       currentCrosshairTime = time
       vertical_crosshair
-      .attr('x1', x)
-      .attr('x2', x)
+        .attr('x1', x)
+        .attr('x2', x)

       crosshair_time
-      .text(moment(time).format('lll'))
-      .attr('x', x)
+        .text(moment(time).format('lll'))
+        .attr('x', x)

       target_values = _.map targets, (t) ->
         i = t.bisector.left t.values, time, 1
@@ -367,8 +367,8 @@
           d0

       legend.selectAll('.crosshair-value')
-      .data(target_values)
-      .text((d) -> d?.value)
+        .data(target_values)
+        .text((d) -> d?.value)

     mouse_position.onValue (p) ->
       positionCrosshair p.x, p.time
@@ -390,10 +390,10 @@
       boundedPositionCrosshair(time)

     target = g.selectAll('.target')
-    .data(targets)
+      .data(targets)
     target.enter().append("g")
-    .attr('class', (d, i) -> "target target#{i}")
-    .call(observe_mouse)
+      .attr('class', (d, i) -> "target target#{i}")
+      .call(observe_mouse)
     target.exit().remove()
     target.selectAll('g > *').remove()

@@ -405,19 +405,19 @@
         lineWidth = params.lineWidth

       path = target.append("path")
-      .attr('class', (d) -> d.lineMode)
-      .attr('stroke', (d, i) -> d.color)
-      .style('stroke-width', lineWidth)
-      .attr('fill', (d, i) -> if d.lineMode is 'area' then d.color)
-      .attr('d', (d, i) -> d.lineFn(d.lineValues))
+        .attr('class', (d) -> d.lineMode)
+        .attr('stroke', (d, i) -> d.color)
+        .style('stroke-width', lineWidth)
+        .attr('fill', (d, i) -> if d.lineMode is 'area' then d.color)
+        .attr('d', (d, i) -> d.lineFn(d.lineValues))
       if hover
         path
-        .style('stroke-opacity', 0)
-        .style('fill-opacity', 0)
+          .style('stroke-opacity', 0)
+          .style('fill-opacity', 0)
       else
         path
-        .style('stroke-opacity', line_opacity)
-        .style('fill-opacity', area_opacity)
+          .style('stroke-opacity', line_opacity)
+          .style('fill-opacity', area_opacity)

     add_circles = (target, hover) ->
       if hover
@@ -446,25 +446,25 @@
       add_circles target, true

     legend_target = legend.selectAll('li')
-    .data(targets)
+      .data(targets)

     legendTargetEnter = legend_target.enter().append('li')
-    .attr('class', (d, i) -> "target#{i}")
-    .attr('data-target', (d) -> d.name)
-    .call(observe_mouse)
+      .attr('class', (d, i) -> "target#{i}")
+      .attr('data-target', (d) -> d.name)
+      .call(observe_mouse)

     legendTargetEnter.append('span')
-    .attr('class', 'color')
+      .attr('class', 'color')
     legendTargetEnter.append('span')
-    .attr('class', 'name')
+      .attr('class', 'name')
     legendTargetEnter.append('span')
-    .attr('class', 'crosshair-value')
+      .attr('class', 'crosshair-value')

     legend_target.select('span.color')
-    .style('color', (d, i) -> d.color)
+      .style('color', (d, i) -> d.color)

     legend_target.select('span.name')
-    .text((d) -> d.name)
+      .text((d) -> d.name)

     legend_target.exit().remove()


Add variable threshold

Add variable threshold

--- app/models/Entity.coffee
+++ app/models/Entity.coffee
@@ -363,6 +363,9 @@
         context.savedTasks = _.clone(context.tasks)
         context.tasks = []

+      threshold = context.velocity/60000
+      threshold = 1 if threshold < 1
+
       # dx = x2 - x1
       dx = Math.floor(positionToMoveTo.x - context.position.x)
       dy = Math.floor(positionToMoveTo.y - context.position.y)
@@ -371,7 +374,7 @@
       ay = Math.abs(dy)

       # if positionToMoveTo reached stop
-      if (ax <= 20 and ay <= 20) or (context.position.x == positionToMoveTo.x and context.position.y == positionToMoveTo.y)
+      if (ax <= threshold and ay <= threshold) or (context.position.x == positionToMoveTo.x and context.position.y == positionToMoveTo.y)
         context.position.x = positionToMoveTo.x
         context.position.y = positionToMoveTo.y
         context.positionToMoveTo = null


Remove old code

Remove old code

--- app/models/Player.coffee
+++ app/models/Player.coffee
@@ -22,9 +22,6 @@

   kill: =>
-    mediator.physicsManager.removeBody @physBody
-    @physBody = null
-    @killed = true

   onTouch: (otherBody, point, impulse) =>


model/group: update singnature to accept password for destroy

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -202,7 +202,7 @@
         transferOwnership:
           (signature String, Function)
         destroy:
-          (signature Function)
+          (signature String, Function)
         addSubscription:
           (signature String, Function)
         fetchDataAt:
@@ -1557,7 +1557,7 @@
       { permission: 'edit own groups', validateWith : Validators.own }
       { permission: 'edit groups',     superadmin   : yes }
     ]
-    success  : (client, callback) ->
+    success  : (client, password, callback) ->

       JName = require '../name'


fix initial height so bars don't fly in from the top

--- src/packages/editor-stats/src/editor-stats-view.coffee
+++ src/packages/editor-stats/src/editor-stats-view.coffee
@@ -65,6 +65,7 @@
       .data(data)
     .enter().append('rect')
       .attr('x', (d, i) => @x i)
+      .attr('height', (d, i) => h - @y(d.value) - @pt - @pb)
       .attr('y', (d) => @y(d.value))
       .attr('width', @x.rangeBand())
       .attr('class', 'bar')


[LoadingFree] implement fetching activity feed from graph

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -17,6 +17,14 @@
     JGroup.one slug: groupName, (err, group)->
       return cb null, [] if err
       group._fetchMembersFromGraph client, {}, cb
+
+  fetchActivityFromGraph = (cb)->
+    return cb null, [] unless bongoModels
+    {CActivity} = bongoModels
+    CActivity._fetchPublicActivityFeed client, {}, (err, data)->
+      return cb null, [] if err
+      return cb null, data
+

   createHTML = ->
     """


don't try and put properties on email if basic email is not configured

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -459,14 +459,14 @@
 			secure: process.env["SHARELATEX_EMAIL_SMTP_SECURE"]
 			ignoreTLS: process.env["SHARELATEX_EMAIL_SMTP_IGNORE_TLS"]

-if process.env["SHARELATEX_EMAIL_SMTP_USER"]? or process.env["SHARELATEX_EMAIL_SMTP_PASS"]?
-	settings.email.parameters.auth =
-		user: process.env["SHARELATEX_EMAIL_SMTP_USER"]
-		pass: process.env["SHARELATEX_EMAIL_SMTP_PASS"]
-
-if process.env["SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH"]?
-	settings.email.parameters.tls =
-		rejectUnauthorized: process.env["SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH"]
+	if process.env["SHARELATEX_EMAIL_SMTP_USER"]? or process.env["SHARELATEX_EMAIL_SMTP_PASS"]?
+		settings.email.parameters.auth =
+			user: process.env["SHARELATEX_EMAIL_SMTP_USER"]
+			pass: process.env["SHARELATEX_EMAIL_SMTP_PASS"]
+
+	if process.env["SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH"]?
+		settings.email.parameters.tls =
+			rejectUnauthorized: process.env["SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH"]

 # Password Settings
 # -----------


parse left and right footers as json

parse left and right footers as json

--- server-ce/settings.coffee
+++ server-ce/settings.coffee
@@ -410,15 +410,22 @@

 #### OPTIONAL CONFIGERABLE SETTINGS

-
 if process.env["SHARELATEX_LEFT_FOOTER"]?
-	settings.left_footer = process.env["SHARELATEX_LEFT_FOOTER"]
+	try
+		settings.nav.left_footer = JSON.parse(process.env["SHARELATEX_LEFT_FOOTER"])
+	catch e
+		console.error("could not parse SHARELATEX_LEFT_FOOTER, not valid JSON")

 if process.env["SHARELATEX_RIGHT_FOOTER"]?
-	settings.right_footer = process.env["SHARELATEX_RIGHT_FOOTER"]
+	settings.nav.right_footer = process.env["SHARELATEX_RIGHT_FOOTER"]
+	try
+		settings.nav.right_footer = JSON.parse(process.env["SHARELATEX_RIGHT_FOOTER"])
+	catch e
+		console.error("could not parse SHARELATEX_RIGHT_FOOTER, not valid JSON")
+

 if process.env["SHARELATEX_HEADER"]?
-	settings.header = process.env["SHARELATEX_HEADER"]
+	settings.nav.header = process.env["SHARELATEX_HEADER"]

 # Sending Email


Remove a proxy request after one use.

Remove a proxy request after one use.

--- src/xo.coffee
+++ src/xo.coffee
@@ -483,6 +483,9 @@
     )
       return next()

+    # A proxy request can only be used once.
+    delete @_proxyRequests[req.url]
+
     $proxyRequest request, req, res

     res.on 'finish', request.onSuccess if request.onSuccess?


moved removeButton to the right side

moved removeButton to the right side

--- vendor/assets/javascripts/token-text-area.coffee
+++ vendor/assets/javascripts/token-text-area.coffee
@@ -176,7 +176,7 @@
     node.contentEditable = false
     node.setAttribute('data-id', id)
     if @options.removeButton
-      node.innerHTML = '' + name + ''
+      node.innerHTML = '' + name + ''
     else
       node.innerHTML = name
     @input.append(node)
@@ -195,7 +195,7 @@
       return '' unless foundItem
       name = foundItem.name
       if self.options.removeButton
-        return ' ' + name + ' '
+        return ' ' + name + ' '
       else
         return ' ' + name + ' '
     )
@@ -249,7 +249,7 @@
       node.contentEditable = false
       node.setAttribute('data-id', id)
       if @options.removeButton
-        node.innerHTML = '' + name + ''
+        node.innerHTML = '' + name + ''
       else
         node.innerHTML = name


rethinkdb: discover pool

rethinkdb: discover pool

--- services/rethinkdb.coffee
+++ services/rethinkdb.coffee
@@ -8,5 +8,6 @@
 r = require('rethinkdbdash')
   host: HOST
   db: DB
+  discovery: true

 module.exports = r


Removed redundant css copy

Removed redundant css copy

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -77,7 +77,6 @@
     copy:
       main:
         files: [
-          { src: ['dist/css/basic.css'], dest: 'dist/server/css/basic.css' }
           { src: ['app/robots.txt'],     dest: 'dist/server/robots.txt' }
           { src: ['**'],                 dest: 'dist/server/images/',   expand: true, cwd: 'app/images/', filter: 'isFile' }
           { src: ['**'],                 dest: 'dist/images/',          expand: true, cwd: 'app/images/', filter: 'isFile' }


Suppress winston in tests (default)

Suppress winston in tests (default)

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,3 +1,4 @@
+process.env.LOG_LEVEL or= 'warn'
 module.exports = (grunt)->
     require('rupert-grunt')(grunt, {
         server: __dirname + '/app.js'


改进版本号

改进版本号

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -139,7 +139,7 @@
 		"cola-ui-license":
 			options:
 				license: """
-/*! Cola - v#{pkg.version}(#{grunt.template.today('yyyy-mm-dd HH:MM:ss')})
+/*! Cola - v#{pkg.version} (#{grunt.template.today('yyyy-mm-dd HH:MM:ss')})
  * Copyright (C) 2015-2017 Benny Bao & Alex Tong.
  * Licensed under the MIT license */

@@ -215,7 +215,7 @@
 				overwrite: true,
 				replacements: [{
 					from: '${version}',
-					to: "#{pkg.version}"
+					to: "#{pkg.version}.#{grunt.template.today('yymmddHHMMss')}"
 				}]

 	grunt.loadNpmTasks "grunt-contrib-coffee"


Copy font-awesome fonts on build

Copy font-awesome fonts on build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -190,7 +190,7 @@
         expand: true
         filter: 'isFile'
         flatten: true
-        src: ['bower_components/bootstrap/fonts/**']
+        src: ['bower_components/font-awesome/fonts/**']
         dest: 'dist/fonts/'

     # Clean


Fix building with aloha editor

Fix building with aloha editor

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,6 +12,11 @@
   alohaBuildConfig.wrap =
     startFile: 'bower_components/aloha-editor/build/aloha/closure-start.frag'
     endFile: 'bower_components/aloha-editor/build/aloha/closure-end.frag'
+  alohaBuildConfig.skipDirOptimize = true
+  alohaBuildConfig.optimize = "uglify2"
+  alohaBuildConfig.optimizeCss = "standard"
+  alohaBuildConfig.separateCSS = false
+  alohaBuildConfig.preserveLicenseComments = false

   # Project configuration.
   grunt.initConfig
@@ -139,7 +144,6 @@
           preserveLicenseComments: false
           skipDirOptimize: true
           optimize: 'uglify2'
-          stubModules: ['cs']
           modules: [{
             name: 'main'
             include: [
@@ -148,16 +152,18 @@
               'cs!pages/contents/contents'
               'cs!pages/search/search'
               'cs!pages/workspace/workspace'
+              'cs!pages/about-us/about-us'
+              'cs!pages/people/people'

               # FIX: edit modules should be loaded in separate modules
               'select2'
+              'bootstrapPopover'
               'cs!modules/media/editbar/editbar'
               'cs!helpers/backbone/views/editable'

               'cs!configs/aloha'
-              'cs!pages/about-us/about-us'
-              'cs!pages/people/people'
             ]
+            stubModules: ['cs']
             exclude: ['coffee-script', 'less/normalize']
             excludeShallow: ['settings']
           }]
@@ -183,9 +189,12 @@

     # Copy
     copy:
-      dist:
+      require:
         src: 'bower_components/requirejs/require.js'
         dest: 'dist/scripts/require.js'
+      aloha:
+        src: 'bower_components/aloha-editor/target/build-profile-with-oer/rjs-output/lib/aloha.js'
+        dest: 'dist/scripts/aloha.js'
       fonts:
         expand: true
         filter: 'isFile'
@@ -204,6 +213,7 @@
           '!dist/scripts/main.js'
           '!dist/scripts/require.js'
           '!dist/scripts/settings.js'
+          '!dist/scripts/aloha.js'
         ]
         filter: 'isFile'
       directories:
@@ -271,9 +281,9 @@
     #'recess' NOTE: Disabled until recess is upgraded to support LESS 1.6+
   ]

-  # Install
+  # Aloha
   # -----
-  grunt.registerTask 'install', [
+  grunt.registerTask 'aloha', [
     'requirejs:aloha'
   ]

@@ -281,8 +291,7 @@
   # -----
   grunt.registerTask 'dist', [
     'requirejs:compile'
-    'copy:dist'
-    'copy:fonts'
+    'copy'
     'targethtml:dist'
     'clean'
     'uglify:dist'
@@ -294,8 +303,7 @@
   # -----
   grunt.registerTask 'default', [
     'requirejs:compile'
-    'copy:dist'
-    'copy:fonts'
+    'copy'
     'targethtml:dist'
     'clean'
     'uglify:dist'


fix Gruntfile for bower libraries

fix Gruntfile for bower libraries

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -82,7 +82,10 @@
       css:
         files: "<%= dir.src %>/css/**.css"
         tasks: ["copy:css"]
-      other:
+      library:
+        files: "bower.json"
+        tasks: ["bower"]
+      html:
         files: "<%= dir.src %>/**.html"
         tasks: ["copy:html", "copy:images", "copy:manifest"]


fixed grunt bump

fixed grunt bump

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -17,10 +17,13 @@
       options:
         files: [
           'package.json'
-          'bower.json'
+        ]
+        commitFiles: [
+          'package.json'
         ]
         tagName: '%VERSION%'
         updateConfigs: ['pkg']
+        pushTo: 'origin'

     githooks:
       'pre-commit':


chore(release): update contributors separate from package bump

Perform contributors update of package.json before version bumping.
 - Don't mix package.json version bump with contributor bump commit msg.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -62,8 +62,8 @@

   grunt.registerTask 'release', 'Build, bump and tag a new release.', (type) ->
     grunt.task.run [
+      'npm-contributors'
       "bump:#{type||'patch'}:bump-only"
-      'npm-contributors'
       'changelog'
       'bump-commit'
     ]


Added encoding warning

Added encoding warning

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -92,6 +92,7 @@
   # $ grunt dist
   # $ grunt dist --war
   grunt.registerTask 'dist', ->
+    grunt.log.writeln("\nYOUR GRUNT ENCODING IS: " + grunt.file.defaultEncoding + " (must be utf8)")
     grunt.config "forDist", true
     grunt.task.run [
       "clean:dist"


Lint Gruntfile.coffee

Lint Gruntfile.coffee

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -19,6 +19,7 @@

       src: ['src/*.coffee']
       test: ['spec/*.coffee']
+      gruntfile: ['Gruntfile.coffee']

     shell:
       browserify:
@@ -47,6 +48,6 @@
   grunt.loadNpmTasks('grunt-contrib-connect')

   grunt.registerTask 'clean', -> require('rimraf').sync('space-pen.js')
-  grunt.registerTask('lint', ['coffeelint:src', 'coffeelint:test'])
+  grunt.registerTask('lint', ['coffeelint'])
   grunt.registerTask('test', ['default', 'shell:browserify', 'shell:test'])
-  grunt.registerTask('default', ['coffee', 'coffeelint'])
+  grunt.registerTask('default', ['coffee', 'lint'])


Remove unused grunt tasks

Remove unused grunt tasks

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -28,32 +28,6 @@
       js:
         files:
           "dist/js/<%= pkg.name %>.min.js": ["dist/js/<%= pkg.name %>.js"]
-    react:
-      files:
-        expand: true
-        cwd: 'src/'
-        src: ['**/*.jsx']
-        dest: 'src/'
-        ext: '.js'
-
-    coffee:
-      options:
-        bare: true
-
-      all:
-        files: [{
-          expand: true
-          cwd: 'src/'
-          src: ['**/*.coffee']
-          dest: 'src/'
-          ext: '.js'
-        }, {
-          expand: true
-          cwd: 'test/'
-          src: ['**/*.coffee']
-          dest: 'test/'
-          ext: '.js'
-        }]

     mocha:
       test:
@@ -77,14 +51,6 @@
         ]
         tasks: ["cssmin"]

-      react:
-        files: "src/**/*.jsx"
-        tasks: ["react"]
-
-      coffee:
-        files: ["src/**/*.coffee", "test/**/*.coffee"]
-        tasks: ["coffee"]
-
       test:
         files: ["test/**/*_test.js", "test/*.html"]
         tasks: ["mocha"]
@@ -98,8 +64,6 @@
         tasks: ["themes"]

   grunt.loadNpmTasks "grunt-contrib-watch"
-  grunt.loadNpmTasks "grunt-contrib-coffee"
-  grunt.loadNpmTasks "grunt-react"
   grunt.loadNpmTasks "grunt-contrib-sass"
   grunt.loadNpmTasks "grunt-contrib-cssmin"
   grunt.loadNpmTasks "grunt-mocha"


Lint empty param lists as errors

Lint empty param lists as errors

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,6 +12,8 @@

     coffeelint:
       options:
+        no_empty_param_list:
+          level: 'error'
         max_line_length:
           level: 'ignore'


Tweaking comments

Tweaking comments

--- scripts/mixins/loadable.coffee
+++ scripts/mixins/loadable.coffee
@@ -2,11 +2,10 @@
 # =======
 #
 # This provides models or collections with a `.load()` method which calls
-# `.fetch()` only once and
+# `.fetch()` only once.
 define ['jquery'], ($) ->

   loadableMixin =
-    # Copy/Pasted from `inherits/base`
     # Returns a promise.
     # If this has not been fully populated it will be fetched exactly once
     load: () ->


Groups: Removed unnecessary check.

Groups: Removed unnecessary check.

--- server/lib/server/index.coffee
+++ server/lib/server/index.coffee
@@ -302,7 +302,7 @@
         if err then next err
         else unless models? then res.send 404, error_404()
         else if models.last?
-          if models.last?.bongo_?.constructorName isnt "JGroup" and not loggedIn
+          if models.last.bongo_?.constructorName isnt "JGroup" and not loggedIn
             return Crawler.crawl koding, req, res, name
           models.last.fetchHomepageView account, (err, view)->
             if err then next err


menu should close when you click to goto a section

--- source/javascripts/main.coffee
+++ source/javascripts/main.coffee
@@ -3,6 +3,9 @@
   constructor: ->
     $('li').find('a:first').click (event) ->
       event.preventDefault() if $(this).parents('.break').length > 0
+
+    $('.top-bar-section').find('li a').click (e) ->
+      $('.top-bar.expanded').removeClass('expanded')

     $(document).foundation {
       index : 0,


Verify buffer positions in atomic tokens when they end before the token

--- spec/display-layer-spec.coffee
+++ spec/display-layer-spec.coffee
@@ -2,7 +2,7 @@
 TextBuffer = require '../src/text-buffer'
 Point = require '../src/point'
 Range = require '../src/range'
-{characterIndexForPoint, isEqual: isEqualPoint, traverse} = require '../src/point-helpers'
+{characterIndexForPoint, isEqual: isEqualPoint, compare: comparePoints, traverse} = require '../src/point-helpers'
 WORDS = require './helpers/words'
 SAMPLE_TEXT = require './helpers/sample-text'
 TestDecorationLayer = require './helpers/test-decoration-layer'
@@ -711,7 +711,6 @@
   for i in [0...1] by 1
     do ->
       seed = now + i
-      seed = 1458149718164
       fit "updates the displayed text correctly when the underlying buffer changes: #{seed}", ->
         random = new Random(seed)
         buffer = new TextBuffer(text: buildRandomLines(random, 10))
@@ -741,7 +740,7 @@
           expectedDisplayLayer = buffer.addDisplayLayer({foldsMarkerLayer: displayLayer.foldsMarkerLayer.copy(), tabLength: 4, invisibles, showIndentGuides, softWrapColumn})
           expect(JSON.stringify(displayLayer.getText())).toBe(JSON.stringify(expectedDisplayLayer.getText()))

-          verifyPositionTranslations(displayLayer, expectedDisplayLayer)
+          verifyPositionTranslations(displayLayer)
           verifyTokens(displayLayer)
           verifyRightmostScreenPosition(displayLayer)
           verifyScreenLineIds(displayLayer, screenLinesById)
@@ -793,6 +792,18 @@
 verifyTokens = (displayLayer) ->
   containingTags = []

+  for tokens in getTokenLines(displayLayer)
+    for {closeTags, openTags, text} in tokens
+      for tag in closeTags
+        mostRecentOpenTag = containingTags.pop()
+        expect(mostRecentOpenTag).toBe(tag)
+      containingTags.push(openTags...)
+
+    expect(containingTags).toEqual([])
+
+  expect(containingTags).toEqual([])
+
+verifyPositionTranslations = (displayLayer) ->
   lineScreenStart = Point.ZERO
   lineBufferStart = Point.ZERO

@@ -814,9 +825,9 @@
             expect(displayLayer.clipScreenPosition(screenPosition, clipDirection: 'forward')).toEqual(tokenScreenEnd)
             expect(displayLayer.translateScreenPosition(screenPosition, clipDirection: 'backward')).toEqual(tokenBufferStart)
             expect(displayLayer.translateScreenPosition(screenPosition, clipDirection: 'forward')).toEqual(tokenBufferEnd)
-            # TODO: converting buffer positions fails for some cases. the above assertions are fine.
-            expect(displayLayer.translateBufferPosition(bufferPosition, clipDirection: 'backward')).toEqual(tokenScreenStart)
-            expect(displayLayer.translateBufferPosition(bufferPosition, clipDirection: 'forward')).toEqual(tokenScreenEnd)
+            if comparePoints(bufferPosition, tokenBufferEnd) < 0
+              expect(displayLayer.translateBufferPosition(bufferPosition, clipDirection: 'backward')).toEqual(tokenScreenStart)
+              expect(displayLayer.translateBufferPosition(bufferPosition, clipDirection: 'forward')).toEqual(tokenScreenEnd)
         else unless token.metadata?.void
           expect(displayLayer.clipScreenPosition(screenPosition, clipDirection: 'backward')).toEqual(screenPosition)
           expect(displayLayer.clipScreenPosition(screenPosition, clipDirection: 'forward')).toEqual(screenPosition)
@@ -830,35 +841,6 @@

     lineBufferStart = traverse(lineBufferStart, screenLine.bufferExtent)
     lineScreenStart = traverse(lineScreenStart, Point(1, 0))
-
-  for tokens in getTokenLines(displayLayer)
-    for {closeTags, openTags, text} in tokens
-      for tag in closeTags
-        mostRecentOpenTag = containingTags.pop()
-        expect(mostRecentOpenTag).toBe(tag)
-      containingTags.push(openTags...)
-
-    expect(containingTags).toEqual([])
-
-  expect(containingTags).toEqual([])
-
-verifyPositionTranslations = (actualDisplayLayer, expectedDisplayLayer) ->
-  {buffer} = actualDisplayLayer
-
-  bufferLines = buffer.getText().split('\n')
-  screenLines = actualDisplayLayer.getText().split('\n')
-
-  for bufferLine, bufferRow in bufferLines
-    for character, bufferColumn in bufferLine
-      actualPosition = actualDisplayLayer.translateBufferPosition(Point(bufferRow, bufferColumn))
-      expectedPosition = expectedDisplayLayer.translateBufferPosition(Point(bufferRow, bufferColumn))
-      expect(actualPosition).toEqual(expectedPosition)
-
-  for screenLine, screenRow in screenLines
-    for character, screenColumn in screenLine
-      actualPosition = actualDisplayLayer.translateScreenPosition(Point(screenRow, screenColumn))
-      expectedPosition = expectedDisplayLayer.translateScreenPosition(Point(screenRow, screenColumn))
-      expect(actualPosition).toEqual(expectedPosition)

 verifyRightmostScreenPosition = (displayLayer) ->
   screenLines = displayLayer.getText().split('\n')


Failing seeds

Failing seeds

--- spec/display-layer-spec.coffee
+++ spec/display-layer-spec.coffee
@@ -500,6 +500,8 @@
   it "updates the displayed text correctly when the underlying buffer changes", ->
     for i in [0...10] by 1
       seed = Date.now()
+      # seed = 1455064492569
+      # seed = 1455064532720
       seedFailureMessage = "Seed: #{seed}"
       random = new Random(seed)
       buffer = new TextBuffer(text: buildRandomLines(random, 10))


popularpost: populate most liked with most recent only if there're most liked posts, don't do it even if there's at least 1 post

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -32,8 +32,10 @@
     @bindInputEvents()

     @fakeMessageMap = {}
+    @mostLikedPostsCount = 0

     @changeFilter MOST_LIKED
+    @realFilter = MOST_LIKED

     {socialapi} = KD.singletons
     @once 'ChannelReady', @bound 'bindChannelEvents'
@@ -43,18 +45,15 @@
       @on 'LazyLoadThresholdReached', @bound 'lazyLoad'

     @on "ItemsArrived", (items)->
-      @getItemTimestampFn = getItemTimestampFn
+      return  if @currentFilter isnt MOST_LIKED
+      @mostLikedPostsCount += items.length
+
+      return  if items.length isnt 0

       # when we run out of posts in most liked list, we switch
       # to getting most recent posts in DESC order
-      if @currentFilter is MOST_LIKED and items.length < ITEMS_COUNT
-        @changeFilter MOST_RECENT
-
-        # this is an edge case when we want to get from top of most
-        # recent posts even though the filter is MOST_RECENT
-        @getItemTimestampFn = ->
-
-        @populate()
+      @changeFilter MOST_RECENT
+      if @mostLikedPostsCount is 0 then @populate()

     KD.singletons.windowController.addFocusListener @bound 'handleFocus'

@@ -70,20 +69,9 @@
         @listController.getListView().on 'ItemWasAdded', @bound 'scrollUp'

   changeFilter: (filter)->
-    @skipItemCount  = 0
-
-    if filter is MOST_LIKED
-      @currentFilter      = MOST_LIKED
-      @getItemTimestampFn = ->
-    else
-      @currentFilter      = MOST_RECENT
-
-      # returns created at timestamp of item, this is not an instance
-      # method due to edge case when we switch from most liked to most
-      # recent filter we want to get posts from top and not the usual
-      # pagination behavior of getting posts that happened after last
-      # post on the feed
-      @getItemTimestampFn = getItemTimestampFn
+    @currentFilter       = filter
+    @skipItemCount       = 0
+    @mostLikedPostsCount = 0

   bindInputEvents: ->

@@ -229,6 +217,7 @@

       filter = if data.active then MOST_LIKED else MOST_RECENT
       @changeFilter filter
+      @realFilter = filter

       @skipItemCount = 0

@@ -361,13 +350,19 @@

     @listController.showLazyLoader()

+    if @realFilter is MOST_LIKED and @mostLikedPostsCount > 0
+      return @listController.hideLazyLoader()
+
     {appManager} = KD.singletons
     last         = @listController.getItemsOrdered().last

     return @listController.hideLazyLoader()  unless last

-    from            = @getItemTimestampFn(last)
-    @skipItemCount += ITEMS_COUNT
+    if @currentFilter is MOST_LIKED
+      from = null
+      @skipItemCount += ITEMS_COUNT
+    else
+      from = last.getData().createdAt

     @fetch {from, skip:@skipItemCount}, (err, items=[])=>
       @listController.hideLazyLoader()
@@ -386,4 +381,3 @@
     @listController.showLazyLoader()
     @populate()

-  getItemTimestampFn = (item)-> item.getData().createdAt


TeamName: fix linter issues

TeamName: fix linter issues

--- client/app/lib/activity/sidebar/teamname.coffee
+++ client/app/lib/activity/sidebar/teamname.coffee
@@ -46,7 +46,7 @@

     avatar_wrapper = new kd.CustomHTMLView
       cssClass : 'HomeAppView-Nav--avatar-wrapper'
-      click : ->
+      click    : ->
         ACCOUNT_MENU.destroy()
         kd.singletons.router.handleRoute '/Home/My-Account'


be able to see all deploys

be able to see all deploys

--- nodeko.coffee
+++ nodeko.coffee
@@ -131,7 +131,8 @@

 # list teams
 get '/teams', ->
-  Team.all { validDeploy: true }, (error, teams) =>
+  q = if @req.param('all') then { url: /\w/ } else { validDeploy: true }
+  Team.all q, (error, teams) =>
     @teams = _.shuffle teams
     @render 'teams/index.html.haml'