Dont consider functionally implied type variables in constraints as ambgious

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2509,8 +2509,10 @@
     isSubset fixedVars, (findUnconstrained constraint)
   [deferred, retained] = partition isFixed, impliedConstraints
   quantifiedVars = findFree finalType = substitute ctx.substitution, type
+  impliedVars = concatSets (map findConstrained, retained)...
+  validVars = concatSets quantifiedVars, impliedVars
   isAmbiguous = (constraint) ->
-    not isSubset quantifiedVars, (findUnconstrained constraint)
+    not isSubset validVars, (findUnconstrained constraint)
   [ambiguous, retained] = partition isAmbiguous, retained
   if _notEmpty ambiguous
     {error:
@@ -4538,6 +4540,10 @@
 findUnconstrained = (constraint) ->
   findFree constraint.types.types[0]

+# Bound variables in RHS of implicit functional dependency
+findConstrained = (constraint) ->
+  findFreeInList constraint.types.types[1...]
+
 freshInstance = (ctx, type) ->
   throw new Error "not a forall in freshInstance #{safePrintType type}" unless type and type.ForAll
   freshes = map ((kind) -> ctx.freshTypeVariable kind), type.kinds


move above lib

move above lib

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -924,122 +924,6 @@
     else
       "function(__a, __b){return __b #{to} __a;}"

-library = """
-var $listize = function (list) {
-  if (list.length == 0) {
-   return {length: 0};
-  }
-  return and_(list[0], $listize(list.slice(1)));
-};
-
-var and_ = function (x, xs) {
-  if (typeof xs === "undefined" || xs === null) {
-    throw new Error('Second argument to & must be a sequence');
-  }
-  if (typeof xs == 'string' || xs instanceof String) {
-    if (xs == '' && !(typeof x == 'string' || x instanceof String)) {
-      return [x];
-    } else {
-      return x + xs;
-    }
-  }
-  if (xs.unshift) {
-    return [x].concat(xs);
-  }// cases for other sequences
-  return {
-    head: x,
-    tail: xs
-  };
-};
-
-var $sequenceSize = function (xs) {
-  if (typeof xs === "undefined" || xs === null) {
-    throw new Error('Pattern matching on size of undefined');
-  }
-  if (xs.length != null) {
-    return xs.length;
-  }
-  return 1 + $sequenceSize(xs.tail);
-};
-
-var $sequenceAt = function (i, xs) {
-  if (typeof xs === "undefined" || xs === null) {
-    throw new Error('Pattern matching required sequence got undefined');
-  }
-  if (xs.length) {
-    return xs[i];
-  }
-  if (i == 0) {
-    return xs.head;
-  }
-  return $sequenceAt(i - 1, xs.tail);
-};
-
-var $sequenceSplat = function (from, leave, xs) {
-  if (xs.slice) {
-    return xs.slice(from, xs.length - leave);
-  }
-  return $listSlice(from, $sequenceSize(xs) - leave - from, xs);
-};
-
-// temporary, will be replaced by typed 0-argument function
-var $empty = function (xs) {
-  if (typeof xs === "undefined" || xs === null) {
-    throw new Error('Empty needs a sequence');
-  }
-  if (typeof xs == 'string' || xs instanceof String) {
-    return "";
-  }
-  if (xs.unshift) {
-    return [];
-  }
-  if ('length' in xs) {
-    return $listize([]);
-  } // cases for other sequences
-  return {};
-};
-
-var $listSlice = function (from, n, xs) {
-  if (n == 0) {
-    return $listize([]);
-  }
-  if (from == 0) {
-    return and_(xs.head, $listSlice(from, n - 1, xs.tail));
-  }
-  return $listSlice(from - 1, n, xs.tail);
-};
-
-var show__list = function (x) {
-  var t = [];
-  while (x.length != 0) {
-    t.push(x.head);
-    x = x.tail;
-  }
-  return t;
-};
-
-var $curry = function (f) {
-  var _curry = function(args) {
-    return f.length == 1 ? f : function(){
-      var params = args ? args.concat() : [];
-      return params.push.apply(params, arguments) < f.length && arguments.length
-        ? _curry.call(this, params)
-        : f.apply(this, params);
-    };
-  };
-  return _curry();
-};
-
-var from__nullable = function (jsValue) {
-  if (typeof jsValue === "undefined" || jsValue === null) {
-    return {';none': true};
-  } else {
-    return {':just': [jsValue]};
-  }
-};
-
-;
-"""

 topScopeDefines = ->
   ids = 'and_ $empty show__list from__nullable'.split ' '
@@ -1055,6 +939,123 @@
     scope[id] = true
   scope

+library = """
+var $listize = function (list) {
+  if (list.length == 0) {
+   return {length: 0};
+  }
+  return and_(list[0], $listize(list.slice(1)));
+};
+
+var and_ = function (x, xs) {
+  if (typeof xs === "undefined" || xs === null) {
+    throw new Error('Second argument to & must be a sequence');
+  }
+  if (typeof xs == 'string' || xs instanceof String) {
+    if (xs == '' && !(typeof x == 'string' || x instanceof String)) {
+      return [x];
+    } else {
+      return x + xs;
+    }
+  }
+  if (xs.unshift) {
+    return [x].concat(xs);
+  }// cases for other sequences
+  return {
+    head: x,
+    tail: xs
+  };
+};
+
+var $sequenceSize = function (xs) {
+  if (typeof xs === "undefined" || xs === null) {
+    throw new Error('Pattern matching on size of undefined');
+  }
+  if (xs.length != null) {
+    return xs.length;
+  }
+  return 1 + $sequenceSize(xs.tail);
+};
+
+var $sequenceAt = function (i, xs) {
+  if (typeof xs === "undefined" || xs === null) {
+    throw new Error('Pattern matching required sequence got undefined');
+  }
+  if (xs.length) {
+    return xs[i];
+  }
+  if (i == 0) {
+    return xs.head;
+  }
+  return $sequenceAt(i - 1, xs.tail);
+};
+
+var $sequenceSplat = function (from, leave, xs) {
+  if (xs.slice) {
+    return xs.slice(from, xs.length - leave);
+  }
+  return $listSlice(from, $sequenceSize(xs) - leave - from, xs);
+};
+
+// temporary, will be replaced by typed 0-argument function
+var $empty = function (xs) {
+  if (typeof xs === "undefined" || xs === null) {
+    throw new Error('Empty needs a sequence');
+  }
+  if (typeof xs == 'string' || xs instanceof String) {
+    return "";
+  }
+  if (xs.unshift) {
+    return [];
+  }
+  if ('length' in xs) {
+    return $listize([]);
+  } // cases for other sequences
+  return {};
+};
+
+var $listSlice = function (from, n, xs) {
+  if (n == 0) {
+    return $listize([]);
+  }
+  if (from == 0) {
+    return and_(xs.head, $listSlice(from, n - 1, xs.tail));
+  }
+  return $listSlice(from - 1, n, xs.tail);
+};
+
+var show__list = function (x) {
+  var t = [];
+  while (x.length != 0) {
+    t.push(x.head);
+    x = x.tail;
+  }
+  return t;
+};
+
+var $curry = function (f) {
+  var _curry = function(args) {
+    return f.length == 1 ? f : function(){
+      var params = args ? args.concat() : [];
+      return params.push.apply(params, arguments) < f.length && arguments.length
+        ? _curry.call(this, params)
+        : f.apply(this, params);
+    };
+  };
+  return _curry();
+};
+
+var from__nullable = function (jsValue) {
+  if (typeof jsValue === "undefined" || jsValue === null) {
+    return {';none': true};
+  } else {
+    return {':just': [jsValue]};
+  }
+};
+
+;
+"""
+
 exports.compile = (source) ->
   library + compileDefinitions source


Fix error in compiler in looking up scopes

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -4582,6 +4582,7 @@
   scoped =
     if savedScopes[scope]
       while scope isnt 0
+        found = savedScopes[scope]
         scope = found.parent
         found.definitions
     else


fix mission list - yes - I think 'service' might be incorrectly nested now

--- src/scripts/controllers/dapiControllers.coffee
+++ src/scripts/controllers/dapiControllers.coffee
@@ -73,7 +73,7 @@
     @fetchRecords()

   fetchRecords: =>
-    @service.get(@fetchParams ? {}).then (results) =>
+    @service.service.get(@fetchParams ? {}).then (results) =>
       @records = (@extendRecord(r) for r in results)
       console.log("Fetched #{@records.length} records")


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -21,7 +21,7 @@
   @param {Point} position
   ###
   closest: (selector, position) ->
-
+    se
   ###*
   Get a selection of GameObjects that match the specified selector criteria. The selector language
   can select objects by id, class, or attributes. Note that this method always returns an Array,


Fix a bug that kept file.exists always true

#408

--- src/lib/models/file.coffee
+++ src/lib/models/file.coffee
@@ -405,7 +405,7 @@
 		if fullPath
 			balUtil.exists fullPath, (_exists) ->
 				exists = _exists
-				file.set({exists:true})
+				file.set({exists})
 				tasks.async()
 		else
 			tasks.async()


back button works?

back button works?

--- src/live_controller.coffee
+++ src/live_controller.coffee
@@ -25,14 +25,23 @@
              .replace(/\/\(/g, '(?:/')
              .replace( /(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, buildRegex)
              .replace(/([\/.])/g, '\\$1')
-             .replace(/\*/g, '(.+)');
-  return new RegExp('^' + path + '$', 'i');
+             .replace(/\*/g, '(.+)')
+  return new RegExp('^' + path + '$', 'i')

 getHelper = (url) ->
   helperClassName = url.replace /^[a-zA-Z]|\/[a-zA-Z]/g, (x) -> x.toUpperCase()
   helperClassName = helperClassName.replace(/\//g, "")+ "Helper"
   return window[helperClassName]
+
+window.onpopstate = (event) ->
+  console.log("derp")
+  event ?= {}
+  state = event.state || {}
+  method = state.method || "get"
+  params = state.params || {}
+  Router[method](document.location.pathname, params)
+

 routes = {}
 routeMethods = ["get", "post", "put", "delete"]


Fixed bugs

Fixed bugs

--- toggler.coffee
+++ toggler.coffee
@@ -36,11 +36,11 @@
   player.active = false
   player.buttonStatus = 0

-setupPromise = Promise.all(ledSetup).then ->
-  Promise.all(buttonSetup).then ->
-  , (e)->
+setupPromise = Promise.all(ledSetup).then (->
+  Promise.all(buttonSetup).then (->
+  ), (e)->
     console.log 'Failed setting up Button', e
-, (e)->
+), (e)->
   console.log 'Failed setting up LED', e

 poll = null
@@ -55,8 +55,8 @@
             player.active = !player.active
             return writePin player.led, player.active

-    Promise.all(readHandled).then ->
-    , (err)->
+    Promise.all(readHandled).then (->
+    ), (err)->
       console.log 'Error checking status', err

   , 10
@@ -70,9 +70,9 @@
       closePin player.led
     cleaned.push closePin player.button

-  Promise.all(cleaned).then ->
+  Promise.all(cleaned).then (->
     console.log 'Cleaned up pins';
-  , (err)->
+  ), (err)->
     console.log 'Failure cleaning up pins', err

   process.exit()


.val is not working here, approximate its intent with .prop("checked")

--- common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+++ common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
@@ -129,7 +129,8 @@
           isPrivilegedUser: DiscussionUtil.isPrivilegedUser()
         })
       )
-      @$(".forum-nav-sort-control").val(@collection.sort_preference)
+      @$(".forum-nav-sort-control option").removeProp("selected")
+      @$(".forum-nav-sort-control option[value=#{@collection.sort_preference}]").prop("selected", true)

       $(window).bind "load scroll resize", @updateSidebar

@@ -524,5 +525,3 @@
           type: "POST"
           error: () =>
             $('input.email-setting').attr('checked','checked')
-
-


Always show username on hover, unless there's something else more interesting to say.

--- app/assets/javascripts/discourse/helpers/application_helpers.js.coffee
+++ app/assets/javascripts/discourse/helpers/application_helpers.js.coffee
@@ -65,7 +65,7 @@
     size: options.hash.imageSize
     extraClasses: Em.get(user, 'extras') || options.hash.extraClasses
     username: username
-    title: Em.get(user, 'title') || Em.get(user, 'description')
+    title: Em.get(user, 'title') || Em.get(user, 'description') || username
     avatarTemplate: Ember.get(user, 'avatar_template') || options.hash.avatarTemplate
   )


Rename EC SRG to EU SRG in dropdown menu

