removing the closure-wrapping-removal optimization, because it was incorrectly formatting the code it optimized.

--- nodes.coffee
+++ nodes.coffee
@@ -243,16 +243,12 @@
   # It would be better not to generate them in the first place, but for now,
   # clean up obvious double-parentheses.
   compileRoot: (o) ->
-    o.indent  = ""
+    o.indent  = if o.bare then '' else TAB
     o.scope   = new Scope null, this, null
     o.level   = LEVEL_TOP
     @spaced   = yes
     prelude   = ""
-    hasReturn = no
-    @traverseChildren no, (e) ->
-      hasReturn = yes if e instanceof Return
-      !hasReturn
-    if hasReturn or not o.bare
+    unless o.bare
       preludeExps = for exp, i in @expressions
         e = exp.unwrap()
         break unless e instanceof Comment or e instanceof Literal
@@ -261,14 +257,9 @@
       @expressions = preludeExps
       prelude = "#{@compileNode o}\n" if preludeExps.length
       @expressions = rest
-      # We assume that we will need the safety wrapper.
-      # This is our best guess without actually compiling.
-      o.indent = TAB
     code = @compileWithDeclarations o
-    # the `1` below accounts for `arguments`, always "in scope"
-    if hasReturn or (not o.bare and o.scope.variables.length > 1)
-      return "#{prelude}(function() {\n#{code}\n}).call(this);\n"
-    prelude + code
+    return prelude + code if o.bare
+    "#{prelude}(function() {\n#{code}\n}).call(this);\n"

   # Compile the expressions body for the contents of a function, with
   # declarations of all inner variables pushed up to the top.


More useful error message.

More useful error message.

--- src/view.coffee
+++ src/view.coffee
@@ -58,6 +58,12 @@
       id = ++counter + ''
       prefix ? prefix + id

+  _cacheTarget: (selector) ->
+    try
+      @_targets[selector] = @$el.find selector
+    catch err
+      console.error "Unable to cache selector "#{selector}" for #{@constructor.name}"
+
   # Find and cache binding targets.
   _cacheTargets: ->
     for name, targets of @bindings
@@ -70,8 +76,8 @@
         if typeof target is 'string'
           [selector, attr] = @_splitTarget target

-          unless @_targets[selector]?
-            @_targets[selector] = @$el.find selector
+          @_cacheTarget selector unless @_targets[selector]?
+    return

   _computeComputed: (name) ->
     args = []


Fix HTML for dropdown filter.  Add button type="button" for button options.

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -414,7 +414,7 @@
 class Backbone.TableView.ButtonGroupFilter extends Backbone.TableView.Filter
     template: _.template """
         <% _.each(options, function (el, i) { %>
-            <button class="btn <%= _.contains(init, el.value) ? "active" : "" %> <%= !_.isUndefined(el.className) ? el.className : "" %>" value="<%= el.value %>"><%= el.name %></button>
+            <button type="button" class="btn <%= _.contains(init, el.value) ? "active" : "" %> <%= !_.isUndefined(el.className) ? el.className : "" %>" value="<%= el.value %>"><%= el.name %></button>
         <% }) %>
     """
     className: "btn-group pull-left tableview-filterbox"
@@ -430,7 +430,7 @@
 class Backbone.TableView.ButtonOptionFilter extends Backbone.TableView.Filter
     template: _.template """
         <% _.each(options, function (el, i) { %>
-            <button class="btn <%= init == el.value ? "active" : "" %>" value="<%= el.value %>"><%= el.name %></button>
+            <button type="button" class="btn <%= init == el.value ? "active" : "" %>" value="<%= el.value %>"><%= el.name %></button>
         <% }) %>
     """
     className: "btn-group pull-left tableview-filterbox"
@@ -446,7 +446,7 @@
     template: _.template """
         <select class="filter <%= filterClass %>">
             <% _.each(options, function (el, i) { %>
-                <option <%= init == el.value ? "selected='selected'" : "" %> value="<%= el.value %>"><%= el.name %></button>
+                <option <%= init == el.value ? "selected='selected'" : "" %> value="<%= el.value %>"><%= el.name %></option>
             <% }) %>
         </select>
     """


update mark validations to accout for new creation position

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -158,7 +158,9 @@
   handleWidth: -> 12 * (@canvasGraph.scale || 1)

   draw: (e) ->
-    markLeftX = Math.max @startingPoint - @maxWidth(), Math.min @startingPoint, @toCanvasXPoint(e)
+    markLeftX = Math.max @startingPoint + @minWidth() - @maxWidth(),
+                         Math.min @startingPoint, @toCanvasXPoint(e)
+
     markRightX = Math.max @startingPoint + @minWidth(), @toCanvasXPoint(e)

     # no overlapping of marks
@@ -218,7 +220,7 @@
       @startingPoint = @canvasXMin
       @dragging = true
     else if e.target.className is "left-border" or e.target.className is "left-handle"
-      @startingPoint = @canvasXMax
+      @startingPoint = @canvasXMax - @minWidth()
       @dragging = true
     else if e.target.className is "mark"
       @moving = true


fix scroll down can result in overlap between sticky board and footer

Former-commit-id: db43bcf4f9f50ceca017a8e05849a2d5615a895f [formerly 8b4b3a5cbeafe23ae959344503c0930a9d34fdec] [formerly 812aa9a6f35eaf6148272cae14b8328e783fd9cf [formerly ed77f1c15ded949cf3e8fe6b2ddbcb540cd66e98]] [formerly 40facbc4dd75b13c95b500f1fea5a9acaefe953d [formerly 530412160f21b508d4c39eadab7ec57fea3ed6fc] [formerly 33f0b81a78c4e3d8ae6d7886dc07d7881d13f8f7 [formerly 33f0b81a78c4e3d8ae6d7886dc07d7881d13f8f7 [formerly 5f90fea44df568eaec712844f91ab8caaf0a8302]]]]
Former-commit-id: 88d5c266a49e811ec98393b16ebc06d675b74fb0 [formerly 71c2fc0ed482089e9c30d6e10995c09cb261cb9f] [formerly 0c222002796f49d703267e207ab3399a52f0451d [formerly 2974a7df8fa0efe72384f923ef2a5aafc345c720]]
Former-commit-id: 9b649844cf1fe478d7ee58d134e8725b086e38f3 [formerly d2b19a74dc9f6368dae989c6d03c14cfddf43cf3]
Former-commit-id: 4b5739d34b5efd1583c90e40a15bcb071e65e85a
Former-commit-id: bfa71d7c6601478565cbf23c86da77cbefd54204

--- @client/sticky_element.coffee
+++ @client/sticky_element.coffee
@@ -173,6 +173,8 @@

       element_height = @$el.height() # check element's height each scroll event because it may have changed
       element_fits_in_viewport = element_height < (@viewport_height - @options.top_offset)
+      element_bottom = @$el.offset().top + element_height #container_top + element_height
+      container_bottom = container_top + @options.container.height()

       if element_fits_in_viewport #the common case, often for an empty decision board or the proposal header
         scroll_y = 0
@@ -184,13 +186,11 @@

       else # if element doesn't fit in viewport, such as if you have a really full decision board
         element_top = @$el.offset().top
-        element_bottom = @$el.offset().top + element_height #container_top + element_height

         viewport_bottom = viewport_top + @viewport_height
         effective_viewport_bottom = viewport_bottom - @options.bottom_offset

         is_scrolling_up = viewport_top < @last_viewport_top
-        container_bottom = container_top + @options.container.height()

         if is_scrolling_up
           if effective_viewport_top <= element_top
@@ -208,9 +208,9 @@
           else
             scroll_y = @scroll_y + (@last_viewport_top - viewport_top) #- (effective_viewport_top - container_top)

-        # make sure that inertial scroll didn't force us to scroll past the bottom of the container
-        if element_bottom + (scroll_y - @scroll_y) + @options.bottom_offset > container_bottom
-          scroll_y = container_bottom - element_bottom + @scroll_y - @options.bottom_offset
+      # make sure that inertial scroll didn't force us to scroll past the bottom of the container
+      if element_bottom + (scroll_y - @scroll_y) + @options.bottom_offset > container_bottom
+        scroll_y = container_bottom - element_bottom + @scroll_y - @options.bottom_offset

       # console.log ""
       # console.log "General:"


