Client: Teams: Added emitItemValueChanged event handler and added click handler to checkbox.

--- client/admin/lib/views/invitations/slackuseritem.coffee
+++ client/admin/lib/views/invitations/slackuseritem.coffee
@@ -9,12 +9,19 @@
     super options, data

+  emitItemValueChanged: ->
+
+    list = @getDelegate()
+    list.emit 'ItemValueChanged'
+
+
   viewAppended: ->

     { real_name, name, profile } = @getData()

     @addSubView @checkBox = new kd.CustomCheckBox
       defaultValue : on
+      click        : => @emitItemValueChanged()

     @addSubView new kd.CustomHTMLView
       tagName  : 'a'
@@ -23,5 +30,4 @@
       partial  : " @#{name} #{real_name}"
       click    : =>
         @checkBox.setValue not @checkBox.getValue()
-        list = @getDelegate()
-        list.emit 'ItemValueChanged'+        @emitItemValueChanged()


sidebar: force fetch all items initially.

Until KodingBot is in prefetchedData this was required.

--- client/app/lib/activity/sidebar/activitysideview.coffee
+++ client/app/lib/activity/sidebar/activitysideview.coffee
@@ -1,10 +1,10 @@
+kd                     = require 'kd'
+JView                  = require '../../jview'
+KDView                 = kd.View
+SidebarMoreLink        = require './sidebarmorelink'
+KDCustomHTMLView       = kd.CustomHTMLView
+KDListViewController   = kd.ListViewController
 isChannelCollaborative = require '../../util/isChannelCollaborative'
-kd = require 'kd'
-KDCustomHTMLView = kd.CustomHTMLView
-KDListViewController = kd.ListViewController
-KDView = kd.View
-JView = require '../../jview'
-SidebarMoreLink = require './sidebarmorelink'

 module.exports = class ActivitySideView extends JView
@@ -89,8 +89,17 @@

   init: ->

+    # TODO: until KodingBot is in the prefetched data, just reload automatically
+    # so that sidebar won't use the prefetched data.
+    return @reload()
+
     {dataPath} = @getOptions()
     items = kd.singletons.socialapi.getPrefetchedData dataPath
+
+    # delete this line if `getPrefetchedeData 'bot'` returns an array.
+    # this is justn an assumption that the result will be a single social
+    # channel instance rather than an array. ~Umut
+    items = [items]  unless Array.isArray items

     if items?.length
     then @renderItems null, items


Looks good on staging, remove debug and prepare for PR

--- app/assets/javascripts/sticky_sidebar/presenter.coffee
+++ app/assets/javascripts/sticky_sidebar/presenter.coffee
@@ -29,7 +29,6 @@

         $container.css('min-height', minHeight)

-        console.log(contentHeight)
       else
         # Exit here, since feature is only applicable to two column xl layout
         return


move development attribution to bottom right on clients request

--- app/assets/javascripts/analysis/analysis.js.coffee
+++ app/assets/javascripts/analysis/analysis.js.coffee
@@ -40,7 +40,7 @@
   L.control.layers(baseMaps, overlayMaps).addTo(map)

   attribution = L.control.attribution(
-    position: 'bottomleft'
+    position: 'bottomright'
     prefix: ''
   )
   attribution.addAttribution('Developed for the Abu Dhabi Blue Carbon Demonstration Project')


stack-editore/stacktemplate: remove review, logs buttons

--- client/stack-editor/lib/editor/stacktemplateview.coffee
+++ client/stack-editor/lib/editor/stacktemplateview.coffee
@@ -40,27 +40,6 @@
       delegate: this, content, contentType, showHelpContent
     }

-    @editorView.addSubView @previewButton = new KDButtonView
-      title    : 'PREVIEW'
-      cssClass : 'HomeAppView--button secondary template-preview-button'
-      loader   : yes
-      loaderOptions: { color: '#858585' }
-      tooltip  :
-        title  : 'Generates a preview of this template with your own account information.'
-      callback : => @emit 'ShowTemplatePreview'
-
-    @editorView.addSubView new KDButtonView
-      title    : 'LOGS'
-      cssClass : 'HomeAppView--button secondary showlogs-button'
-      callback : => @emit 'ShowOutputView'
-
-    @editorView.addSubView new KDButtonView
-      cssClass : 'HomeAppView--button secondary fullscreen-button'
-      title    : ''
-      callback : =>
-        kd.singletons.appManager.tell 'Stacks', 'toggleFullscreen'
-        kd.utils.wait 250, => @editorView.resize()
-
     @editorView.on 'click', => @emit 'HideOutputView'


remove staging interfaces

remove staging interfaces

--- app/assets/javascripts/annotation_lifecycle.coffee
+++ app/assets/javascripts/annotation_lifecycle.coffee
@@ -33,7 +33,7 @@
     $.each($('.template', '.interfaces-staging'), (index, template) ->
       interfaceType = $(template).data('interface-type')
       _this.templates[interfaceType] = template
-      # $(this).remove()
+      $(this).remove()
     )

     this.$blankSlate = $('.no-annotation-document-blank-slate')


fix another silly mistake

fix another silly mistake

--- app/assets/javascripts/components/mark-button.cjsx
+++ app/assets/javascripts/components/mark-button.cjsx
@@ -36,7 +36,7 @@

     <Draggable onEnd={@props.onDrag}>
       <g
-        className="mark-tool button-button"
+        className="mark-tool mark-button"
         transform=
           "
             translate(#{@props.position.x},#{@props.position.y})


added unsaved changes message when leaving

--- app/assets/javascripts/type_station/init.js.coffee
+++ app/assets/javascripts/type_station/init.js.coffee
@@ -22,12 +22,20 @@
   @onEnable()
   @editors.each (id, editor) -> editor.enable()
   @onEnabled()
+  $(window).on 'beforeunload', =>
+    dirtyArray = []
+    @models.each (id, model) -> dirtyArray.push model.isChanged()
+    if $.grep(dirtyArray, (a) ->  a == true ).length > 0
+      return 'You have unsaved changes. Are you sure you want to leave?'
+    else
+      return

 window.TS.disable = ->
   window.location.hash = '#!ts-edit-disable'
   @onDisable()
   @editors.each (id, editor) -> editor.disable()
   @onDisabled()
+  $(window).off 'beforeunload'

 window.TS.save = ->
   @onSave()


Added count field to tests.

Added count field to tests.

--- tests.coffee
+++ tests.coffee
@@ -146,6 +146,9 @@

   @Meta
     name: 'Person'
+    fields: =>
+      count: @GeneratedField 'self', ['posts', 'nestedPosts'], (fields) ->
+        [fields._id, (fields.posts?.length or 0) + (fields.nestedPosts?.length or 0)]

   formatName: ->
     "#{ @username }-#{ @displayName or "none" }"
@@ -455,7 +458,7 @@
   test.isFalse Person.Meta.parent
   test.equal Person.Meta._name, 'Person'
   test.equal Person.Meta.collection._name, 'Persons'
-  test.equal _.size(Person.Meta.fields), 2
+  test.equal _.size(Person.Meta.fields), 3
   test.instanceOf Person.Meta.fields.posts, Person._ReferenceField
   test.equal Person.Meta.fields.posts.ancestorArray, 'posts'
   test.isTrue Person.Meta.fields.posts.required
@@ -482,6 +485,19 @@
   test.equal Person.Meta.fields.nestedPosts.fields, ['body', 'subdocument.body', 'nested.body']
   test.isNull Person.Meta.fields.nestedPosts.reverseName
   test.equal Person.Meta.fields.nestedPosts.reverseFields, []
+  test.instanceOf Person.Meta.fields.count, Person._GeneratedField
+  test.isNull Person.Meta.fields.count.ancestorArray, Person.Meta.fields.count.ancestorArray
+  test.isTrue _.isFunction Person.Meta.fields.count.generator
+  test.equal Person.Meta.fields.count.sourcePath, 'count'
+  test.equal Person.Meta.fields.count.sourceDocument, Person
+  test.equal Person.Meta.fields.count.targetDocument, Person
+  test.equal Person.Meta.fields.count.sourceCollection._name, 'Persons'
+  test.equal Person.Meta.fields.count.targetCollection._name, 'Persons'
+  test.equal Person.Meta.fields.count.sourceDocument.Meta.collection._name, 'Persons'
+  test.equal Person.Meta.fields.count.targetDocument.Meta.collection._name, 'Persons'
+  test.equal Person.Meta.fields.count.fields, ['posts', 'nestedPosts']
+  test.isUndefined Person.Meta.fields.count.reverseName
+  test.isUndefined Person.Meta.fields.count.reverseFields

   test.equal Recursive.Meta._name, 'Recursive'
   test.isFalse Recursive.Meta.parent
@@ -772,6 +788,7 @@
       displayName: 'Person 1'
       field1: 'Field 1 - 1'
       field2: 'Field 1 - 2'
+      count: 0
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -779,6 +796,7 @@
       displayName: 'Person 2'
       field1: 'Field 2 - 1'
       field2: 'Field 2 - 2'
+      count: 0
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -786,6 +804,7 @@
       displayName: 'Person 3'
       field1: 'Field 3 - 1'
       field2: 'Field 3 - 2'
+      count: 0

     Post.documents.insert
       author:
@@ -962,6 +981,7 @@
         subdocument:
           body: 'SubdocumentFooBar'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -978,6 +998,7 @@
         subdocument:
           body: 'SubdocumentFooBar'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -985,6 +1006,7 @@
       displayName: 'Person 3'
       field1: 'Field 3 - 1'
       field2: 'Field 3 - 2'
+      count: 0

     # Sleep so that observers have time to update documents
     Meteor.setTimeout expect(), WAIT_TIME
@@ -1958,6 +1980,7 @@
       displayName: 'Person 1'
       field1: 'Field 1 - 1'
       field2: 'Field 1 - 2'
+      count: 0
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -1965,6 +1988,7 @@
       displayName: 'Person 2'
       field1: 'Field 2 - 1'
       field2: 'Field 2 - 2'
+      count: 0
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -1972,6 +1996,7 @@
       displayName: 'Person 3'
       field1: 'Field 3 - 1'
       field2: 'Field 3 - 2'
+      count: 0

     Post.documents.insert
       author:
@@ -2152,6 +2177,7 @@
         subdocument:
           body: 'SubdocumentFooBar'
       ]
+      count: 1

     @postLink = PostLink.documents.findOne @postLinkId,
       transform: null # So that we can use test.equal
@@ -2267,16 +2293,19 @@
       _schema: '1.0.0'
       username: 'person1'
       displayName: 'Person 1'
+      count: 0
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
       username: 'person2'
       displayName: 'Person 2'
+      count: 0
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
       username: 'person3'
       displayName: 'Person 3'
+      count: 0

     Post.documents.insert
       author:
@@ -3382,6 +3411,7 @@
       displayName: 'Person 1'
       field1: 'Field 1 - 1'
       field2: 'Field 1 - 2'
+      count: 0
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -3389,6 +3419,7 @@
       displayName: 'Person 2'
       field1: 'Field 2 - 1'
       field2: 'Field 2 - 2'
+      count: 0
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -3396,6 +3427,7 @@
       displayName: 'Person 3'
       field1: 'Field 3 - 1'
       field2: 'Field 3 - 2'
+      count: 0

     Post.documents.insert
       author:
@@ -3707,6 +3739,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -3733,6 +3766,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -3759,6 +3793,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     # Sleep so that observers have time to update documents
     Meteor.setTimeout expect(), WAIT_TIME
@@ -3956,6 +3991,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -4145,6 +4181,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -4333,6 +4370,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -4511,6 +4549,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -4680,6 +4719,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -4850,6 +4890,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -5030,6 +5071,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -5221,6 +5263,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -5402,6 +5445,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -5596,6 +5640,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -5622,6 +5667,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -5648,6 +5694,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -5842,6 +5889,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -5868,6 +5916,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -5894,6 +5943,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -6088,6 +6138,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -6114,6 +6165,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -6140,6 +6192,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -6334,6 +6387,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -6360,6 +6414,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -6386,6 +6441,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -6579,6 +6635,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -6605,6 +6662,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -6631,6 +6689,7 @@
         ]
         body: 'FooBar'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -6821,6 +6880,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -6847,6 +6907,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -6873,6 +6934,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -7070,6 +7132,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1
     test.equal @person2,
       _id: @person2Id
       _schema: '1.0.0'