--- app/assets/javascripts/species/controllers/events_controller.js.coffee
+++ app/assets/javascripts/species/controllers/events_controller.js.coffee
@@ -16,7 +16,7 @@
     },
     {
       id: 'EcSrg',
-      name: 'EC SRG'
+      name: 'EU SRG'
     },
     {
       id: 'CitesTc',


Change selection of meeting types

Change selection of meeting types

--- app/assets/javascripts/species/controllers/events_controller.js.coffee
+++ app/assets/javascripts/species/controllers/events_controller.js.coffee
@@ -4,23 +4,19 @@
   eventTypes: [
     {
       id: 'CitesCop',
-      name: 'CITES CoP'
+      name: 'CITES CoP Proposals'
     },
     {
-      id: 'CitesAc',
-      name: 'CITES Animals Committee'
+      id: 'CitesAc,CitesTc',
+      name: 'Review of Significant Trade (animals)'
     },
     {
       id: 'CitesPc',
-      name: 'CITES Plants Committee'
+      name: 'Review of Significant Trade (plants)'
     },
     {
       id: 'EcSrg',
       name: 'EU Scientific Review Group'
-    },
-    {
-      id: 'CitesTc',
-      name: 'CITES Technical Committee'
     }
   ]


Hide autocomplete dropdown when user clicks an item of the dropdown

--- app/assets/javascripts/species/controllers/search_controller.js.coffee
+++ app/assets/javascripts/species/controllers/search_controller.js.coffee
@@ -57,9 +57,11 @@

   openTaxonPage: (taxonConceptId) ->
     @set('redirected', false);
+    $(".search fieldset").removeClass('parent-focus parent-active')
     @transitionToRoute('taxon_concept.legal', Species.TaxonConcept.find(taxonConceptId))

   openSearchPage: () ->
+    $(".search fieldset").removeClass('parent-focus parent-active')
     @transitionToRoute('search', {
       taxonomy: @get('taxonomy'),
       taxon_concept_query: @get('taxonConceptQuery'),


whoops

whoops

--- resources/assets/coffee/react/beatmap-discussions/discussions.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussions.coffee
@@ -183,7 +183,7 @@
     @props.mode == 'timeline' && @currentSort() == 'timeline'

-  sortedDiscussions: (discussions) ->
+  sortedDiscussions: () ->
     @props.currentDiscussions[@props.mode].slice().sort (a, b) =>
       mapperNoteCompare =
         # no sticky for timeline sort


Usability fixes for auto complete controller

--- client/Framework/components/autocomplete/autocompletecontroller.coffee
+++ client/Framework/components/autocomplete/autocompletecontroller.coffee
@@ -31,6 +31,7 @@
     @selectedItemData    = []
     @hiddenInputs        = {}
     @selectedItemCounter = 0
+    @readyToShowDropDown = yes

   reset:->
     subViews    = @itemWrapper.getSubViews().slice()
@@ -61,6 +62,7 @@
           @submitAutoComplete autoCompleteView.getValue()
           event.stopPropagation()
           event.preventDefault()
+          @readyToShowDropDown = no
           return no
         else
           return yes
@@ -82,6 +84,8 @@
           event.preventDefault()
           return no
         # @getView().$input().blur()
+      else
+        @readyToShowDropDown = yes
     no

   getPrefix:()->
@@ -129,6 +133,9 @@
     dropdownWrapper.$().fadeOut 75

   showDropdown:->
+
+    return unless @readyToShowDropDown
+
     windowController = @getSingleton('windowController')
     dropdownWrapper = @dropdown.getView()
     dropdownWrapper.unsetClass "hidden"
@@ -283,7 +290,8 @@
     @emit 'AutocompleteSuggestionWasAdded', title

   addItemToSubmitQueue:(item,data)->
-    data or= item.getData()
+    data or= item?.getData()
+    return unless data

     {itemDataPath,form,submitValuesAsText} = @getOptions()


Teams: Integrations: Select all events by default.

--- client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
+++ client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
@@ -133,10 +133,12 @@

   createEventCheckboxes: ->

-    selectedEvents = @getData().selectedEvents or []
-    mainWrapper    = new kd.CustomHTMLView cssClass: 'event-cbes'
-
     return  unless @data.settings?.events
+
+    { selectedEvents, updatedAt, createdAt } = @getData()
+    mainWrapper      = new kd.CustomHTMLView cssClass: 'event-cbes'
+    selectedEvents   = selectedEvents or []
+    isNewIntegration = updatedAt is createdAt

     for item in @data.settings.events

@@ -147,7 +149,7 @@
         type         : 'checkbox'
         name         : name
         label        : label
-        defaultValue : selectedEvents.indexOf(name) > -1
+        defaultValue : (selectedEvents.indexOf(name) > -1) or isNewIntegration

       wrapper.addSubView checkbox
       wrapper.addSubView label


Admin: Integrations: Fix regenerate token.

--- client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
+++ client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
@@ -20,6 +20,7 @@
     options.cssClass = 'integration-details'

     super options, data
+
     { integration } = data
     { instructions } = integration
     data.repositories or= []
@@ -53,13 +54,13 @@
           selectOptions : channels
           defaultValue  : data.selectedChannel
         url             :
-          label         : "
Webhook URL
When setting up this integration, this is the URL that you will paste into #{data.title}."
+          label         : "
Webhook URL
When setting up this integration, this is the URL that you will paste into #{integration.title}."
           defaultValue  : data.webhookUrl
           attributes    : readonly: 'readonly'
           nextElement   :
             regenerate  :
-              itemClass : CustomLinkView
-              title     : 'Regenerate'
+              itemClass : KDCustomHTMLView
+              partial   : 'Regenerate'
               cssClass  : 'link'
               click     : @bound 'regenerateToken'
         label           :
@@ -243,7 +244,7 @@
     return  if @regenerateLock

     @regenerateLock = yes
-    { id, name }    = @getData()
+    { id, integration: { name } } = @getData()

     integrationHelpers.regenerateToken { id }, (err, res) =>
       return showError  if err


Workspace: Added host disconnected modal.

--- client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -98,6 +98,37 @@
         @addSubView new CollaborativeWorkspace options

     @container.addSubView notValid
+
+  showDisconnectedModal: ->
+    @disconnectedModal = new KDModalView
+      title        : "Host disconnected"
+      cssClass     : "host-disconnected-modal"
+      content      : "
It seems host is disconnected
"
+      overlay      : yes
+      buttons      :
+        Start      :
+          title    : "Start New Session"
+          callback : =>
+            @disconnectedModal.destroy()
+            delete @disconnectedModal
+            @showSessionModal()
+        Join       :
+          title    : "Join Another Session"
+          callback : =>
+            @disconnectedModal.destroy()
+            delete @disconnectedModal
+            @showSessionModal (modal) ->
+              modal.modalTabs.showPaneByIndex(1)
+        Exit       :
+          title    : "Exit App"
+          cssClass : "modal-cancel"
+          callback : ->
+            @disconnectedModal.destroy()
+            delete @disconnectedModal
+            appManager = KD.getSingleton("appManager")
+            appManager.quit appManager.frontApp
+
+  showSessionModal: (callback = noop) ->
     modal                       = new KDModalViewWithForms
       title                     : "Manage Your Session"
       content                   : ""
@@ -159,3 +190,5 @@
                 title           : "Close"
                 cssClass        : "modal-cancel"
                 callback        : -> modal.destroy()
+
+    callback modal


lod_and_effect: change mouseover to click; better treatment of lower LOD curve

  - had used hide/unhide via opacity.  Now changing the data in the path

--- lod_and_effect/lod_and_effect.coffee
+++ lod_and_effect/lod_and_effect.coffee
@@ -99,6 +99,7 @@
      .data(data.chr)
      .enter()
      .append("rect")
+     .attr("id", (d) -> "rect#{d}")
      .attr("x", (d) -> chrPixelStart[d] - chrGap/2)
      .attr("y", pad.top)
      .attr("width", (d) -> chrPixelEnd[d] - chrPixelStart[d]+chrGap)
@@ -128,21 +129,21 @@
           .x((d) -> botLxScale[j](d))
           .y((d,i) -> yScale(data.lod[j].lod[i]))

-  detailedLod = {}
-  for i in data.chr
-    detailedLod[i] = botsvg.append("g").append("path")
-       .datum(data.lod[i].pos)
-       .attr("d", botlodcurve(i))
+  botsvg.append("g").append("path")
+       .attr("d", botlodcurve("2")(data.lod["2"].pos))
        .attr("class", "thickline")
+       .attr("id", "detailedLod")
        .attr("stroke", "blue")
-       .attr("opacity", 0)

-  chrRect.on("mouseover", (d) ->
+  last = 0
+  chrRect.on("click", (d) ->
              d3.select(this).attr("fill", "#E9CFEC")
-             detailedLod[d].attr("opacity", 1))
-         .on("mouseout", (d) ->
-             d3.select(this).attr("fill", chrColor[d])
-             detailedLod[d].attr("opacity", 0))
+             topsvg.select("#rect#{last}").attr("fill", chrColor[last]) if last != 0
+             last = d
+             botsvg.select("path#detailedLod")
+                .transition().duration(0)
+                .attr("d", botlodcurve(d)(data.lod[d].pos)))
+

   # chr labels
   topsvg.append("g").selectAll("empty")


Moved some logic to `setQuery`.

Moved some logic to `setQuery`.

--- core/app/backbone/views/facts/auto_complete_fact_relations_view.coffee
+++ core/app/backbone/views/facts/auto_complete_fact_relations_view.coffee
@@ -55,8 +55,7 @@
       created_by: currentUser.toJSON()
       fact_relation_authority: '1.0'

-
-
+  setQuery: (text) -> @model.set text: text

 class PreviewView extends Backbone.Marionette.Layout
   template:
@@ -131,9 +130,10 @@
   createFactRelation: (fact_relation)->
     @collection.add fact_relation
     @inputRegion.switchTo('search_view')
-    @inputRegion.getView('search_view').model.set text: ''
+    @inputRegion.getView('search_view').setQuery ''
     fact_relation.save {},
       error: =>
-        @inputRegion.getView('search_view').model.set( text: fact_relation.get('fact_base').displaystring )
+        @collection.remove fact_relation
+        @inputRegion.getView('search_view').setQuery fact_relation.get('fact_base').displaystring
         alert('Something went wrong')


Adds more supported expression types to the expression equality library

--- math/expression_equality_spec.coffee
+++ math/expression_equality_spec.coffee
@@ -89,3 +89,41 @@
           @comps.addition.build()
         )).toEqual true

+
+  describe "blank elements", ->
+    it "accepts vs a blank element", ->
+      expect(
+        @isEqual(
+          @comps.blank.build(),
+          @comps.blank.build(),
+        )).toEqual true
+
+    it "rejects against numbers", ->
+      expect(
+        @isEqual(
+          @comps.blank.build(),
+          @comps.number.build(value: 1)
+        )).toEqual false
+    it "accepts against expressions that contain a single blank element", ->
+      throw "TODO fill this in!"
+
+
+  describe "exponentiation", ->
+    it "accepts exponents with same base and power", ->
+      a = @exp_builder('^': [11, 22])
+      b = @exp_builder('^': [11, 22])
+      expect(a).toBeAnEqualExpressionTo b
+
+    it "rejects exponents with differing bases", ->
+      new_exp = @exp_builder('^': [11, 32])
+      expected = @exp_builder('^': [12, 32])
+      expect(new_exp).not.toBeAnEqualExpressionTo(expected)
+
+    it "rejects exponents different powers",->
+      new_exp = @exp_builder('^': [10, 32])
+      expected = @exp_builder('^': [10, 22])
+      expect(new_exp).not.toBeAnEqualExpressionTo(expected)
+
+
+
+


got pattern matching working

got pattern matching working

--- nodes.coffee
+++ nodes.coffee
@@ -594,12 +594,13 @@
     o.top: true
     o.as_statement: true
     for obj, i in @variable.base.objects
-      [obj, i]: [obj.value, obj.variable.base] if @variable.is_object()
+      idx: i
+      [obj, idx]: [obj.value, obj.variable.base] if @variable.is_object()
       access_class: if @variable.is_array() then IndexNode else AccessorNode
       if obj instanceof SplatNode
         val: new LiteralNode(obj.compile_value(o, val_var, @variable.base.objects.indexOf(obj)))
       else
-        val: new ValueNode(new LiteralNode(val_var), [new access_class(new LiteralNode(i))])
+        val: new ValueNode(new LiteralNode(val_var), [new access_class(idx)])
       assigns.push(new AssignNode(obj, val).compile(o))
     assigns.join("\n")


caching the length property lookup for vanilla array comprehensions and rebuilding docs

--- nodes.coffee
+++ nodes.coffee
@@ -447,7 +447,7 @@
     return    @compile_array(o) unless o.index
     idx:      del o, 'index'
     step:     del o, 'step'
-    vars:     idx + '=' + @from_var
+    vars:     idx + ' = ' + @from_var
     step:     if step then step.compile(o) else '1'
     equals:   if @exclusive then '' else '='
     intro:    '(' + @from_var + ' <= ' + @to_var + ' ? ' + idx
@@ -922,9 +922,11 @@
     else
       index_var:    null
       source_part:  svar + ' = ' + @source.compile(o) + ';\n' + @idt()
-      step_part:    if @step then ivar + ' += ' + @step.compile(o) else ivar + '++'
-      for_part:     ivar + ' = 0; ' + ivar + ' < ' + svar + '.length; ' + step_part
       var_part:     body_dent + name + ' = ' + svar + '[' + ivar + '];\n' if name
+      if not @object
+        lvar:       scope.free_variable()
+        step_part:  if @step then ivar + ' += ' + @step.compile(o) else ivar + '++'
+        for_part:   ivar + ' = 0, ' + lvar + ' = ' + svar + '.length; ' + ivar + ' < ' + lvar + '; ' + step_part
     set_result:     if rvar then @idt() + rvar + ' = []; ' else @idt()
     return_result:  rvar or ''
     body:           ClosureNode.wrap(body, true) if top_level and @contains (n) -> n instanceof CodeNode


show tutorial to new and non-logged-in users

--- overrides.coffee
+++ overrides.coffee
@@ -8,6 +8,8 @@
 ClassifyMenu = require "./classify-menu"
 project = require "zooniverse-readymade/current-project"

+
+User = require "zooniverse/models/user"
 classifyPage = project.classifyPages[0]
 subjectViewer = classifyPage.subjectViewer
 tools = subjectViewer.markingSurface.tools
@@ -60,6 +62,14 @@
   tut = new Tutorial

   $("#tutorial-tab").on 'click', => tut.start()
+
+  showTutorialIfNew = ->
+    firstVisit = User?.current?.preferences?.kelp?.first_visit
+    tut.start() if firstVisit isnt "false"
+    User.current.setPreference "first_visit", "false"
+
+  User.on('change', showTutorialIfNew)
+  User.fetch()

 class ClassifyPageEvents
   @firstSubject = true


Reset data value when dependent values changes

Closes #7

One of the worse hack ever but, from my undestanding if a component dependencies' values are different it's because @props.onDataChanged was triggered by another one so we are still in the @props.onDataChanged setState or  in the render of another component aka yeah we lost control and should use immutable structure

--- mixins/FormDataSourcedMixin.jsx.coffee
+++ mixins/FormDataSourcedMixin.jsx.coffee
@@ -26,7 +26,13 @@
       @loadDataSource(name, @props.dependencies)

   componentWillReceiveProps: (nextProps) ->
-    for name, dataSource of @props.dataSources
+    # if parent dependencies change we reset the data value
+    if @props.data && !_.isEqual(_.values(nextProps.dependencies), _.values(@props.dependencies))
+      setTimeout =>
+        @props.onDataChanged(@props.dataKey, undefined)
+      , 1
+
+    else for name, dataSource of @props.dataSources
       if dataSource.dependentKeys? && dataSource.type is 'service'
         @loadDataSource(name, nextProps.dependencies)


Client: Activity: Feed: Formatting

Client: Activity: Feed: Formatting

--- client/activity/lib/components/chatlistitem/simplechatlistitem.coffee
+++ client/activity/lib/components/chatlistitem/simplechatlistitem.coffee
@@ -1,13 +1,13 @@
-kd                   = require 'kd'
-MessageBody          = require 'activity/components/common/messagebody'
-ChatListItem         = require 'activity/components/chatlistitem'
-MessageTime          = require 'activity/components/chatlistitem/messagetime'
-ActivityLikeLink     = require 'activity/components/chatlistitem/activitylikelink'
-classnames           = require 'classnames'
-MessageLink          = require 'activity/components/messagelink'
+kd               = require 'kd'
+React            = require 'kd-react'
+MessageBody      = require 'activity/components/common/messagebody'
+ChatListItem     = require 'activity/components/chatlistitem'
+MessageTime      = require 'activity/components/chatlistitem/messagetime'
+classnames       = require 'classnames'
+MessageLink      = require 'activity/components/messagelink'
+ActivityLikeLink = require 'activity/components/chatlistitem/activitylikelink'

 module.exports = class SimpleChatListItem extends ChatListItem
-

   getContentClassNames: -> classnames
     'ChatItem-contentWrapper MediaObject SimpleChatListItem': yes


Workspace: Added getValue method for CollaborativeEditorPane.

--- client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
@@ -53,6 +53,9 @@
     else
       @ref.child("WaitingSaveRequest").set yes

+  getValue: ->
+    return @codeMirrorEditor.getValue()
+
   createEditor: ->
     @codeMirrorEditor = CodeMirror @container.getDomElement()[0],
       lineNumbers     : yes


bugfix?

bugfix?

--- wrapper/actions/get_selectable_classes2.coffee
+++ wrapper/actions/get_selectable_classes2.coffee
@@ -34,8 +34,12 @@
           chosenClass.info = chosenClass.label.match(/^\w+_\w+_C(\d+)_(?:F|S)\d{2}$/i)
           continue

-        if chosenClass && chosenClass.id==c.id
-          foundChosen = true
+        try
+          if chosenClass.id==c.id
+            foundChosen = true
+        catch e
+          console.log chosenClass, c
+
         result.push {
           id: parseInt(c.id)
           totalEnrolled: parseInt(cInfo[1])


Bing - generate word when it can't be retrieved from site

--- bots/Bing.coffee
+++ bots/Bing.coffee
@@ -52,7 +52,7 @@
         [earn,per,upTo] = elem.querySelector('.desc').innerText.match(/\d+/g)[0..2]
         soFar = elem.querySelector('.progress').innerText.match(/\d+/)[0]
         queryCount = Math.ceil(per * (upTo - soFar) / earn) # queriesPerPts * remainingPoints / earnedPerQueries
-      else if title is 'Connect to Facebook' then facebookLogin = true
+      else if title is 'Connect to Facebook' then facebookLogin = true # free points by logging in with facebook
       else if title not in ['Refer a Friend','Bing Newsletter']
         offers.push elem.href # add the link of the offer
     return [offers,queryCount,facebookLogin]
@@ -69,11 +69,18 @@
   @echo "Executing #{ARGS[2]} quer#{if ARGS[2] is 1 then 'y' else 'ies'}." # tell user how many queries are being executed
   @repeat ARGS[2], execQuery = -> # repeat `queryCount` times
     @wait Math.floor(Math.random() * ((ARGS[4] - ARGS[3]) * 1000 + 1)) + (ARGS[3] * 1000), -> # wait between queries
-      word = ''; len = Math.floor(Math.random()*5)+5
-      for i in [1..len] by 2 # generate a random word (length of 5..9 letters)
-        word += CONSONANTS[Math.floor(Math.random()*CONSONANTS.length)] # add a consonant
-        if i<len then word += VOWELS[Math.floor(Math.random()*VOWELS.length)] # add a vowel if there is room
-      word = word[0].toUpperCase() + word[1..-1] # capitalize the first letter of the word
+      word = ''
+      @thenOpen 'http://randomword.setgetgo.com/get.php' # visit site to retrieve random word
+      @waitFor (checkSite = ->
+        not @exists 'body > *' # if page loaded and matches format
+      ), (retrieve = -> # the random word generator was loaded
+        word = @fetchText('body').trim() # get the random word (without excess whitespace)
+      ), generate = -> # could not load the site, generating a `fake` word
+        len = Math.floor(Math.random()*5)+5
+        for i in [1..len] by 2 # generate a random word (length of 5..9 letters)
+          word += CONSONANTS[Math.floor(Math.random()*CONSONANTS.length)] # add a consonant
+          if i<len then word += VOWELS[Math.floor(Math.random()*VOWELS.length)] # add a vowel if there is room
+        if Math.floor(Math.random()*2) is 1 then word = word[0].toUpperCase() + word[1..-1] # randomly capitalize word
       @thenOpen ("http://www.bing.com/search?q=#{word}"), -> @echo "#{++executed}) #{word}" # query bing with the word

 casper.thenOpen DASHBOARD, getTotalPoints = -> # get the number of unused points on the account


Check for user properly

Check for user properly

--- resources/assets/coffee/react/mp-history/event.coffee
+++ resources/assets/coffee/react/mp-history/event.coffee
@@ -28,6 +28,8 @@
     'host-changed': ['exchange']

   render: ->
+    console.log @props.event.user_id
+
     user = @props.lookupUser @props.event.user_id

     event_type = @props.event.event_type
@@ -45,7 +47,7 @@
           el Icon, name: m, key: m
       div className: 'mp-history-event__info-box',
         if @props.event.event_type != 'match-disbanded'
-          if !user?
+          if user?
             a
               className: 'mp-history-event__text mp-history-event__text--username'
               href: laroute.route 'users.show', users: user.id


detecting when some user left the chat

detecting when some user left the chat

--- server/index.coffee
+++ server/index.coffee
@@ -14,15 +14,24 @@
 io.sockets.on 'connection', (socket) ->
   # console.log "Socket connected: #{socket.id}"

+  socket.on "join", (user) ->
+    socket.set("user", user)
+
+    io.sockets.emit "message", {
+      from : "Parrot"
+      message : "User #{user} joined the chat! Welcome to Parrot ;]"
+    }
+
+  socket.on "disconnect", () ->
+    socket.get 'user', (err, user) ->
+      io.sockets.emit "message", {
+        from : "Parrot"
+        message : "User #{user} left the chat!"
+      }
+
   socket.on "message", (msg) ->
     io.sockets.emit "message", msg
     # console.log "MESSAGE -> ", msg

-  socket.on "join", (user) ->
-    io.sockets.emit "message", {
-      from : "Parrot"
-      message : "User #{user} joined the chat!"
-    }
-
 server.listen(3300)
 module.exports = app


let aggregates handle id as string or value object

--- source/domain/aggregate.coffee
+++ source/domain/aggregate.coffee
@@ -61,7 +61,7 @@
     unless event instanceof Event
       throw new Error Aggregate.ERRORS.domainEventRequired

-    unless event.sourceId.equals @getId()
+    unless event.sourceId.toString() == @getId().toString()
       throw new Error Aggregate.ERRORS.invalidEventSourceId

     unless @_getEventHandler(event)?


Fix i18n and Google API credentials

Fix i18n and Google API credentials

--- model/Recipe/ListenToYourHeart.coffee
+++ model/Recipe/ListenToYourHeart.coffee
@@ -27,13 +27,7 @@
         steps = @stepsByKey()
         input =
           ListenToYourHeart:
-            GoogleWriteSpreadsheets:
-              avatarId: steps["GoogleChooseAvatar"].avatarId
-              params: {}
-            BitlyDownloadLinks:
-              avatarId: steps["BitlyChooseAvatar"].avatarId
-              params: {}
-            GoogleWriteSpreadsheetCells:
+            Echo:
               avatarId: steps["GoogleChooseAvatar"].avatarId
               params: {}
       else


Change :speech_balloon:

Change :speech_balloon:

--- _assets/stuff.coffee
+++ _assets/stuff.coffee
@@ -35,7 +35,7 @@
   window.target_emoji = $(this).val()
   $('.canvas').css 'background-image', "url(#{img.attr('src')})"

-# drag and drop!
+# drag
 $(document).on 'mousedown', '.cell', (e) ->
   markSelected $(e.target)
   $(document).on 'mouseover', '.cell', (e) ->
@@ -43,7 +43,7 @@

 $(document).on 'mouseup', '.cell', (e) ->
   $(document).off 'mouseover', '.cell'
-# drag and drop!
+# drag

 $(document).on 'click', '#set-number', ->
   return false if $('.cell.selected').length == 0


Improve coding of search bar plugin

Improve coding of search bar plugin

--- src/opac/search_bar.coffee
+++ src/opac/search_bar.coffee
@@ -223,11 +223,15 @@
 				when 27 then $('button[type=reset]', @).click()
 			return false

-		# Upon reset, nullify all input and select values.
-		@delegate 'button[type=reset]', 'click', => reset_search_form.call @
-
-		# Handle click of submit button.
+		# Upon reset, we nullify all input and select values.
+		@delegate 'button[type=reset]', 'click', =>
+			reset_search_form.call @
+			return false
+
+		# Handle submission events
 		@submit ->
+
+			$this = $(@)

 			# A valid submission needs at least one input terms to contain non-whitespace.
 			ok = false
@@ -240,7 +244,7 @@
 			# Build request object from input and select values of search form.
 			# Multiple values for a property name are cast into an array, eg, name:[1, 2]
 			o = {}
-			for x in $(@).children('form').serializeArray()
+			for x in $this.children('form').serializeArray()
 				unless o[x.name]?
 					o[x.name] = x.value
 				else
@@ -249,10 +253,10 @@
 					o[x.name].push x.value

 			# Calculate search depth from indentation of selected ou name.
-			o.depth = $('select[name=org_unit]', $(@)).find(':selected').text().match(/\_ /g)?.length or 0
+			o.depth = $('select[name=org_unit]', @).find(':selected').text().match(/\_ /g)?.length or 0

 			# Publish the search form content on the search data channel.
-			$(@).publish 'search', [o]
+			$this.publish 'search', [o]
 			return false


Search completion; fix yet more tweaks.

Search completion; fix yet more tweaks.

--- background_scripts/completion_engines.coffee
+++ background_scripts/completion_engines.coffee
@@ -221,7 +221,8 @@
         queue = @inTransit[completionCacheKey] = []
         engine = @lookupEngine searchUrl
         fetchSuggestions engine, (suggestions) =>
-          callback @completionCache.set completionCacheKey, suggestions unless engine.doNotCache
+          @completionCache.set completionCacheKey, suggestions unless engine.doNotCache
+          callback suggestions
           delete @inTransit[completionCacheKey]
           console.log "callbacks", queue.length, completionCacheKey if @debug and 0 < queue.length
           callback suggestions for callback in queue


Set up resolveLoader configuration

Set up resolveLoader configuration

Now it will be able to seek for loaders in the correct place.

--- webpack.coffee
+++ webpack.coffee
@@ -23,6 +23,11 @@
       ]
       modulesDirectories: [
         path.join process.cwd(), 'node_modules'
+        'node_modules'
+      ]
+    resolveLoader:
+      modulesDirectories: [
+        path.join(__dirname, 'node_modules')
         'node_modules'
       ]
     jshint:
@@ -95,6 +100,7 @@
         loader: 'json!yaml-frontmatter-loader'
     ]
     resolve: common.resolve