Fix pool view (fix #139).

Fix pool view (fix #139).

--- app/modules/vm/index.coffee
+++ app/modules/vm/index.coffee
@@ -4,15 +4,15 @@

 #=====================================================================

-module.exports = angular.module 'xoWebApp.pool', [
+module.exports = angular.module 'xoWebApp.vm', [
   'ui.router'
 ]
   .config ($stateProvider) ->
     $stateProvider.state 'VMs_view',
       url: '/vms/:id'
-      controller: 'PoolCtrl'
+      controller: 'VmCtrl'
       template: require './view'
-  .controller 'PoolCtrl', (
+  .controller 'VmCtrl', (
     $scope, $state, $stateParams
     xoApi, xo
     sizeToBytesFilter, bytesToSizeFilter


Add example data

Add example data

--- app/pages/lab/workflow.cjsx
+++ app/pages/lab/workflow.cjsx
@@ -17,6 +17,21 @@
   '6' # Cats
 else
   '1166' # Ghosts
+
+EXAMPLE_GOLD_STANDARD_DATA = '''
+  [{
+    "links": {
+      "subjects": ["123"]
+    },
+    "annotations": [{
+      task: "T1",
+      value: 3
+    }, {
+      task: "T2",
+      value: "The value"
+    }, ...]
+  }, ...]
+'''

 EditWorkflowPage = React.createClass
   displayName: 'EditWorkflowPage'
@@ -179,7 +194,11 @@

             <FileButton className="standard-button" accept="application/json" multiple onSelect={@handleGoldStandardDataImport}>Import gold standard classifications</FileButton>

-            Import gold standard classifications to provide some feedback for volunteers. After they classify, they’ll be able to compare their own classification to the gold standard data to make sure they’re on the right track.
+            Import gold standard classifications to provide some feedback for volunteers. After they classify, they’ll be able to compare their own classification to the gold standard data to make sure they’re on the right track.{' '}
+            <TriggeredModalForm trigger={}>
+
Gold standard classificaitons should be in the form:

+
{EXAMPLE_GOLD_STANDARD_DATA}

+            </TriggeredModalForm>


Add a description for each package

Add a description for each package

--- package.cson
+++ package.cson
@@ -1 +1,2 @@
-'main': 'lib/tabs'
+'main': './lib/tabs'
+'description': 'Display a selectable tab for each editor open.'


translations

translations

--- @client/list.coffee
+++ @client/list.coffee
@@ -483,12 +483,16 @@
                   className: 'LIST-field-edit-label'
                   htmlFor: 'list_permit_new_items'

-                  SPAN
-                    style:
-                      fontWeight: 700
-                    'Permissions. '
-
-                  translator "engage.list-config-who-can-add", "Who can add items to this list?"
+
+                  TRANSLATE
+                    id: "engage.list-config-who-can-add"
+                    span:
+                      component: SPAN
+                      args:
+                        style:
+                          fontWeight: 700
+
+                    "Permissions. Who can add items to this list?"

                 DIV
                   style: option_block
@@ -550,13 +554,16 @@
                   LABEL
                     className: 'LIST-field-edit-label'

-                    SPAN
-                      style:
-                        fontWeight: 700
-                      'Slider. '
-
-                    translator "engage.list-config-spectrum", "On what spectrum is each item evaluated?"
-
+
+                    TRANSLATE
+                      id: "engage.list-config-spectrum"
+                      span:
+                        component: SPAN
+                        args:
+                          style:
+                            fontWeight: 700
+
+                      "Slider. On what spectrum is each item evaluated?"

                 DIV
@@ -676,7 +683,7 @@
                             fontSize: 14
                             marginRight: 12
                             cursor: 'pointer'
-                          'change spectrum'
+                          translator 'engage.list-config-spectrum-select', 'change spectrum'

                         SPAN style: _.extend cssTriangle 'bottom', focus_color(), 15, 9,
                           display: 'inline-block'
@@ -829,15 +836,15 @@
         DIV
           className: 'LIST-field-edit-label'

-          SPAN
-            style:
-              fontWeight: 700
-            'Title [optional].'
-          SPAN
-            style:
-              fontWeight: 400
-            #' Can be a category title like "Ideas", or an open-ended question like "What are our values?"'
-            ' Usually an open-ended question like "What are your ideas?" or a list label like "Recommended actions for mitigation"'
+          TRANSLATE
+            id: "engage.list-config-title"
+            span:
+              component: SPAN
+              args:
+                style:
+                  fontWeight: 700
+
+            "Title [optional]. Usually an open-ended question like \"What are your ideas?\" or a list label like \"Recommended actions for mitigation\""

       H1
         className: 'LIST-header'
@@ -937,14 +944,15 @@
         DIV
           className: 'LIST-field-edit-label'

-          SPAN
-            style:
-              fontWeight: 700
-            'Category. '
-          SPAN
-            style:
-              fontWeight: 400
-            'e.g. "Ideas", "Policies", "Questions", "Strategies"'
+          TRANSLATE
+            id: "engage.list-config-category"
+            span:
+              component: SPAN
+              args:
+                style:
+                  fontWeight: 700
+
+            "Category. e.g. \"Ideas\", \"Policies\", \"Questions\", \"Strategies\""

       H1 null,

@@ -1002,14 +1010,16 @@
           style:
             textAlign: 'right'

-          SPAN
-            style:
-              fontWeight: 700
-            'Opinion title. '
-          SPAN
-            style:
-              fontWeight: 400
-            'e.g. "Ratings", "Gut checks"'
+          TRANSLATE
+            id: "engage.list-config-opinion-title"
+            span:
+              component: SPAN
+              args:
+                style:
+                  fontWeight: 700
+
+            "Opinion title. e.g. \"Ratings\", \"Gut checks\""
+

       H1 null,
         if edit_list.editing
@@ -1064,14 +1074,15 @@
             DIV
               className: 'LIST-field-edit-label'

-              SPAN
-                style:
-                  fontWeight: 700
-                'Description [optional].'
-              SPAN
-                style:
-                  fontWeight: 400
-                ' Give any additional information or direction here.'
+              TRANSLATE
+                id: "engage.list-config-description"
+                span:
+                  component: SPAN
+                  args:
+                    style:
+                      fontWeight: 700
+
+                "Description [optional]. Give any additional information or direction here."

             DIV
               id: 'edit_description'


ComputeProvider: updateGroupResourceUsage: to create required logs when something went wrong with limitations

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -451,19 +451,37 @@

   @updateGroupResourceUsage = (options, callback) ->

-    { group, instanceCount, change } = options
+    { group, instanceCount, instanceOnly, change, details } = options

     return callback null  if group.slug is 'koding'

+    handleResult = (err, item) ->
+
+      if err and change is 'increment'
+
+        { account, template, provider } = details
+        user = account?.getAt?('profile.nickname') ? 'an unknown user'
+
+        KodingLogger.warn group, \
+          "#{user} failed to create #{item} due to plan limitations."
+
+      callback err
+
     options = { group, change, amount: instanceCount }
+
+    if instanceOnly
+      @updateGroupInstanceUsage options, handleResult
+      return
+
     @updateGroupStackUsage group, change, (err) =>
-      return callback err  if err
+      return handleResult err, 'stack'  if err
+
       @updateGroupInstanceUsage options, (err) =>
         if err and change is 'increment'
           @updateGroupStackUsage group, 'decrement', ->
-            callback err
+            handleResult err, 'machine'
         else
-          callback err
+          handleResult err, 'resource'

   @createGroupStack = (client, options, callback) ->


stub metrics api

stub metrics api

--- test/unit/coffee/TagsRepositoryTests.coffee
+++ test/unit/coffee/TagsRepositoryTests.coffee
@@ -35,6 +35,7 @@
 			'logger-sharelatex': log:->
 			'settings-sharelatex': {}
 			'mongojs':@mongojs
+			'metrics-sharelatex': {timeAsyncMethod: ()->}

 	describe 'finding users tags', ->
 		it 'should find all the documents with that user id', (done)->


need to figure out how to get this to work in the test runner

--- jsapp/test/amdrunner.coffee
+++ jsapp/test/amdrunner.coffee
@@ -1,7 +1,7 @@
 define [
         'cs!test/unit/xlform/survey.tests',
         'cs!test/unit/xlform/deserializer.tests',
-        'cs!test/unit/xlform/integration.tests',
+        # 'cs!test/unit/xlform/integration.tests',
         'cs!test/unit/xlform/aliases.tests',
         'cs!test/unit/xlform/inputParser.tests',
         'cs!test/unit/xlform/group.tests',


delete keymap

delete keymap

--- keymaps/cursor-history.cson
+++ keymaps/cursor-history.cson
@@ -1,9 +0,0 @@
-# https://atom.io/docs/latest/behind-atom-keymaps-in-depth
-# Chose your preference, I don't want to set default keymap,
-# since it's easily conflict to others.
-
-# 'atom-workspace':
-#   'ctrl-i': 'cursor-history:next'
-#   'ctrl-o': 'cursor-history:prev'
-#   'f7': 'cursor-history:dump'
-#   'f8': 'cursor-history:clear'


[skip] Added introspection to burndown chart (5m)

--- app/assets/javascripts/houston-scheduler/views/show_milestone_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/show_milestone_view.coffee
@@ -69,6 +69,13 @@
       regLast3 = @computeRegression(data.slice(-4))     if data.length >= 4  # last 3 weeks only
       regLast2 = @computeRegression(data.slice(-3))     if data.length >= 3  # last 2 weeks only

+    console.log 'mostRecentDataPoint', new Date(mostRecentDataPoint)
+    console.log 'lastCompleteSprint', new Date(lastCompleteSprint)
+
+    console.log 'regAll', new Date(regAll.x2) if regAll
+    console.log 'regLast3', new Date(regLast3.x2) if regLast3
+    console.log 'regLast2', new Date(regLast2.x2) if regLast2
+
     # Widen the graph so that it includes the X intercept
     projections = []
     projections.push regAll.x2 if regAll


[fix] Showed milestones in the same order in the thumbnail as in the main view (8m)

--- app/assets/javascripts/houston/roadmaps/views/gantt_thumbnail_view.coffee
+++ app/assets/javascripts/houston/roadmaps/views/gantt_thumbnail_view.coffee
@@ -95,12 +95,8 @@

-    milestoneBands = d3.nest()
-      .key (milestone)-> milestone.band
-      .entries(visibleMilestones)
-
     bands = @roadmap.selectAll('.roadmap-thumbnail-band')
-      .data(milestoneBands, (band)-> band.key)
+      .data(@groupMilestonesIntoBands(), (band)-> band.key)

     bands.enter()
       .append('g')
@@ -110,7 +106,7 @@
     bands.exit().remove()

     milestones = bands.selectAll('.roadmap-thumbnail-milestone')
-      .data(((band)-> band.values), (milestone)-> milestone.cid)
+      .data(((band)-> band.milestones), (milestone)-> milestone.cid)

     # update
     milestones
@@ -171,3 +167,18 @@
       .attr('x', ({date})=> @x(date))

     markers.exit().remove()
+
+  groupMilestonesIntoBands: ->
+    milestoneBands = {}
+    milestones = (@toJSON(milestone) for milestone in @milestones.models)
+    for milestone in milestones when milestone.startDate and milestone.endDate and !milestone.removed
+      (milestoneBands[milestone.band] ||=
+        key: milestone.band
+        number: milestone.band
+        milestones: []).milestones.push(milestone)
+    _.values(milestoneBands)
+
+  toJSON: (milestone)->
+    json = milestone.toJSON()
+    json.cid = milestone.id # Use `id` because the view is readonly
+    json


Make global.setImmediate work in browser process

--- atom/common/lib/init.coffee
+++ atom/common/lib/init.coffee
@@ -45,6 +45,8 @@
   # recalculate the timeout in browser process.
   global.setTimeout = wrapWithActivateUvLoop timers.setTimeout
   global.setInterval = wrapWithActivateUvLoop timers.setInterval
+  global.setImmediate = wrapWithActivateUvLoop timers.setImmediate
+  global.clearImmediate = wrapWithActivateUvLoop timers.clearImmediate
 else
   # There are no setImmediate under renderer process by default, so we need to
   # manually setup them here.


No need to stack those items to main environment view, whole logic moved to environmentsScene

--- client/landing/app/Applications/Environments.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/AppView.coffee
@@ -9,53 +9,44 @@
     # @addSubView @header = new HeaderViewSection type : "big", title : "Environments"

     # Action Area for Domains
-    @addSubView @actionArea = new KDView cssClass : 'action-area'
+    @addSubView actionArea = new KDView cssClass : 'action-area'

     # Domain Creation form in actionArea
-    @actionArea.addSubView @domainCreateForm = new DomainCreationForm
+    actionArea.addSubView domainCreateForm = new DomainCreationForm

     # Domain Creation form connections
-    @domainCreateForm.on 'DomainCreationCancelled', => @scene.unsetClass 'out'
-    @domainCreateForm.on 'CloseClicked',            => @scene.unsetClass 'out'
+    domainCreateForm.on 'DomainCreationCancelled', => @scene.unsetClass 'out'
+    domainCreateForm.on 'CloseClicked',            => @scene.unsetClass 'out'

     # Main scene for DIA
     @addSubView @scene = new EnvironmentScene

     # Domains Container
-    @domainsContainer  = new EnvironmentDomainContainer
-    @scene.addContainer @domainsContainer, x: 40
+    domainsContainer = new EnvironmentDomainContainer
+    @scene.addContainer domainsContainer

     # VMs / Machines Container
-    @machinesContainer = new EnvironmentMachineContainer
-    @scene.addContainer @machinesContainer, x: 340
+    machinesContainer = new EnvironmentMachineContainer
+    @scene.addContainer machinesContainer

     # After Domains and Machines container load finished
     # Call updateConnections to draw lines between corresponding objects
     @scene.whenItemsLoadedFor \
-      [@domainsContainer, @machinesContainer], @bound 'updateConnections'
+      [domainsContainer, machinesContainer], @scene.bound 'updateConnections'

-    @domainCreateForm.on 'DomainSaved', =>
-      @domainsContainer.once "DataLoaded", @bound 'updateConnections'
-      @domainsContainer.loadItems()
+    domainCreateForm.on 'DomainSaved', =>
+      domainsContainer.once "DataLoaded", @scene.bound 'updateConnections'
+      domainsContainer.loadItems()

     # Plus button on domainsContainer opens up the action area
-    @domainsContainer.on 'PlusButtonClicked', =>
+    domainsContainer.on 'PlusButtonClicked', =>
       @scene.setClass 'out'
-      @domainCreateForm.emit 'DomainNameShouldFocus'
-      # @domainsContainer.loadItems()
+      domainCreateForm.emit 'DomainNameShouldFocus'
+      # domainsContainer.loadItems()

     # Plus button on machinesContainer uses the vmController
-    @machinesContainer.on 'PlusButtonClicked', =>
+    machinesContainer.on 'PlusButtonClicked', =>
       KD.getSingleton('vmController').createNewVM()
-
-  updateConnections:->
-    for _, machine of @machinesContainer.dias
-      for _, domain of @domainsContainer.dias
-        if domain.data.aliases and machine.data.title in domain.data.aliases
-          @scene.connect \
-            {dia : domain , joint : 'right', container: @domainsContainer}, \
-            {dia : machine, joint : 'left',  container: @machinesContainer}
-

 #  - DO NOT TOUCH BELOW


Fixed issue where flex template sometimes didn't get created/rendered

--- client/lib/sideNav.coffee
+++ client/lib/sideNav.coffee
@@ -76,6 +76,10 @@
 		return flexNav.opened

 	setFlex = (template, data={}) ->
+		# first, reset it to make sure it gets re-rendered properly
+		Session.set "flex-nav-template", null
+		Session.set "flex-nav-data", null
+
 		Session.set "flex-nav-template", template
 		Session.set "flex-nav-data", data


Initialize draggingViewport when rendering scroller.

--- client/publication.coffee
+++ client/publication.coffee
@@ -244,6 +244,7 @@

   $viewport = $(@find '.viewport')

+  draggingViewport = false
   $viewport.draggable
     containment: 'parent'
     axis: 'y'


Added some more tests for collection_difference.

--- core/spec/javascripts/backbone/factlink/collection_difference_spec.coffee
+++ core/spec/javascripts/backbone/factlink/collection_difference_spec.coffee
@@ -51,3 +51,23 @@
     collection1.reset([new Backbone.Model(someField: 1, otherField: 10), new Backbone.Model(someField: 2, otherField: 3)])
     collections[0].reset([new Backbone.Model(someField: 2, otherField: 10), new Backbone.Model(someField: 3, otherField: 2)])
     expect(result.pluck('otherField')).toEqual([10])
+
+  it 'should work with arrays and objects instead of collections and models', ->
+    result = collectionDifference(type, 'someField', collection1, [{someField: 2}, {someField: 3}], collections[1])
+    expect(result.pluck('someField')).toEqual([1, 4])
+
+  it 'should work with subsequent add, removes, and resets', ->
+    collection1.add(models[6])
+    collection1.add(models[7])
+    collection1.reset(models[1..7])
+    collection1.remove(models[7])
+    collection1.remove(models[6])
+    collections[0].add(models[1])
+    collections[1].add(models[1])
+    collections[1].add(models[4])
+    collections[0].add(models[1])
+    collections[0].reset(models[2..4])
+    collections[0].remove(models[4])
+    collections[1].remove(models[1])
+    collections[1].remove(models[4])
+    expect(result.pluck('someField')).toEqual([1, 4])


Ignore swap files

Ignore swap files

--- virtual-file-system/file-system-node.coffee
+++ virtual-file-system/file-system-node.coffee
@@ -77,8 +77,13 @@
     @digest = digest

   entries: ->
-    @tree.map (node) ->
-      node.name
+    entries = []
+
+    @tree.forEach (node) ->
+      unless node.name.endsWith('.swp')
+        entries.push(node.name)
+
+    entries

   list: (extension) ->
     if extension?


Copy options from lineman (still not inheriting)

--- config/application.coffee
+++ config/application.coffee
@@ -46,6 +46,20 @@
       cmd: "open -g -a 'Google Chrome' http://reload.extensions"

   jshint:
+    options:
+      # enforcing options
+      curly: true
+      eqeqeq: true
+      latedef: true
+      newcap: true
+      noarg: true
+      # relaxing options
+      boss: true
+      eqnull: true
+      sub: true
+      # globals
+      browser: true
+
     background:
       src: "<%= files.background.js.app %>"


Fixed message display

Fixed message display

With the previous @hasParent() code, it would show only every other time...

--- lib/bookmarklet-view.coffee
+++ lib/bookmarklet-view.coffee
@@ -16,25 +16,22 @@
     @detach()

   show: (pMessage, pStatus) ->
-    if @hasParent()
-      @detach()
-    else
-      switch pStatus
-        when 'ok'
-          @status.attr('class', 'icon-check padded text-success')
-          @message.attr('class', 'message text-success')
-        when 'error'
-          @status.attr('class', 'icon-stop padded text-error')
-          @message.attr('class', 'message text-error')
+    switch pStatus
+      when 'ok'
+        @status.attr('class', 'icon-check padded text-success')
+        @message.attr('class', 'message text-success')
+      when 'error'
+        @status.attr('class', 'icon-stop padded text-error')
+        @message.attr('class', 'message text-error')

-      @message.text pMessage
+    @message.text pMessage

-      @panel ?= atom.workspace.addModalPanel(item: this)
-      @panel.show()
+    @panel ?= atom.workspace.addModalPanel(item: this)
+    @panel.show()

-      setTimeout(=>
-        @close()
-      , 2000)
+    setTimeout(=>
+      @close()
+    , 2000)

   close: ->
     panelToDestroy = @panel


Skip invalid heading jumps

Skip invalid heading jumps

--- lib/commands/jump-to.coffee
+++ lib/commands/jump-to.coffee
@@ -1,6 +1,6 @@
 utils = require "../utils"

-HEADING_REGEX   = /// ^\# {1,6} \ + .+$ ///
+HEADING_REGEX   = /// ^\#{1,6} \ + .+$ ///
 REFERENCE_REGEX = /// \[ ([^\[\]]+) (?:\]|\]:) ///
 TABLE_COL_REGEX = /// ([^\|]*?) \s* \| ///

@@ -22,29 +22,24 @@

   previousHeading: ->
     range = [[0, 0], [@cursor.row - 1, 0]]
-
-    found = false
-    @editor.buffer.backwardsScanInRange HEADING_REGEX, range, (match) ->
-      found = match.range.start
-      match.stop()
-    return found
+    return @_findNextHeading(range, "backwardsScanInRange")

   nextHeading: ->
     eof = @editor.getEofBufferPosition()
+    # find to end of file
+    range = @_findNextHeading([[@cursor.row + 1, 0], [eof.row + 1, 0]])
+    return range if range
+    # find around the top of file
+    return @_findNextHeading([[0, 0], [eof.row + 1, 0]])

-    range =
-      # find to end of file
-      @_findNextHeading([[@cursor.row + 1, 0], [eof.row + 1, 0]]) ||
-      # find around the top of file
-      @_findNextHeading([[0, 0], [eof.row + 1, 0]])
-
-    return range
-
-  _findNextHeading: (range) ->
+  _findNextHeading: (range, scan = "scanInRange") ->
     found = false
-    @editor.buffer.scanInRange HEADING_REGEX, range, (match) ->
-      found = match.range.start
-      match.stop()
+    @editor.buffer[scan] /// #{HEADING_REGEX.source} ///g, range, (match) =>
+      descriptors = @editor.scopeDescriptorForBufferPosition(match.range.start).getScopesArray()
+      # exclude headings in comments/code blocks
+      if descriptors.find((descriptor) -> descriptor.indexOf("heading") >= 0)
+        found = match.range.start
+        match.stop()
     return found

   referenceDefinition: ->


don't need this

don't need this

--- lib/atom-ternjs-autocomplete.coffee
+++ lib/atom-ternjs-autocomplete.coffee
@@ -49,9 +49,6 @@
     requestHandler: (options) ->
         return [] unless options?.editor? and options?.buffer? and options?.cursor?
         prefix = options.prefix
-
-        begin = options.cursor.getBeginningOfCurrentWordBufferPosition()
-        char = options.editor.getTextInRange([[begin.row, begin.column - 1], [begin.row, begin.column]])

         # .. crashes the server
         if prefix.indexOf('..') != -1


activity: deprecate option "itemTypeString" of `NewCommentForm` class

--- client/Social/Activity/views/comments/newcommentform.coffee
+++ client/Social/Activity/views/comments/newcommentform.coffee
@@ -2,27 +2,23 @@

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

-    options.type           or= "new-comment"
-    options.cssClass       or= "item-add-comment-box"
-    options.itemTypeString or= 'comment'
-    options.editable       or= no
+    options.type     or= "new-comment"
+    options.cssClass or= "item-add-comment-box"
+    options.editable or= no

     super options, data
-
-    {itemTypeString} = @getOptions()
-    data = @getData()

     @input = new KDHitEnterInputView
       type          : "textarea"
       delegate      : this
-      placeholder   : "Type your #{itemTypeString} and hit enter..."
+      placeholder   : "Type your comment and hit enter..."
       autogrow      : yes
       validate      :
         rules       :
           required  : yes
           maxLength : 2000
         messages    :
-          required  : "Please type a #{itemTypeString}..."
+          required  : "Please type a comment..."
       callback      : @bound "commentInputReceivedEnter"

   viewAppended:->


"Send invites" => "Send invitation batch"

--- client/app/Applications/Groups.kdapplication/AppView.coffee
+++ client/app/Applications/Groups.kdapplication/AppView.coffee
@@ -277,7 +277,7 @@
       cssClass          : 'invite-tools'
       buttons           :
         'Send invites'  :
-          title         : 'Send invites'
+          title         : 'Send invitation batch'
           callback      : =>
             @emit 'BatchInvitationsAreSent', +@inviteTools.getFormData().Count
       fields            :


sidebar/machine: check template accesslevel in proper place

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

--- client/app/lib/sidebar/components/ownedresourceslist.coffee
+++ client/app/lib/sidebar/components/ownedresourceslist.coffee
@@ -137,9 +137,6 @@
     menuItems = {}
     { stack, template } = resource

-    isMyTemplate = template?.originId is whoami()._id
-
-    debug 'accessLevel', template.accessLevel

     if !!resource.unreadCount
       menuItems['Update'] = { callback }
@@ -156,6 +153,10 @@
       ['VMs', 'Destroy VMs'].forEach (name) -> menuItems[name] = { callback }

     else
+
+      isMyTemplate = template?.originId is whoami()._id
+
+      debug 'accessLevel', template.accessLevel

       if isAdmin() or isMyTemplate
         menuItems['Edit'] = { callback }


:guardsman: Guard against accessing destroyed binding

--- lib/minimap-find-and-replace.coffee
+++ lib/minimap-find-and-replace.coffee
@@ -30,8 +30,8 @@
       @bindingsById[minimap.id] = binding

       @subscriptionsById[minimap.id] = minimap.onDidDestroy =>
-        @subscriptionsById[minimap.id].dispose()
-        @bindingsById[minimap.id].destroy()
+        @subscriptionsById[minimap.id]?.dispose()
+        @bindingsById[minimap.id]?.destroy()

         delete @bindingsById[minimap.id]
         delete @subscriptionsById[minimap.id]


Add reload to editor context

Add reload to editor context

--- lib/ide-haskell-repl.coffee
+++ lib/ide-haskell-repl.coffee
@@ -90,6 +90,10 @@
         @open(ed, false)
         .then (model) ->
           model.runCommand(cmd)
+      'ide-haskell-repl:ghci-reload': ({currentTarget}) =>
+        @open(currentTarget.getModel(), false)
+        .then (model) ->
+          model.ghciReload()
       'ide-haskell-repl:reload-repeat':  ({currentTarget}) =>
         @open(currentTarget.getModel(), false)
         .then (model) ->


Always unsubscribe when changing buffers

Previously subscribeToBuffer() returned early
in certain cases without first unsubscribing to
the current buffer.

--- lib/spell-check-view.coffee
+++ lib/spell-check-view.coffee
@@ -21,12 +21,15 @@
   subscribeToBuffer: ->
     @destroyViews()
     @task?.abort()
-    return unless @spellCheckCurrentGrammar()

-    @buffer?.off '.spell-check'
-    @buffer = @editor.getBuffer()
-    @buffer.on 'contents-modified.spell-check', => @updateMisspellings()
-    @updateMisspellings()
+    if @buffer?
+      @buffer.off '.spell-check'
+      @buffer = null
+
+    if @spellCheckCurrentGrammar()
+      @buffer = @editor.getBuffer()
+      @buffer.on 'contents-modified.spell-check', => @updateMisspellings()
+      @updateMisspellings()

   spellCheckCurrentGrammar: ->
     grammar = @editor.getGrammar().scopeName


Remove init from loop statements, it's confusing for debugging

--- lib/transforms/loops.coffee
+++ lib/transforms/loops.coffee
@@ -95,12 +95,15 @@
     else
       'WhileStatement'

-    if node.init?
+    i = node.init
+    delete node.init
+
+    if i?
       type: 'BlockStatement'
       body: [
         {
           type: 'ExpressionStatement'
-          expression: node.init
+          expression: i
         }
         node
       ]


simplified js router

simplified js router

--- lib/web/views/router.coffee
+++ lib/web/views/router.coffee
@@ -63,7 +63,7 @@
   pushStateSupport: history && history.pushState
   hashchangeSupport: "onhashchange" of window
   hashBaseUrl: false
-  hashPreviousView: false
+  previousView: false
   disableUrlUpdate: false
   fallbackRoute: false
   initPushState: () =>
@@ -77,16 +77,11 @@
       if !(event.shiftKey || event.ctrlKey || event.altKey || event.metaKey)
         href = target.attributes.href.nodeValue
         @log("Processing click", href)
-        if @pushStateSupport
-          if @exec(href)
-            event.preventDefault();
-            @updateUrl(href)
-        else
-          if @exec(href)
-            @log("Updating hash with", href)
-            event.preventDefault();
-            @hashPreviousView = href
-            @updateUrl(href)
+        if @exec(href)
+          @log("New url", href)
+          event.preventDefault();
+          @previousView = href
+          @updateUrl(href)

   attachLocationChangeListener: =>
     if @pushStateSupport
@@ -99,7 +94,7 @@
         window.onhashchange = (event) =>
           if window.location.hash.substring(0, 2) == "#!"
             href = window.location.hash.substring(2)
-            if href != @hashPreviousView
+            if href != @previousView
               @log("Rendering onhashchange", href)
               @renderUrl(href)


change formula that decides symbol interval

--- src/ui.coffee
+++ src/ui.coffee
@@ -63,7 +63,7 @@

   renderSymbols = ->
     geo.selectAll('.symbol-osmc').remove()
-    interval = 2 / sc
+    interval = 500000 / (s0 * sc)
     symbols = []
     for segment in segments
       for n in d3.range(0, d3.round(turf.lineDistance(segment, 'kilometers') / interval))
@@ -100,6 +100,7 @@
     projection.translate(t0 = [width / 2, height / 2])

     render()
+    renderSymbols()

   zoom.on 'zoom', ->
     tr = d3.event.translate


lines behind objects

lines behind objects

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -168,7 +168,7 @@
     @ve.scene.add(@baseRect.obj3d)

   addElement: (ef, x, y) ->
-    e = new ef.constructor(@baseRect, x, y, 10)
+    e = new ef.constructor(@baseRect, x, y, 50)
     @ve.render()
     e

@@ -200,6 +200,8 @@
     @raycaster = new THREE.Raycaster()

   render: ->
+    @renderer.clear()
+    @renderer.clearDepth()
     @renderer.render(@scene, @camera)

 #Mouse handler encapsulates the logic of dealing with mouse events


nicer line colors

nicer line colors

--- coffee/main.coffee
+++ coffee/main.coffee
@@ -132,7 +132,9 @@

   Line: class Line
     constructor: (color, from, to, z) ->
-      @material = new THREE.LineBasicMaterial({color: color, linewidth: 3})
+      @material = new THREE.LineBasicMaterial(
+        {color: color, linewidth: 3, transparent: false, opacity: 0.7}
+      )
       @geom = new THREE.Geometry()
       @geom.dynamic = true
       @geom.vertices.push(from, to)
@@ -435,7 +437,7 @@
       @endpoint = [null, null]
       @ep_ifx = ["",""]
       #TODO: s/ln/shp/g for consistency
-      @ln = new Shapes.Line(0xababab, from, to, z)
+      @ln = new Shapes.Line(0x5f5f5f, from, to, z)
       @ln.obj3d.userData = this
       @props = {
         name: "link0",


Minor fix (thanks CoffeeLint ;)).

Minor fix (thanks CoffeeLint ;)).

--- src/xo.coffee
+++ src/xo.coffee
@@ -101,7 +101,7 @@
       admin: 3
     }

-    perms[@get 'permission'] => perms[permission]
+    perms[@get 'permission'] >= perms[permission]

 class $Users extends $RedisCollection
   model: $User


bug fix

bug fix

--- app/controllers/state.coffee
+++ app/controllers/state.coffee
@@ -69,7 +69,7 @@
           channel: tool.channel
           filters: tool.filters

-        params.dashboard.createTool Ubret[tool.name], options
+        new_tool = params.dashboard.createTool Ubret[tool.name], options

         new_tool.data = tool.data
         new_tool.setBindOptions tool.bind_options.source, tool.bind_options.params


Adding Tap as dependency of Tap

Adding Tap as dependency of Tap

--- framer/GestureManager.coffee
+++ framer/GestureManager.coffee
@@ -39,9 +39,10 @@
 		return eventFamily

-	_getExistingRecognizersForEventFamily: (eventFamily) ->
+	_getDependentRecognizersForEventFamily: (eventFamily) ->

-		# We need to mix recognizers for some gestures to be detected together
+		# We need to add simultaneous recognition for certain gestures to be detected together
+		# See http://hammerjs.github.io/recognize-with/
 		# All these dependencies come from https://cdn.rawgit.com/hammerjs/hammer.js/master/tests/manual/visual.html
 		existingRecognizers = []

@@ -70,6 +71,11 @@
 					existingRecognizers.push(pan)
 				if rotate = @_manager.get(Events.Pinch)
 					existingRecognizers.push(rotate)
+
+			when Events.DoubleTap # DoubleTap depends on Tap
+				if tap = @_manager.get(Events.Tap)
+					existingRecognizers.push(tap)
+
 			else

 		return existingRecognizers
@@ -121,7 +127,7 @@

 		if recognizer
 			# Add other recognizers if they existed already
-			existingRecognizers = @_getExistingRecognizersForEventFamily(eventFamily)
+			existingRecognizers = @_getDependentRecognizersForEventFamily(eventFamily)
 			if existingRecognizers.length > 0
 				@_manager.add(recognizer).recognizeWith(existingRecognizers)
 			else


select2 fix

select2 fix


the url-sanitizer-checkbox now remembers user choices

--- client/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
+++ client/app/Applications/Activity.kdapplication/widgets/statuswidget.coffee
@@ -132,11 +132,26 @@
         Disable URL auto-complete.</label><input name="stop-sanitizing" class="stop-sanitizing" type="checkbox" />
       </p>
       """
+      click:(event)=>
+        if $(event.target).is("input")
+          @utils.wait =>
+            if $(event.target).prop "checked"
+              @appStorage.setValue 'UrlSanitizerCheckboxIsChecked', yes, =>
+            else
+              @appStorage.setValue 'UrlSanitizerCheckboxIsChecked', no, =>

     @inputLinkInfoBox.addSubView @inputLinkInfoBoxCloseButton

     @tagAutoComplete = @tagController.getView()
+
+    # checkbox autocheck
+    @appStorage = new AppStorage 'Activity', '1.0'
+    @updateCheckboxFromStorage()
+
+  updateCheckboxFromStorage:->
+    @appStorage.fetchValue 'UrlSanitizerCheckboxIsChecked',(checked)=>
+      @inputLinkInfoBox.$("input.stop-sanitizing").prop {checked}

   # will automatically add // to any non-protocol urls
   sanitizeUrls:(text)->
@@ -277,6 +292,7 @@
     @initialRequest = yes
     @inputLinkInfoBoxPermaHide = off
     @inputLinkInfoBox.hide()
+    @updateCheckboxFromStorage()

     super

@@ -288,6 +304,8 @@
     tabView.on "MainInputTabsReset", =>
       @reset()
       @switchToSmallView()
+
+

   pistachio:->
     """


Add in missing error checks

Add in missing error checks

--- services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee
+++ services/web/app/coffee/Features/Collaborators/CollaboratorsHandler.coffee
@@ -65,8 +65,10 @@

 	getProjectsUserIsCollaboratorOf: (user_id, fields, callback = (error, readAndWriteProjects, readOnlyProjects) ->) ->
 		Project.find {collaberator_refs:user_id}, fields, (err, readAndWriteProjects)=>
+			return callback(err) if err?
 			Project.find {readOnly_refs:user_id}, fields, (err, readOnlyProjects)=>
-				callback(err, readAndWriteProjects, readOnlyProjects)
+				return callback(err) if err?
+				callback(null, readAndWriteProjects, readOnlyProjects)

 	removeUserFromProject: (project_id, user_id, callback = (error) ->)->
 		logger.log user_id: user_id, project_id: project_id, "removing user"


Remove commented config, whoops

Remove commented config, whoops

--- karma.conf.coffee
+++ karma.conf.coffee
@@ -40,7 +40,7 @@
         return main.concat(glob.sync(pattern))
       return main.concat(files.tests, files.docs)

-    reporters: ['dots']#progress']
+    reporters: ['dots']

     autoWatch: true


Fixed some bugs in RegExp

Fixed some bugs in RegExp

--- lib/RegExp.coffee
+++ lib/RegExp.coffee
@@ -15,9 +15,13 @@

     if typeof expression == 'object' and expression instanceof RegExpNative
       expression = expression.source
-    expression = String(expression)
+      this.global = expression.global
+      this.ignoreCase = expression.ignoreCase
+      this.multiline = expression.multiline
+    else
+      expression = String(expression)
+      parseFlags this, flags

-    parseFlags this, flags
     re2Flags = ''
     if this.ignoreCase
       re2Flags += 'i'
@@ -40,6 +44,7 @@
     match.input = str
     if this.global
       this.lastIndex = match.index + 1
+    return match

   test: (str) ->
     return this.exec(str) != null


move raw copy code

move raw copy code

--- lib/render.coffee
+++ lib/render.coffee
@@ -19,13 +19,7 @@
     dest = path.join(site.destination, post.permalink)
     dest_dir = path.dirname dest
     if post.type is 'html' # copy raw type assets
-      if not fs.existsSync dest_dir
-        directory.mkdir_parent dest_dir, null, ->
-          exec "cp -r #{path.dirname(post.src)}/* #{dest_dir}", ->
-            callback and callback()
-      else
-        exec "cp -r #{path.dirname(post.src)} #{dest_dir}"
-        callback and callback()
+      @render_raw path.dirname(post.src), dest_dir, callback
     else
       # copy post assets
       src_dir = path.join path.dirname(post.src), 'assets'
@@ -62,7 +56,14 @@
     fs.writeFileSync dest, html, 'utf8'
     callback and callback()

-render.render_raw = ->
+render.render_raw = (src, dest, callback) ->
+  if not fs.existsSync dest
+    directory.mkdir_parent dest, null, ->
+      exec "cp -r #{src}/* #{dest}", ->
+        callback and callback()
+  else
+    exec "cp -r #{src}/* #{dest}", ->
+      callback and callback()

 render.render_feed = (site, callback) ->
   feed = new rss {


remove get-instaled-path dependency

remove get-instaled-path dependency

--- lib/server.coffee
+++ lib/server.coffee
@@ -16,7 +16,6 @@
 path            = require 'path'
 express         = require 'express'
 _               = require 'underscore'
-getInstalledPath = require 'get-installed-path'

 # configuration options
 Config =
@@ -66,7 +65,6 @@

   setupWebServer: ->
     # serve the client side JS for browsers that listen to live updates
-    # takanaClientDistPath = getInstalledPath('takana-client', true) + '/dist';
     @app.use express.static(path.join(__dirname, '..', '/node_modules/takana-client/dist'))
     @app.use express.json()
     @app.use express.urlencoded()


Doesn't work inside a function

Doesn't work inside a function

--- t9n.coffee
+++ t9n.coffee
@@ -1,7 +1,6 @@
 if Meteor.isClient
   if Package.ui
-    ->
-      Handlebars = Package.ui.Handlebars
+    Handlebars = Package.ui.Handlebars

   Handlebars.registerHelper 't9n', (x, prefix='') ->
     T9n.get(x, prefix)


Respect `Focus Viewer` settings for tab View

Also respect `Focus Viewer` setting for on launching from preview icon

--- lib/viewer.coffee
+++ lib/viewer.coffee
@@ -66,9 +66,13 @@
     else if atom.config.get('atom-latex.preview_after_build') is\
         'View in PDF viewer window'
       @openViewerNewWindow()
+      if !atom.config.get('atom-latex.focus_viewer')
+        @latex.viewer.focusMain()
     else if atom.config.get('atom-latex.preview_after_build') is\
         'View in PDF viewer tab'
       @openViewerNewTab()
+      if !atom.config.get('atom-latex.focus_viewer')
+        @latex.viewer.focusMain()

   openViewerNewWindow: ->
     if !@latex.manager.findMain()
@@ -108,6 +112,7 @@
     if !@getUrl()
       return

+    @self = atom.workspace.getActivePane()
     if @tabView? and atom.workspace.paneForItem(@tabView)?
       atom.workspace.paneForItem(@tabView).activateItem(@tabView)
     else


fixed bug

fixed bug

--- validate.coffee
+++ validate.coffee
@@ -123,7 +123,7 @@
       alias = ''
     if type is 2 and (_r = obj.attr('rcheck'))
       alias = 'rcheck'
-      _msgPrefix = obj.attr('rcheckLabel') ||  @.getLabel($('input[name="' + _r + '"]'))
+      _msgPrefix = 'rcheck'
     else
       _msgPrefix = alias + msgAttr[+type]
     msgKey = _msgPrefix + 'msg'
@@ -136,8 +136,7 @@
         try
           _msg = @.settings[_name][msgKey]
         catch E
-
-          _msg = if obj.attr('rcheck') then '{{label}}两次输入不一致' else buildRule[alias] and buildRule[alias][3 - type]
+          _msg = if obj.attr('rcheck') and type is 2 then '{{label}}两次输入不一致' else buildRule[alias] and buildRule[alias][3 - type]
       _msg ||= msgTip[type]
     _msg = parseMsg(_msg,obj,@)
     obj.attr(msgKey,_msg)
@@ -168,6 +167,7 @@
       msgPlace = obj
     else
       _class = @.getDisplay(obj,type,'icon') and 'validate-' + msgAttr[type] || ''
+      !msg and !type and type = 3
       if /^[23]$/.test tipType
         par = obj.parents('.form-item')
         msgPlace = par.find('.input-msg')
@@ -188,7 +188,7 @@
             msgPlace.data('tipName',name)
     parseTipClass(msgPlace,type)
     +tipType isnt 1 and msgPlace.html(msg).addClass(_class)
-    if @.settings.checkSubmit and !submitTrigger
+    if @.settings.checkSubmit and !submitTrigger  or !(_class or msg)
       msgPlace.css 'visibility','hidden'
     else
       msgPlace.css 'visibility','visible'
@@ -230,22 +230,9 @@
     if obj.attr('ajaxurl') and !type  #ajax验证结果不在此处显示 TODO  此处需要修正
       return true
     tipType = self.getTipType(obj)
-    if self.getDisplay(obj,type,'msg')
-      msg = self.getMsg(obj,type,result.alias,tipType)
-    else
-      msg = ''
+    msg = self.getDisplay(obj,type,'msg') and self.getMsg(obj,type,result.alias,tipType) || ''
     self.showMsg(obj,msg,type,tipType,submitTrigger)
     return if /1|2/.test(type) and !self.settings.showAllError then false else true
-
-    ###if type is 2 and _r = obj.attr('rcheck')
-      _msgPrefix = obj.attr('rcheckLabel') ||  self.getLabel($('input[name="' + _r + '"]'))
-      msg = _msgPrefix + '输入不一致'
-    else if type is 2 or (type is 1 and (obj.attr('showNull') || self.settings.showNull)) or (!type and (obj.attr('showSucc') or self.settings.showSucc))
-      msg = self.getMsg(obj,type,result.alias,tipType)
-    else if !type and !(obj.attr('showSucc') or self.settings.showSucc)
-      msg = ' '
-    msg isnt undefined and self.showMsg(obj,msg,type,tipType,submitTrigger)
-    if /1|2/.test(type) and !self.settings.showAllError then return false else return true###

   ###自动生成提示信息头###
   Validate::getLabel = (obj)->
@@ -296,7 +283,7 @@
       that = $(@)
       that.data('lastVal',null)
       tipType = self.getTipType(that)
-      self.showMsg(that,'',3,tipType,false)
+      self.showMsg(that,'',0,tipType,false)
     self
   ###检测结果存储###
   Validate::pass = (obj,status)->
@@ -431,7 +418,7 @@
       while eithor < alias.length
         dtp = 0
         while dtp < alias[eithor].length
-          checkResult = _check(alias[eithor][dtp],val,obj,submitTrigger,_v)
+          checkResult = _check(alias[eithor][dtp],val,obj,submitTrigger,_v) || {}
           break unless checkResult.passed
           dtp++
         break if checkResult.passed
@@ -441,11 +428,11 @@
         rule =
           alias: j
           reg: k
-        checkResult = _check(rule,val,obj,submitTrigger,_v)
+        checkResult = _check(rule,val,obj,submitTrigger,_v) || {}
         rule = null
         return false unless checkResult
     else
-      checkResult = _check(alias,val,obj,submitTrigger,_v)
+      checkResult = _check(alias,val,obj,submitTrigger,_v) || {}
     if checkResult.passed
       checkResult.passed = enhanceCheck(obj,val,checkResult.alias,submitTrigger,_v)
     return checkResult


Add whitespace.

Add whitespace.

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -90,6 +90,7 @@
 modules = [
     "ngRoute",
     "ngSanitize",
+
     "greenmine.controllers.common",
     "greenmine.controllers.auth",
     "greenmine.controllers.backlog",


Fix urls.projectHome

Fix urls.projectHome

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -295,8 +295,11 @@
                 return $rootScope.urls.backlogUrl(project.slug)
             else if project.is_kanban_activated
                 return $rootScope.urls.kanbanUrl(project.slug)
-
-            return $rootScope.urls.issuesUrl(project.slug)
+            else if project.is_issues_activated
+                return $rootScope.urls.issuesUrl(project.slug)
+            else if project.is_wiki_activated
+                return $rootScope.urls.wikiUrl(project.slug, "home")
+            return $rootScope.urls.adminUrl(project.slug)

         backlogUrl: (projectId, raw) ->
             url = gm.format($rootScope.baseUrls.backlog, [projectId])


Use non-linear projection for interpenetration resolution

Non-linear means resolve interpenetration with rotational changes along with
linear.

--- app/js/physics.coffee
+++ app/js/physics.coffee
@@ -5,15 +5,17 @@
   window.rect = new Rectangle 2, 2,
     position: [0, -1]
     inverseMass: 1/4
-    velocity: [0, 1]
-    angularVelocity: Math.PI/4
+    inverseMass: 0
+    velocity: [0, 0.0001]
+    # angularVelocity: Math.PI/4
     color: "#F00"

   window.rect2 = new Rectangle 1, 1,
-    position: [1.6, 1.4]
-    # angle: Math.PI/4
+    position: [0,0.5]
+    angle: 0.2
     inverseMass: 1/1
     # velocity: [0, -1.5]
+    # angularVelocity: -Math.PI/2
     color: "#08F"

   world.addObject rect
@@ -165,7 +167,7 @@

 class Polygon
   position: [0, 0]
-  rotation: [1, 0] # Rotation.fromAngle(0)
+  orientation: [1, 0] # Rotation.fromAngle(0)

   velocity: [0, 0]
   angularVelocity: 0
@@ -416,6 +418,18 @@
   angularVelocityAt: (point) ->
     Vec.scale Vec.perpendicular(@relativePositionAt(point)), @angularVelocity

+  angularInertiaAt: (position, direction) ->
+    qRel = @relativePositionAt position
+    torquePerUnitImpulse = Vec.crossProduct qRel, direction
+    rotationPerUnitImpulse = torquePerUnitImpulse * @inverseMoment
+    velocityPerUnitImpulse = Vec.perpendicular Vec.scale(qRel, rotationPerUnitImpulse)
+    Vec.dotProduct velocityPerUnitImpulse, direction
+
+  applyImpulse: (impulse, position) ->
+    impulsiveTorque = Vec.crossProduct @relativePositionAt(position), impulse
+    @velocity = Vec.add @velocity, Vec.scale impulse, @inverseMass
+    @angularVelocity += impulsiveTorque * @inverseMoment
+
 class Rectangle extends Polygon
   constructor: (sizeX, sizeY, opts = {}) ->
     @position = opts.position or @position
@@ -473,72 +487,59 @@

     # TODO save acceleration per frame and compensate here

-    console.log "sepV", sepV
-
-    qRel = @from.relativePositionAt @position
-    torquePerUnitImpulse = Vec.crossProduct qRel, @normal
-    rotationPerUnitImpulse = torquePerUnitImpulse * @from.inverseMoment
-    velocityPerUnitImpulse = Vec.perpendicular Vec.scale(qRel, rotationPerUnitImpulse)
-    # constrain to normal:
+    # console.log "sepV", sepV
+
+    # calculate distribution of desired deltaV between linear and angular
+    # components on both bodies:
     deltaV = @from.inverseMass
-    deltaV += Vec.dotProduct velocityPerUnitImpulse, @normal
-    console.log "from:", "linear", @from.inverseMass
-    console.log "from:", "qRel", qRel, "torque per unit", torquePerUnitImpulse, "invMoment", @from.inverseMoment
-    console.log "from:", "rot per unit", rotationPerUnitImpulse, "vel per unit", velocityPerUnitImpulse
-    console.log "from:", "dV", Vec.dotProduct(velocityPerUnitImpulse, @normal)
+    deltaV += @from.angularInertiaAt(@position, @normal)

     if @to
-      qRel = @to.relativePositionAt @position
-      torquePerUnitImpulse = Vec.crossProduct qRel, @normal
-      rotationPerUnitImpulse = torquePerUnitImpulse * @to.inverseMoment
-      velocityPerUnitImpulse = Vec.perpendicular Vec.scale(qRel, rotationPerUnitImpulse)
-      deltaV += Vec.dotProduct velocityPerUnitImpulse, @normal
       deltaV += @to.inverseMass
-      console.log "to:", "linear", @to.inverseMass
-      console.log "to:", "qRel", qRel, "torque per unit", torquePerUnitImpulse, "invMoment", @to.inverseMoment
-      console.log "to:", "rot per unit", rotationPerUnitImpulse, "vel per unit", velocityPerUnitImpulse
-      console.log "to:", "dV", Vec.dotProduct(velocityPerUnitImpulse, @normal)
+      deltaV += @to.angularInertiaAt(@position, @normal)

     desiredDeltaV = -sepV * (1 + @restitution)
-    console.log "deltaV per unit impulse", deltaV, "desired deltaV", desiredDeltaV
-
     impulse = Vec.scale @normal, desiredDeltaV / deltaV

-    console.log "impulse", impulse
-
-    impulsiveTorque = Vec.crossProduct @from.relativePositionAt(@position), impulse
-
-    console.log "from:", "impulse", impulse
-    console.log "from:", "impulsive torque", impulsiveTorque, "d-omega", impulsiveTorque * @from.inverseMoment
-    @from.velocity = Vec.add @from.velocity, Vec.scale impulse, @from.inverseMass
-    @from.angularVelocity += impulsiveTorque * @from.inverseMoment
-    console.log "from:", "vel", @from.velocity, "angVel", @from.angularVelocity
+    @from.applyImpulse impulse, @position

     if @to
       impulse = Vec.invert impulse
-      impulsiveTorque = Vec.crossProduct @to.relativePositionAt(@position), impulse
-
-      console.log "to:", "impulse", impulse, "at", @to.relativePositionAt(@position)
-      console.log "to:", "impulsive torque", impulsiveTorque, "d-omega", impulsiveTorque * @to.inverseMoment
-      @to.velocity = Vec.add @to.velocity, Vec.scale impulse, @to.inverseMass
-      @to.angularVelocity += impulsiveTorque * @to.inverseMoment
-      console.log "to:", "vel", @to.velocity, "angVel", @to.angularVelocity
-
-    console.log "sepV is now", @separatingVelocity()
+      @to.applyImpulse impulse, @position
+
+    # console.log "sepV is now", @separatingVelocity()

   resolveInterpenetration: ->
     return if @depth <= 0

-    totalInvMass = @totalInvMass()
-    return if totalInvMass <= 0 # nobody's goin' nowhere
-
-    movePerIMass = Vec.scale @normal, (@depth / totalInvMass)
-
-    @from.position = Vec.add @from.position, Vec.scale movePerIMass, @from.inverseMass
+    # Similar to velocity resolution, distribute penetration resolution between
+    # linear and angular components proportional to the bodies' inertia.
+    # Velocity change per unit impulse is inertia by a different name.
+
+    totalInertia = @from.inverseMass
+    totalInertia += @from.angularInertiaAt(@position, @normal)
+
     if @to
-      @to.position = Vec.sub @to.position, Vec.scale movePerIMass, @to.inverseMass
-
-    @depth = 0 # FIXME
+      totalInertia += @to.inverseMass
+      totalInertia += @to.angularInertiaAt(@position, @normal)
+
+    return if totalInertia <= 0 # nobody's goin' nowhere
+
+    move = @depth / totalInertia
+
+    @from.position = Vec.add @from.position, Vec.scale @normal, move * @from.inverseMass
+    impulsiveTorque = Vec.crossProduct @from.relativePositionAt(@position), @normal
+    rotationChange = move * @from.inverseMoment * impulsiveTorque
+    @from.orientation = Rotation.addAngle @from.orientation, rotationChange
+
+    if @to
+      @to.position = Vec.sub @to.position, Vec.scale @normal, move * @to.inverseMass
+      impulsiveTorque = Vec.crossProduct @to.relativePositionAt(@position), @normal
+      rotationChange = move * @to.inverseMoment * impulsiveTorque
+      @to.orientation = Rotation.addAngle @to.orientation, rotationChange
+
+    @depth = 0
+    # TODO update depth of related contacts with corrected version

   totalInvMass: ->
     total = @from.inverseMass


Draw all the lines around the boxes

Draw all the lines around the boxes

--- app/js/physics.coffee
+++ app/js/physics.coffee
@@ -241,6 +241,7 @@
     ctx.moveTo points[0]...
     for point in points[1..]
       ctx.lineTo point...
+    ctx.lineTo points[0]...

     ctx.globalAlpha = 0.5
     ctx.fillStyle = @color


Nicer buttons when already voted

Nicer buttons when already voted

--- client.coffee
+++ client.coffee
@@ -41,15 +41,26 @@
 		ranking = Db.personal.ref('rounds', round_no)
 		my_vote = Db.shared.get('votes', Plugin.userId()) || null

-		Ui.bigButton ->
-			Dom.style marginTop: '14px', marginLeft: '0px', height: '90px', backgroundColor: (if my_vote == 2 then "#aaa" else ""), textAlign: 'center', fontSize: '250%', paddingTop: '50px'
-			Dom.text 'I have!'
-		, -> Server.call 'registerVote', Plugin.userId(), 1
+		if my_vote
+			Ui.button ->
+				Dom.style marginTop: '14px', marginLeft: '0px', backgroundColor: (if my_vote == 2 then "#aaa" else ""), textAlign: 'center', fontSize: '120%', width: '45%'
+				Dom.text 'I have!'
+			, -> Server.call 'registerVote', Plugin.userId(), 1

-		Ui.bigButton ->
-			Dom.style marginTop: '14px', marginRight: '0px', height: '90px', backgroundColor: (if my_vote == 1 then "#aaa" else ""), textAlign: 'center', fontSize: '250%', paddingTop: '50px'
-			Dom.text 'I have not!'
-		, -> Server.call 'registerVote', Plugin.userId(), 2
+			Ui.button ->
+				Dom.style marginTop: '14px', marginRight: '0px', backgroundColor: (if my_vote == 1 then "#aaa" else ""), textAlign: 'center', fontSize: '120%', width: '45%'
+				Dom.text 'I have not!'
+			, -> Server.call 'registerVote', Plugin.userId(), 2
+		else
+			Ui.bigButton ->
+				Dom.style marginTop: '14px', marginLeft: '0px', height: '90px', textAlign: 'center', fontSize: '250%', paddingTop: '50px'
+				Dom.text 'I have!'
+			, -> Server.call 'registerVote', Plugin.userId(), 1
+
+			Ui.bigButton ->
+				Dom.style marginTop: '14px', marginRight: '0px', height: '90px', textAlign: 'center', fontSize: '250%', paddingTop: '50px'
+				Dom.text 'I have not!'
+			, -> Server.call 'registerVote', Plugin.userId(), 2

 	if round.get('finished') or Db.shared.get('votes', Plugin.userId())
 		did = []


Fixed typo

Fixed typo

--- atom/browser/lib/init.coffee
+++ atom/browser/lib/init.coffee
@@ -38,7 +38,7 @@
   # Show error in GUI.
   stack = error.stack ? "#{error.name}: #{error.message}"
   message = "Uncaught Exception:\n#{stack}"
-  require('dialog').showErrorBox 'A JavaScript error occured in the main process', message
+  require('dialog').showErrorBox 'A JavaScript error occurred in the main process', message

 # Emit 'exit' event on quit.
 app = require 'app'


Add server feedback for rendered-on-demand files.

--- development/server.coffee
+++ development/server.coffee
@@ -32,14 +32,15 @@

         if file_set[target_file]
             [_type, _content] = file_set[target_file].render()
+            util.log(SDKError.colors.green("[200] #{ req.method }: #{ req.url } ") + SDKError.colors.grey(target_file))
             res.writeHead 200,
                 'Content-Type': _type
             res.end(_content)
         else if fs.existsSync(target_file_full_path)
-            util.log(SDKError.colors.green("[200] #{ req.method }: #{ req.url } ") + SDKError.colors.grey(target_file_full_path))
+            util.log(SDKError.colors.green("[200] #{ req.method }: #{ req.url } ") + SDKError.colors.grey(target_file))
             returnFile(target_file_full_path, req, res)
         else
-            util.log(SDKError.colors.yellow("[404] #{ req.method }: #{ req.url } ") + SDKError.colors.grey(target_file_full_path))
+            util.log(SDKError.colors.yellow("[404] #{ req.method }: #{ req.url } ") + SDKError.colors.grey(target_file))
             not_found_file = path.join(directory, '404.html')
             if fs.existsSync(not_found_file)
                 returnFile(not_found_file, req, res, 404)


[Backport] fix issue 4251 - autoscroller in taskboard

--- app/coffee/modules/taskboard/sortable.coffee
+++ app/coffee/modules/taskboard/sortable.coffee
@@ -86,7 +86,7 @@
                 $scope.$apply ->
                     $rootscope.$broadcast("taskboard:task:move", itemTask, newUsId, newStatusId, itemIndex)

-            scroll = autoScroll(containers, {
+            scroll = autoScroll([$('.taskboard-table-body')[0]], {
                 margin: 20,
                 pixels: 30,
                 scrollWhenOutside: true,


update log init order

update log init order

--- core/mewbot.coffee
+++ core/mewbot.coffee
@@ -8,11 +8,11 @@

 class MewBot
     constructor : (adapter)->
+        @logger = new Log process.env.MEWBOT_LOG_LEVEL or 'info'
         @brain  = new Brain @
         @mm     = new ModuleManager @
         @test   = new TestManager @
-        @logger = new Log process.env.MEWBOT_LOG_LEVEL or 'info'
-
+
     init : (profile,callback)->
         @changeProfile profile,(err)=>
             if err


D.bb() returns last 1000 log messages

D.bb() returns last 1000 log messages

--- proxy.coffee
+++ proxy.coffee
@@ -11,7 +11,7 @@
   n = 1000; i = 0; a = Array n
   window.D.bb = (m) -> # m: limit
     if typeof m != 'number' || !(0 < m <= n) || m != ~~m then m = n
-    (if m <= n - i then a[i...i + m] else a[i..].concat a[...i + m - n]).join ''
+    (if m <= i then a[i - m...i] else a[n - m + i..].concat a[...i]).join ''
   bbRecord = (s) -> a[i++] = s; i %= n; return
   return


Update Atom config

Update Atom config

--- atom/atom/config.cson
+++ atom/atom/config.cson
@@ -15,7 +15,20 @@
       showDirectoryColumn: true
     "atom-ide-find-references": {}
     use:
+      "atom-ide-busy-signal": "default"
+      "atom-ide-code-actions": "default"
+      "atom-ide-code-format": "default"
+      "atom-ide-code-highlight": "default"
       "atom-ide-console": "always"
+      "atom-ide-datatip": "default"
+      "atom-ide-definitions": "default"
+      "atom-ide-diagnostics": "default"
+      "atom-ide-diagnostics-ui": "default"
+      "atom-ide-find-references": "default"
+      "atom-ide-global": "default"
+      "atom-ide-outline-view": "default"
+      "atom-ide-signature-help": "default"
+      hyperclick: "default"
   "atom-jest-snippets":
     toggleSemicolons: false
   "atom-material-ui":


improve authorization error page detection

--- chrome/js/src/content.coffee
+++ chrome/js/src/content.coffee
@@ -137,7 +137,10 @@
         return
       return

-    @isAuthorisationErrorPage: (contents) -> /reauth/i.test contents
+    @isAuthorisationErrorPage: (contents) ->
+      (/reauth/i.test contents) or
+      (contents and contents.indexOf('continue%3Dhttps://api.mailfred.de/schedule') isnt -1) or
+      (contents and contents.indexOf('google.com/accounts/AddSession') isnt -1)

     @isAuthorisationErrorResponse: (resp) ->
       (resp?.toLowerCase().indexOf "authorization") isnt -1


Update en.coffee

Update en.coffee

minor fix

--- client/app/locales/en.coffee
+++ client/app/locales/en.coffee
@@ -201,12 +201,12 @@
     "cozic description": "An audio player to play your music from your browser."
     "databrowser description": "Browse and visualize all your data (raw format)."
     "feeds description": "Aggregate your feeds and save your favorite links in bookmarks."
-    "kyou description": "Improve your hapiness and your health by quantifying you."
+    "kyou description": "Improve your happiness and your health by quantifying you."
     "konnectors description": "Import data from external services (Twitter, Jawbone...)."
     "kresus description": "Additional tools for your personal finance manager."
     "nirc description": "Access to your favorite IRC channel from your Cozy."
     "notes description": "Organize and write smart notes."
-    "owm description": "Know how is going the weather anywhere in the world."
+    "owm description": "Know how is the weather anywhere in the world."
     "pfm description": "Manage your bank accounts without logging each time (French Banks only)."
     "remote storage description": "A Remote Storage appliance to store data from your Unhosted applications."
     "tasky description": "Super fast and simple tag-based task manager."


ide/routes: select only machine with data for templates

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -62,7 +62,7 @@
   selectWorkspaceOnSidebar data
   actions.setSelectedMachineId machine._id

-  if machine.data.generatedFrom?
+  if machine.data?.generatedFrom?
     actions.setSelectedTemplateId machine.data.generatedFrom.templateId

   appManager = kd.getSingleton 'appManager'


rm extra whitespace from tooltip in kbd prefs

--- client/prefs-keyboard.coffee
+++ client/prefs-keyboard.coffee
@@ -57,8 +57,7 @@
     Prefix: <input class="text-field pk" size=1>
     Reset

+           title='Prefix followed by shift+key produces the character in red.\nPrefix followed by an unshifted key produces the character in blue.'>

⇧x	` ⇧x

x	` x


return 404 for file not found

return 404 for file not found

--- web.coffee
+++ web.coffee
@@ -22,7 +22,8 @@

 app.get "/file/:hash", (req, res) ->
   storage.get "/hash/#{req.params.hash}", (err, get) ->
-    res.setHeader "Content-Length", get.headers["content-length"]
+    res.writeHead get.statusCode,
+      "Content-Length": get.headers["content-length"]
     get.on "data", (chunk) -> res.write chunk
     get.on "end",          -> res.end()


rewrite cpdir with gzip

rewrite cpdir with gzip

--- src/modules/build.coffee
+++ src/modules/build.coffee
@@ -85,83 +85,30 @@
   return
 # end of rmdir

-cpfile = (from, to, callback) -> # from, to must be filename not dirname
-  if callback? # async (callback is not func means no callback)
-    from = fs.createWriteStream from
-    to = fs.createReadStream to
-    to.once 'open', (fd) ->
-      util.pump from, to
-      callback?()
-  else
-    # @makeDirSync path.dirname targetFile
-    fs.writeFileSync to, fs.readFileSync from
-    # log "Copy <#{sourceFile}> to <#{targetFile}>"
-    # with buffer
-    # BUF_LENGTH = 64*1024
-    # buff = new Buffer BUF_LENGTH
-    # fdr = fs.openSync srcFile, 'r'
-    # fdw = fs.openSync destFile, 'w'
-    # bytesRead = 1
-    # pos = 0
-    # while bytesRead > 0
-    #   bytesRead = fs.readSync fdr, buff, 0, BUF_LENGTH, pos
-    #   fs.writeSync fdw,buff,0,bytesRead
-    #   pos += bytesRead
-    # fs.closeSync fdr
-    # fs.closeSync fdw
-  return
-# end of copy
-
-cpdir = (from, to, callback) ->
+cpdirgz = (from, to, callback) ->
   # do not copy sub dirs for now
-  if callback? # async (callback is not func means no callback)
-    fs.readdir from, (err, files) ->
-      throw err if err
-      async.forEach files, (name, callback) ->
-        src = path.join from, name
-        des = path.join to, name
-        fs.stat src, (err, stats) ->
-          cpfile src, des, callback unless stats.isDirectory()
-          return
-      , -> callback?()
-  else # sync
-    files = fs.readdirSync from
-
-    for name in files
+  fs.readdir from, (err, files) ->
+    throw err if err
+    async.forEach files, (name, callback) ->
+      # console.log 'find', name
       src = path.join from, name
-      srcStats = fs.statSync src
-      des = path.join to, name
-
-      cpfile src, des unless srcStats.isDirectory()
-  return
-# cpdir = (from, to, callback) ->
-#   if callback? # async (callback is not func means no callback)
-#     fs.readdir (err, files) ->
-#       throw err if err
-#       for name in files
-#         src = path.join from, name
-#         des = path.join to, name
-#         srcStats = fs.statSync src
-#         # temp use sync for sub items
-#         if srcStats.isDirectory()
-#           cpdir src, des#, true
-#         else
-#           cpfile src, des#, true
-#       callback?()
-#   else # sync
-#     files = fs.readdirSync sourceDir
-
-#     for name in files
-#       src = path.join from, name
-#       srcStats = fs.to src
-#       des = path.join targetDir, name
-
-#       if srcStats.isDirectory()
-#         cpdir src, des
-#       else
-#         cpfile src, des
-#   return
-# end of cpdir
+      fs.stat src, (err, stats) ->
+        if stats.isDirectory()
+          # console.log 'is dir', name
+          callback()
+        else
+          console.log 'copy file', name
+          fs.readFile src, 'binary', (err, data) ->
+            # console.log 'read', data.length
+            des = path.join to, name
+            write des, data,
+              encoding: 'binary'
+              withgz: on
+              callback: callback
+        return
+    , -> callback?()
+  return
+# end of cpdirgz

 write = (filename, data, {encoding, withgz, callback} = {}) ->
   throw 'need filename and data' unless filename and data
@@ -173,6 +120,7 @@
     data = "#{data}<!-- #{header} -->\n"
   else
     data += '\n'
+  # default encoding is urf-8
   if callback? # async (callback is not func means no callback)
     fs.writeFile filename, data, encoding, (err) ->
       callback? err
@@ -250,11 +198,10 @@

 module.exports = {
   async
-  cpfile
-  cpdir
   mkdir
   rmdir
   write
+  cpdirgz
   coffee: _coffee
   coffeekup: _coffeekup
   stylus: _stylus


Add docs for BufferedReadStream

Add docs for BufferedReadStream

--- src/nack/buffered.coffee
+++ src/nack/buffered.coffee
@@ -1,5 +1,14 @@
 {EventEmitter} = require 'events'

+# **BufferedReadStream** wraps any readable stream and captures any events
+# it fires. The events are held in a buffer until `flush` is called.
+#
+#     http.createServer (req, res) ->
+#       bufferedReq = new BufferedReadStream req
+#       fs.readFile path, () ->
+#         bufferedReq.on 'data' (chunk) ->
+#           console.log
+#
 exports.BufferedReadStream = class BufferedReadStream extends EventEmitter
   constructor: (@stream) ->
     @readable = true
@@ -12,33 +21,42 @@
       else
         @_queue.push ['emit', event, args...]

+    # Listen and queue up any events on the @stream
     @stream.on 'data',  (args...) -> queueEvent 'data', args...
     @stream.on 'end',   (args...) -> queueEvent 'end', args...
     @stream.on 'error', (args...) -> queueEvent 'error', args...
     @stream.on 'close', (args...) -> queueEvent 'close', args...
     @stream.on 'fd',    (args...) -> queueEvent 'fd', args...

+    # Tell the @stream to pause and stop emitting new events
     @stream.pause()

+    # Foward any properties to @stream
     for all name, fun of @stream when !this[name] and name[0] != '_'
       @__defineGetter__ name, (args...) -> @stream[name]

+  # Ignore requests to resume the stream
   resume: ->

+  # Ignore requrests to pause the stream
   pause: ->

   flush: ->
+    # Tell the @stream to resume
     try
       @stream.resume()
     catch error
       # Stream is probably closed now

+    # Flush the event buffer and re-emit the events.
     for [fun, args...] in @_queue
       switch fun
         when 'emit'
           @emit args...

     @_flushed = true
+
+    # Emit a 'drain' event to signal the buffer is empty.
     @emit 'drain'

 exports.BufferedWriteStream = class BufferedWriteStream extends EventEmitter


Remove unused file

Remove unused file

--- assets/javascripts/default_observable.coffee
+++ assets/javascripts/default_observable.coffee
@@ -1,4 +0,0 @@
-module.exports =
-  root:
-    type: 'fromTime', args: "{1000: 1, 3000: 2, 5000: 3}"
-  operators: [ { type: 'map' } ]


disable history.js for now

disable history.js for now

--- src/server/assets.coffee
+++ src/server/assets.coffee
@@ -1,6 +1,6 @@
 module.exports.javascripts = ->
   [
-    'bower_components/history.js/scripts/bundled-uncompressed/html4+html5/native.history.js'
+    # 'bower_components/history.js/scripts/bundled-uncompressed/html4+html5/native.history.js'
     'bower_components/lodash/lodash.min.js'
     'bower_components/helfer/lib/helfer.js'
     'bower_components/bluebird/js/browser/bluebird.js'


Use given params for basic vars (port and env)

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -10,23 +10,21 @@
 eventric                = require "eventric"
 eventricMongoStore      = require "eventric-store-mongodb"

-
-
-# # # #
-# Helper (Todo, move into helper-structur)
-# #
-getParamFromArg = (param) ->
-  process.argv[process.argv.indexOf(param)+1]
-
+paramHandler = require '../helper/paramHandler'

 # # # #
 # Initial Variables
 # #
-__PORT = getParamFromArg '--port'
+__PORT        = undefined
+__environment = undefined
+
+# - Define Vars From Arguments
+paramHandler.all (payload) ->
+  __PORT        = payload.port  or false
+  __environment = payload.env   or false
+
 __projectdir = "#{__dirname.replace('/src/server','')}/build/ui"
-__Environment = if __PORT is 8000 then "production" else "dev"
-

@@ -67,7 +65,6 @@
     res.send feedsCollection
     io.emit 'feedUpdate', feedsCollection

-
   feedparser.on "error", (error) ->
   # always handle errors

@@ -85,7 +82,7 @@
 # # # #
 # Eventric Feed Stuff
 # #
-if __Environment is "dev"
+if __environment is "development"
   eventric.log.setLogLevel "debug"
   eventric.addStore "mongodb", eventricMongoStore
   eventric.set "default domain events store", "mongodb"


Added assert.

Added assert.

--- lib.coffee
+++ lib.coffee
@@ -416,6 +416,9 @@
           # @ is a template instance.

           if componentParent
+            # TODO: Should we support that the same component can be rendered multiple times in parallel? How could we do that? For different component parents or only the same one?
+            assert not @componentParent()
+
             # We set the parent only when the component is created, not just constructed.
             component.componentParent componentParent
             componentParent.addComponentChild component
@@ -431,7 +434,7 @@

           @component = component

-          # TODO: Should we support that the same component can be rendered multiple times in parallel? How could we do that?
+          # TODO: Should we support that the same component can be rendered multiple times in parallel? How could we do that? For different component parents or only the same one?
           assert not @component.templateInstance
           @component.templateInstance = @


Incrementing path's only relative paths.

--- src/toaster/toast.coffee
+++ src/toaster/toast.coffee
@@ -99,11 +99,13 @@
       return error msg

     for dir, i in config.dirs
-      dir = path.join @basepath, dir
+      if dir.indexOf( @basepath ) < 0
+        dir = path.join @basepath, dir
+
       if fs.existsSync dir
         config.dirs[i] = dir
       else
-        msg = 'Informed dir doesn\'t exist:\n\t#{dir.yellow}'
+        msg = "Informed dir doesn't exist:\n\t#{dir.yellow}"
         msg += '\nCheck your `toaster.coffee` config file.'
         return error msg

@@ -113,7 +115,8 @@
       msg += '\nCheck your `toaster.coffee` config file.'
       return error msg
     else
-      config.release_dir = path.join @basepath, config.release_dir
+      if config.release_dir.indexOf( @basepath ) < 0
+        config.release_dir = path.join @basepath, config.release_dir
       unless fs.existsSync (path.dirname config.release_dir)
         msg = "`release_dir` doesn't exist:\n\t#{config.release_dir.yellow}"
         msg += '\nCheck your `toaster.coffee` config file.'


break down retrieval of entities for slower connections

--- src/ui/dispatcher.coffee
+++ src/ui/dispatcher.coffee
@@ -208,7 +208,10 @@
                 throttleWaterByConv[conv_id] = sendWater
         sendWater()

-handle 'getentity', (ids) -> ipc.send 'getentity', ids
+handle 'getentity', (ids) ->
+    for ix in [0..(ids.length - 1)] by chunkSize = 5
+        ipc.send 'getentity', (ids.slice(ix, ix + chunkSize))
+
 handle 'addentities', (es, conv_id) ->
     entity.add e for e in es ? []
     if conv_id # auto-add these ppl to a conv


改进Template的Clone机制，防止事件绑定丢失

改进Template的Clone机制，防止事件绑定丢失

--- src/widget/mixins.coffee
+++ src/widget/mixins.coffee
@@ -102,12 +102,13 @@
 		if template instanceof Array
 			if supportMultiNodes and template.length > 1
 				fragment = document.createDocumentFragment()
-				fragment.appendChild(templ.cloneNode(true)) for templ in template
+				for templ in template
+					fragment.appendChild($fly(templ).clone(true, true)[0])
 				return fragment
 			else
-				return template[0].cloneNode(true)
+				return $fly(template[0]).clone(true, true)[0]
 		else
-			return template.cloneNode(true)
+			return $fly(template).clone(true, true)[0]

 cola.DataWidgetMixin =
 	_dataWidget: true


Plan.subscribe() gets two parameters

Plan.subscribe() gets two parameters

--- client/app/CommonViews/paymentwidgets.coffee
+++ client/app/CommonViews/paymentwidgets.coffee
@@ -78,7 +78,7 @@
   subscribe:(callback)->
     KD.remote.api.JRecurlyPlan.getPlanWithCode @planCode, (err, plan)=>
       if not err and plan
-        plan.subscribe callback
+        plan.subscribe {}, callback

   askBillingInfo:(callback)->
     paymentController = KD.getSingleton('paymentController')


minor

minor

--- client/app/MainApp/ApplicationManager.coffee
+++ client/app/MainApp/ApplicationManager.coffee
@@ -181,10 +181,11 @@
     callback()

   quitAll:->
+
     for own name, apps of @appControllers
-      @quit app  for app in apps
-
-  get:(name)-> @appControllers[name]?.first or null
+      @quit app  for app in apps.instances
+
+  get:(name)->

     if apps = @appControllers[name]
       apps.instances[apps.lastActiveIndex] or apps.instances.first


set requestInProgress

set requestInProgress

--- client/lanes/models/query/ArrayResult.coffee
+++ client/lanes/models/query/ArrayResult.coffee
@@ -25,10 +25,11 @@

         _.extend(options, _.omit(@result.query.syncOptions, 'include'))
         @result.query.trigger('request', @result.query, 'GET', {})
-        @pendingLoad = Lanes.Models.Sync.perform('GET', options).then (resp) =>
+        @result.requestInProgress = options
+        Lanes.Models.Sync.perform('GET', options).then (resp) =>
             @result.total = resp.total
             @rows  = resp.data
-            delete @pending
+            delete @result.requestInProgress
             @result.onPageLoad(@)

     _normalizedIndex: (index) ->
@@ -124,7 +125,14 @@
     eachRow: (fn) ->
         for i in [0...@length]
             row = @rowAt(i)
-            fn(row, row[@xDataColumn])
+            fn(row, row[@xDataColumn], i)
+
+    map: (fn) ->
+        rows = []
+        @query.results.eachRow (row, xd, i) ->
+            rows.push fn(row, xd, i)
+        rows
+

     filteredRows: (fn) ->
         found = []


test/credentials: check credentials tab

test/credentials: check credentials tab

--- client/test/lib/dashboard/credentials.coffee
+++ client/test/lib/dashboard/credentials.coffee
@@ -23,3 +23,13 @@
         teamsHelpers.createCredential browser, 'aws', 'aws2', yes, ->
           done()

+
+  credentials: (browser) ->
+
+    sectionSelector = '.kdview.kdtabpaneview.credentials'
+
+    browser
+      .url credentialsUrl
+      .waitForElementVisible sectionSelector, 20000
+
+


Made default and ttCounter class variables

--- core/app/backbone/factlink/tooltip_jq.coffee
+++ core/app/backbone/factlink/tooltip_jq.coffee
@@ -8,21 +8,22 @@

 Backbone.Factlink ||= {}
-defaults =
-  closingtimeout: 500
-  #$el: owner
-  #selector: what to hover over
-  #makeTooltip: $el, $target -> $tooltip
-  #removeTooltip: $el, $target, $tooltip ->

-ttCounter = 0 #each call to TooltipJQ gets its own id based on this.

 #returns an object with a close method which when called
 #removes all open tooltips
 class Backbone.Factlink.TooltipJQ

+  defaults =
+    closingtimeout: 500
+    #$el: owner
+    #selector: what to hover over
+    #makeTooltip: $el, $target -> $tooltip
+    #removeTooltip: $el, $target, $tooltip ->
+
+  ttCounter = 0 #each call to TooltipJQ gets its own id based on this.
+
   constructor: (options) ->
-    console.info 'constructor!'
     @_options = options

     @_uid = ttCounter++


removed skeleton.css

removed skeleton.css

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -56,12 +56,6 @@
         src: 'raleway-fontface.css'
         dest: 'vendor/css/'
       } ]
-      skeleton: files: [ {
-        expand: true
-        cwd: 'bower_components/skeleton/css'
-        src: 'skeleton.css'
-        dest: 'vendor/css/'
-      } ]
     exec: jekyll: cmd: 'jekyll build --trace'
     watch:
       options: livereload: false


chore(Gruntfile.coffee): ...but don't version-stamp images

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -270,14 +270,12 @@
                         cwd: './client/'
                         src: ['*.gif']
                         dest: './dist/<%= pkg.name %>-<%= pkg.version %>/'
-                        ext: '-<%= pkg.version %>.gif'
                     }
                     {
                         expand: true
                         cwd: './client/'
                         src: ['*.gif']
                         dest: './dist/jquery.<%= pkg.name %>-<%= pkg.version %>/'
-                        ext: '-<%= pkg.version %>.gif'
                     }
                     {
                         expand: true


chore(Gruntfile): Added watcher for tests

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -269,6 +269,9 @@
           "clean"
           "copy:public"
         ]
+      test:
+        files: ["test/**/*.spec.coffee"]
+        tasks: ["karma:unit:run"]
       css:
         files: ["src/css/*.styl", "vendor/vendor.styl"]
         tasks: ["stylus", "concat:css", "clean"]


A ha!

A ha!

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -108,7 +108,6 @@
         expand: true
         src: ['test/server/**/*Spec.coffee']
         options:
-          globals: ['should']
           timeout: 3000
           ignoreLeaks: false
           ui: 'bdd'
@@ -118,7 +117,6 @@
         expand: true
         src: ['test/server/**/*Spec.coffee']
         options:
-          globals: ['should']
           timeout: 3000
           ignoreLeaks: false
           reporter: 'dot'


Gruntfile watch task now builds as well

Gruntfile watch task now builds as well

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -42,7 +42,7 @@
 		watch:
 			dev:
 				files: ['src/**/*.{js,coffee}', 'test/**/*.{js,coffee}']
-				tasks: ['lint', 'test']
+				tasks: ['lint', 'test', 'build', 'build-web']

 			test:
 				files: ['src/**/*.{js,coffee}', 'test/**/*.{js,coffee}']


use constante por pkg and change DIST_PATH to DIST_FILE

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -4,9 +4,8 @@
     SRC_DIR: 'src/'
     TESTS_DIR: 'test/'
     SPEC_DIR: '<%= TESTS_DIR %>spec/'
-    DIST_FILE: 'dist/<%= pkg.name %>'
-
-    pkg: @file.readJSON('package.json')
+    DIST_FILE: 'dist/<%= PKG.name %>'
+    PKG: @file.readJSON('package.json')

     concat:
       dist:
@@ -26,11 +25,11 @@

       options:
         banner: '/*!\n' +
-          ' * <%= pkg.name %> v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' +
-          ' * <%= pkg.homepage %>\n' +
-          ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
-          ' * Released under the <%= pkg.licenses[0].type %> license\n' +
-          ' * <%= pkg.licenses[0].url %>\n' +
+          ' * <%= PKG.name %> v<%= PKG.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' +
+          ' * <%= PKG.homepage %>\n' +
+          ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= PKG.author %>\n' +
+          ' * Released under the <%= PKG.licenses[0].type %> license\n' +
+          ' * <%= PKG.licenses[0].url %>\n' +
           ' */\n'
         separator: '\n\n'

@@ -71,7 +70,7 @@
           '<%= DIST_FILE %>.min.js': ['<%= DIST_FILE %>.js']

       options:
-        banner: '/*! <%= pkg.name %> v<%= pkg.version %> | <%= pkg.homepage %> | <%= pkg.licenses[0].url %> */\n'
+        banner: '/*! <%= PKG.name %> v<%= PKG.version %> | <%= PKG.homepage %> | <%= PKG.licenses[0].url %> */\n'
         sourceMap: '<%= DIST_FILE %>.sourcemap.js'

     watch:


Added task to grunt compilation

Added task to grunt compilation

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,11 +29,19 @@
     bin: 'bin/'
     nodes: 'node_modules/'

+  # Configuration for minify.
+  uglifyConfiguration =
+    main:
+      files:
+        'bin/Commander.js': 'bin/Commander.js',
+        'bin/Downloader.js': 'bin/Downloader.js'
+
   # Add all seperate configurations to
   # the main GruntConfiguration
   Grunt.initConfig
     coffee: coffeeConfiguration
     clean: cleanConfiguration
+    uglify: uglifyConfiguration

   # Add executable line to top of file
   Grunt.registerTask 'prepare-executable', () ->
@@ -47,4 +55,4 @@

   # Generate custom build commands that I use often
   Grunt.registerTask 'clear', ['clean:bin']
-  Grunt.registerTask 'compile', ['coffee', 'prepare-executable']
+  Grunt.registerTask 'compile', ['coffee', 'prepare-executable', uglify]


fix(style):added styles folder to grunt dist...

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -14,6 +14,7 @@
                     'slides/{,*/}*.{md,html}'
                     'js/*.js'
                     'resources/**'
+                    'styles/**'
                 ]

             index:


Add Terms of Use page to build

Add Terms of Use page to build

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -149,6 +149,7 @@
               'cs!pages/search/search'
               'cs!pages/workspace/workspace'
               'cs!pages/about/about'
+              'cs!pages/tos/tos'
               'cs!pages/donate/donate'
               'cs!pages/role-acceptance/role-acceptance'


refactor: watch for reassembly with Brunch directly

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -49,7 +49,7 @@
       # Build and run test case
       develop:
         files: ["#{sourceDir}/**/*"]
-        tasks: ['exec:brunchCompile', 'karma:unit:run']
+        tasks: ['karma:unit:run']

     # Testing
     karma:
@@ -65,10 +65,19 @@
     # Concurrently run watch and server
     concurrent:
       options:
-        limit: 3
+        limit: 4
         logConcurrentOutput: true
       # When developing, just run the server and watch for changes
-      develop: ['watch', 'karma:unit:start', 'exec:harpServer']
+      develop: [
+        # Start the Karma server
+        'karma:unit:start'
+        # Re-assemble on change
+        'exec:brunchWatch'
+        # Run tests on change
+        'watch'
+        # Run local web server
+        'exec:harpServer'
+      ]

     ## Execute arbitrary commands

@@ -82,6 +91,9 @@
       # Build for production with Brunch
       brunchBuild:
         cmd: 'node_modules/.bin/brunch build -P'
+      # Watch for changes for re-assembly
+      brunchWatch:
+        cmd: 'node_modules/.bin/brunch watch'
       # Run Harp server
       harpServer:
         cmd: "node_modules/.bin/harp server #{buildDir}"


fix: Gruntfile

fix: Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -11,7 +11,7 @@
       options:
         jshintrc: '.jshintrc'
       all:
-        src: ['*.js', '<%= nodeunit.files %>']
+        src: ['*.js', 'lib/*.js', '<%= nodeunit.files %>']

     pkgFile: 'package.json'

@@ -28,10 +28,10 @@
       options:
         checkTravisBuild: true

-      watch:
-        all:
-          files: '<%= jshint.all.src %>',
-          tasks: ['jshint', 'nodeunit']
+    watch:
+      all:
+        files: '<%= jshint.all.src %>',
+        tasks: ['jshint', 'nodeunit']

   grunt.registerTask 'release', 'Bump the version and publish to NPM.', (type) ->
     grunt.task.run [


Remove unnecessary loadNpmTasks calls

Remove unnecessary loadNpmTasks calls

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,8 +1,6 @@
 "use strict"

 module.exports = (grunt) ->
-  grunt.loadNpmTasks "grunt-mocha-test"
-  grunt.loadNpmTasks "grunt-release"
   grunt.initConfig
     mochaTest:
       test:


Updated Grunt tasks for Browserify

Updated Grunt tasks for Browserify

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,48 +10,46 @@
       specs  : 'specs'

     source:
-      micro  : '<%= meta.source %>/<%= meta.name %>.coffee'
-      utils  : '<%= meta.source %>/<%= meta.name %>.utils.coffee'
-      ajax   : '<%= meta.source %>/<%= meta.name %>.ajax.coffee'
       specs  : '<%= meta.specs %>/*.spec.coffee'

     coffee:
-      micro  : files: '<%= meta.build %>/<%= meta.name %>.js'       : '<%= source.micro %>'
-      utils  : files: '<%= meta.build %>/<%= meta.name %>.utils.js' : '<%= source.utils %>'
-      ajax   : files: '<%= meta.build %>/<%= meta.name %>.ajax.js'  : '<%= source.ajax %>'
-      specs  : files: '<%= meta.build %>/<%= meta.name %>.spec.js'  : '<%= source.specs %>'
+      specs  : files: '<%= meta.build %>/<%= meta.name %>.spec.js' : '<%= source.specs %>'

     clean:
       build  : src: ['build']
-      dist   : src: ['dist']
+      dist   : src: ['dist/**/*']
+
+    browserify:
+      dist:
+        files: 'dist/micro.js' : ['source/micro.coffee']
+        options:
+          transform: ['coffeeify']

     uglify:
       options: mangle: true, report: 'gzip'
-      micro  : files: '<%= meta.dist %>/<%= meta.name %>.js'       : '<%= meta.build %>/<%= meta.name %>.js'
-      utils  : files: '<%= meta.dist %>/<%= meta.name %>.utils.js' : '<%= meta.build %>/<%= meta.name %>.utils.js'
-      ajax   : files: '<%= meta.dist %>/<%= meta.name %>.ajax.js'  : '<%= meta.build %>/<%= meta.name %>.ajax.js'
+      micro  : files: '<%= meta.dist %>/<%= meta.name %>.min.js' : '<%= meta.dist %>/<%= meta.name %>.js'

     jasmine:
-      pivotal: src: [
-        '<%= meta.build %>/<%= meta.name %>.js'
-        '<%= meta.build %>/<%= meta.name %>.utils.js'
-        '<%= meta.build %>/<%= meta.name %>.ajax.js'
-      ]
-      options: specs: '<%= meta.build %>/<%= meta.name %>.spec.js'
+      pivotal:
+        src: [
+          '<%= meta.dist %>/<%= meta.name %>.js'
+        ]
+        options: specs: '<%= meta.build %>/<%= meta.name %>.spec.js'

     watch:
-      specs: files: [
-        '<%= meta.source %>/*.coffee',
-        '<%= meta.specs %>/*.coffee'
-      ]
-      tasks: ['coffee', 'jasmine']
+      specs:
+        files: [
+          '<%= meta.source %>/*.coffee',
+          '<%= meta.specs %>/*.coffee'
+        ]
+        tasks: ['coffee', 'browserify', 'jasmine']

     grunt.loadNpmTasks 'grunt-contrib-coffee'
     grunt.loadNpmTasks 'grunt-contrib-clean'
-    grunt.loadNpmTasks 'grunt-contrib-concat'
     grunt.loadNpmTasks 'grunt-contrib-jasmine'
+    grunt.loadNpmTasks 'grunt-browserify'
     grunt.loadNpmTasks 'grunt-contrib-uglify'
     grunt.loadNpmTasks 'grunt-contrib-watch'

-    grunt.registerTask 'test', ['clean', 'coffee', 'jasmine']
-    grunt.registerTask 'default', ['clean', 'coffee', 'uglify', 'jasmine']+    grunt.registerTask 'test', ['clean', 'coffee', 'browserify', 'jasmine']
+    grunt.registerTask 'default', ['clean', 'coffee', 'browserify', 'uglify', 'jasmine', 'clean:build']


Test: Tweak coveralls settings

Test: Tweak coveralls settings

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -19,9 +19,10 @@
     coveralls:
       options:
         debug: true
-        coverage_dir: "coverage"
+        coverage_dir: "coverage/"
         dryRun: true
         force: true
+        recursive: true

     uglify:
       my_target:


Remove some unnecessary arrays from Gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -29,14 +29,14 @@
       options:
         banner: "<%= meta.banner %>"
       dist:
-        src  : ['<%= concat.dist.dest %>']
+        src  : '<%= concat.dist.dest %>'
         dest : 'static/js/scripts.min.js'

     coffee:
       glob_to_multiple:
         expand : true
         cwd    : 'js/src'
-        src    : ['*.coffee']
+        src    : '*.coffee'
         dest   : 'js/src/'
         ext    : '.js'

@@ -44,7 +44,7 @@
       compress:
         files:
           "static/css/styles.min.css" : ["css/bootstrap-compiled.min.css", "css/isvalid.css"],
-          "static/css/embed.min.css"  : ["css/embed.css"]
+          "static/css/embed.min.css"  : "css/embed.css"

   # Default task.


Don't blow up on missing settings

Don't blow up on missing settings

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -88,7 +88,7 @@

 		shell:
 			migrate:
-				command: "./node_modules/east/bin/east migrate --adapter east-mongo --url #{settings.mongo.url}"
+				command: "./node_modules/east/bin/east migrate --adapter east-mongo --url #{settings?.mongo?.url}"

 		availabletasks:
 			tasks:


updated how tests run

updated how tests run

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -34,6 +34,7 @@
   grunt.registerTask 'unit', 'run unit tests', [
     'install'
     'coffeelint'
+    'coffee:dev'
     'karma:unit'
   ]


Use grunt API for is directory

Use grunt API for is directory

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,7 +10,7 @@

 module.exports = (grunt) ->
   cp = (source, destination) ->
-    if fs.statSync(source).isDirectory()
+    if grunt.file.isDir(source)
       grunt.file.recurse source, (sourcePath, rootDirectory, subDirectory='', filename) ->
         destinationPath = path.join(destination, subDirectory, filename)
         grunt.file.copy(sourcePath, destinationPath)


Unify build using UMD. Supports browserify, AMD, globals, etc

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -38,20 +38,7 @@
       }
     }

-    # Compiles coffee for module distribution
-    coffee: {
-      module: {
-        options: {
-          bare: true
-        }
-        files: {
-          'lib/SUtil.js': 'src/SUtil.coffee'
-          'lib/ScrollProxy.js': 'src/ScrollProxy.coffee'
-        }
-      }
-    }
-
-    # Browserify testing bundle and bundle for standalone lib
+    # Browserify bundles
     browserify: {
       standalone: {
         src: ['src/*.coffee']
@@ -118,9 +105,10 @@
     clean: [
       'dist'
       'docs'
-      'lib'
       'demo/scroll-proxy.js'
       'test/testBundle.js'
+      'bower_components'
+      'node_modules'
     ]

     # Watch tasks to run while developing
@@ -187,7 +175,6 @@
   grunt.loadNpmTasks('grunt-coffeelint')
   grunt.loadNpmTasks('grunt-contrib-clean')
   grunt.loadNpmTasks('grunt-mochify')
-  grunt.loadNpmTasks('grunt-contrib-coffee')
   grunt.loadNpmTasks('grunt-contrib-connect')
   grunt.loadNpmTasks('grunt-contrib-watch')
   grunt.loadNpmTasks('grunt-http-server')
@@ -207,7 +194,6 @@
   ])
   grunt.registerTask('build', [
     'test'
-    'coffee'
     'browserify:standalone'
     'docs'
   ])


Remove test block

Remove test block

--- scripts/dialogue.coffee
+++ scripts/dialogue.coffee
@@ -84,14 +84,6 @@

     org_receive.bind(robot)(message)

-  robot.respond /dialogue/i, (msg) ->
-    user = msg.envelope.user.name
-    room = msg.envelope.user.room
-
-    robot.emit "dialogue:start", user, room, (message) ->
-      robot.send {user: @user, room: @room}, "wahaha"
-      @end()
-
   # backword compatibility
   robot.brain.on "loaded", ->
     if robot.brain.data.dialogs?


reinstate original comment

reinstate original comment

--- test/command_spec.coffee
+++ test/command_spec.coffee
@@ -30,6 +30,7 @@
 prep = (done) ->
     robot = new Robot path.resolve(__dirname), 'shell', yes, 'TestHubot'
     robot.adapter.on 'connected', ->
+        # Project script
         robot.loadFile path.resolve('.'), 'index.coffee'
         # Some test users
         user = robot.brain.userForId "1",


updated consolidating user ids for tags to reference db

--- server/bootstrap.coffee
+++ server/bootstrap.coffee
@@ -40,12 +40,14 @@
             }
           'two': {
             users: [2]
+            weight: 2
             }
           'red': {
-            [1]
+            users: [1]
             }
           'blue': {
-            [2]
+            users: [2]
+            weight: 4
             }
         }
       },
@@ -60,6 +62,7 @@
             }
           'red': {
             users: [1]
+            weight: 3
             }
         }
       },
@@ -100,4 +103,7 @@
       if 'instance_id' of post
         post.instance_id = instance_ids[post.instance_id]
       post_ids.push Posts.insert(post)
-  +    for post in posts
+      for tag in post.tags
+       for user in tag.users
+        user = user_ids[user]


Ace: Fixed an existence check.

Ace: Fixed an existence check.

--- client/Ace/ace.coffee
+++ client/Ace/ace.coffee
@@ -414,7 +414,10 @@
             details.destroy()

   saveAllFiles: ->
-    {aceViews} = KD.singletons.appManager.get("Ace")?.getView()
+    aceApp = KD.singletons.appManager.get 'Ace'
+    return unless aceApp
+
+    {aceViews} = aceApp.getView()

     for path, aceView of aceViews when aceView.data.parentPath isnt "localfile:"
       aceView.ace.requestSave()