@@ -7098,6 +7161,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -7126,6 +7190,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -7317,6 +7382,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1
     test.equal @person3,
       _id: @person3Id
       _schema: '1.0.0'
@@ -7337,6 +7403,7 @@
         ]
         body: 'FooBarZ'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -7440,6 +7507,7 @@
         nested: []
         body: 'FooBarZ'
       ]
+      count: 1

     @post = Post.documents.findOne @postId,
       transform: null # So that we can use test.equal
@@ -7561,16 +7629,19 @@
       _schema: '1.0.0'
       username: 'person1'
       displayName: 'Person 1'
+      count: 0
     test.equal plainObject(@person2),
       _id: @person2Id
       _schema: '1.0.0'
       username: 'person2'
       displayName: 'Person 2'
+      count: 0
     test.equal plainObject(@person3),
       _id: @person3Id
       _schema: '1.0.0'
       username: 'person3'
       displayName: 'Person 3'
+      count: 0

     test.equal @person1.formatName(), 'person1-Person 1'
     test.equal @person2.formatName(), 'person2-Person 2'


minor naming correction

minor naming correction

--- tests.coffee
+++ tests.coffee
@@ -1812,7 +1812,7 @@
   Counter.counter = 0

   Counter answer
-  ﹍﹍incrementInPlace
+  ﹍﹍increment
   ﹍by
   ﹍﹍self.counter = self.counter plus 1

@@ -1822,7 +1822,7 @@
   MyClass answer
   ﹍whenNew
   by
-  ﹍self.instantiationsCounter incrementInPlace
+  ﹍self.instantiationsCounter increment
   ﹍self

   MyClass answer
@@ -1856,7 +1856,7 @@
   Counter.counter = 0

   Counter answer
-  ﹍﹍incrementInPlace
+  ﹍﹍increment
   ﹍by
   ﹍﹍self.counter = self.counter plus 1


KDRouter dom exception fix

KDRouter dom exception fix

--- client/Framework/core/KDRouter.coffee
+++ client/Framework/core/KDRouter.coffee
@@ -132,7 +132,7 @@

     if shouldPushState
       method = if replaceState then 'replaceState' else 'pushState'
-      history[method] objRef, '', path
+      history[method] objRef, path, path

     for edge in frag
       if node[edge]


added min and max labels under slider

added min and max labels under slider

--- slider.coffee
+++ slider.coffee
@@ -57,12 +57,16 @@
     ngModelLow:   '=?'
     ngModelHigh:  '=?'
   template: '''
-

-

-
{{ values.length ? values[floor || 0] : floor }}

-
{{ values.length ? values[ceiling || values.length - 1] : ceiling }}

-
{{ values.length ? values[local.ngModelLow || local.ngModel || 0] : local.ngModelLow || local.ngModel || 0 }}

-
{{ values.length ? values[local.ngModelHigh] : local.ngModelHigh }}
'''
+

+

+

+