+    resolveLoader: common.resolveLoader
     jshint: common.jshint
   }

@@ -117,6 +123,7 @@
       libraryTarget: 'commonjs2'
     plugins: [ new ExtractTextPlugin('main.css', allChunks: true) ]
     resolve: common.resolve
+    resolveLoader: common.resolveLoader
     module: loaders: [
         test: /\.scss$/
         loader: ExtractTextPlugin.extract('style-loader', 'css-loader!autoprefixer-loader?{browsers:["last 2 version", "ie 10", "Android 4"]}!sass-loader')


Calendar and Projects responses

Calendar and Projects responses

--- src/scripts/diagnostics.coffee
+++ src/scripts/diagnostics.coffee
@@ -66,12 +66,13 @@
     }

   robot.respond /users/i, (res) ->
+    user = Organization.getUserBySlackName res.message.user.name
     if isAdminUser res.message.user.name
       response = ''
-      for user in Organization.users
-        response += user.description() + '\n\n'
+      for u in Organization.users
+        response += u.description() + '\n\n'
       user.directMessage response, Logger
-      Logger.addReaction 'dog2', res.message.user.name
+      Logger.addReaction 'dog2', res.message
     else
       user.directMessage strings.adminonly, Logger
       Logger.addReaction 'x', res.message
@@ -93,7 +94,19 @@
     res.json {
       "text": response
     }
-
+
+  robot.respond /projects/i, (res) ->
+    user = Organization.getUserBySlackName res.message.user.name
+    if isAdminUser res.message.user.name
+      response = ''
+      for project in Organization.projects
+        response += project.description() + '\n\n'
+      user.directMessage response, Logger
+      Logger.addReaction 'dog2', res.message
+    else
+      user.directMessage strings.adminonly, Logger
+      Logger.addReaction 'x', res.message
+
   robot.router.post '/ibizan/diagnostics/calendar', (req, res) ->
     body = req.body
     if body.token is process.env.SLASH_CALENDAR_TOKEN
@@ -109,6 +122,15 @@
     res.json {
       "text": response
     }
+
+  robot.respond /calendar/i, (res) ->
+    user = Organization.getUserBySlackName res.message.user.name
+    if isAdminUser res.message.user.name
+      user.directMessage Organization.calendar.description(), Logger
+      Logger.addReaction 'dog2', res.message
+    else
+      user.directMessage strings.adminonly, Logger
+      Logger.addReaction 'x', res.message

   robot.router.post '/ibizan/diagnostics/sync', (req, res) ->
     body = req.body
@@ -167,7 +189,7 @@
     .done(
       (status) ->
         Logger.logToChannel "Resynced with spreadsheet",
-                            res.message.user.name
+                            res.message.rawMessage.channel
         Logger.removeReaction 'clock4', res.message
         Logger.addReaction 'dog2', res.message
     )


fix underscore

fix underscore

--- data/points/underscore.coffee
+++ data/points/underscore.coffee
@@ -1,6 +1,6 @@
 id: "underscore"
 title: "Underscore"
-function: [ "base-libirary" ]
+function: [ "base-library" ]
 language: [ "javascript" ]
 relevance: [ "jquery", "sugar" ]
 desc: "Underscore.js是一个实用的JavaScript工具库，提供了类似Prototype功能的编程支持，但没有对JavaScript内置的对象进行扩展。"


Use proper quotes for strings

Use proper quotes for strings

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -3823,7 +3823,11 @@

 toHtml = (highlighted) ->
   crawl highlighted, (node, symbol, parent) ->
-    colorize(theme[labelOf node, parent], symbol)
+    colorize(theme[labelOf node, parent],
+      if node.label is 'string'
+        "“#{symbol[1...-1]}”"
+      else
+        symbol)

 print = (ast) ->
   if not ast


Fixed tests and matching by simplifying the caching, which is now slightly more difficult to use, having to push undefined in ms_match

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -224,16 +224,34 @@
   resetIsOperator: ->
     @_isOperator.pop()

+  # Assignment translation works as follows:
+  #   1. parent sets assign to
+  #   2. patterns look at the assign to value
+  #   3. patterns can request caching of the rhs, which will replace the value
+  #      with cache name and remember the cache
+  #   4. parent resets assign to and obtains the translation cache, if it
+  #      expects there could be one
   setAssignTo: (compiled) ->
-    @_assignTos.push compiled
+    @_assignTos.push value: compiled

   assignTo: ->
-    rhs = @_assignTos[@_assignTos.length - 1]
-    cacheName = @_translationCache()?[0]
-    if rhs then cacheName or rhs
+    @_assignTos[@_assignTos.length - 1]?.value
+
+  cacheAssignTo: ->
+    assignTo = @_assignTos[@_assignTos.length - 1]
+    if assignTo?.value and not assignTo.cache
+      # Replace assignTo with cache name
+      cacheName = @newJsVariable()
+      cache = [cacheName, @assignTo()]
+      @_assignTos[@_assignTos.length - 1] =
+        value: cacheName
+        cache: cache

   resetAssignTo: ->
-    @_assignTos.pop()
+    if cache = @_assignTos.pop().cache
+      [compileVariableAssignment cache]
+    else
+      []

   _scope: ->
     @scopes[@scopes.length - 1]
@@ -371,26 +389,6 @@

   newJsVariable: ->
     "i#{@variableIndex++}"
-
-  setGroupTranslation: ->
-    @cacheScopes.push []
-
-  cacheAssignTo: ->
-    if @assignTo() and not @_translationCache()
-      # Replace assign to with cache name
-      cacheName = @newJsVariable()
-      cache = [cacheName, @assignTo()]
-      # @_assignTos[@_assignTos.length - 1] = cacheName #instead change assignTo
-      @cacheScopes[@cacheScopes.length - 1][0] = cache
-
-  _translationCache: ->
-    @cacheScopes[@cacheScopes.length - 1][0]
-
-  translationCache: ->
-    if cache = @cacheScopes.pop()[0]
-      [compileVariableAssignment cache]
-    else
-      []

   doDefer: (expression, dependencyName) ->
     @_setDeferIn @_deferrableDefinition(), expression, dependencyName
@@ -813,9 +811,9 @@
   if ctx.isAtDefinition()
     to = ctx.definitionPattern()

-    ctx.setGroupTranslation()
-    {precs, assigns} = patternCompile ctx, to, expression,
-      (irDefinition expression.tea, translatedExpression)
+    ctx.setAssignTo (irDefinition expression.tea, translatedExpression)
+    {precs, assigns} = patternCompile ctx, to, expression
+    translationCache = ctx.resetAssignTo()

     #log "ASSIGN #{ctx.definitionName()}", ctx.shouldDefer()
     if ctx.shouldDefer()
@@ -824,17 +822,15 @@

     if assigns.length is 0
       return malformed to, 'Not an assignable pattern'
-    join ctx.translationCache(), map compileVariableAssignment, assigns
+    join translationCache, map compileVariableAssignment, assigns
   else
     translatedExpression

-patternCompile = (ctx, pattern, matched, translatedMatched) ->
-
-  ctx.setAssignTo ctx._translationCache() or translatedMatched
+patternCompile = (ctx, pattern, matched) ->
+
   # caching can occur while compiling the pattern
   # precs are {cond}s and {cache}s, sorted in order they need to be executed
   {precs, assigns} = expressionCompile ctx, pattern
-  ctx.resetAssignTo()

   definedNames = ctx.definedNames()

@@ -1330,17 +1326,20 @@
     # To make sure all results have the same type
     resultType = ctx.freshTypeVariable star

-    ctx.setGroupTranslation()
+    # ctx.setGroupTranslation()
+    ctx.setAssignTo subjectCompiled
     varNames = []
     constraints = []
     compiledCases = conditional (for [pattern, result] in pairs cases

       ctx.newScope() # for variables defined inside pattern
       ctx.defineNonDeferrablePattern pattern
-      {precs, assigns} = patternCompile ctx, pattern, subject, subjectCompiled
+      {precs, assigns} = patternCompile ctx, pattern, subject

       # Compile the result, given current scope
+      ctx.setAssignTo undefined
       compiledResult = termCompile ctx, result #compileImpl result, furtherHoistable
+      ctx.resetAssignTo()
       ctx.leaveDefinition()
       ctx.closeScope()

@@ -1355,20 +1354,21 @@

       matchBranchTranslate precs, assigns, compiledResult
     ), "throw new Error('match failed to match');" #TODO: what subject?
+    translationCache = ctx.resetAssignTo()
     call.tea = new Constrained constraints, resultType
     assignCompile ctx, call, iife concat (filter _is, [
-      ctx.translationCache()
+      translationCache
       varList varNames
       compiledCases])

-# ms['='] = ms_eq = (ctx, call) ->
-#     [a, b] = _arguments call
-#     operatorCompile ctx, call
-#     compiledA = termCompile ctx, a
-#     compiledB = termCompile ctx, b
-
-#     callTyping ctx, call
-#     assignCompile ctx, call, (jsBinary "===", compiledA, compiledB)
+ms['=='] = ms_eq = (ctx, call) ->
+    [a, b] = _arguments call
+    operatorCompile ctx, call
+    compiledA = termCompile ctx, a
+    compiledB = termCompile ctx, b
+
+    callTyping ctx, call
+    assignCompile ctx, call, (jsBinary "===", compiledA, compiledB)

 builtInMacros = ms

@@ -2581,7 +2581,7 @@

     # '+', binaryMathOpType
     # '*', binaryMathOpType
-    # '=', comparatorOpType
+    '==', comparatorOpType
     # '!=', comparatorOpType
     # 'and', '(Fn Bool Bool Bool)'
     # 'or', '(Fn Bool Bool Bool)'
@@ -3302,8 +3302,8 @@
     log (collapse toHtml compiled.ast)
     if not isMapEmpty compiled.subs
       log compiled.subs
-    if result isnt (eval compiled.compiled)
-      log "Wrong result", result
+    if result isnt (got = eval compiled.compiled)
+      log "'#{testName}' expected", result, "got", got
   catch e
     logError "Error in test |#{testName}|\n#{teaSource}\n", e

@@ -3322,7 +3322,7 @@
     r Red
     b Blue
     r2 Red"""
-  "(= r r2)", true
+  "(== r r2)", true

   'match numbers'
   """positive (fn [n]
@@ -3364,6 +3364,8 @@
       (match pair
         [x y] y))"""
   "(snd [1 2])", 2
+
+  # TODO: add test for matching on tuples with multiple branches

   'match data'
   """Person (record name: String id: Num)