+
+        {{ values.length ? values[local.ngModelLow || local.ngModel || 0] : local.ngModelLow || local.ngModel || 0 }}
+       -
+        {{ values.length ? values[local.ngModelHigh] : local.ngModelHigh }}
+
+
'''
   compile: (element, attributes) ->

     # Check if it is a range slider
@@ -77,8 +81,9 @@

     post: (scope, element, attributes) ->
       # Get references to template elements
-      [bar, minPtr, maxPtr, flrBub, ceilBub, lowBub, highBub] = (angularize(e) for e in element.children())
+      [bar, minPtr, maxPtr, valText] = (angularize(e) for e in element.children())
       selection = angularize bar.children()[0]
+      [lowBub, highBub] = (angularize(e) for e in valText.children())

       # Remove range specific elements if not a range slider
       unless range
@@ -131,7 +136,6 @@
         pixelsToOffset = (percent) -> pixelize percent * offsetRange / 100

         setPointers = ->
-          offset ceilBub, pixelize(barWidth - width(ceilBub))
           newLowValue = percentValue scope.local[low]
           offset minPtr, pixelsToOffset newLowValue
           offset lowBub, pixelize(offsetLeft(minPtr) - (halfWidth lowBub) + handleHalfWidth)


Environments: MainScene follow mainController events to update environments when requested

--- client/Environments/views/environmentsmainscene.coffee
+++ client/Environments/views/environmentsmainscene.coffee
@@ -5,19 +5,22 @@
     options.cssClass = KD.utils.curry 'environment-content', options.cssClass
     super options, data

+    @stacks = []
     @on "CloneStackRequested", @bound "cloneStack"

   viewAppended:->

     @addSubView @renderHeader()
-    KD.singletons.mainController.ready @bound 'renderStacks'
-
+    { mainController } = KD.singletons
+    mainController.ready @bound 'renderStacks'
+    mainController.on "renderStacks", @bound 'renderStacks'

   renderStacks:->

     ComputeProvider.fetchStacks (err, stacks = [])=>

+      (stack?.destroy?() for stack in @stacks)
       @stacks = []

       for stack, index in stacks
@@ -26,7 +29,6 @@
         @forwardEvent stackView, "CloneStackRequested"

       @emit "StacksCreated"
-      callback?()

   createNewStack: (meta, modal)->


config: switch vagrant to warning goLogLevel

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -44,7 +44,7 @@
     read        : "http://localhost"
     write       : "http://localhost"
     port        : 7474
-  goLogLevel    : "debug"
+  goLogLevel    : "warning"
   runNeo4jFeeder: yes
   runGoBroker   : yes
   runKontrol    : no


fix after search param

fix after search param

--- static/coffee/services.coffee
+++ static/coffee/services.coffee
@@ -137,7 +137,7 @@
       return {
         folder: search.folder,
         text: search.text,
-        after: utils.formatIso8601(search.after),
+        after: if search.last_refresh then utils.formatIso8601(search.last_refresh) else utils.formatIso8601(search.after),
         before: utils.formatIso8601(search.before),
         groups: if search.groups then (g.id for g in search.groups) else null,
         contact: if search.contact then search.contact.id else null,


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -34,7 +34,7 @@
   equals player.I.alpha, 0.2

   player.update()
-  equals pl
+  equals player.I.alpha, 0


added module to test

added module to test

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -1,16 +1,20 @@
-test "GameObject()", ->
+module "GameObject"
+
+test "()", ->
   gameObject = GameObject()

-test "GameObject.construct", ->
+test "construct", ->
   gameObject = GameObject.construct
     x: 20
     y: 20

   equals(gameObject.position().x, 20)

-test "GameObject [event] step ", ->
+test "[event] step ", ->
   gameObject = GameObject
     step: "equals(I.age, 0, 'Age should be 0 on first step')"

   gameObject.update()

+module()
+


We don't have to listen for "navigate" in the ModalWindowContainer, as there are no navigates originating from a modal that don't also trigger a 'load_url' (ie. those that use navigate url, false)

--- core/app/backbone/modules/modal_window_container.coffee
+++ core/app/backbone/modules/modal_window_container.coffee
@@ -26,7 +26,7 @@
   FactlinkApp.addRegions
     modalRegion: "#modal_region"

-  FactlinkApp.vent.on 'navigate load_url', ->
+  FactlinkApp.vent.on 'load_url', ->
     ModalWindowContainer.close()

   ModalWindowContainer.show = (content_view)->


Function for formatting players' names as part of library

--- static/script/settlers.coffee
+++ static/script/settlers.coffee
@@ -129,6 +129,20 @@

   return pager

+window.settlers.fmt_player = (player) ->
+  s = player.user.name
+
+  if player.user.is_online == true
+    s = '' + s + ''
+
+  if player.is_confirmed != true
+    s = '' + s + ''
+
+  if player.is_on_turn == true
+    s = '' + s + ''
+
+  return s
+
 $(window).bind 'hlib_startup', () ->
   window.hlib.setup
     message_dialog:		'#message_dialog'


chmod +x on test_runner.coffee

chmod +x on test_runner.coffee


add teacher specification for calendar tests

--- test-integration/calendar.coffee
+++ test-integration/calendar.coffee
@@ -12,7 +12,7 @@
   @eachCourse = (msg, fn) =>
     _.each ['BIOLOGY', 'PHYSICS'], (courseCategory) =>
       @it "#{msg} for #{courseCategory}", (done) ->
-        @courseSelect.goToByType(courseCategory)
+        @courseSelect.goToByType(courseCategory, 'teacher')
         @calendar.waitUntilLoaded()
         fn.call(@, courseCategory)
         # Go back to the course selection after the spec


Check both 'accounts-base' and Accounts objects

 - Should fix issue described in #114 and #99

--- files.coffee
+++ files.coffee
@@ -484,7 +484,7 @@
       delete @interceptDownload
       delete @continueUploadTTL

-      if _.has(Package, 'accounts-base')
+      if _.has(Package, 'accounts-base') and Accounts
         setTokenCookie = ->
           if (not cookie.has('meteor_login_token') and Accounts._lastLoginTokenWhenPolled) or (cookie.has('meteor_login_token') and (cookie.get('meteor_login_token') isnt Accounts._lastLoginTokenWhenPolled))
             cookie.set 'meteor_login_token', Accounts._lastLoginTokenWhenPolled, null, '/'
@@ -1024,13 +1024,13 @@
     if Meteor.isServer
       if http
         cookie = http.request.Cookies
-        if _.has(Package, 'accounts-base') and cookie.has 'meteor_login_token'
+        if _.has(Package, 'accounts-base') and Accounts and cookie.has 'meteor_login_token'
           user = Meteor.users.findOne 'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken cookie.get 'meteor_login_token'
           if user
             result.user = () -> return user
             result.userId = user._id
     else
-      if _.has(Package, 'accounts-base') and Meteor.userId()
+      if _.has(Package, 'accounts-base') and Accounts and Meteor.userId()
         result.user = -> return Meteor.user()
         result.userId = Meteor.userId()


Add Selenium address.

Add Selenium address.

--- test/conf/protractor-conf.coffee
+++ test/conf/protractor-conf.coffee
@@ -1,17 +1,44 @@
 exports.config =
+  # Note: the debug and logLevel options are set in the grunt
+  # protractor task based on whether the grunt command is
+  # executed with the --debug option.
+
+  # Run under the mocha framework rather than the default jasmine.
   framework: 'mocha'
-
-  capabilities:
-    'browserName': 'chrome'
-
+
+  # Although protractor can run CoffeeScript tests, stack trace line
+  # numbers are for the compiled Javascript rather than the source
+  # CoffeeScript. StackOverflow has some posts on this, but as of
+  # 9/10/2014 no solution was forthcoming. Work-around is to compile
+  # the error test spec and go to the line number in that file.
+  # The coding standard that all expectations include a string
+  # failure message facilitates tracing the error without resorting
+  # to a compile.
   plugins: ['protractor-coffee-preprocessor']

+  # If the selenium address is not specified, then protractor
+  # issues the following error message:
+  #    Could not find chromedriver
+  seleniumAddress: 'http://localhost:4444/wd/hub'
+
+  # The test server is on port 3001, not 3000.
   baseUrl: 'http://localhost:3001/quip/'

-  specs: ['../e2e/**/*Spec.coffee']
+  # Run all e2e specs. The command line --specs option overrides
+  # this setting.
+  specs: ['../e2e/**Spec.coffee']

+  # Run the qirest seed and link the test fixtures into _public,
+  # if necessary.
   onPrepare: '../e2e/helpers/seed'

+  # Pass options into mocha.
   mochaOpts:
+    # Allow four seconds per test rather than the default two seconds.
+    timeout: 4000
+
+    # The spec reporter has nice checkmarks.
     reporter: 'spec'
+
+    # Raise the threshold for showing slow tests in red to 1.2 seconds.
     slow: 1200


Bump the timeout.

Bump the timeout.

--- test/conf/protractor-conf.coffee
+++ test/conf/protractor-conf.coffee
@@ -29,7 +29,7 @@
   # Pass options into mocha.
   mochaOpts:
     # Allow five seconds per test rather than the default two seconds.
-    timeout: 5000
+    timeout: 15000

     # The spec reporter has nice checkmarks.
     reporter: 'spec'


test for nested blocks

test for nested blocks

--- test/parser/statements.coffee
+++ test/parser/statements.coffee
@@ -15,6 +15,11 @@

 assert parses(';'), 'an empty statement should parse'
 assert parses(';;;'), 'multiple empty statements should parse'
+
+# statement block
+
+assert parses('{}'), 'an empty block should parse'
+assert parses('{;}'), 'a non-empty block should parse'

 # expression statement


OAuth2Strategy constructor tests

OAuth2Strategy constructor tests

--- test/strategies/OAuth2.coffee
+++ test/strategies/OAuth2.coffee
@@ -53,15 +53,33 @@
     client_secret:  'secret'
     scope:          ['c']

+  verify = (req, res, profile) ->

-  strategy = new OAuth2Strategy provider, config
+  strategy = new OAuth2Strategy provider, config, verify

-  describe 'object', ->
+  describe 'instance', ->

-    it 'should be an instance of Strategy', ->
+    it 'should inherit from Strategy', ->
       expect(strategy).to.be.instanceof Strategy

+
+  describe 'constructor', ->
+
+    it 'should set provider', ->
+      strategy.provider.should.equal provider
+
+    it 'should set endpoints', ->
+      strategy.endpoints.should.equal provider.endpoints
+
+    it 'should set client', ->
+      strategy.client.should.equal config
+
+    it 'should set name', ->
+      strategy.name.should.equal provider.id
+
+    it 'should set verify', ->
+      strategy.verify.should.equal verify

   describe 'base64credentials', ->


fixed trigger service tests.

fixed trigger service tests.

--- test/trigger-service-spec.coffee
+++ test/trigger-service-spec.coffee
@@ -1,6 +1,7 @@
 describe 'TriggerService', =>
   beforeEach ->
     module 'blu', ($provide) =>
+      $provide.value '$cookies', {}
       return

     inject (_$httpBackend_, TriggerService) =>


misremove

misremove

--- browser/src/coffee/search.coffee
+++ browser/src/coffee/search.coffee
@@ -58,7 +58,7 @@
             handle.match()
         loadWeb: (e)->
             item = e.targetVM.result
-            @data.webContentSrc = "http://localhost:#{S.docPort}/" +
+            @webContentSrc = "http://localhost:#{S.docPort}/" +
                 encodeURIComponent(item.docset.name) +
                 "/Contents/Resources/Documents/#{item.path}"
             $('ul#doc-list li').removeClass 'active'


signtool is now on the PATH

signtool is now on the PATH

--- build/tasks/codesign-task.coffee
+++ build/tasks/codesign-task.coffee
@@ -28,7 +28,7 @@
         args = ['-f', '-v', '-s', 'Developer ID Application: GitHub', grunt.config.get('atom.shellAppDir')]
         spawn {cmd, args}, (error) -> callback(error)
       when 'win32'
-        cmd = 'C:\\Users\\jenkins\\bin\\signtool.bat'
+        cmd = 'signtool.bat'
         args = [path.join(grunt.config.get('atom.shellAppDir'), 'atom.exe')]
         spawn {cmd, args}, (error) -> callback(error)
       else


payment: show paypal not allowed for paypal {up,down}grade

--- client/Main/payment/modal.coffee
+++ client/Main/payment/modal.coffee
@@ -33,8 +33,12 @@

     @addSubView @errors = new KDCustomHTMLView
       cssClass : 'errors hidden'
+    { provider, isUpgrade, planTitle } = @state
+    { PAYPAL } = PaymentWorkflow.provider
+    { FREE }   = PaymentWorkflow.planTitle

     @addSubView @form = new PaymentForm { @state }
+    @handlePaypalNotAllowed()  if provider is PAYPAL and planTitle isnt FREE

   initEvents: ->
@@ -54,6 +58,17 @@
     paymentController.on 'PaypalRequestFinished', @bound 'handlePaypalResponse'

+  handlePaypalNotAllowed: ->
+
+    @setTitle 'Contact support'
+    @form.showPaypalNotAllowedStage()
+
+
+  handlePaypalUpgradeFinished: ->
+
+    @once 'KDModalViewDestroyed', =>
+      @emit 'PaymentWorkflowFinishedWithError', @state
+
   handlePaypalResponse: (err) ->

     @form.paypalForm.buttons['paypal'].hideLoader()
@@ -64,7 +79,7 @@

     paymentController.subscriptions (err, subscription) =>

-      # return KD.showError err  if err
+      return KD.showError err  if err

       @state = KD.utils.extend @state, subscription

@@ -91,6 +106,7 @@

   handleError: (error) ->
+
     msg = error?.description or error?.message or "Something went wrong."
     KD.showError msg


teams: activity: handle sidebar shortcuts only for public product

--- client/activity/lib/index.coffee
+++ client/activity/lib/index.coffee
@@ -4,6 +4,7 @@
 globals         = require 'globals'
 getGroup        = require 'app/util/getGroup'
 checkFlag       = require 'app/util/checkFlag'
+isKoding        = require 'app/util/isKoding'
 AppStorage      = require 'app/appstorage'
 AppController   = require 'app/appcontroller'
 KodingAppsController = require 'app/kodingappscontroller'
@@ -133,9 +134,12 @@

     kd.utils.stopDOMEvent e

-    switch e.model.name
-      when 'prevwindow' then @getView().openPrev()
-      when 'nextwindow' then @getView().openNext()
+    if isKoding()
+      switch e.model.name
+        when 'prevwindow' then @getView().openPrev()
+        when 'nextwindow' then @getView().openNext()
+    # else
+    #   throw 'flux action'

 helper =


Ace: Deleted window key binding.

Ace: Deleted window key binding.

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -90,11 +90,6 @@

       @addKeyCombo "preview", "Ctrl-Shift-P", => @getDelegate().preview()

-    KD.getSingleton('windowController').on "keydown", (e) =>
-      {findAndReplaceView} = @getDelegate()
-      findAndReplaceView.close() if e.keyCode is 27 and findAndReplaceView
-
-
   showFindReplaceView: (openReplaceView) ->
     {findAndReplaceView} = @getDelegate()
     selectedText         = @editor.session.getTextRange @editor.getSelectionRange()


hackathon: spelling error...grrrr

hackathon: spelling error...grrrr

--- client/landing/site.hackathon/coffee/home/AppViewFull.coffee
+++ client/landing/site.hackathon/coffee/home/AppViewFull.coffee
@@ -503,7 +503,7 @@

-
3Scale will offer an additional $1,500 to the team that makes the best usage of any API on thier site.

+
3Scale will offer an additional $1,500 to the team that makes the best usage of any API on their site.

@@ -523,7 +523,7 @@

-
APITools will offer $1,500 to the team that uses APItools in the creation of thier project.

+
APITools will offer $1,500 to the team that uses APItools in the creation of their project.


IDE: Fetch environment data before routing.

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -1583,8 +1583,10 @@

     if destroy
       kd.singletons.appManager.quit this, =>
-        route = if @mountedMachine then "/IDE/#{@mountedMachine.slug}" else '/IDE'
-        kd.singletons.router.handleRoute route
+        # fetch data to ensure target workspace is still exist
+        environmentDataProvider.fetch =>
+          route = if @mountedMachine then "/IDE/#{@mountedMachine.slug}" else '/IDE'
+          kd.singletons.router.handleRoute route

       @once 'KDObjectWillBeDestroyed', @lazyBound 'emit', 'IDEDidQuit'


IDE: use new resource state modal

IDE: use new resource state modal

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -737,11 +737,9 @@
     container   ?= @getView()
     modalOptions = { state, container, initial }

-    if checkFlag 'new-resource-state-modal'
+    if isTeamReactSide()
       @machineStateModal = new ResourceStateModal modalOptions, machineItem
     else
-      if isTeamReactSide() and state is Stopping
-        modalOptions.cssClass = 'env-machine-state team full'
       @machineStateModal = new EnvironmentsMachineStateModal modalOptions, machineItem

     @machineStateModal.once 'KDObjectWillBeDestroyed', => @machineStateModal = null


IDE: remove unnecessary defers, fix cyclomatic complexity

--- client/ide/lib/index.coffee
+++ client/ide/lib/index.coffee
@@ -535,50 +535,46 @@

   createInitialView: (withFakeViews) ->

-    kd.utils.defer =>
-
-      @getMountedMachine (err, machine) =>
-
-        return unless machine
-
-        machine = new Machine { machine }  unless machine instanceof Machine
-
-        for ideView in @ideViews
-          ideView.mountedMachine = @mountedMachine
-
-        if not @isMachineRunning() or withFakeViews
-          nickname     = machine.getOwner()
-          machineLabel = machine.slug or machine.label
-          splashes     = splashMarkups
+    @getMountedMachine (err, machine) =>
+
+      return  unless machine
+
+      for ideView in @ideViews
+        ideView.mountedMachine = @mountedMachine
+
+      if not @isMachineRunning() or withFakeViews
+
+        nickname     = machine.getOwner()
+        machineLabel = machine.slug or machine.label
+        splashes     = splashMarkups
+
+        @splitTabView { type: 'horizontal', dontSave: yes }
+
+        @fakeEditor       = @ideViews.first.createEditor()
+        @fakeTabView      = @activeTabView
+        fakeTerminalView  = new kd.CustomHTMLView { partial: splashes.getTerminal nickname }
+        @fakeTerminalPane = @fakeTabView.parent.createPane_ fakeTerminalView, { name: 'Terminal' }
+        @fakeFinderView   = new kd.CustomHTMLView { partial: splashes.getFileTree nickname, machineLabel }
+
+        @finderPane.addSubView @fakeFinderView, '.nfinder .jtreeview-wrapper'
+        @fakeEditor.once 'EditorIsReady', => kd.utils.wait 1500, => @fakeEditor.setFocus no
+
+      else
+
+        @fetchSnapshot (snapshot) =>
+
+          # Just resurrect snapshot for host or without collaboration.
+          # Because we need check the `@myWatchMap` and it is not possible here.
+          if snapshot and (@amIHost or @mountedMachine.isPermanent())
+            return @layoutManager.resurrectSnapshot snapshot
+
+          # Be quiet. Don't write initial views's changes to snapshot.
+          # After that, get participant's snapshot from collaboration data and build workspace.
+          @silent = yes  if @isInSession and not @amIHost and not @mountedMachine.isPermanent()

           @splitTabView { type: 'horizontal', dontSave: yes }

-          @fakeEditor       = @ideViews.first.createEditor()
-          @fakeTabView      = @activeTabView
-          fakeTerminalView  = new kd.CustomHTMLView { partial: splashes.getTerminal nickname }
-          @fakeTerminalPane = @fakeTabView.parent.createPane_ fakeTerminalView, { name: 'Terminal' }
-          @fakeFinderView   = new kd.CustomHTMLView { partial: splashes.getFileTree nickname, machineLabel }
-
-          @finderPane.addSubView @fakeFinderView, '.nfinder .jtreeview-wrapper'
-          @fakeEditor.once 'EditorIsReady', => kd.utils.wait 1500, => @fakeEditor.setFocus no
-
-        else
-
-          @fetchSnapshot (snapshot) =>
-
-
-            # Just resurrect snapshot for host or without collaboration.
-            # Because we need check the `@myWatchMap` and it is not possible here.
-            if snapshot and (@amIHost or @mountedMachine.isPermanent())
-              return @layoutManager.resurrectSnapshot snapshot
-
-            # Be quiet. Don't write initial views's changes to snapshot.
-            # After that, get participant's snapshot from collaboration data and build workspace.
-            @silent = yes  if @isInSession and not @amIHost and not @mountedMachine.isPermanent()
-
-            @splitTabView { type: 'horizontal', dontSave: yes }
-
-            @addInitialViews()
+          @addInitialViews()

   setMountedMachine: (machine) ->
@@ -596,14 +592,19 @@

   getMountedMachine: (callback = noop) ->

-    return callback()  unless @mountedMachineUId
-
-    kd.utils.defer =>
-      environmentDataProvider.fetchMachineByUId @mountedMachineUId, (machine, ws) =>
-        machine = new Machine { machine }  unless machine instanceof Machine
-        @setMountedMachine machine
-
-        callback null, machine
+    unless @mountedMachineUId
+      return callback null, null
+
+    if @mountedMachine
+      return callback null, @mountedMachine
+
+    environmentDataProvider.fetchMachineByUId @mountedMachineUId, (machine, ws) =>
+
+      unless machine instanceof Machine
+        machine = new Machine { machine }
+
+      @setMountedMachine machine
+      callback null, machine

   showNoMachineState: ->
@@ -1850,8 +1851,7 @@
   fetchFromKiteStorage: (callback, prefix) ->

     if not @mountedMachine or not @mountedMachine.isRunning()
-      callback null
-      return
+      return callback null

     handleError = (err) ->


scrollToEl use thead height

scrollToEl use thead height

--- app/assets/javascripts/ab_admin/core/utils.js.coffee
+++ app/assets/javascripts/ab_admin/core/utils.js.coffee
@@ -89,8 +89,8 @@

 $.fn.scrollToEl = (speed='slow') ->
   return unless $(this)[0]
-  margin_top = $('.navbar-fixed-top').height() || 0
-  $('html, body').stop(true, true).animate({scrollTop: $(this).offset().top - margin_top}, speed)
+  top = $(this).offset().top - to_i($('.navbar-fixed-top').height()) + to_i($('#list > thead').height())
+  $('html, body').stop(true, true).animate({scrollTop: top}, speed)

 $.fn.toHref = ->
   $el = $(this)


Naming opinionators => opinonaters

Naming opinionators => opinonaters

--- core/app/backbone/collections/ndp_evidence_collection.coffee
+++ core/app/backbone/collections/ndp_evidence_collection.coffee
@@ -1,4 +1,4 @@
-class OpinionatorsCollection extends Backbone.Factlink.Collection
+class OpinionatersCollection extends Backbone.Factlink.Collection
   model: OpinionatersEvidence

   default_fetch_data:
@@ -25,12 +25,12 @@
     @on 'change sync', @sort, @
     @fact = options.fact

-    @_opinionatorsCollection = new OpinionatorsCollection null, fact: @fact
+    @_opinionatersCollection = new OpinionatersCollection null, fact: @fact
     @_supportingCollection = new OneSidedEvidenceCollection null, fact: @fact, type: 'supporting'
     @_weakeningCollection = new OneSidedEvidenceCollection null, fact: @fact, type: 'weakening'

     collectionUtils = new CollectionUtils
-    collectionUtils.union this, @_opinionatorsCollection, @_supportingCollection, @_weakeningCollection
+    collectionUtils.union this, @_opinionatersCollection, @_supportingCollection, @_weakeningCollection

     @loading = false

@@ -45,6 +45,6 @@
       success? arguments...
       @trigger 'sync'

-    @_opinionatorsCollection.fetch options
+    @_opinionatersCollection.fetch options
     @_supportingCollection.fetch options
     @_weakeningCollection.fetch options


Rerender the bottom view on change

Rerender the bottom view on change

This fixes the bottom view for newly added fact relations

--- core/app/backbone/views/evidence/evidence_bottom_view.coffee
+++ core/app/backbone/views/evidence/evidence_bottom_view.coffee
@@ -5,6 +5,9 @@

   triggers:
     'click .js-sub-comments-link': 'toggleSubCommentsList'
+
+  initialize: ->
+    @bindTo @model, 'change', @render, @

   templateHelpers: ->
     showTime: false
@@ -17,7 +20,7 @@
       @fact_base?.scroll_to_link?
     fact_url_host: ->
       new Backbone.Factlink.Url(@fact_url).host() if @fact_url?
-
+
   onRender: ->
     @bindTo @model, 'change:sub_comments_count', @updateSubCommentsLink, @
     @updateSubCommentsLink()


Change default value for paginated route

--- app/assets/javascripts/mixins/paginated_route.coffee
+++ app/assets/javascripts/mixins/paginated_route.coffee
@@ -15,7 +15,7 @@
       @gotoPage current + 1

     previousPage: ->
-      current = @get('controller.model.meta.page') or 0
+      current = @get('controller.model.meta.page') or 2
       @gotoPage current - 1

     lastPage: ->


fixed the test for fbFormController.

fixed the test for fbFormController.

--- test/specs/controllerSpec.coffee
+++ test/specs/controllerSpec.coffee
@@ -211,10 +211,12 @@

     describe 'fbFormController', ->
+        $timeout = null
         $scope = null
         controller = null

         beforeEach inject ($rootScope, $controller, $injector) ->
+            $timeout = $injector.get '$timeout'
             $scope = $rootScope.$new()
             controller = $controller 'fbFormController',
                 $scope: $scope
@@ -230,6 +232,7 @@
                     component: 'textbox'
                 ]
                 $scope.$digest()
+                $timeout.flush()
                 expect($scope.input.length).toBe 1
                 expect(spyBroadcast).toHaveBeenCalled()


move things around to preserve loading spinner correctly

--- desktop/apps/auction_support/client/registration_form.coffee
+++ desktop/apps/auction_support/client/registration_form.coffee
@@ -94,8 +94,11 @@
               resolve()
             else
               reject "Registration submission error: #{xhr.responseJSON?.message}"
-    .then (bidder) ->
+    .then (bidder) =>
+      # Executes if registration is successful
       analyticsHooks.trigger 'registration:success', bidder_id: bidder.id
+      @disableForm() if @comboForm
+      @success()

   savePhoneNumber: ->
     # Always resolves; just delays until the process completes
@@ -107,14 +110,11 @@
       else
         resolve()

+  # Lock the form- action.finally() callback executes when form submission is complete regardless of success
   loadingLock: ($element, action) ->
     return if $element.hasClass('is-loading')
     $element.addClass 'is-loading'
-    action().finally =>
-      if @comboForm
-        @disableForm()
-      else
-        $element.removeClass 'is-loading'
+    action().finally => $element.removeClass 'is-loading' unless @comboForm

   onSubmit: =>
     analyticsHooks.trigger 'registration:submit-address'
@@ -125,5 +125,4 @@
         @showError error
       .then =>
         @trigger('submitted')
-        @success()


:wolf: CoffeeScript Bob - Private methods and idomatic

--- coffeescript/bob/bob.coffee
+++ coffeescript/bob/bob.coffee
@@ -1,22 +1,22 @@
 class Bob
   hey: (said) ->
-    return 'Fine. Be that way!' if @silence(said)
-    return 'Whoa, chill out!' if @shouting(said)
-    return 'Sure.' if @question(said)
+    return 'Fine. Be that way!' if silence(said)
+    return 'Whoa, chill out!' if shouting(said)
+    return 'Sure.' if question(said)
     'Whatever.'

-  silence: (said) -> said.trim() == ''
+silence = (said) -> said.trim() is ''

-  shouting: (said) -> @upperCase(said) && @containsLetters(said)
+shouting = (said) -> upperCase(said) and containsLetters(said)

-  question: (said) -> @endsWith('?', said)
+question = (said) -> endsWith('?', said)

-  upperCase: (said) -> said == said.toUpperCase()
+upperCase = (said) -> said is said.toUpperCase()

-  containsLetters: (said) -> said.match(/[A-Z]/i)
+containsLetters = (said) -> said.match(/[A-Z]/i)

-  endsWith: (ending, value) ->
-    value.length >= ending.length &&
-    value.lastIndexOf(ending) == (value.length - ending.length)
+endsWith = (ending, value) ->
+  value.length >= ending.length and
+  value.lastIndexOf(ending) is (value.length - ending.length)

 module.exports = Bob


Updated AI/Vision pathfinding/line-of-sight default terrain updates.

--- app/views/editor/level/systems/SystemsTabView.coffee
+++ app/views/editor/level/systems/SystemsTabView.coffee
@@ -128,17 +128,26 @@

   onTerrainChanged: (e) ->
     defaultPathfinding = e.terrain in ['Dungeon', 'Indoor']
-    return unless AI = @systemsTreema.get 'original=528110f30268d018e3000001'
-    return if AI.config?.findsPaths is defaultPathfinding
-    AI.config ?= {}
-    AI.config.findsPaths = defaultPathfinding
-    @systemsTreema.set 'original=528110f30268d018e3000001', AI
-    noty {
-      text: "AI System defaulted pathfinding to #{defaultPathfinding} for terrain #{e.terrain}."
-      layout: 'topCenter'
-      timeout: 5000
-      type: 'information'
-    }
+    changed = false
+    if AI = @systemsTreema.get 'original=528110f30268d018e3000001'
+      unless AI.config?.findsPaths is defaultPathfinding
+        AI.config ?= {}
+        AI.config.findsPaths = defaultPathfinding
+        @systemsTreema.set 'original=528110f30268d018e3000001', AI
+        changed = true
+    if Vision = @systemsTreema.get 'original=528115040268d018e300001b'
+      unless Vision.config?.checksLineOfSight is defaultPathfinding
+        Vision.config ?= {}
+        Vision.config.checksLineOfSight = defaultPathfinding
+        @systemsTreema.set 'original=528115040268d018e300001b', Vision
+        changed = true
+    if changed
+      noty {
+        text: "AI/Vision System defaulted pathfinding/line-of-sight to #{defaultPathfinding} for terrain #{e.terrain}."
+        layout: 'topCenter'
+        timeout: 5000
+        type: 'information'
+      }

   buildDefaultSystems: ->
     [


JApiToken: remove deprecated signature of ::create

--- workers/social/lib/social/models/apitoken.coffee
+++ workers/social/lib/social/models/apitoken.coffee
@@ -29,10 +29,8 @@
       code             : 'unique'
     sharedMethods      :
       static:
-        create: [
+        create:
           (signature Function)
-          (signature Object, Function)
-        ]
       instance:
         remove:
           (signature Function)


Issue #2: correct settings of the Atom plugin

--- tools/atom-plugin/language-micro/settings/language-micro.cson
+++ tools/atom-plugin/language-micro/settings/language-micro.cson
@@ -3,6 +3,7 @@
 		'commentStart': 'nb ',
 		'increaseIndentPattern': '(?x)
 			\\bfn\\b[^(]*\\([^)]*\\)[^;]*$
+			| \\blet\\b[^;]*$
 			| \\(\\s*$
 			| ^\\s*\\)
 			| \\bif\\b.*$


social: keep guestuser as reserver username

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -886,6 +886,9 @@
     if /^guest-/.test username
       return callback createKodingError "Reserved username!"

+    if username is "guestuser"
+      return callback createKodingError "Reserved username: 'guestuser'!"
+
     if password isnt passwordConfirm
       return callback createKodingError "Passwords must match!"


social: forgot comma

social: forgot comma

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -251,7 +251,7 @@
   @authenticateClient:(clientId, context, callback)->
     JSession.one {clientId}, (err, session)=>
       if err
-        console.error "JUser.authenticateClient error finding session" {err, clientId}
+        console.error "JUser.authenticateClient error finding session", {err, clientId}
         callback createKodingError err
       else unless session?
         JSession.createSession (err, { session, account })->


social: use proper case for file names

social: use proper case for file names

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1203,7 +1203,7 @@
     unless typeof email is 'string'
       return callback createKodingError 'Not a valid email!'

-    email = (require "./emailSanitize")(email)
+    email = (require "./emailsanitize")(email)

     @count {email}, (err, count)->
       callback err, count is 0


fix issue with pin not setting

fix issue with pin not setting

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1409,7 +1409,7 @@
       queue.next()

-  confirmDevAccount = (options, queue, callback) ->
+  confirmDevAccount = (options, queue, callback, fetchData) ->

     { user, email, username } = options

@@ -1429,7 +1429,7 @@
         if err
           console.warn 'Failed to send verification token:', err
         else
-          pin = confirmation.pin
+          fetchData confirmation.pin

         queue.next()

@@ -1543,7 +1543,7 @@
         # This config should be no for production! ~ GG
         confirmDevAccount {
           user, email, username
-        }, queue, callback
+        }, queue, callback, (pin_) -> pin = pin_

       ->
         # don't block register


jUser: no need to fail when activationEmailSend failed, check if user exists before doing anything

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -654,7 +654,7 @@
           queue.next()
       =>
         @sendEmailConfirmationByUsername username, (err) =>
-          return console.error err if err
+          console.error err if err
           queue.next()
       ->
         JAccount.emit "AccountRegistered", account, referrer
@@ -896,4 +896,5 @@
   @sendEmailConfirmationByUsername:(username, callback)->
     @one {username}, (err, user)->
       return callback err  if err
+      return callback null unless user
       user.sendEmailConfirmation callback


email: send proper text for password reset email

--- workers/social/lib/social/models/passwordrecovery.coffee
+++ workers/social/lib/social/models/passwordrecovery.coffee
@@ -114,6 +114,7 @@
         return callback null # pretend like everything went fine.

       options.email = email
+      options.resetPassword = yes
       @create options, callback

   @create = (options, callback)->


cyclomatic complexity is too damn high

cyclomatic complexity is too damn high

--- resources/assets/coffee/react/_components/comment.coffee
+++ resources/assets/coffee/react/_components/comment.coffee
@@ -99,6 +99,8 @@
       comment = store.comments.get(@props.comment.id)
       parent = store.comments.get(comment.parent_id)
       user = @userFor(comment)
+      @comment = comment #lazy
+      @user = user #lazy

       modifiers = @props.modifiers?[..] ? []
       modifiers.push 'top' if @props.depth == 0
@@ -110,44 +112,19 @@
       div
         className: osu.classWithModifiers 'comment', modifiers

-        if @props.showReplies && @props.depth == 0 && @children.length > 0
-          div className: 'comment__float-container comment__float-container--right',
-            button
-              className: 'comment__top-show-replies'
-              type: 'button'
-              onClick: @toggleReplies
-              span className: "fas #{if @state.expandReplies then 'fa-angle-up' else 'fa-angle-down'}"
-
-        if @props.showCommentableMeta
-          @commentableMeta()
+        @renderRepliesToggle()
+        @renderCommentableMeta()

         div className: "comment__main #{if @isDeleted() then 'comment__main--deleted' else ''}",
           if @canHaveVote()
             div className: 'comment__float-container comment__float-container--left hidden-xs',
               @renderVoteButton()

-          if user.id?
-            a
-              className: 'comment__avatar js-usercard'
-              'data-user-id': user.id
-              href: laroute.route('users.show', user: user.id)
-              el UserAvatar, user: user, modifiers: ['full-circle']
-          else
-            span
-              className: 'comment__avatar'
-              el UserAvatar, user: user, modifiers: ['full-circle']
+          @renderUserAvatar()
+
           div className: 'comment__container',
             div className: 'comment__row comment__row--header',
-              if user.id?
-                a
-                  'data-user-id': user.id
-                  href: laroute.route('users.show', user: user.id)
-                  className: 'js-usercard comment__row-item comment__row-item--username comment__row-item--username-link'
-                  user.username
-              else
-                span
-                  className: 'comment__row-item comment__row-item--username'
-                  user.username
+              @renderUsername()

               if parent?
                 span
@@ -182,72 +159,16 @@
                 className: 'comment__row-item comment__row-item--info'
                 dangerouslySetInnerHTML: __html: osu.timeago(comment.created_at)

-              div className: 'comment__row-item',
-                a
-                  href: laroute.route('comments.show', comment: comment.id)
-                  className: 'comment__action comment__action--permalink'
-                  osu.trans('common.buttons.permalink')
-
-              if @props.showReplies && !@isDeleted()
-                div className: 'comment__row-item',
-                  button
-                    type: 'button'
-                    className: "comment__action #{if @state.showNewReply then 'comment__action--active' else ''}"
-                    onClick: @toggleNewReply
-                    osu.trans('common.buttons.reply')
-
-              if @canEdit()
-                div className: 'comment__row-item',
-                  button
-                    type: 'button'
-                    className: "comment__action #{if @state.editing then 'comment__action--active' else ''}"
-                    onClick: @toggleEdit
-                    osu.trans('common.buttons.edit')
-
-              if @isDeleted() && @canRestore()
-                div className: 'comment__row-item',
-                  button
-                    type: 'button'
-                    className: 'comment__action'
-                    onClick: @restore
-                    osu.trans('common.buttons.restore')
-
-              if !@isDeleted() && @canDelete()
-                div className: 'comment__row-item',
-                  button
-                    type: 'button'
-                    className: 'comment__action'
-                    onClick: @delete
-                    osu.trans('common.buttons.delete')
-
-              if @canReport()
-                div className: 'comment__row-item',
-                  el ReportComment,
-                    className: 'comment__action'
-                    comment: comment
-                    user: @userFor(comment)
-
+              @renderPermalink()
+              @renderReplyButton()
+              @renderEdit()
+              @renderRestore()
+              @renderDelete()
+              @renderReport()
               @renderRepliesText()
-
-              if !@isDeleted() && comment.edited_at?
-                editor = userStore.get(comment.edited_by_id)
-                div
-                  className: 'comment__row-item comment__row-item--info'
-                  dangerouslySetInnerHTML:
-                    __html: osu.trans 'comments.edited',
-                      timeago: osu.timeago(comment.edited_at)
-                      user:
-                        if editor.id?
-                          osu.link(laroute.route('users.show', user: editor.id), editor.username, classNames: ['comment__link'])
-                        else
-                          _.escape editor.username
-
-            if @state.showNewReply
-              div className: 'comment__reply-box',
-                el CommentEditor,
-                  parent: comment
-                  close: @closeNewReply
-                  modifiers: @props.modifiers
+              @renderEditedBy()
+
+            @renderReplyBox()

         if @props.showReplies && comment.replies_count > 0
           div
@@ -276,6 +197,49 @@
       modifiers: @props.modifiers

+  renderDelete: =>
+    if !@isDeleted() && @canDelete()
+      div className: 'comment__row-item',
+        button
+          type: 'button'
+          className: 'comment__action'
+          onClick: @delete
+          osu.trans('common.buttons.delete')
+
+
+  renderEdit: =>
+    if @canEdit()
+      div className: 'comment__row-item',
+        button
+          type: 'button'
+          className: "comment__action #{if @state.editing then 'comment__action--active' else ''}"
+          onClick: @toggleEdit
+          osu.trans('common.buttons.edit')
+
+
+  renderEditedBy: =>
+    if !@isDeleted() && @comment.edited_at?
+      editor = userStore.get(@comment.edited_by_id)
+      div
+        className: 'comment__row-item comment__row-item--info'
+        dangerouslySetInnerHTML:
+          __html: osu.trans 'comments.edited',
+            timeago: osu.timeago(@comment.edited_at)
+            user:
+              if editor.id?
+                osu.link(laroute.route('users.show', user: editor.id), editor.username, classNames: ['comment__link'])
+              else
+                _.escape editor.username
+
+
+  renderPermalink: =>
+    div className: 'comment__row-item',
+      a
+        href: laroute.route('comments.show', comment: @comment.id)
+        className: 'comment__action comment__action--permalink'
+        osu.trans('common.buttons.permalink')
+
+
   renderRepliesText: =>
     return if @props.comment.replies_count == 0

@@ -302,6 +266,81 @@
         osu.formatNumber(@props.comment.replies_count)

+  renderRepliesToggle: =>
+    if @props.showReplies && @props.depth == 0 && @children.length > 0
+      div className: 'comment__float-container comment__float-container--right',
+        button
+          className: 'comment__top-show-replies'
+          type: 'button'
+          onClick: @toggleReplies
+          span className: "fas #{if @state.expandReplies then 'fa-angle-up' else 'fa-angle-down'}"
+
+
+  renderReplyBox: =>
+    if @state.showNewReply
+      div className: 'comment__reply-box',
+        el CommentEditor,
+          parent: @comment
+          close: @closeNewReply
+          modifiers: @props.modifiers
+
+
+  renderReplyButton: =>
+    if @props.showReplies && !@isDeleted()
+      div className: 'comment__row-item',
+        button
+          type: 'button'
+          className: "comment__action #{if @state.showNewReply then 'comment__action--active' else ''}"
+          onClick: @toggleNewReply
+          osu.trans('common.buttons.reply')
+
+
+  renderReport: =>
+    if @canReport()
+      div className: 'comment__row-item',
+        el ReportComment,
+          className: 'comment__action'
+          comment: @comment
+          user: @userFor(@comment)
+
+
+  renderRestore: =>
+    if @isDeleted() && @canRestore()
+      div className: 'comment__row-item',
+        button
+          type: 'button'
+          className: 'comment__action'
+          onClick: @restore
+          osu.trans('common.buttons.restore')
+
+
+  renderUserAvatar: =>
+    if @user.id?
+      a
+        className: 'comment__avatar js-usercard'
+        'data-user-id': @user.id
+        href: laroute.route('users.show', user: @user.id)
+        el UserAvatar, user: @user, modifiers: ['full-circle']
+    else
+      span
+        className: 'comment__avatar'
+        el UserAvatar, user: @user, modifiers: ['full-circle']
+
+
+  renderUsername: =>
+    if @user.id?
+      a
+        'data-user-id': @user.id
+        href: laroute.route('users.show', user: @user.id)
+        className: 'js-usercard comment__row-item comment__row-item--username comment__row-item--username-link'
+        @user.username
+    else
+      span
+        className: 'comment__row-item comment__row-item--username'
+        @user.username
+
+
+  # mobile vote button
   renderVoteButton: =>
     className = osu.classWithModifiers('comment-vote', @props.modifiers)
     className += ' comment-vote--posting' if @state.postingVote
@@ -365,9 +404,9 @@
     !@isOwner()

-  commentableMeta: =>
-    comment = store.comments.get(@props.comment.id)
-    meta = commentableMetaStore.get(comment.commentable_type, comment.commentable_id)
+  renderCommentableMeta: =>
+    return unless @props.showCommentableMeta
+    meta = commentableMetaStore.get(@comment.commentable_type, @comment.commentable_id)

     if meta.url
       component = a


KodingLogger: make PaperTrail optional

KodingLogger: make PaperTrail optional

--- workers/social/lib/social/models/kodinglogger.coffee
+++ workers/social/lib/social/models/kodinglogger.coffee
@@ -8,7 +8,11 @@
 DEFAULT_GROUP_ID = KONFIG.papertrail.groupId
 token            = KONFIG.papertrail.token

-Papertrail       = new (require 'papertrail') { token }
+try
+  Papertrail = new (require 'papertrail') { token }
+catch e
+  PAPERTRAIL_DISABLED = new Error \
+    'Papertrail config is missing, feature disabled'

 stripcolorcodes  = require 'stripcolorcodes'
 winston          = require 'winston'
@@ -72,6 +76,7 @@

   @connect = ->

+    return  if PAPERTRAIL_DISABLED
     return @logger  if @logger

     require('winston-papertrail').Papertrail
@@ -86,6 +91,8 @@

   @processMessage = (scope, group, message...) ->
+
+    return  if PAPERTRAIL_DISABLED

     scope   = 'log' if scope not in SCOPES
     message = "#{@getIdentifier scope, group} #{message}"
@@ -109,6 +116,8 @@

   @search = (options = {}, callback) ->
+
+    return callback PAPERTRAIL_DISABLED  if PAPERTRAIL_DISABLED

     { query, from, limit } = options


KodingLogger: use group object if possible

--- workers/social/lib/social/models/kodinglogger.coffee
+++ workers/social/lib/social/models/kodinglogger.coffee
@@ -60,6 +60,8 @@

       group = if typeof requester is 'string'
         requester
+      else if requester.slug?
+        requester.slug
       else if requester.context?.group?
         requester.context.group
       else


Message if instance it isn't available

Message if instance it isn't available

--- source/core/attributes.coffee
+++ source/core/attributes.coffee
@@ -33,7 +33,7 @@
   chemistry: (children) ->
     children = @attributes.children or @default.children or []
     for item, index in children
-      for key of item # when not @constructor.available or key in @constructor.available
+      for key of item
         base = key.split(".")
         type = base[0]
         class_name = base[1]
@@ -51,7 +51,7 @@
       @[attributes.id] = child if attributes.id
       child
     else
-      throw "Instance no available in current container."
+      console.error "Instance #{type}.#{class_name} no available in #{constructor.name} (#{constructor.base})"

   __available: (type, class_name) ->
     instance = Atoms[type][class_name]


don't log errors from redis backend

don't log errors from redis backend

this also picks up errors from RedisManager like "doc ops range is not
loaded in redis"

--- services/document-updater/app/coffee/RedisBackend.coffee
+++ services/document-updater/app/coffee/RedisBackend.coffee
@@ -100,7 +100,8 @@
 					cb(error, result)
 		async.parallel jobs, (error, results) ->
 			if error?
-				logger.error {err: error}, "error in redis backend"
+				# suppress logging of errors
+				# logger.error {err: error}, "error in redis backend"
 			else
 				compareResults(results, "exec")
 			callback(primaryError, primaryResult)


fixed issue with drawing images and scoping in coffeescript, learned something new today.

--- lib/assets/javascripts/directives.js.coffee
+++ lib/assets/javascripts/directives.js.coffee
@@ -280,26 +280,27 @@
       scope.image_set.remove()
       scope.image_set.clear()
       for image in scope.settings.images
-        img = new Image()
-        img.onload = ->
-          pimage = paper.image img.src, image.x+padding or padding, image.y+padding or padding, img.width, img.height
-          scope.image_set.push(pimage)
-          if scope.settings.editing
-            pimage.drag (dx, dy) ->
-              this.attr x: this.x+dx, y: this.y+dy
-            , ->
-              this.x = this.attr('x')
-              this.y = this.attr('y')
-            , ->
-              image.x = this.attr('x')-padding
-              image.y = this.attr('y')-padding
-              scope.$emit 'changed'
-          else
-            pimage.mouseup (e) -> _handle_click(e)
-            pimage.mousemove (e) -> _handle_mouse_move(e)
-        img.src = image.path
-
-
+        do (image, img = new Image()) ->
+          img.onload = ->
+            console.log(this.src)
+            pimage = paper.image this.src, image.x+padding or padding, image.y+padding or padding, this.width, this.height
+            scope.image_set.push(pimage)
+            if scope.settings.editing
+              pimage.drag (dx, dy) ->
+                this.attr x: this.x+dx, y: this.y+dy
+              , ->
+                this.x = this.attr('x')
+                this.y = this.attr('y')
+              , ->
+                image.x = this.attr('x')-padding
+                image.y = this.attr('y')-padding
+                scope.$emit 'changed'
+            else
+              pimage.mouseup (e) -> _handle_click(e)
+              pimage.mousemove (e) -> _handle_mouse_move(e)
+          img.src = image.path
+      return
+
     _drawAxis = (origin = null, editing = false) ->
       if origin
         Y = origin.y+padding


page:before-change on back button (draft)

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -275,11 +275,14 @@
       triggerEvent 'page:update'

 installHistoryChangeHandler = (event) ->
-  if event.state?.turbolinks
-    if cachedPage = pageCache[event.state.position]
-      fetchHistory cachedPage
-    else
-      visit event.target.location.href
+  unless pageChangePrevented()
+    if event.state?.turbolinks
+      if cachedPage = pageCache[event.state.position]
+        fetchHistory cachedPage
+      else
+        visit event.target.location.href
+  else
+    window.history.pushState { turbolinks: true, position: currentState.position + 1 }, '', referrer

 initializeTurbolinks = ->
   rememberCurrentUrl()


Delay asset check until page fetch

Delay asset check until page fetch

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -1,7 +1,7 @@
 initialized    = false
 currentState   = null
 referer        = document.location.href
-trackingAssets = []
+loadedAssets   = null
 pageCache      = {}
 createDocument = null

@@ -99,9 +99,6 @@
 rememberCurrentState = ->
   currentState = window.history.state

-rememberCurrentTrackingAssets = ->
-  trackingAssets = extractTrackAssets document
-
 rememberInitialPage = ->
   unless initialized
     rememberCurrentUrl()
@@ -121,13 +118,14 @@
   event.initEvent name, true, true
   document.dispatchEvent event

+
 extractTrackAssets = (doc) ->
   (node.src || node.href) for node in doc.head.childNodes when node.getAttribute?('data-turbolinks-track')?

-
-assetsChanged = (doc)->
-  extractedTrackAssets = extractTrackAssets doc
-  extractedTrackAssets.length isnt trackingAssets.length or intersection(extractedTrackAssets, trackingAssets).length != trackingAssets.length
+assetsChanged = (doc) ->
+  loadedAssets ||= extractTrackAssets document
+  fetchedAssets  = extractTrackAssets doc
+  fetchedAssets.length isnt loadedAssets.length or intersection(fetchedAssets, loadedAssets).length isnt loadedAssets.length

 intersection = (a, b) ->
   [a, b] = [b, a] if a.length > b.length
@@ -205,7 +203,6 @@
   window.history and window.history.pushState and window.history.replaceState and window.history.state != undefined

 if browserSupportsPushState
-  rememberCurrentTrackingAssets()
   document.addEventListener 'click', installClickHandlerLast, true

   window.addEventListener 'popstate', (event) ->


FlexResizer: fix typo on fractions definition

--- lib/components/flexsplit/flexresizer.coffee
+++ lib/components/flexsplit/flexresizer.coffee
@@ -79,7 +79,7 @@

     view.on KDFlex.EVENT_HIDE, =>

-      fractions = [Flex.MAX, KDFlex.MAX]
+      fractions = [KDFlex.MAX, KDFlex.MAX]
       viewIndex = @_getViewIndex view
       fractions[viewIndex] = KDFlex.MIN


Key bindings; add line missing from tests.

--- tests/dom_tests/dom_tests.coffee
+++ tests/dom_tests/dom_tests.coffee
@@ -370,6 +370,7 @@

   should "suppress passKeys with a non-empty key state (a count)", ->
     sendKeyboardEvent "5"
+    sendKeyboardEvent "p"
     assert.equal 0, pageKeyboardEventCount

   should "suppress passKeys with a non-empty key state (a key)", ->


change time step

change time step

--- mechanics/001-binary-stars/simulator.coffee
+++ mechanics/001-binary-stars/simulator.coffee
@@ -14,7 +14,7 @@
         derivative = b2.derivative(m1, m2)
         phase = [x1, y1, vx1, vy1, x2, y2, vx2, vy2]
         evolve = ->
-            phase = solver.step(phase, derivative, 1)
+            phase = solver.step(phase, derivative, 0.1)
             self.postMessage(phase)
         handle = setInterval(evolve, 100)


test(navbar): compiled text

test(navbar): compiled text

--- modules/directives/navbar/test/index.coffee
+++ modules/directives/navbar/test/index.coffee
@@ -26,13 +26,13 @@
       $text = element.find '.bp-navbar-text'
       expect($text.attr 'role' ).toBe 'heading'

-    # it 'should be compiled', ->
-    #   $text = element.find '.bp-navbar-text'
-    #   expect($text.text()).toBe ' Bar'
-    #   scope.label = 'Foo'
-    #   scope.$apply()
-    #   $text = element.find '.bp-navbar-text'
-    #   expect($text.text()).toBe 'Foo Bar'
+    it 'should be compiled', ->
+      $text = element.find '.bp-navbar-text'
+      expect($text.text()).toBe ' Bar'
+      scope.label = 'Foo'
+      scope.$apply()
+      $text = element.find '.bp-navbar-text'
+      expect($text.text()).toBe 'Foo Bar'

   describe 'buttons', ->
     it 'should have correct order', ->


more tests

more tests

--- src/test/spec/util/actionqueue.coffee
+++ src/test/spec/util/actionqueue.coffee
@@ -1,14 +1,40 @@

-describe "ActionQueue@", ->
+describe "ActionQueue", ->
   ActionQueue = require 'util/actionqueue'
   it "should load", ->
     expect(ActionQueue).to.not.be.undefined

   beforeEach ->
     @aq = new ActionQueue ['q1', 'q2', 'q3']
+    @addFunc1 = (next) => @foo1Ran = true; next()
+    @addFunc2 = (next) => @foo2Ran = true; next()
+    @addFunc3 = (next) => @foo3Ran = true; next()
+    @aq.add 'q1', @addFunc1
+    @aq.add 'q2', @addFunc2
+    @aq.add 'q3', @addFunc3

   describe "ActionQueue@constructor", ->
     it "should be defined", ->
       expect(@aq).to.not.be.undefined
     it "should be instance of ActionQueue", ->
       expect(@aq).to.be.an.instanceOf ActionQueue
+
+  describe "ActionQueue#add", ->
+    it "should add it to the queue", ->
+      expect(@aq._queues.q1.jobs[0]).to.equal @addFunc1
+      expect(@aq._queues.q2.jobs[0]).to.equal @addFunc2
+      expect(@aq._queues.q3.jobs[0]).to.equal @addFunc3
+
+  describe "ActionQueue#run", ->
+    it "should execute queue", ->
+      @aq.run 'q1', => expect(@foo1Ran).to.be.true
+      @aq.run 'q2', => expect(@foo2Ran).to.be.true
+      @aq.run 'q3', => expect(@foo3Ran).to.be.true
+    it "should emit error", (done) ->
+      @aq.on 'error', (e) ->
+        console.log e
+        expect(true).to.be.true
+        done()
+      @aq.add 'q1', (next) -> next 'error'
+      @aq.run 'q1'
+


set course data props on content div, remove banner

--- tutor/src/components/task-step/step-with-reading-content.cjsx
+++ tutor/src/components/task-step/step-with-reading-content.cjsx
@@ -2,6 +2,7 @@

 {TaskStepStore} = require '../../flux/task-step'
 {ArbitraryHtmlAndMath, ChapterSectionMixin} = require 'shared'
+CourseData = require '../../helpers/course-data'
 {BookContentMixin, LinkContentMixin} = require '../book-content-mixin'
 RelatedContent = require '../related-content'
 CourseTitleBanner = require '../course-title-banner'
@@ -34,8 +35,7 @@
     {courseId} = Router.currentParams()

-

-        <CourseTitleBanner courseId={courseId} />
+

         <RelatedContent contentId={id} {...related_content?[0]} />
         <ArbitraryHtmlAndMath


payment: show login/email collection forms directly

--- client/Main/payment/existingaccountworkflow.coffee
+++ client/Main/payment/existingaccountworkflow.coffee
@@ -1,47 +1,20 @@
 class ExistingAccountForm extends JView
   viewAppended: ->
+    @loginForm = new LoginInlineForm
+      cssClass : "login-form"
+      testPath : "login-form"
+      callback : (credentials) =>
+        KD.getSingleton('mainController').handleLogin credentials, (err) =>
+          @loginForm.button.hideLoader()
+          if (KD.showError err) and err?.field of loginForm
+              @loginForm[err.field].decorateValidation err
+          else
+            @emit "DataCollected",
+              loggedIn      : yes
+              createAccount : no
+              email         : no

-    @exitingAccountButton = new KDButtonView
-      title : "I have an account"
-      callback: => @emit 'DataCollected',
-        createAccount : no
-        email         : no
-
-    @createAccountButton = new KDButtonView
-      title : "I'll create an account"
-      callback: => @emit 'DataCollected',
-        createAccount : yes
-        account       : yes
-
-    super()
-
-  pistachio: ->
-    """
-    Are you an existing user?
-    {{> @exitingAccountButton}}
-    {{> @createAccountButton}}
-    """
-
-class ExistingAccountWorkflow extends FormWorkflow
-
-  prepareWorkflow: ->
-    { all, any } = Junction
-
-    @requireData all(
-      'createAccount'
-      'email'
-      'loggedIn'
-    )
-
-    existingAccountForm = new ExistingAccountForm
-    existingAccountForm.on 'DataCollected', (data) =>
-      @collectData data
-
-    @addForm 'existingAccount', existingAccountForm, [
-      'createAccount'
-    ]
-
-    emailCollectionForm = new KDFormViewWithFields
+    @emailCollectionForm = new KDFormViewWithFields
       fields:
         email:
           cssClass         : "thin"
@@ -53,30 +26,26 @@
           type             : 'submit'
           style            : 'solid green fr'
       callback             : ({ email }) =>
-        { JUser } = KD.remote.api
+        KD.remote.api.JUser.changeEmail { email }, (err) =>
+          return  if KD.showError err
+          @emit 'DataCollected',
+            loggedIn      : no
+            createAccount : yes
+            account       : yes
+            email         : email

-        JUser.changeEmail { email }, (err) =>
-          return  if KD.showError err
+    super

-          @collectData { email, loggedIn: no }
+  pistachio: ->
+    """
+    {{> @loginForm}}
+    {{> @emailCollectionForm}}
+    """

-    emailCollectionForm.activate = -> @inputs.email.setFocus()
-
-    @addForm 'email', emailCollectionForm, ['email']
-
-    loginForm = new LoginInlineForm
-      cssClass : "login-form"
-      testPath : "login-form"
-      callback : (credentials) =>
-        KD.getSingleton('mainController').handleLogin credentials, (err) =>
-
-          loginForm.button.hideLoader()
-
-          if (KD.showError err) and err?.field of loginForm
-              loginForm[err.field].decorateValidation err
-          else
-            @collectData loggedIn: yes
-
-    @addForm 'login', loginForm, ['loggedIn']
-
+class ExistingAccountWorkflow extends FormWorkflow
+  prepareWorkflow: ->
+    @requireData Junction.any 'createAccount', 'loggedIn'
+    existingAccountForm = new ExistingAccountForm
+    existingAccountForm.on 'DataCollected', @bound "collectData"
+    @addForm 'existingAccount', existingAccountForm, ['createAccount', 'loggedIn']
     @enter()


sidebar/resources: clean up empty state logic

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

--- client/app/lib/sidebar/components/resources.coffee
+++ client/app/lib/sidebar/components/resources.coffee
@@ -1,7 +1,6 @@
 React = require 'app/react'

 List = require 'app/components/list'
-SidebarNoStacks = require 'app/components/sidebarstacksection/sidebarnostacks'

 OwnedResourcesList = require './ownedresourceslist'
 SharedResourcesList = require './sharedresourceslist'
@@ -16,8 +15,6 @@
       permanent: React.PropTypes.array
       collaboration: React.PropTypes.array
     disabled: React.PropTypes.bool
-    hasTemplate: React.PropTypes.bool
-    canCreateStacks: React.PropTypes.bool

   getSectionCount: ->
@@ -33,23 +30,9 @@
   getRowCount: (sectionIndex) -> 1

-  renderEmptySectionAtIndex: (sectionIndex) ->
-
-    if sectionIndex > 0
-      return null
-
-    { canCreateStacks, hasTemplate } = @props
-
-    <SidebarNoStacks
-      hasTemplate={hasTemplate}
-      hasPermission={canCreateStacks} />
-
-
   renderRowAtIndex: (sectionIndex) ->

     { owned, shared } = @props
-
-    console.log '>>>>>>>', sectionIndex, sections[sectionIndex]

     switch sections[sectionIndex]
       when 'owned'
@@ -69,6 +52,6 @@
         numberOfSections={@bound 'getSectionCount'}
         numberOfRowsInSection={@bound 'getRowCount'}
         renderRowAtIndex={@bound 'renderRowAtIndex'}
-        renderEmptySectionAtIndex={@bound 'renderEmptySectionAtIndex'} />
+      />

     </div>


replaced the real codeboxes with mocks. specs went from 5 seconds back down to .213 seconds

--- public/spec/try-it-spec.coffee
+++ public/spec/try-it-spec.coffee
@@ -61,8 +61,8 @@
     iframe = { contentWindow: iframeWindow}
     spyOn($.fn, "get").andReturn(iframe)
     jsmin = iframeWindow.jasmine
-    specEditor = makeEditor('specs')
-    sourceEditor = makeEditor('src')
+    specEditor = fakeEditor('specs')
+    sourceEditor = fakeEditor('src')
     sandbox = Sandbox()

   describe "#runSpecs", ->
@@ -155,8 +155,8 @@
     name = 'specs'
     script = 'some script'
     $default = $.jasmine.inject("
 #{script}
")
-    specEditor = makeEditor('specs')
-    sourceEditor = makeEditor('src')
+    specEditor = fakeEditor('specs')
+    sourceEditor = fakeEditor('src')

   describe ".init", ->
     beforeEach ->
@@ -298,38 +298,16 @@
     it "invokes tryIt", ->
       expect(tryIt).toHaveBeenCalled()

-  xdescribe "hitting tab", ->
-    $field=null
-    beforeEach ->
-      spyOn($.fn, "insertAtCaret")
-      $field = $.jasmine.inject('')
-
-    context "just tab", ->
-      beforeEach -> $field.trigger({ type: 'keydown', keyCode: 9 })
-      it "inserts two spaces", ->
-        expect($.fn.insertAtCaret).toHaveBeenCalledWith('  ')
-
-      it "inserts them on the source", ->
-        expect($.fn.insertAtCaret.mostRecentCall.object[0]).toBe($field[0])
-
-    context "holding shift", ->
-      beforeEach -> $field.trigger({ type: 'keydown', keyCode: 9, shiftKey: true })
-
-      it "does nothing", ->
-        expect($.fn.insertAtCaret).not.toHaveBeenCalled()
-
   describe "hitting a snippet button", ->
-    snippet=result=null
-    beforeEach ->
-      specEditor = makeEditor('specs')
+    snippet=specEditor=result=null
+    beforeEach ->
+      specEditor = fakeEditor('specs')
       snippet = '1337 codez'
-      $button = $("")
-
-      $button.trigger('click')
+      $("").trigger('click')
       result = specEditor.getSession().getValue()

     it "inserts the snippet", ->
-      expect(result).toBe(snippet)
+      expect(specEditor.insert).toHaveBeenCalledWith(snippet)

   describe "clicking a clear-saved button", ->
     $button=null
@@ -381,4 +359,17 @@
       it "executes the specs", ->
         expect(tryIt).toHaveBeenCalled()

-makeEditor = (id) -> $.jasmine.inject("

").codeBox().data('editor')+fakeEditor = (id) ->
+  editor = {
+    value: '',
+    name: id,
+    switchMode: jasmine.createSpy('#switchMode'),
+    insert: jasmine.createSpy('#insert'),
+    getSession: -> {
+      getValue: -> editor.value,
+      setValue: (val) -> editor.value = val,
+      setMode: jasmine.createSpy('#getSession#setMode')
+    }
+  }
+  $.jasmine.inject("

").data('editor',editor)
+  editor


VSTS-438 - User Notifications

VSTS-438 - User Notifications

* Actually call the `size()` function on the notification badge array.
  We want this call to return 0 if there are notifications so we don't
  make an AJAX request.
* Previously, the function was being referenced and used as a
  conditional and was always truthy, resulting in the dismissal API
  always being called.

SEE: https://amaabca.visualstudio.com/membership_backlog/_workitems?id=438

--- app/assets/javascripts/ama_layout/notifications.coffee
+++ app/assets/javascripts/ama_layout/notifications.coffee
@@ -3,7 +3,7 @@
     $(document).on 'click', '[data-notifications-toggle]', (event) =>
       event.preventDefault();
       badge = event.currentTarget
-      if $(badge).find('[data-notification-count]').remove().size
+      if $(badge).find('[data-notification-count]').remove().size()
         @request()

   request: () ->


update workflow-methods-mixin to always advance user to the next subject set when they click the green done button

--- app/assets/javascripts/lib/workflow-methods-mixin.cjsx
+++ app/assets/javascripts/lib/workflow-methods-mixin.cjsx
@@ -354,9 +354,11 @@
     new_subject_index = @state.subject_index + 1

     # If we've exhausted pages in this subject set, move to next one:
-    if new_subject_index >= @getCurrentSubjectSet().subjects.length
-      new_subject_set_index += 1
-      new_subject_index = 0
+    # NB: By commenting out the following line we can ensure user is always sent to the
+    # next subject set after clicking the big green Done button in the Mark stage
+    #if new_subject_index >= @getCurrentSubjectSet().subjects.length
+    new_subject_set_index += 1
+    new_subject_index = 0

     # If we've exhausted all subject sets, collapse in shame
     if new_subject_set_index >= @state.subjectSets.length


Fixed session tooltip

Fixed session tooltip

--- app/assets/javascripts/views/sessions/dialog.js.coffee
+++ app/assets/javascripts/views/sessions/dialog.js.coffee
@@ -141,7 +141,7 @@
     t = "
"
     $.each errors, (index,error) ->
       t += "
#{error.ref_node} #{error.message}
" if error.type == "field"
-      t += "
#{error.message}
" if error.type == "general"
+      t += "
#{error.message}
" unless error.type == "field"
     t += "
"

   submitRestore: ->
@@ -183,17 +183,15 @@
         @hide(logout: false, callback: true)
       error: (response) =>
         resp = $.parseJSON(response.responseText)
-
         switch resp.status
+          when 401
+            @showTooltip((resp.flash.title + " " + resp.flash.message),false)
           when 422
             @showTooltip(@buildErrors(resp.errors),true)
-          when 401
-            @showTooltip(@buildErrors(resp.errors),true)
           when 403
             @logoutUser()
           else
             @logoutUser()
-

   submitLogin: ->
@@ -215,10 +213,11 @@
         @hide(logout: false, callback: true)
       error: (response) =>
         resp = $.parseJSON(response.responseText)
-
         switch resp.status
           when 401
             @showTooltip((resp.flash.title + " " + resp.flash.message),false)
+          when 422
+            @showTooltip(@buildErrors(resp.errors),true)
           when 403
             @logoutUser()

@@ -243,6 +242,8 @@
         resp = $.parseJSON(response.responseText)

         switch resp.status
+          when 401
+            @showTooltip((resp.flash.title + " " + resp.flash.message),false)
           when 422
             @showTooltip(@buildErrors(resp.errors),true)
           when 403
@@ -251,7 +252,7 @@
           #   # do stuff

   showTooltip: (message,html) =>
-    @.$('.input-group').tooltip(
+    @.$('.auth-form-register').tooltip(
       placement: 'top'
       trigger: 'manual'
       animation: false
@@ -260,6 +261,6 @@
     ).tooltip('show')
     $('.tooltip:last-child').addClass('session-tooltip')

-  hideTooltip: => @.$('.input-group').tooltip('hide')
-
-
+  hideTooltip: => @.$('.auth-form-register').tooltip('hide')
+
+


test testing

test testing

--- spec/CompatibilityTest.coffee
+++ spec/CompatibilityTest.coffee
@@ -6,4 +6,4 @@

 describe "Just testing", ->
   it "should always work", ->
-    console.log "hooray"
+    console.log "hooray!"


Clean up remaining position translation spec for folds. It still has one legit failure though.

--- spec/app/renderer-spec.coffee
+++ spec/app/renderer-spec.coffee
@@ -345,33 +345,26 @@
           expect(event.newRange).toEqual [[1, 0], [1, 9]]

     describe "position translation", ->
-      describe "when there is single fold spanning multiple lines", ->
-        it "translates positions to account for folded lines and characters and the placeholder", ->
-          renderer.createFold([[4, 29], [7, 4]])
-
-          # preceding fold: identity
-          expect(renderer.screenPositionForBufferPosition([3, 0])).toEqual [3, 0]
-          expect(renderer.screenPositionForBufferPosition([4, 0])).toEqual [4, 0]
-          expect(renderer.screenPositionForBufferPosition([4, 29])).toEqual [4, 29]
-
-          expect(renderer.bufferPositionForScreenPosition([3, 0])).toEqual [3, 0]
-          expect(renderer.bufferPositionForScreenPosition([4, 0])).toEqual [4, 0]
-          expect(renderer.bufferPositionForScreenPosition([4, 29])).toEqual [4, 29]
-
-          # inside of fold: translate to the start of the fold
-          expect(renderer.screenPositionForBufferPosition([4, 35])).toEqual [4, 29]
-          expect(renderer.screenPositionForBufferPosition([5, 5])).toEqual [4, 29]
-
-          # following fold, on last line of fold
-          expect(renderer.screenPositionForBufferPosition([7, 4])).toEqual [4, 32]
-          expect(renderer.bufferPositionForScreenPosition([4, 32])).toEqual [7, 4]
-
-          # # following fold, subsequent line
-          expect(renderer.screenPositionForBufferPosition([8, 0])).toEqual [5, 0]
-          expect(renderer.screenPositionForBufferPosition([11, 13])).toEqual [8, 13]
-
-          expect(renderer.bufferPositionForScreenPosition([5, 0])).toEqual [8, 0]
-          expect(renderer.bufferPositionForScreenPosition([9, 2])).toEqual [12, 2]
+      it "translates positions to account for folded lines and characters and the placeholder", ->
+        renderer.createFold(4, 7)
+
+        # preceding fold: identity
+        expect(renderer.screenPositionForBufferPosition([3, 0])).toEqual [3, 0]
+        expect(renderer.screenPositionForBufferPosition([4, 0])).toEqual [4, 0]
+
+        expect(renderer.bufferPositionForScreenPosition([3, 0])).toEqual [3, 0]
+        expect(renderer.bufferPositionForScreenPosition([4, 0])).toEqual [4, 0]
+
+        # inside of fold: translate to the start of the fold
+        expect(renderer.screenPositionForBufferPosition([4, 35])).toEqual [4, 0]
+        expect(renderer.screenPositionForBufferPosition([5, 5])).toEqual [4, 0]
+
+        # following fold
+        expect(renderer.screenPositionForBufferPosition([8, 0])).toEqual [5, 0]
+        expect(renderer.screenPositionForBufferPosition([11, 2])).toEqual [8, 2]
+
+        expect(renderer.bufferPositionForScreenPosition([5, 0])).toEqual [8, 0]
+        expect(renderer.bufferPositionForScreenPosition([9, 2])).toEqual [12, 2]

   describe ".clipScreenPosition(screenPosition, wrapBeyondNewlines: false, wrapAtSoftNewlines: false, skipAtomicTokens: false)", ->
     beforeEach ->


Remove dangling logs

Remove dangling logs

--- client/landing/app/Applications/Account.kdapplication/account/accSettingsDevDatabases.coffee
+++ client/landing/app/Applications/Account.kdapplication/account/accSettingsDevDatabases.coffee
@@ -70,7 +70,7 @@
         withArgs  :
           dbUser  : KD.whoami().profile.nickname
       , (err, response)=>
-        log "RESPONSE: ", response
+        # log "RESPONSE: ", response
         if err then warn err
         else
           @instantiateListItems response
@@ -413,7 +413,7 @@
 class AccountDatabaseListItem extends KDListItemView
   constructor:(options = {},data)->

-    log "Create Item with Data", data
+    # log "Create Item with Data", data

     options.tagName = "li"


Add USER liked your comment notification support for comment likes

--- client/landing/app/CommonViews/messagesList.coffee
+++ client/landing/app/CommonViews/messagesList.coffee
@@ -68,6 +68,7 @@
     JCodeSnip       : "your status update."
     JAccount        : "started following you."
     JPrivateMessage : "your private message."
+    JComment        : "your comment."

   bucketNameMap = ->
     CReplieeBucketActivity  : "comment"
@@ -145,16 +146,20 @@

   click:->

+    showPost = (err, post)->
+      if post
+        appManager.tell "Activity", "createContentDisplay", post
+      else
+        new KDNotificationView
+          title : "This post has been deleted!"
+          duration : 1000
+
     if @snapshot.anchor.constructorName is "JPrivateMessage"
       appManager.openApplication "Inbox"
+    else if @snapshot.anchor.constructorName is "JComment"
+      KD.remote.api[@snapshot.anchor.constructorName].fetchRelated @snapshot.anchor.id, showPost
     else
-      KD.remote.api[@snapshot.anchor.constructorName].one _id : @snapshot.anchor.id, (err, post)->
-        if post
-          appManager.tell "Activity", "createContentDisplay", post
-        else
-          new KDNotificationView
-            title : "This post has been deleted!"
-            duration : 1000
+      KD.remote.api[@snapshot.anchor.constructorName].one _id : @snapshot.anchor.id, showPost

     # {sourceName,sourceId} = @getData()[0]
     # contentDisplayController = @getSingleton('contentDisplayController')


invalid destructuring assignments like `{0} = x` now causes syntax error

--- nodes.coffee
+++ nodes.coffee
@@ -770,6 +770,8 @@
       @value.klass = match[1]
     val = @value.compile o, LEVEL_LIST
     return "#{name}: #{val}" if @context is 'object'
+    unless @variable.isComplex() or IDENTIFIER.test assignee = @variable.unwrap().value
+      throw SyntaxError "#{assignee} cannot be assigned."
     o.scope.find name unless isValue and (@variable.hasProperties() or @variable.namespaced)
     val = name + " #{ @context or '=' } " + val
     if o.level <= LEVEL_LIST then val else "(#{val})"


memory only updates when user clicks out of the window

--- client/src/app/game/memory/memoryController.coffee
+++ client/src/app/game/memory/memoryController.coffee
@@ -1,6 +1,3 @@
-WAIT_TIME = 2000
-
-
 saveExpandedNodes = (node) ->
   field: node.field
   type: node.type
@@ -21,18 +18,33 @@

 angular.module('gamEvolve.game.memory', [])

-.controller 'MemoryCtrl', ($scope, gameHistory, gameTime, currentGame, circuits) ->
+.controller 'MemoryCtrl', ($scope, $window, gameHistory, gameTime, currentGame, circuits) ->
   $scope.gameHistoryMeta = gameHistory.meta
   $scope.gameTime = gameTime

-  onEditorChange = -> $scope.$apply(onUpdateMemoryEditor)
+  memoryModified = false

   editor = new JSONEditor $("#memoryEditor")[0],
-    change: _.debounce(onEditorChange, WAIT_TIME)
+    change: -> memoryModified = true
     name: "memory"
     history: false
     search: false
     modes: ["tree", "code", "text"]
+
+  hasFocus = false
+  receivedFocus = (event) ->
+    isChildOnJsonEditor = $(event.target).parents(".jsoneditor").length > 0
+    if !hasFocus and isChildOnJsonEditor
+      hasFocus = true
+    else if hasFocus and not isChildOnJsonEditor
+      if memoryModified
+        $scope.$apply(onUpdateMemoryEditor)
+        memoryModified = false
+      hasFocus = false
+    return true
+
+  $($window).on("click", receivedFocus)
+  $scope.$on("$destroy", -> $($window).off("click", receivedFocus))

   # Update from gameHistory
   onUpdateMemoryModel = ->


removed a redundancy in splatted assignment

--- nodes.coffee
+++ nodes.coffee
@@ -961,8 +961,8 @@
   # A compiling a splat as a destructuring assignment means slicing arguments
   # from the right-hand-side's corresponding array.
   compileValue: (o, name, index, trailings) ->
-    trail = if trailings then ", #{name}.length - #{trailings}" else ''
-    "#{ utility 'slice' }.call(#{name}, #{index}#{trail})"
+    trail = if trailings then ', -' + trailings else ''
+    utility('slice') + ".call(#{name}, #{index}#{trail})"

   # Utility function that converts arbitrary number of elements, mixed with
   # splats, to a proper array


fixing doubled semis

fixing doubled semis

--- nodes.coffee
+++ nodes.coffee
@@ -394,6 +394,8 @@
 exports.CurryNode: class CurryNode extends CallNode
   type: 'Curry'

+  body: 'func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)))'
+
   constructor: (meth, args) ->
     @children:  flatten [@meth: meth, @context: args[0], @args: (args.slice(1) or [])]

@@ -403,7 +405,7 @@
     (new ArrayNode(@args)).compile o

   compile_node: (o) ->
-    body: Expressions.wrap([literal('func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)));')])
+    body: Expressions.wrap([literal @body])
     curried: new CodeNode([], body)
     curry: new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried]))
     (new ParentheticalNode(new CallNode(curry, [@meth, @context, literal(@arguments(o))]))).compile o


removing code that restricts duplicate key names and associated tests

--- nodes.coffee
+++ nodes.coffee
@@ -801,16 +801,6 @@

   compileNode: (o) ->
     props = @properties
-    propNames = []
-    normalise = (v) -> if IDENTIFIER.test v then v else "" + do Function "return #{v}"
-    for prop in @properties
-      prop = prop.variable if prop.isComplex()
-      if prop?
-        propName = prop.unwrapAll().value.toString()
-        normalisedPropName = normalise propName
-        if normalisedPropName in propNames
-          throw SyntaxError "multiple object literal properties named \"#{propName}\""
-        propNames.push normalisedPropName
     return (if @front then '({})' else '{}') unless props.length
     if @generated
       for node in props when node instanceof Value


Fixes #1467. Catch now introduces its parameter to scope.

--- nodes.coffee
+++ nodes.coffee
@@ -1402,6 +1402,7 @@
     o.indent  += TAB
     errorPart = if @error then " (#{ @error.compile o }) " else ' '
     catchPart = if @recovery
+      o.scope.add @error.value, 'param'
       " catch#{errorPart}{\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}}"
     else unless @ensure or @recovery
       ' catch (_e) {}'


inArray -> indexOf ...

inArray -> indexOf ...

--- nodes.coffee
+++ nodes.coffee
@@ -1262,7 +1262,9 @@
     "(#{tests.join(' || ')})"

   compileLoopTest: (o) ->
-    "#{utility 'inArray'}(#{@object.compile o}, #{@array.compile o})"
+    [obj1, obj2] = @object.compileReference merge(o, top: yes), precompile: yes
+    prefix = if obj1 isnt obj2 then "#{obj1}, " else ''
+    "(#{prefix}#{utility 'indexOf'}.call(#{@array.compile o}, #{obj2}) >= 0)"

 #### Try

@@ -1462,7 +1464,7 @@
         unstepPart = "\n#{@tab}" + unstepPart
     if @object
       forPart     = "#{ivar} in #{sourcePart}"
-      guardPart   = "\n#{idt1}if (!#{utility('hasProp')}.call(#{svar}, #{ivar})) continue;" unless @raw
+      guardPart   = "\n#{idt1}if (!#{utility('hasProp')}.call(#{svar}, #{ivar})) continue;" unless @raw
     body          = body.compile merge o, indent: idt1, top: true
     vars          = if range then name else "#{name}, #{ivar}"
     """
@@ -1663,16 +1665,13 @@
       return function() { return func.apply(context, arguments); };
     }
   '''
-
+
   # Discover if an item is in an array.
-  inArray: '''
-    (function() {
-      var indexOf = Array.prototype.indexOf || function(item) {
-        var i = this.length; while (i--) if (this[i] === item) return i;
-        return -1;
-      };
-      return function(item, array) { return indexOf.call(array, item) > -1; };
-    })()
+  indexOf: '''
+    Array.prototype.indexOf || function(item) {
+      for (var i = 0, l = this.length; i < l; i++) if (this[i] === item) return i;
+      return -1;
+    }
   '''

   # Shortcuts to speed up the lookup time for native functions.