@@ -3417,7 +3419,7 @@

     showed-simply (show "Hello")
     showed-via-alias (aliased-show "Hello")"""
-  "(= showed-simply showed-via-alias)", yes
+  "(== showed-simply showed-via-alias)", yes

   'multiple methods'
   """Util (class [a]


Unfocus spec

Unfocus spec

--- spec/language-mode-spec.coffee
+++ spec/language-mode-spec.coffee
@@ -71,7 +71,7 @@
         expect(languageMode.rowRangeForCodeFoldAtBufferRow(4)).toEqual [4, 7]

     describe ".rowRangeForCommentAtBufferRow(bufferRow)", ->
-      fit "returns the start/end rows of the foldable comment starting at the given row", ->
+      it "returns the start/end rows of the foldable comment starting at the given row", ->
         buffer.setText("//this is a multi line comment\n//another line")
         expect(languageMode.rowRangeForCommentAtBufferRow(0)).toEqual [0, 1]
         expect(languageMode.rowRangeForCommentAtBufferRow(1)).toEqual [0, 1]


Add spec for isProduct check.

Add spec for isProduct check.

--- src/spec/validator.spec.coffee
+++ src/spec/validator.spec.coffee
@@ -51,6 +51,13 @@
     it 'should be false for a product', ->
       expect(@validator.isVariant ['myProduct', 1]).toBe false

+  describe '#isProduct', ->
+    beforeEach ->
+      @validator.header = new Header CONS.BASE_HEADERS
+
+    it 'should be false for a variantId > 1 with a product type give', ->
+      expect(@validator.isProduct ['foo', '', 2]).toBe false
+
   describe '#buildProducts', ->
     beforeEach ->

@@ -76,19 +83,33 @@
         expect(@validator.rawProducts[1].startRow).toBe 5
         done()

-    xit 'should return error if first row in not a product', (done) ->
+    it 'should return error if row isnt a variant nor product', (done) ->
       csv =
         """
         productType,name,variantId
+        myType,,1
         ,,1
+        myType,,2
+        """
+      @validator.parse csv, (content) =>
+        @validator.buildProducts content
+        expect(@validator.errors.length).toBe 2
+        expect(@validator.errors[0]).toBe '[row 2] Could not be identified as product or variant!'
+        expect(@validator.errors[1]).toBe '[row 3] Could not be identified as product or variant!'
+        done()
+
+    it 'should return error if first row isnt a product row', (done) ->
+      csv =
+        """
+        productType,name,variantId
         ,,2
         """
       @validator.parse csv, (content) =>
         @validator.buildProducts content
-        expect(@validator.errors.length).toBe 2
+        expect(@validator.errors.length).toBe 1
         expect(@validator.errors[0]).toBe '[row 1] We need a product before starting with a variant!'
-        expect(@validator.errors[1]).toBe '[row 2] We need a product before starting with a variant!'
         done()
+

   describe '#valProduct', ->
     it 'should return no error', ->


Update the ResolverView spec.

Update the ResolverView spec.

--- spec/resolver-view-spec.coffee
+++ spec/resolver-view-spec.coffee
@@ -13,7 +13,14 @@
     }
     view = new ResolverView(fakeEditor)

-    GitBridge._gitCommand = -> 'git'
+    atom.config.set('merge-conflicts.gitPath', 'git')
+    done = false
+    GitBridge.locateGitAnd (err) ->
+      throw err if err?
+      done = true
+
+    waitsFor -> done
+
     GitBridge.process = ({stdout, exit}) ->
       stdout('UU lib/file1.txt')
       exit(0)


update spec for unknown type

update spec for unknown type

--- spec/views/subject_spec.coffee
+++ spec/views/subject_spec.coffee
@@ -17,4 +17,4 @@

     it 'returns Subject for unknown type', ->
       model = new Backbone.Model()
-      expect(App.Views.Subject.for(model)).toBe(App.Views.Subject)
+      expect(App.Views.Subject.for(model)).toBe(App.Views.Subject.Unknown)


make it easy to get and set project settings from global client

--- salvus/bup_server.coffee
+++ salvus/bup_server.coffee
@@ -1341,6 +1341,63 @@
                                 delete @_mount_remote_interval[opts.mount_point]
                             opts.cb?(err)

+    set_settings: (opts) =>
+        # For the options see the settings method of ClientProject.
+        # This method changes settings in the database; also, if the project is currently running
+        # somewhere, it updates the settings on that running instance.
+        dbg = (m) => winston.info("set_settings(#{misc.to_json(opts)}): #{m}")
+        dbg()
+
+        cb0     = opts.cb
+        timeout = opts.timeout
+        delete opts.cb
+        delete opts.timeout
+        async.series([
+            (cb) =>
+                dbg("change settings in the database")
+                f = (key, c) =>
+                    if opts[key]?
+                        @database.cql("UPDATE projects SET settings[?]=? WHERE project_id=?", [key, "#{opts[key]}", @project_id], c)
+                    else
+                        c()
+                async.map(misc.keys(opts), f, (err) => cb(err))
+            (cb) =>
+                dbg("checking if project is running, and if so set settings locally there.")
+                @get_host_where_running
+                    cb : (err, server_id) =>
+                        if err or not server_id?
+                            dbg("project not running")
+                            cb(err)
+                        else
+                            dbg("project running at #{server_id}, so changing settings there")
+                            @project
+                                server_id : server_id
+                                cb        : (err, project) =>
+                                    if err
+                                        cb(err)
+                                    else
+                                        opts.cb = cb
+                                        opts.timeout = timeout
+                                        project.settings(opts)
+        ], (err) => cb0?(err))
+
+    get_settings: (opts) =>
+        opts = defaults opts,
+            cb          : undefined
+        # Get project settings from the database; these are only the settings that
+        # over-ride defaults.
+        @database.select_one
+            table     : 'projects'
+            columns   : ['settings']
+            objectify : false
+            where     : {project_id : @project_id}
+            cb        : (err, result) =>
+                if err
+                    opts.cb(err)
+                else
+                    opts.cb(undefined, result[0])
+
+


More analytics hooks

More analytics hooks

--- picoCTF-web/web/coffee/problems.coffee
+++ picoCTF-web/web/coffee/problems.coffee
@@ -29,10 +29,13 @@
   apiCall "POST", "/api/problems/submit", {pid: input.data("pid"), key: input.val()}
   .done (data) ->
     if data['status'] is 1
+      ga('send', 'event', 'Problem', 'Solve', 'Basic')
       loadProblems()
       setTimeout( ->
         $("div[data-target='#" + input.data("pid") + "']").click()
       , 100)
+    else
+      ga('send', 'event', 'Problem', 'Wrong', 'Basic')
     apiNotify data
     apiCall "GET", "/api/achievements"
     .done (data) ->
@@ -68,6 +71,7 @@
   .done (data) ->
     loadProblems()
     apiNotify data
+    ga('send', 'event', 'Problem', 'Review', 'Basic')
     apiCall "GET", "/api/achievements"
     .done (data) ->
       if data['status'] is 1
@@ -76,6 +80,7 @@

 toggleHint = (e) ->
   pid = $(e.target).data("pid")
+  ga('send', 'event', 'Problem', 'OpenHint', 'Basic')
   apiCall "GET", "/api/problems/hint", {"pid": pid, "source": "basic"}
   #$("#"+pid+"-hint").toggle("fast")


extension

extension

--- app/assets/javascripts/assetable/assetable_uploader.js.coffee
+++ app/assets/javascripts/assetable/assetable_uploader.js.coffee
@@ -83,7 +83,7 @@
             extensions: "zip"
           ,
             title: "Documents"
-            extensions: "pdf, doc, docx, xls, xlsx"
+            extensions: "pdf,doc,docx,xls,xlsx"
           ]
           max_file_size: assetable_uploader.options.max_file_size
           prevent_duplicates: true


groups: enter link now has slug as target

--- client/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
+++ client/app/Applications/Groups.kdapplication/Views/GroupsListItemView.coffee
@@ -104,7 +104,7 @@
       tagName : 'a'
       attributes :
         href : '/'+@getData().slug+"/Activity"
-        target : '_blank'
+        target : @getData().slug
       partial : 'Open'


Corrected porting over of sky test command to modern JS.


Loading of the rounds and selecting the correct one

--- client.coffee
+++ client.coffee
@@ -11,19 +11,21 @@
 Util = require 'util'

 exports.render = ->
-	rounds = Db.shared.ref 'rounds'
+	rounds = Db.shared.get 'rounds'

 	Dom.h2 Page.state.get(0) + ': ' + Page.state.get(1)
 	if Page.state.get(0) is 'advanced'
 		renderAdvanded()
 	else if Page.state.get(0) is 'round'
-		renderRound()
+		# renderRound(rounds[Page.state.get(1)])
+		renderRound(0)
 	else
 		renderRoundList()

-renderRound = ->
-	Dom.div !->
+renderRound = (round) ->
+	Dom.div ->
 		Dom.style fontSize: '150%', fontWeight: 'bold', textShadow: '0 1px 0 #fff', textAlign: 'center', padding: '4px 10px 10px 10px'
+		# Dom.text round
 		Dom.text Util.indexToQuestion(0)

 	Ui.bigButton 'I have!', ->
@@ -38,7 +40,7 @@
 	Ui.list ->
 		Ui.item ->
 			Dom.h2 "This is a list entry!"
-			Dom.onTap -> Page.nav ['round', 5]
+			Dom.onTap -> Page.nav ['round', 1]
 		Ui.item ->
 			Dom.h2 "This is also a list entry!"
 			Dom.onTap -> Page.nav ['round', 0]


:new: Allow string private keys

:new: Allow string private keys

--- Source/SSH.coffee
+++ Source/SSH.coffee
@@ -2,17 +2,20 @@

 FS = require 'fs'
 Driver = require 'ssh2'
+Path = require 'path'
 Promise = require 'a-promise'
+
 class SSH
   constructor:(Config)->
     @Config = Config
     if typeof Config.username is 'undefined' then throw new Error "Please specify a valid Username"
     if typeof Config.host is 'undefined' then throw new Error "Please specify a valid Host"
     if Config.privateKey
-      try
-        Config.privateKey = FS.readFileSync Config.privateKey
-      catch
-        throw new Error "Unable to read the Private Key File"
+      if Path.isAbsolute(Config.privateKey)
+        try
+          Config.privateKey = FS.readFileSync Config.privateKey
+        catch
+          throw new Error "Unable to read the Private Key File"
     else if typeof Config.password is 'undefined'
       throw new Error "Please specify a valid password or Private Key"
     Config.port = Config.port || 22


ADD: iframe data

ADD: iframe data

--- app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
+++ app/assets/javascripts/media_lib/jquery.nmdUploader.js.coffee
@@ -60,14 +60,23 @@

         openUploader() if settings.open

+        # Вешаем открытие окна с загрузчиком на кнопку с классом js-uploader-open
         $uploader.find('.js-uploader-open').on 'click', (e) ->
           e.preventDefault()
           e.stopPropagation()
           openUploader()

-        $uploader.find('.js-uploader-iframe').html "
-
-        "
+        # Инициализируем iframe с загрузчиком
+        iframeTemplate = _.template '''
+          <iframe width='<%= width %>' height='<%= height %>' src='<%= src %>' frameborder='0'>
+        '''
+        $uploader.find('.js-uploader-iframe').each ->
+          $iframe = $ @
+          iframeData = $iframe.data()
+          $iframe.html iframeTemplate
+            src: getUrl()
+            width: iframeData.width or '100%'
+            height: iframeData.height or 500

     select: (options) ->
       settings = _.extend {}, options


more permissions logging

more permissions logging

--- noddy/service/v2/permissions.coffee
+++ noddy/service/v2/permissions.coffee
@@ -79,6 +79,7 @@
         ru = 'https://api.greenoait.org/permissions/doi/' + meta.doi
         if uid and uc = API.service.deposit.config(uid)
           ru += '?affiliation=' + uc.ROR_ID if uc.ROR_ID
+        API.log 'Permissions check connecting to Ricks for ' + ru
         try
           perms.ricks = HTTP.call('GET',ru).data.authoritative_permission
         catch


Remove logging...

Remove logging...

--- modules/titlebar/main/index.coffee
+++ modules/titlebar/main/index.coffee
@@ -68,7 +68,6 @@
     if arguments.length > 0
       selection = hx.selectAll('.hx-titlebar-link').classed('hx-selected', false)
       if id?
-        console.log('id', id, hx.select(id))
         @_.active = if hx.isString(id) or isElement(id) or isSelection(id)
           hx.select(id).classed('hx-selected', true)
         else


Convert model in templatehelper to json earlier for better performance

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -8,9 +8,9 @@
   return class MediaHeaderView extends BaseView
     template: template
     templateHelpers: () ->
-      currentPage = @model.get('currentPage')
-      currentPage.encodedTitle = encodeURI(currentPage.get('title'))
-      return {currentPage: currentPage.toJSON()}
+      currentPage = @model.get('currentPage').toJSON()
+      currentPage.encodedTitle = encodeURI(currentPage.title)
+      return {currentPage: currentPage}

     initialize: () ->
       super()


revised implementation of ? and added more tests

--- src/vocabulary/question.coffee
+++ src/vocabulary/question.coffee
@@ -6,23 +6,32 @@
 # Roll (`?`)
 #
 # n←6 ⋄ r←?n ⋄ (0≤r)∧(r<n) <=> 1
-# ?0                       <=> 0
-# ?1                       <=> 0
-roll = pervasive monad: real (x) -> Math.floor Math.random() * x
+# ?0   !!! DOMAIN ERROR
+# ?1   <=> 0
+# ?1.5 !!! DOMAIN ERROR
+# ?'a' !!! DOMAIN ERROR
+# ?1j2 !!! DOMAIN ERROR
+# ?∞   !!! DOMAIN ERROR
+roll = pervasive monad: (⍵) ->
+  if not isInt ⍵, 1 then domainError()
+  Math.floor Math.random() * ⍵

 # Deal (`?`)
 #
-# n←100 ⋄ (+/n?n)=(+/⍳n)
-# ... <=> 1 # a permutation (an "n?n" dealing) contains all 0...n
-# n←100 ⋄ A←(n÷2)?n ⋄ ∧/(0≤A),A<n
-# ... <=> 1 # any number x in a dealing is 0 <= x < n
-# 0 ? 100 <=> ⍬
-# 0 ? 0   <=> ⍬
-# 1 ? 1   <=> ,0
-# 5 ? 3   !!! DOMAIN ERROR
+# n←100 ⋄ (+/n?n)=(+/⍳n) <=> 1 # a permutation (an "n?n" dealing) contains all 0...n
+# n←100 ⋄ A←(n÷2)?n ⋄ ∧/(0≤A),A<n <=> 1 # any number x in a dealing is 0 <= x < n
+# 0?100 <=> ⍬
+# 0?0   <=> ⍬
+# 1?1   <=> ,0
+# 1?1 1 !!! LENGTH ERROR
+# 5?3   !!! DOMAIN ERROR
+# ¯1?3  !!! DOMAIN ERROR
 deal = (⍵, ⍺) ->
-  y = ⍵.unwrap()
-  x = ⍺.unwrap()
-  if x > y then domainError()
-  available = [0...y]
-  new APLArray(for [0...x] then available.splice(Math.floor(available.length * Math.random()), 1)[0])
+  ⍺ = ⍺.unwrap()
+  ⍵ = ⍵.unwrap()
+  if not (isInt(⍵, 0) and isInt ⍺, 0, ⍵ + 1) then domainError()
+  r = [0...⍵]
+  for i in [0...⍺] by 1
+    j = i + Math.floor Math.random() * (⍵ - i)
+    h = r[i]; r[i] = r[j]; r[j] = h
+  new APLArray r[...⍺]


Removed superfluous Socket.packet

Removed superfluous Socket.packet

--- src/wrathnet/net/socket.coffee
+++ src/wrathnet/net/socket.coffee
@@ -26,9 +26,8 @@
     # Holds buffered data
     @buffer = new ByteBuffer(0, ByteBuffer.LITTLE_ENDIAN)

-    # Holds (partial) packet (if any) and its remaining size in bytes
-    @packet = null
-    @remaining = 0
+    # Holds incoming packet's remaining size in bytes (false if no packet is being handled)
+    @remaining = false

     # Holds signals this socket dispatches
     @on = {


Avoid nulls

Avoid nulls

They cause crashes in PushInbox.

--- src/xmpp/backend_pubsub.coffee
+++ src/xmpp/backend_pubsub.coffee
@@ -201,12 +201,10 @@
             async.map updates, (update, cb2) =>
                 if update.type is 'subscription'
                     @disco.findService update.user, (err, service) =>
-                        if err
-                            cb2 err, null
-                        else
+                        if service?
                             # If remote user, listener = sender. If local user, listener = user.
                             update.listener = if @getMyJids().indexOf(service) >= 0 then update.user else sender
-                            cb2 null, update
+                        cb2 err, update
                 else
                     cb2 null, update
             , (err, updates) =>


add logging in db.coffee

add logging in db.coffee

--- app/coffee/db.coffee
+++ app/coffee/db.coffee
@@ -1,6 +1,7 @@
 Sequelize = require("sequelize")
 Settings = require("settings-sharelatex")
 _ = require("underscore")
+logger = require "logger-sharelatex"

 options = _.extend {logging:false}, Settings.mysql.clsi

@@ -32,5 +33,7 @@
 		]
 	})

-	sync: () -> sequelize.sync()
+	sync: () ->
+		logger.log dbPath:Settings.mysql.clsi.storage, "syncing db schema"
+		sequelize.sync()


change ill demo url

change ill demo url

--- noddy/service/oabutton/ill.coffee
+++ noddy/service/oabutton/ill.coffee
@@ -54,7 +54,7 @@

 API.service.oab.ill.subscription = (uid, meta={}, all=false, refresh=false) ->
   if meta.doi is '10.1234/567890' and (uid is 'qZooaHWRz9NLFNcgR' or uid is 'eZwJ83xp3oZDaec86') # dev and live demo accounts that always return a fixed answer
-    return {findings:{}, uid: uid, lookups:[], error:[], url: 'https://instantill.org', demo: true}
+    return {findings:{}, uid: uid, lookups:[], error:[], url: 'https://scholarworks.iupui.edu/bitstream/handle/1805/20422/07-PAXTON.pdf?sequence=1&isAllowed=y', demo: true}

   do_serialssolutions_xml = true
   do_sfx_xml = true


Perform entry open/close animations with velocity.js, intead of jquery.animate

--- app/assets/javascripts/ng-services/ng-AnimationsSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-AnimationsSvc.js.coffee
@@ -36,14 +36,14 @@
       # Set height back to 0px and animate the transition to its final height
       $("#entry-#{entry.id}-summary")
         .css('height', '0')
-        .animate {height: height_auto, 'padding-top': 15, 'padding-bottom': 15}, 300, 'swing', add_open_class
+        .velocity {height: height_auto, 'padding-top': 15, 'padding-bottom': 15}, 300, 'swing', add_open_class

     #---------------------------------------------
     # Animate closing an entry, by transitioning its height from its current value to zero
     #---------------------------------------------
     close_entry: (entry)->
       $("#entry-#{entry.id}-summary")
-        .animate {height: 0, 'padding-top': 0, 'padding-bottom': 0}, 300, 'swing', remove_open_class
+        .velocity {height: 0, 'padding-top': 0, 'padding-bottom': 0}, 300, 'swing', remove_open_class

   return service
 ]


fixed modal title dissappearing

fixed modal title dissappearing

--- app/assets/javascripts/tenon/features/modal_windows.js.coffee
+++ app/assets/javascripts/tenon/features/modal_windows.js.coffee
@@ -52,7 +52,7 @@

   _appendContent: =>
     target = if @_bodyProvided() then '.modal-content' else '.modal-body'
-    @$template.find(target).html(@$el)
+    @$template.find(target).append(@$el)

   _runShownHandler: =>


add resolves to email-settings-page

add resolves to email-settings-page

--- app/components/email-settings-page/email-settings-page.coffee
+++ app/components/email-settings-page/email-settings-page.coffee
@@ -1,5 +1,10 @@
 module.exports =
   url: '/email_settings'
+  resolve:
+    userValidated: ($auth) ->
+      $auth.validateUser()
+    membershipsLoaded: ->
+      global.cobudgetApp.membershipsLoaded
   template: require('./email-settings-page.html')
   controller: ($scope, CurrentUser) ->


#545 Fix individual layer colors

#545 Fix individual layer colors

--- app/assets/javascripts/oxalis/view/settings/category_views/colors_category_view.coffee
+++ app/assets/javascripts/oxalis/view/settings/category_views/colors_category_view.coffee
@@ -38,13 +38,14 @@

   initialize : ->

-    for key, color of @model.get("layerColors")
+    for key of @model.get("layerColors")

-      @subviewCreators[key] = -> new ColorSettingView(
-        model : @model
-        options :
-          name : "layerColors.#{key}"
-          displayName : "Layer: #{key}"
-      )
+      do (key) =>
+        @subviewCreators[key] = -> new ColorSettingView(
+          model : @model
+          options :
+            name : "layerColors.#{key}"
+            displayName : "Layer: #{key}"
+        )

     super()


Deleted an unused file

Deleted an unused file

--- app/assets/javascripts/facebook.coffee
+++ app/assets/javascripts/facebook.coffee
@@ -1,36 +0,0 @@
-
-$ ->
-  loadFacebookSDK()
-  bindFacebookEvents() unless window.fbEventsBound
-
-bindFacebookEvents = ->
-  $(document)
-    .on('page:fetch', saveFacebookRoot)
-    .on('page:change', restoreFacebookRoot)
-    .on('page:load', ->
-      FB?.XFBML.parse()
-    )
-  @fbEventsBound = true
-
-saveFacebookRoot = ->
-  if $('#fb-root').length
-    @fbRoot = $('#fb-root').detach()
-
-restoreFacebookRoot = ->
-  if @fbRoot?
-    if $('#fb-root').length
-      $('#fb-root').replaceWith @fbRoot
-    else
-      $('body').append @fbRoot
-
-loadFacebookSDK = ->
-  window.fbAsyncInit = initializeFacebookSDK
-  $.getScript("//connect.facebook.net/en_US/sdk.js")
-
-initializeFacebookSDK = ->
-  FB.init
-    appId  : '1777475805837381'
-    status : true
-    cookie : true
-    xfbml  : true
-    version: 'v2.7'


Correcly updates the status of the comment buttons on reload

Closes #5534

--- app/assets/javascripts/notes.js.coffee
+++ app/assets/javascripts/notes.js.coffee
@@ -17,10 +17,10 @@
     @noteable_url = document.URL
     @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")

+    @cleanBinding()
+    @addBinding()
     @initRefresh()
     @setupMainTargetNoteForm()
-    @cleanBinding()
-    @addBinding()
     @initTaskList()

   addBinding: ->
@@ -37,7 +37,7 @@

     # Reopen and close actions for Issue/MR combined with note form submit
     $(document).on "click", ".js-comment-button", @updateCloseButton
-    $(document).on "keyup", ".js-note-text", @updateTargetButtons
+    $(document).on "keyup input", ".js-note-text", @updateTargetButtons

     # remove a note (in general)
     $(document).on "click", ".js-note-delete", @removeNote


Learnsets: Gliscor cannot learn Defog + Poison Heal.

--- test/learnsets_spec.coffee
+++ test/learnsets_spec.coffee
@@ -214,6 +214,12 @@
       moveset = [ "Softboiled" ]
       @checkMoveset(6, moveset).should.be.true

+  testLearnset "Gliscor", ->
+    it "cannot learn Defog if it has Poison Heal", ->
+      @pokemon.ability = "Poison Heal"
+      moveset = [ "Defog" ]
+      @checkMoveset(6, moveset).should.be.false
+
   describe "learnableMoves", ->
     it "returns all possible learnable moves for a pokemon", ->
       learnableMoves = learnsets.learnableMoves.bind(learnsets, GenerationJSON)


geert cred

geert cred


Update wording of authors page

Update wording of authors page

--- app/lib/en_us.coffee
+++ app/lib/en_us.coffee
@@ -401,8 +401,9 @@

Authors

The science of Planet Four is made possible by the work of our
-    volunteers; those who gave us permission to publish their names are
-    included below:

+    volunteers. These are all the Planet Four volunteers who contributed to the
+    project's first paper and
+    gave us permission to publish their names:

A Dunnachie, A. Cramer, A. Sky Heller, A.Alber, A.Caffrey, A.Gilstrap,
     A.K., A.Messenger, A.N, A._Zubik, A0074098, A0087779, A0097993A, A0102796,


remove deprecated shim test file

remove deprecated shim test file


moved invitationMessage data field to membership policy

--- client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationview.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupsinvitationview.coffee
@@ -103,7 +103,8 @@
           @modalCallback @inviteByEmail, noop, err
           if saveMessage
             @getData().saveInvitationMessage message
-            @getData().invitationMessage = message
+            @policy.communications ?= {}
+            @policy.communications.invitationMessage = message
       fields             :
         emails           :
           label          : 'Emails'
@@ -119,7 +120,7 @@
           label          : 'Message'
           type           : 'textarea'
           cssClass       : 'message-input'
-          defaultValue   : @getData().invitationMessage or defaultMessage
+          defaultValue   : @policy.communications?.invitationMessage or defaultMessage
           validate       :
             rules        :
               required   : yes


use separate loop for pendingUpdates metric

--- app/coffee/RealTimeRedisManager.coffee
+++ app/coffee/RealTimeRedisManager.coffee
@@ -21,6 +21,9 @@
 		multi.exec (error, replys) ->
 			return callback(error) if error?
 			jsonUpdates = replys[0]
+			for jsonUpdate in jsonUpdates
+				# record metric for each update removed from queue
+				metrics.summary "redis.pendingUpdates", jsonUpdate.length, {status: "pop"}
 			updates = []
 			for jsonUpdate in jsonUpdates
 				try
@@ -28,8 +31,6 @@
 				catch e
 					return callback e
 				updates.push update
-				# record metric for updates removed from queue
-				metrics.summary "redis.pendingUpdates", jsonUpdate.length, {status: "pop"}
 			callback error, updates

 	getUpdatesLength: (doc_id, callback)->


fixed clear other selected when selecting signal

--- app/coffeescripts/MapSignalView.coffee
+++ app/coffeescripts/MapSignalView.coffee
@@ -56,6 +56,7 @@
   # the nodes. The inputs and output can be used in the future but test
   # data was not configured correctly
   selectSelfandMyNodes: () ->
+    @_triggerClearSelectEvents()
     @makeSelected()
     $a.broker.trigger("map:select_item:#{@model.get('node').cid}")
     $a.broker.trigger("app:tree_highlight:#{@model.get('node').cid}")


Update fr.coffee

Update fr.coffee

--- app/locale/fr.coffee
+++ app/locale/fr.coffee
@@ -451,8 +451,8 @@
     replay_level: "Rejouer le Niveau"
     combo_concepts_used: "__complete__/__total__ Concepts Utilisés"
     combo_all_concepts_used: "Tu as utilisé tous les concepts possibles pour résoudre le défi. Beau travail!"
-#    combo_not_all_concepts_used: "You used __complete__ out of the __total__ concepts possible to solve the challenge. Try to get all __total__ concepts next time!"
-#    start_challenge: "Start Challenge"
+    combo_not_all_concepts_used: "Tu as utilisé __complete__ des __total__ concepts possible pour résoudre le défi. Essaie d'utiliser __total__ concepts la prochaine fois!"
+    start_challenge: "Démarrer le Défi"
     next_game: "Prochaine jeu"
     languages: "Langues"
     programming_language: "Langage de programmation"


Client: Activity: Test: Deleted unnecessary classname

--- client/activity/lib/components/publicfeedpane/feeditem.coffee
+++ client/activity/lib/components/publicfeedpane/feeditem.coffee
@@ -93,7 +93,7 @@

             <ProfileLinkContainer origin={message.get('account').toJS()}>
-              <Avatar className="FeedItem-Avatar" width={35} height={35} />
+              <Avatar width={35} height={35} />
             </ProfileLinkContainer>


Invoke track initial turn on utility function

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -26,6 +26,7 @@
 showError               = require 'app/util/showError'
 applyMarkdown           = require 'app/util/applyMarkdown'
 sendDataDogEvent        = require 'app/util/sendDataDogEvent'
+trackInitialTurnOn      = require 'app/util/trackInitialTurnOn'
 environmentDataProvider = require 'app/userenvironmentdataprovider'

@@ -93,6 +94,7 @@

     @on 'MachineTurnOnStarted', (machine) ->
       sendDataDogEvent 'MachineTurnedOn', tags: {label: machine.label}
+      trackInitialTurnOn machine

   triggerEventTimer: (percentage) ->


More robust project results linking, closes #270

--- app/controllers/project-results.coffee
+++ app/controllers/project-results.coffee
@@ -27,6 +27,14 @@

   onHashChange: =>
     # yuck
+    currentHash = if location.hash[location.hash.length - 1] == '/'
+      location.hash.slice(0, location.hash.length - 1)
+    else
+      location.hash
+
+    if currentHash.indexOf(@project) == currentHash.length - @project.length
+      location.hash = currentHash + '/summary'
+
     @menuItems.removeClass('show').find("a[href='#{ location.hash }']").parent().addClass('show')

 module.exports = ProjectResults


Switched to dicrete getter and setters

Switched to dicrete getter and setters

--- app/controllers/providers/index.coffee
+++ app/controllers/providers/index.coffee
@@ -18,12 +18,11 @@
   nextOffset: ( ->
     parseInt(@get('offset')) + @get('content.meta').resultsPerPage
     ).property('offset', 'content.meta')
-  query: ( (key, newVal, oldVal)->
-    if arguments.length > 1 #setter
+  query: Ember.computed 'q',
+    get: ->
+      @get('q')
+    set: (key, newVal, oldVal)->
       newVal
-    else #getter
-      @get('q')
-    ).property('q')
   actions:
     updateQuery: (value)->
       @set('q', value)


Added check for base language

Added check for base language

--- src/code/utils/translate.coffee
+++ src/code/utils/translate.coffee
@@ -1,29 +1,38 @@
 urlParams = require './url-params'

 languageFiles = [
-  {key: 'de' ,   contents: require './lang/de'}
-  {key: 'el' ,   contents: require './lang/el'}
-  {key: 'en-US', contents: require './lang/en-US'}
-  {key: 'es' ,   contents: require './lang/es'}
-  {key: 'he' ,   contents: require './lang/he'}
-  {key: 'tr' ,   contents: require './lang/tr'}
-  {key: 'zh-TW', contents: require './lang/zh-TW'}
+  {key: 'de',    contents: require './lang/de'}      # German
+  {key: 'el',    contents: require './lang/el'}      # Greek
+  {key: 'en-US', contents: require './lang/en-US'}   # US English
+  {key: 'es',    contents: require './lang/es'}      # Spanish
+  {key: 'he',    contents: require './lang/he'}      # Hebrew
+  {key: 'tr',    contents: require './lang/tr'}      # Turkish
+  {key: 'zh-TW', contents: require './lang/zh-TW'}   # Chinese (Taiwan)
 ]
+
+# returns baseLANG from baseLANG-REGION if REGION exists
+getBaseLanguage = (langKey) ->
+  dashLoc = langKey.indexOf('-')
+  return langKey.substring(0, dashLoc) if dashLoc isnt -1
+  undefined
+
+getFirstBrowserLanguage = ->
+  nav = window.navigator
+  languages = if nav then (nav.languages or []).concat([nav.language, nav.browserLanguage, nav.systemLanguage, nav.userLanguage]) else []
+  for language in languages
+    return language if language
+  undefined

 translations =  {}
 languageFiles.forEach (lang) ->
   translations[lang.key] = lang.contents
   # accept full key with region code or just the language code
-  if (dashLoc = lang.key.indexOf('-')) > 0
-    translations[lang.key.substring(0, dashLoc)] = lang.contents
+  baseLang = getBaseLanguage(lang.key)
+  translations[baseLang] = lang.contents if baseLang

-defaultLang = null
-# default to English unless the user expresses another preference (via URL param for now)
-defaultLang = urlParams.lang if urlParams.lang and translations[urlParams.lang]
-# use language of page, which is used by CODAP, with separate build for each language
-if (not defaultLang?) and document.documentElement.lang and (document.documentElement.lang isnt "unknown")
-  defaultLang = document.documentElement.lang
-defaultLang = 'en' if not defaultLang?
+lang = urlParams.lang or getFirstBrowserLanguage()
+baseLang = getBaseLanguage(lang or '')
+defaultLang = if lang and translations[lang] then lang else if baseLang and translations[baseLang] then baseLang else "en"

 varRegExp = /%\{\s*([^}\s]*)\s*\}/g


Fix transfer node image after rebase/merge

--- src/code/views/node-view.coffee
+++ src/code/views/node-view.coffee
@@ -271,7 +271,7 @@
         })
       else
         (SquareImage {
-          image: node.image
+          image: if node.isTransfer then 'img/nodes/transfer.png' else node.image
         })

     nodeImage = getNodeImage(@props.data)


Intercept clicks on dates and stopPropogation on invalid

--- src/components/tutor-input.cjsx
+++ src/components/tutor-input.cjsx
@@ -42,10 +42,16 @@
     date:  React.PropTypes.string.isRequired
     label: React.PropTypes.string.isRequired

+  isInvalid: ->
+    moment(@props.date).startOf('day') < TimeStore.getNow()
+
+  onClick: (ev) ->
+    ev.stopPropagation() if @isInvalid()

   render: ->
-    className = 'is-invalid-date' if moment(@props.date).startOf('day') < TimeStore.getNow()
-    {@props.label}
+
+      {@props.label}
+

 TutorDateInput = React.createClass


Add themedir and requirement block snippets

--- completions.cson
+++ completions.cson
@@ -210,6 +210,26 @@
     {
       'scope': '.text.html.php'
       'body': 'Requirements::css(\'${1}\');${0}'
+    }
+  ]
+
+'Requirement block':
+  'prefix': 'requireblock'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'Requirements::block(\'${1}\');${0}'
+
+'Theme directory':
+  'prefix': 'themedir'
+  'module': 'silverstripe/framework'
+  'variations': [
+    {
+      'scope': '.text.html.ss'
+      'body': 'ThemeDir'
+    }
+    {
+      'scope': '.text.html.php'
+      'body': '$this->ThemeDir()'
     }
   ]


Fix typo

Fix typo

--- src/browser/Application.coffee
+++ src/browser/Application.coffee
@@ -71,11 +71,11 @@
         super
         new Disposable => @off event, listener

-    onDidWindowAdded    : (fn) ->
+    onDidWindowAdd      : (fn) ->
         @on "did-window-added", fn

     onDidWindowRemove   : (fn) ->
         @on "did-window-remove", fn

-    onDidFocusedWindowChanged   : (fn) ->
+    onDidFocusedWindowChange    : (fn) ->
         @on "did-focused-window-changed", fn


Added automatic update to sub comments count

--- core/app/backbone/views/sub_comments/sub_comments_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_view.coffee
@@ -105,6 +105,9 @@
   events:
     'click .js-sub-comments-link': 'toggleList'

+  initialize: ->
+    @count = @model.get('sub_comments_count')
+
   onRender: ->
     @bindTo @model, 'change:sub_comments_count', @updateLink, @
     @updateLink()
@@ -132,10 +135,9 @@
     @subCommentsListRegion.close()

   updateLink: ->
-    count = @model.get('sub_comments_count')
     count_str = ""

-    if count
-      count_str = " (#{count})"
+    if @count
+      count_str = " (#{@count})"

     @$(".js-sub-comments-link").text "Comments#{count_str}"


Removes route-recognizer and replaces the logic

--- src/remote/services.coffee
+++ src/remote/services.coffee
@@ -1,4 +1,4 @@
-define ->
+define ['underscore'], (_)->
   rpc = null

   catchAll = (res)->
@@ -8,24 +8,21 @@
   (app)->
     require:
       paths:
-        'easyXDM':            'components/easyXDM/easyXDM'
-        'route-recognizer' :  'components/route-recognizer/dist/route-recognizer.amd'
+        'easyXDM': 'components/easyXDM/easyXDM'
       shim:
         easyXDM: { exports: 'easyXDM' }

     initialize: (app)->
       core = app.core
-      Router = require('route-recognizer')
-      core.services = new Router
+      core.routeHandlers = {}

       onRemoteMessage = (req, callback, errback)->
         throw new Error("Path not recognized #{JSON.stringify(req)}") unless req.path

-        routes  = core.services.recognize(req.path)
-
-        if routes && route = routes[0]
-          route.handler(req, route, callback, errback)
+        handler = core.routeHandlers[req.provider]
+        if _.isFunction handler
+          handler(req, callback, errback)
         else
           errback(catchAll(req))


JStackTemplate: clone: compare with ::equals

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -730,8 +730,7 @@
       hasAccess = yes

       if @getAt('accessLevel') is 'private'
-        if @getAt('originId') isnt delegate.getId()
-          hasAccess = no
+        hasAccess = delegate.getId().equals @getAt('originId')

       else if @getAt('accessLevel') is 'group'
         if @getAt('group') isnt group


Only add sub comment when text is entered

--- core/app/backbone/views/sub_comments/sub_comments_view.coffee
+++ core/app/backbone/views/sub_comments/sub_comments_view.coffee
@@ -60,9 +60,10 @@
     @$el.toggleClass 'evidence-sub-comments-form-active', active

   submit: ->
-    @addModel new SubComment
-      content: @text()
-      created_by: currentUser
+    if @text().length > 0
+      @addModel new SubComment
+        content: @text()
+        created_by: currentUser

   addModelError: -> @alertError()
   addModelSuccess: (model) ->


skip ops marked as broken in database

skip ops marked as broken in database

--- app/coffee/DiffGenerator.coffee
+++ app/coffee/DiffGenerator.coffee
@@ -11,7 +11,7 @@
 	ConsistencyError: ConsistencyError

 	rewindUpdate: (content, update) ->
-		for op, i in update.op by -1
+		for op, i in update.op by -1 when op.broken isnt true
 			try
 				content = DiffGenerator.rewindOp content, op
 			catch e


add express.js logging

add express.js logging

--- code/serv.coffee
+++ code/serv.coffee
@@ -18,6 +18,7 @@
 app = express.createServer()

 app.use express.bodyParser()
+app.use express.logger()

 mongoose.connect process.env['COBALT_DB']


Add the ribbon to the settings page

Add the ribbon to the settings page

--- app/pages/settings/profile.cjsx
+++ app/pages/settings/profile.cjsx
@@ -3,6 +3,7 @@
 ImageSelector = require '../../components/image-selector'
 apiClient = require '../../api/client'
 putFile = require '../../lib/put-file'
+ClassificationsRibbon = require '../../components/classifications-ribbon'

 MAX_AVATAR_SIZE = 65536
 MAX_HEADER_SIZE = 256000
@@ -31,6 +32,18 @@
         null

+

+
Classifications ribbon

+
<ClassificationsRibbon user={@props.user} />

+

+
+            <input type="checkbox" disabled />{' '}
+            Display my ribbon publicly (coming soon!)
+
+

+
Your ribbon shows your classifications per project.

+

+

Change avatar

         <PromiseRenderer promise={@avatarGet}>{(avatar) =>


Corrige erro de duplo modal do Bottstrap

--- src/coffee/vtex-message.coffee
+++ src/coffee/vtex-message.coffee
@@ -121,7 +121,7 @@
 						@visible = true

 			if not flagVisibleSet
-				$(@domElement).on 'shown', => @visible = true
+				$(@domElement).on 'show', => @visible = true
 				$(@domElement).modal('show')

 			return


minor bugfix in submit code

minor bugfix in submit code

--- example_project/segmentation/static/js/mturk/mt_submit.coffee
+++ example_project/segmentation/static/js/mturk/mt_submit.coffee
@@ -72,7 +72,6 @@

   mt_submit_impl = (data_callback) -> ->
     if not mt_submit_ready then return
-    window.show_modal_loading("Submitting...", 0)

     data = data_callback()
     feedback = window.get_modal_feedback?() if window.ask_for_feedback
@@ -82,6 +81,7 @@
     console.log "submit data:"
     console.log data

+    window.show_modal_loading("Submitting...", 0)
     $.ajax(
       type: 'POST'
       url: window.location.href


[Ban User] changed function signature to send account id

--- client/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
+++ client/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
@@ -145,7 +145,7 @@

       menu['Block User'] =
         callback : ->
-          activityController.emit "ActivityItemBlockUserClicked", data
+          activityController.emit "ActivityItemBlockUserClicked", data.originId

       return menu


Add examples for dummy project

Add examples for dummy project

--- test/dummy-project/gulp.coffee
+++ test/dummy-project/gulp.coffee
@@ -20,6 +20,15 @@
         ]
       }
     })
+
+  testNode: () ->
+    all.task.test.node()
+
+  testClient: () ->
+    all.task.test.client()
+
+  testExamples: () ->
+    all.task.test.examples()
 }

 all.task.exportTasks(tasks)


Minor edit to grade calculation function.

--- app/javascripts/services.coffee
+++ app/javascripts/services.coffee
@@ -580,17 +580,14 @@
     getOverallTumorGrade = (grade) ->
       # Calculate the overall Nottingham grade.
       # Values for all three grade components are necessary,
-      # so if any are missing then return as 'undetermined'.
-      if Object.keys(grade).length < 3
-        overall_grade = 'undetermined'
-      else
-        overall_grade = 0
-        for key of grade
-          if grade[key]
-            overall_grade += grade[key]
-          else
-            overall_grade = 'undetermined'
-            break
+      # so if any are null then return as 'undetermined'.
+      overall_grade = 0
+      for key of grade
+        if grade[key]
+          overall_grade += grade[key]
+        else
+          overall_grade = 'undetermined'
+          break
       overall_grade
     getTumorStaging = (tnm) ->
       # Add the T value without any prefixes (i.e. c or p) to the outcome.


Remove rogue debuggers

Remove rogue debuggers

--- app/utils/connector-mini.coffee
+++ app/utils/connector-mini.coffee
@@ -58,9 +58,7 @@
       u = URL.createObjectURL blob
       img.src = u
     token =  @deviceToken
-    debugger
     @ws.onopen = ->
-      debugger
       console.log 'onopen', arguments
       that.ws.send JSON.stringify type: "subscribe", token: token


Removing groups iteration. Planning getting subject count from Project.subject_count

--- app/models/project_stats.coffee
+++ app/models/project_stats.coffee
@@ -15,11 +15,7 @@
       # following field may not be available
       if project.subject_count?
         @totalSubjectCount = project.subject_count
-      else
-        # This method of obtaining the total subject count depends on Project.extended being set
-        # see zoonivers/models/Project.onFetch()
-        for group in project.groups? when project.groups.stats?
-          @totalSubjectCount += group.stats["total"]
+

    percentComplete: ->
      unless @totalSubjectCount > 0 then return 0


Add test for negative numbers

Add test for negative numbers

--- test/unit/specs/Runtime-functional-spec.coffee
+++ test/unit/specs/Runtime-functional-spec.coffee
@@ -147,6 +147,10 @@
 	text: 'does not crash when trying to write outside of the playfield'
 	code: '999p@'
 	outRecord: []
+}, {
+	text: 'can store a negative number on the playfield'
+	code: '05-00p00g.@'
+	outRecord: [-5]
 }]


Removed some old commented out coffeescript from the browser tests code which was no longer needed.

--- app/coffee/tests/browser.coffee
+++ app/coffee/tests/browser.coffee
@@ -187,23 +187,6 @@
       'value': value
       'path': path

-# @fill "form[action='/search']", q: "casperjs", true
-
-# casper.then ->
-  # aggregate results for the 'casperjs' search
-#   links = @evaluate getLinks
-  # search for 'phantomjs' from google form
-#   @fill "form[action='/search']", q: "phantomjs", true
-
-# casper.then ->
-  # concat results for the 'phantomjs' search
-#   links = links.concat @evaluate(getLinks)
-
-# casper.run ->
-  # display results
-#  @echo links.length + " links found:"
-#  @echo(" - " + links.join("\n - ")).exit()
-
 casper.run ->
   casper.log "shutting down..."
   casper.open 'http://localhost:5000/shutdown',


Move copyContent from controllers to services.

--- app/javascripts/services.coffee
+++ app/javascripts/services.coffee
@@ -1,6 +1,6 @@
 svcs = angular.module 'qiprofile.services', ['ngResource']

-svcs.factory 'Subject', ['$resource', ($resource) ->
+svcs.factory 'Subject', ['$q', '$resource', ($q, $resource) ->
   $resource '/api/subjects/:number/', null,
     detail:
       method: 'GET'
@@ -8,6 +8,12 @@
 ]

+# Since the REST Session objects are embedded in SubjectDetail,
+# this Session factory only operates on the session-detail
+# REST objects. The preferred access method is detail, which
+# is a SessionDetail get. The query and delete methods are not
+# meaningful. Session delete is accomplished by the parent
+# Subject delete, which cascades to its embedded sessions.
 svcs.factory 'Session', ['$resource', ($resource) ->
   $resource '/api/session-detail/:id/', null,
     detail:
@@ -17,6 +23,16 @@

 svcs.factory 'Helpers', ->
+  # Copies the given source object properties into the
+  # destination object.
+  copyContent: (source, dest) ->
+    # Copy the detail properties into the parent object.
+    srcProps = Object.getOwnPropertyNames(source)
+    destProps = Object.getOwnPropertyNames(dest)
+    fields = _.difference(srcProps, destProps)
+    for field in fields
+      dest[field] = source[field]
+
   # If the given attribute value is a string, then this function
   # resets it to the parsed date.
   fixDate: (obj, attr) ->
@@ -282,8 +298,7 @@
   # @returns the nvd3 chart format
   configureChart: (sessions, href) ->
     chartCfg = Helpers.configureChart(sessions, config)
-    chartCfg.xValues =
-      config.x.accessor(sess) for sess in sessions
+    chartCfg.xValues = (config.x.accessor(sess) for sess in sessions)
     chartCfg.xFormat = Helpers.dateFormat
     chartCfg
 ]


ensure that the reset happens before the rest of the code executes

--- src/deploy/gulp-espruino.coffee
+++ src/deploy/gulp-espruino.coffee
@@ -2,6 +2,14 @@
 fs = require 'fs'
 through = require 'through2'
 extend = require 'extend'
+Q = require 'q'
+
+output = (->
+  consumed = ''
+
+  append: (content) -> consumed += content
+  all: -> consumed
+)()

 module.exports =
   deploy: (port, overrides = {}) ->
@@ -29,22 +37,34 @@
         published: -> published
       )()

+      executor = (->
+        deferred = Q()
+
+        execute: (command) ->
+          deferred = Q.defer()
+          serialPort.write command, (error) -> publish.error(error) if error
+          deferred.promise
+
+        finished: -> deferred.resolve()
+      )()
+
       consume = (->
-        consumed = ''
         timeout = null

         (content) ->
-          consumed += content
+          output.append(content)
           clearTimeout(timeout)
-          timeout = setTimeout((-> publish.content(consumed) unless publish.published()), options.idleReadTimeBeforeClose)
+          timeout = setTimeout((-> executor.finished()), options.idleReadTimeBeforeClose)
       )()

       serialPort.on 'data', (data) -> consume(data.toString())
       serialPort.on 'error', (error) -> publish.error(null, error)

       serialPort.open ->
-        serialPort.write "reset();\n { #{chunk.contents.toString()} }\n save();\n", (error) ->
-          publish.error(error) if error
+        executor.execute("reset();\n")
+          .then(-> executor.execute("{ #{chunk.contents.toString()} }\n"))
+          .then(-> executor.execute("save();\n"))
+          .then(-> publish.content(output.all() unless publish.published()))

     onFinish = (callback) ->
       self = this


#1114 Highlight preferred mode in task list

--- app/assets/javascripts/admin/views/tasktype/task_type_item_view.coffee
+++ app/assets/javascripts/admin/views/tasktype/task_type_item_view.coffee
@@ -20,7 +20,10 @@
       <%= formattedShortText %>

         <% _.each(settings.allowedModes, function (mode) { %>
-          <%= mode[0].toUpperCase() + mode.slice(1) %>
+          <% var cssClass = mode == settings.preferredMode ? "label-primary" : "label-default"; %>
+          <span class="label <%= cssClass %>">
+            <%= mode[0].toUpperCase() + mode.slice(1) %>
+
         <% }) %>


[skip] Cut down on log output (1m)

[skip] Cut down on log output (1m)

--- app/assets/javascripts/houston-scheduler/views/show_sprint_view.coffee
+++ app/assets/javascripts/houston-scheduler/views/show_sprint_view.coffee
@@ -37,7 +37,6 @@
         +s
       type: 'numeric'
     $('.table-sortable').tablesorter
-      debug: true
       headers:
         0:
           sorter: 'integerBlankLast'


Add hard coded music selection for module 2 (#297)

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -153,12 +153,16 @@
         '/music/OzariaCarnivalMusicLoop3',
         '/music/OzariaCityMusicLoop1',
         '/music/OzariaCityMusicLoop2',
+        '/music/OzariaCityMusicLoop3',
         '/music/OzariaLoopMusic1',
         '/music/OzariaLoopMusic2',
         '/music/OzariaLoopMusic3',
         '/music/OzariaLoopMusic5',
         '/music/OzariaMusicLoop4',
         '/music/SpiritLandsAmbient01',
+        '/music/OzariaSpiritLands1',
+        '/music/OzariaSpiritLands2',
+        '/music/OzariaSpiritLands3',
         '/music/music_level_1',
         '/music/music_level_2',
         '/music/music_level_3',


Allow extra SpriteCommand say moods that match Little Sophia

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -86,7 +86,7 @@
   select: {title: 'Select', description: 'Select or deselect this Thang.', type: 'boolean'}
   say: c.object {title: 'Say', description: 'Make this Thang say a message.', required: ['text'], default: { mood: 'explain' }},
     blurb: c.shortString(title: 'Blurb', description: 'A very short message to display above this Thang\'s head. Plain text.', maxLength: 50)
-    mood: c.shortString(title: 'Mood', description: 'The mood with which the Thang speaks.', 'enum': ['explain', 'debrief', 'congrats', 'attack', 'joke', 'tip', 'alarm'])
+    mood: c.shortString(title: 'Mood', description: 'The mood with which the Thang speaks.', 'enum': ['explain', 'confused', 'curious', 'dance-happy', 'dance', 'idle', 'neutral', 'sad', 'debrief', 'congrats', 'attack', 'joke', 'tip', 'alarm'])
     text: {title: 'Text', description: 'A short message to display in the dialogue area. Markdown okay.', type: 'string', maxLength: 400}
     sound: c.object {title: 'Sound', description: 'A dialogue sound file to accompany the message.', required: ['mp3', 'ogg'] },
       mp3: c.shortString(title: 'MP3', format: 'sound-file')


fixes missing semi-colon in generated classes

--- app/scripts/services/sml.coffee
+++ app/scripts/services/sml.coffee
@@ -23,7 +23,7 @@
       if _.isEmpty classes
         return '\n'
       else
-        return _.foldl classes, ((x, y) -> (x + ";\n").concat(y))
+        return _.foldl classes, ((x, y) -> x.concat(y + ";\n"))

     toProperties = (mapping, table, lookup) ->
       columns = _.keys mapping.properties[table]