Major overhaul of the expression manipulation library

--- math/expression_manipulation_spec.coffee
+++ math/expression_manipulation_spec.coffee
@@ -1,208 +1,267 @@
 #= require lib/math/expression_manipulation
-
-it_plays_manipulation_role = (subject, math)->
+it_plays_manipulation_role = (options)->
   describe "acting as an expression manipulation", ->
     beforeEach ->
-      @subject = subject.call(@)
-
-    it "has an invoke method", ->
-      expect(typeof @subject.invoke).toEqual "function"
-
-    it "returns an expression", ->
-      ret = @subject.invoke(@components.build_expression())
-      unless ret instanceof @components.classes.expression
-        throw "The return value was not an instance of expression"
+      @subject = options.subject.call(@)
+
+    it "has a perform method", ->
+      expect(typeof @subject.perform).toEqual "function"
+
+    it "returns an ExpressionPosition object", ->
+      orig = options.expression_for_performance.call(@)
+      results = @subject.perform(orig)
+      unless results instanceof ttm.lib.math.ExpressionPosition
+        throw "The return value was not an instance of expression position"

 it_inserts_component_into_the_last_nested_open_expression = (specifics)->
   it "adds #{specifics.name} to the deepest final open expression", ->
     @exp = @exp_builder(open_expression: {open_expression: []})
-    exp = specifics.manipulator.call(@).invoke(@exp)
+    exp = specifics.manipulator.call(@).perform(@exp)
     expected = specifics.should_equal_after(@exp_builder)
-    expect(exp).toBeAnEqualExpressionTo expected
+    expect(exp.expression).toBeAnEqualExpressionTo expected

 describe "expression manipulations", ->
   beforeEach ->
-    #@math = ttm.require('lib/math')
     @components = ttm.lib.math.ExpressionComponentSource.build()
-    @manip = ttm.require('lib/math/expression_manipulation').build(@components)
-    @exp_builder = ttm.require('lib/math/build_expression_from_javascript_object').buildExpression
-    @expression_to_string = ttm.require('lib/math/expression_to_string').toString
+    @pos = ttm.lib.math.ExpressionPosition
+    @manip = ttm.require('lib/math/expression_manipulation').build(@components, @pos)
+
+    builder_lib = ttm.require('lib/math/build_expression_from_javascript_object')
+    @exp_builder = builder_lib.build(@comps).builderFunction()
+    @exp_pos_builder = builder_lib.build(@comps).builderFunctionExpressionWithPositionAsLast()
+    expression_to_string = ttm.require('lib/math/expression_to_string').toString
     @expect_value = (expression, value)->
-      expect(@expression_to_string(expression)).toEqual value
+      expect(expression_to_string(expression)).toEqual value

   describe "a proof-of-concept example", ->
     it "produces the correct result TODO make this larger", ->
       exp = @exp_builder()
-      exp = @manip.build_append_number(value: 1).invoke(exp)
-      exp = @manip.build_append_number(value: 0).invoke(exp)
-      exp = @manip.build_append_multiplication().invoke(exp)
-      exp = @manip.build_open_sub_expression().invoke(exp)
-      exp = @manip.build_append_number(value: 2).invoke(exp)
-      exp = @manip.build_append_addition().invoke(exp)
-      exp = @manip.build_append_number(value: 4).invoke(exp)
-      exp = @manip.build_close_sub_expression().invoke(exp)
+      position = ttm.lib.math.ExpressionPosition.build(position: exp.id(), expression: exp)
+
+      exp = @manip.build_append_number(value: 1).perform(exp, position).expression
+      exp = @manip.build_append_number(value: 0).perform(exp, position).expression
+      exp = @manip.build_append_multiplication().perform(exp, position).expression
+
+      exp = @manip.build_append_open_sub_expression().perform(exp, position).expression
+      exp = @manip.build_append_number(value: 2).perform(exp, position).expression
+      exp = @manip.build_append_addition().perform(exp, position).expression
+      exp = @manip.build_append_number(value: 4).perform(exp, position).expression
+      exp = @manip.build_close_sub_expression().perform(exp, position).expression
+
       expected = @exp_builder(10, '*', [2, '+', 4])
       expect(exp).toBeAnEqualExpressionTo expected

-  describe "exponentiate last element", ->
-    describe "on a single number-only expression", ->
-      beforeEach ->
-        @exp = @exp_builder(10)
-        @new_exp = @manip.build_exponentiate_last().invoke(@exp)
-
-      it "replaces the content of the expression with an exponentiation", ->
-        expect(@new_exp.first()).toBeInstanceOf @components.classes.exponentiation
-
-      it "provides the exponentiation its base", ->
-        expect(@new_exp.first().base()).toBeAnEqualExpressionTo @exp_builder([10]).first()
-
-    describe "on an expression that currently ends with an operator ", ->
-      it "replaces the trailing operator", ->
-        exp = @exp_builder(10, '+')
-        new_exp = @manip.build_exponentiate_last().invoke(exp)
-        expected = @exp_builder('^': [10, {open_expression: null}])
-        expect(new_exp).toBeAnEqualExpressionTo expected
-
-    describe "on an expression that has a trailing exponent", ->
-      it "manipulates expression correctly", ->
-        exp = @exp_builder('^': [10, {open_expression: null}])
-        new_exp = @manip.build_exponentiate_last().invoke(exp)
-        expected = @exp_builder('^': [10, {open_expression: null}])
-        expect(new_exp).toBeAnEqualExpressionTo expected
-
-  describe "append number", ->
-    describe "when the previous element is a closed expression", ->
-      beforeEach ->
-        @exp = @exp_builder([1])
-
-      it "adds a multiplication symbol between elements", ->
-        exp = @manip.build_append_number(value: 11).invoke(@exp)
-        expected = @exp_builder([1], '*', 11)
-        expect(exp).toBeAnEqualExpressionTo expected
-
-    describe "when the previous element is a nested open expression", ->
-      beforeEach ->
-        @exp = @exp_builder(open_expression: {open_expression: []})
-
-      it "adds the number at the correct place", ->
-        exp = @manip.build_append_number(value: 8).invoke(@exp)
-        expected = @exp_builder(open_expression: {open_expression: [8]})
-        expect(exp).toBeAnEqualExpressionTo expected
-
-    describe "when the previous element is an exponentiation ", ->
-      describe "with no power", ->
-        beforeEach ->
-          @exp = @exp_builder('^': [10, {open_expression:  null}])
-
-        it "inserts the number into the exponentiation", ->
-          new_exp = @manip.build_append_number(value: 11).invoke(@exp)
-          expected = @exp_builder('^': [10, {open_expression: 11}])
-          expect(new_exp).toBeAnEqualExpressionTo expected
-
-      describe "with a power", ->
-        beforeEach ->
-          @exp = @exp_builder('^': [10, 11])
-
-        it "inserts a multiplication and then the number", ->
-          new_exp = @manip.build_append_number(value: 7).invoke(@exp)
-          expected = @exp_builder({'^': [10,11]}, '*', 7)
-          expect(new_exp).toBeAnEqualExpressionTo expected
-
-    describe "when the previous element is a root", ->
-      it "inserts as root's radicand when radicand expression is open", ->
-        @exp = @exp_builder('root': [10, {open_expression: null}])
-        new_exp = @manip.build_append_number(value: 6).invoke(@exp)
-        expected = @exp_builder('root': [10, {open_expression: 6}])
-        expect(new_exp).toBeAnEqualExpressionTo expected
-
-    describe "when the prevous element is none of the other special cases", ->
-      it "adds the number as a new number", ->
-        exp = @exp_builder(1, '+', 3, '=')
-        manipulated_exp = @manip.build_append_number(value: 8).invoke(exp)
-        expected = @exp_builder(1, '+', 3, '=', 8)
-        expect(manipulated_exp).toBeAnEqualExpressionTo expected
-
-
-  describe "appending an equals", ->
-    it "inserts an equals sign into the equation", ->
-      @exp = @exp_builder(1, '+', 3)
-      new_exp = @manip.build_append_equals().invoke(@exp)
-      expected = @exp_builder(1, '+', 3, '=')
-      expect(new_exp).toBeAnEqualExpressionTo expected
-
-  describe "opening a new sub expression", ->
-    it "adds a sub-expression to the expression", ->
-      @exp = @exp_builder(1, '+')
-      new_exp = @manip.build_open_sub_expression().invoke(@exp)
-      expected = @exp_builder(1, '+', {open_expression: []})
-      expect(new_exp).toBeAnEqualExpressionTo expected
-
-    it "adds an expression that isOpen", ->
-      @exp = @exp_builder(1, '+')
-      new_exp = @manip.build_open_sub_expression().invoke(@exp)
-
-      exp = new_exp.nth(2)
-      expect(exp).toBeInstanceOf @components.classes.expression
-      expect(exp.isOpen()).toEqual true
-
-    describe "when adding to a sub-expression", ->
-      it "should add everything correctly", ->
-        exp = @exp_builder()
-        exp = @manip.build_append_number(value: 6).invoke(exp)
-        exp = @manip.build_append_addition().invoke(exp)
-
-        exp = @manip.build_open_sub_expression().invoke(exp)
-        exp = @manip.build_append_number(value: 6).invoke(exp)
-        exp = @manip.build_append_addition().invoke(exp)
-
-        exp = @manip.build_open_sub_expression().invoke(exp)
-        exp = @manip.build_close_sub_expression().invoke(exp)
-        exp = @manip.build_close_sub_expression().invoke(exp)
-        expected = @exp_builder(6, '+', [6, '+', []])
-
-        expect(exp).toBeAnEqualExpressionTo expected
-
-  describe "closing a sub expression ", ->
-    it "takes an open subexpression and closes it", ->
-      exp = @exp_builder({open_expression: []})
-      new_exp = @manip.build_close_sub_expression().invoke(exp)
-      expect(new_exp).toBeAnEqualExpressionTo @exp_builder([])
-
-    it "correctly handles nested open subexpressions", ->
-      exp = @exp_builder(open_expression: {open_expression: null})
-      new_exp = @manip.build_close_sub_expression().invoke(exp)
-      expected = @exp_builder(open_expression: [[]])
-      expect(new_exp).toBeAnEqualExpressionTo expected

   describe "appending multiplication", ->
-    it_plays_manipulation_role (test)->
-      @manip.build_append_multiplication()
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_multiplication()
+      expression_for_performance: ->
+        @exp_pos_builder(10)
+
+    it "does nothing if the expression is empty", ->
+      exp = @exp_builder()
+      position = ttm.lib.math.ExpressionPosition.build(position: exp.id(), expression: exp)
+      exp = @manip.build_append_multiplication().perform(exp, position).expression
+      expected = @exp_builder()
+      expect(exp).toBeAnEqualExpressionTo expected
+
+    it "appends a multiplication", ->
+      @exp = @exp_builder(10)

     it "adds multiplication to the end of the expression", ->
       exp = @exp_builder(1)
-      new_exp = @manip.build_append_multiplication().invoke(exp)
-      expect(new_exp.last() instanceof @components.classes.multiplication).toEqual true
+      results = @manip.build_append_multiplication().perform(exp)
+      expect(results.expression.last() instanceof @components.classes.multiplication).toEqual true

     it "correctly adds multiplication to an exponentiation", ->
       exp = @exp_builder('^': [1, 2])
-      new_exp = @manip.build_append_multiplication().invoke(exp)
+      new_exp = @manip.build_append_multiplication().perform(exp)
       expected = @exp_builder({'^': [1, 2]}, '*')
-      expect(new_exp).toBeAnEqualExpressionTo expected
+      expect(new_exp.expression).toBeAnEqualExpressionTo expected

     it_inserts_component_into_the_last_nested_open_expression(
       name: 'multiplication'
       manipulator: -> @manip.build_append_multiplication()
       should_equal_after: (builder)-> builder(open_expression: {open_expression: ['*']}))

+  describe "exponentiate last element", ->
+    describe "on a single number-only expression", ->
+      beforeEach ->
+        @new_exp = @manip.build_exponentiate_last().perform(@exp)
+
+      it "replaces the content of the expression with an exponentiation", ->
+        expect(@new_exp.first()).toBeInstanceOf @components.classes.exponentiation
+
+      it "provides the exponentiation its base", ->
+        expect(@new_exp.first().base()).toBeAnEqualExpressionTo @exp_builder([10]).first()
+
+    describe "on an expression that currently ends with an operator ", ->
+      it "replaces the trailing operator", ->
+        exp = @exp_builder(10, '+')
+        new_exp = @manip.build_exponentiate_last().perform(exp)
+        expected = @exp_builder('^': [10, {open_expression: null}])
+        expect(new_exp).toBeAnEqualExpressionTo expected
+
+    describe "on an expression that has a trailing exponent", ->
+      it "manipulates expression correctly", ->
+        exp = @exp_builder('^': [10, {open_expression: null}])
+        new_exp = @manip.build_exponentiate_last().perform(exp)
+        expected = @exp_builder('^': [10, {open_expression: null}])
+        expect(new_exp).toBeAnEqualExpressionTo expected
+
+  describe "append number", ->
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_number(value: 8)
+      expression_for_performance: ->
+        @exp_pos_builder(10)
+
+    it "inserts a number when the expression is empty", ->
+      @exp_pos = @exp_pos_builder()
+      exp = @manip.build_append_number(value: 8).perform(@exp_pos)
+      expected = @exp_builder(8)
+      expect(exp.expression()).toBeAnEqualExpressionTo expected
+
+    describe "when the previous element is a closed expression", ->
+      beforeEach ->
+        @exp_pos = @exp_pos_builder([1])
+
+      it "adds a multiplication symbol between elements", ->
+        exp = @manip.build_append_number(value: 11).perform(@exp_pos)
+        expected = @exp_builder([1], '*', 11)
+        expect(exp.expression()).toBeAnEqualExpressionTo expected
+
+  describe "append exponentiation", ->
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_exponentiation()
+      expression_for_performance: ->
+        @exp_pos_builder(10)
+
+    # this is x'd out because, for now, this should be failing
+    # we need to fix later to get cursor-manipulation code that
+    # is appropriate for the calculate and
+    # gets rid of most of the concept of open expressions
+    xdescribe "when the previous element is a nested open expression", ->
+      beforeEach ->
+        @exp_pos = @exp_pos_builder(open_expression: {open_expression: []})
+
+      it "adds the number at the correct place", ->
+        exp = @manip.build_append_number(value: 8).perform(@exp_pos.expression(), @exp_pos)
+        expected = @exp_builder(open_expression: {open_expression: [8]})
+        expect(exp.expression).toBeAnEqualExpressionTo expected
+
+    describe "when the pointed-at element is an exponentiation ", ->
+      # this is x'd out because, for now, this should be failing
+      # we need to fix later to get cursor-manipulation code that
+      # is appropriate for the calculate and
+      # gets rid of most of the concept of open expressions
+      xdescribe "with no power", ->
+        beforeEach ->
+          @exp = @exp_builder('^': [10, {open_expression:  null}])
+
+        it "inserts the number into the exponentiation", ->
+          new_exp = @manip.build_append_number(value: 11).perform(@exp)
+          expected = @exp_builder('^': [10, {open_expression: 11}])
+          expect(new_exp).toBeAnEqualExpressionTo expected
+
+      describe "with a power", ->
+        beforeEach ->
+          @exp_pos = @exp_pos_builder('^': [10, 11])
+
+        it "inserts a multiplication and then the number", ->
+          new_exp = @manip.build_append_number(value: 7).perform(@exp_pos)
+          expected = @exp_builder({'^': [10,11]}, '*', 7)
+          expect(new_exp.expression()).toBeAnEqualExpressionTo expected
+
+    describe "when the pointed-at element is a root", ->
+      # this is x'd out because, for now, this should be failing
+      # we need to fix later to get cursor-manipulation code that
+      # is appropriate for the calculate and
+      # gets rid of most of the concept of open expressions
+      xit "inserts as root's radicand when radicand expression is open", ->
+        @exp = @exp_builder('root': [10, {open_expression: null}])
+        new_exp = @manip.build_append_number(value: 6).perform(@exp)
+        expected = @exp_builder('root': [10, {open_expression: 6}])
+        expect(new_exp).toBeAnEqualExpressionTo expected
+
+    describe "when the pointed at element is not one of those special cases", ->
+      it "adds the number as a new number", ->
+        exp_pos = @exp_pos_builder(1, '+', 3, '=')
+        manipulated_exp = @manip.build_append_number(value: 8).perform(exp_pos)
+        expected = @exp_builder(1, '+', 3, '=', 8)
+        expect(manipulated_exp.expression()).toBeAnEqualExpressionTo expected
+
+
+  describe "appending an equals", ->
+    it "inserts an equals sign into the equation", ->
+      @exp = @exp_builder(1, '+', 3)
+      new_exp = @manip.build_append_equals().perform(@exp)
+      expected = @exp_builder(1, '+', 3, '=')
+      expect(new_exp).toBeAnEqualExpressionTo expected
+
+  describe "appending a sub expression", ->
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_open_sub_expression()
+      expression_for_performance: ->
+        @exp_pos_builder(10)
+
+    it "adds a sub-expression to the expression", ->
+      @exp = @exp_builder(1, '+')
+      new_exp = @manip.build_append_open_sub_expression().perform(@exp)
+      expected = @exp_builder(1, '+', {open_expression: []})
+      expect(new_exp).toBeAnEqualExpressionTo expected
+
+    it "adds an expression that isOpen", ->
+      @exp = @exp_builder(1, '+')
+      new_exp = @manip.build_append_open_sub_expression().perform(@exp)
+
+      exp = new_exp.nth(2)
+      expect(exp).toBeInstanceOf @components.classes.expression
+      expect(exp.isOpen()).toEqual true
+
+    describe "when adding to a sub-expression", ->
+      it "should add everything correctly", ->
+        exp = @exp_builder()
+        exp = @manip.build_append_number(value: 6).perform(exp)
+        exp = @manip.build_append_addition().perform(exp)
+
+        exp = @manip.build_append_open_sub_expression().perform(exp)
+        exp = @manip.build_append_number(value: 6).perform(exp)
+        exp = @manip.build_append_addition().perform(exp)
+
+        exp = @manip.build_append_open_sub_expression().perform(exp)
+        exp = @manip.build_close_sub_expression().perform(exp)
+        exp = @manip.build_close_sub_expression().perform(exp)
+        expected = @exp_builder(6, '+', [6, '+', []])
+
+        expect(exp).toBeAnEqualExpressionTo expected
+
+  describe "closing a sub expression ", ->
+    it "takes an open subexpression and closes it", ->
+      exp = @exp_builder({open_expression: []})
+      new_exp = @manip.build_close_sub_expression().perform(exp)
+      expect(new_exp).toBeAnEqualExpressionTo @exp_builder([])
+
+    it "correctly handles nested open subexpressions", ->
+      exp = @exp_builder(open_expression: {open_expression: null})
+      new_exp = @manip.build_close_sub_expression().perform(exp)
+      expected = @exp_builder(open_expression: [[]])
+      expect(new_exp).toBeAnEqualExpressionTo expected
+
+
   describe "appending division", ->
-    it_plays_manipulation_role (test)->
-      @manip.build_append_division()
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_division()

     it "adds a division to the expression", ->
       exp = @exp_builder(1)

-      new_exp = @manip.build_append_division().invoke(exp)
+      new_exp = @manip.build_append_division().perform(exp)
       expect(new_exp.last() instanceof @components.classes.division).toBeTruthy()

@@ -213,12 +272,13 @@

   describe "appending addition", ->
-    it_plays_manipulation_role (test)->
-      @manip.build_append_addition()
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_addition()

     it "adds addition to the end of the expression", ->
       exp = @exp_builder(1)
-      new_exp = @manip.build_append_addition().invoke(exp)
+      new_exp = @manip.build_append_addition().perform(exp)
       expect(new_exp.last() instanceof @components.classes.addition).toEqual true

     it_inserts_component_into_the_last_nested_open_expression(
@@ -229,12 +289,13 @@

   describe "appending subtraction", ->
-    it_plays_manipulation_role (test)->
-      @manip.build_append_subtraction()
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_subtraction()

     it "adds subtraction to the end of the expression", ->
       exp = @exp_builder(1)
-      new_exp = @manip.build_append_subtraction().invoke(exp)
+      new_exp = @manip.build_append_subtraction().perform(exp)
       expect(new_exp.last() instanceof @components.classes.subtraction).toEqual true

     it_inserts_component_into_the_last_nested_open_expression(
@@ -243,14 +304,15 @@
       should_equal_after: (builder)-> builder(open_expression: {open_expression: ['-']}))

   describe "appending a decimal", ->
-    it_plays_manipulation_role ->
-      @manip.build_append_decimal(value: 5)
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_decimal(value: 5)

     it "correctly adds a decimal to the value", ->
       exp = @components.build_expression()
-      exp = @manip.build_append_number(value: 1).invoke(exp)
-      exp = @manip.build_append_decimal().invoke(exp)
-      exp = @manip.build_append_number(value: 1).invoke(exp)
+      exp = @manip.build_append_number(value: 1).perform(exp)
+      exp = @manip.build_append_decimal().perform(exp)
+      exp = @manip.build_append_number(value: 1).perform(exp)
       @expect_value(exp, '1.1')

     it_inserts_component_into_the_last_nested_open_expression(
@@ -260,25 +322,24 @@

   describe "square expression", ->
-    beforeEach ->
-      @square = @manip.build_square()
-
-    it_plays_manipulation_role (test)->
-      @square
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_square()

     it "returns a squared expression", ->
       exp = @exp_builder(10)
-      squared = @square.invoke(exp)
+      squared = @manip.build_square().perform(exp)
       @expect_value(squared, '100')

   describe "AppendRoot", ->
-    it_plays_manipulation_role ->
-      @manip.build_append_root()
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_root()

     it "adds a mulitplication and root to the expression", ->
       exp = @exp_builder([1])

-      new_exp = @manip.build_append_root(degree: 2).invoke(exp)
+      new_exp = @manip.build_append_root(degree: 2).perform(exp)

       expect(new_exp.last() instanceof @components.classes.root).toBeTruthy()
       expect(new_exp.last(1) instanceof @components.classes.multiplication).toBeTruthy()
@@ -286,56 +347,84 @@
     it "adds a root with a radicand that is an open expression", ->
       exp = @exp_builder([1])

-      new_exp = @manip.build_append_root(degree: 2).invoke(exp)
+      new_exp = @manip.build_append_root(degree: 2).perform(exp)

       expect(new_exp.last() instanceof @components.classes.root).toBeTruthy()
       expect(new_exp.last().radicand().isOpen()).toBeTruthy()

   describe "AppendVariable", ->
-    it_plays_manipulation_role ->
-      @manip.build_append_variable()
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_append_variable()

     it "adds itself to an empty expression", ->
       exp = @exp_builder()
       append_var = @manip.build_append_variable(variable: "doot")
-      new_exp = append_var.invoke(exp)
+      new_exp = append_var.perform(exp)
       expected = @exp_builder(variable: "doot")
       expect(new_exp).toBeAnEqualExpressionTo expected

+  describe "Reset", ->
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_reset()
+
+      expression_for_performance: ->
+        false # not important at all
+
+    beforeEach ->
+      @reset = @manip.build_reset()
+      @result = @reset.perform()
+
+    it "returns an empty resulting expression", ->
+      expect(@result.expression()).toBeAnEqualExpressionTo @exp_builder()
+
+    it "returns an pointer which points to that expression", ->
+      expect(@result.position()).toEqual @result.expression().id()
+
+  describe "update position", ->
+    it_plays_manipulation_role
+      subject: ->
+        @manip.build_update_position()
+
+      expression_for_performance: ->
+        false
+
+
   describe "(moved over from other test file)", ->
-    describe "NumberManipulation", ->
-      it_plays_manipulation_role (test)->
-        @manip.build_append_number(value: 5)

     describe "NegationManipulation", ->
-      it_plays_manipulation_role (test)->
-        @manip.build_negate_last()
+      it_plays_manipulation_role
+        subject: ->
+          @manip.build_negate_last()

       it "will negate the last element", ->
         exp = @exp_builder(1)
-        new_exp = @manip.build_negate_last().invoke(exp)
+        new_exp = @manip.build_negate_last().perform(exp)
         expect(new_exp.last().value()).toEqual -1

     describe "AppendPi", ->
-      it_plays_manipulation_role (test)->
-        @manip.build_append_pi()
+      it_plays_manipulation_role
+        subject: ->
+          @manip.build_append_pi()

       it "adds a mulitplication and pi to the expression", ->
         exp = @exp_builder(1)
-        new_exp = @manip.build_append_pi().invoke(exp)
+        new_exp = @manip.build_append_pi().perform(exp)
         expect(new_exp.last() instanceof @components.classes.pi).toBeTruthy()
         expect(new_exp.last(1) instanceof @components.classes.multiplication).toBeTruthy()

     describe "SquareRootManipulation", ->
-      it_plays_manipulation_role (test)->
-        @manip.build_square_root()
+      it_plays_manipulation_role
+        subject: ->
+          @manip.build_square_root()

       it "finds the square root of an expression", ->
         exp = @exp_builder([4])
-        new_exp = @manip.build_square_root().invoke(exp)
+        new_exp = @manip.build_square_root().perform(exp)
         expect(new_exp.last().value()).toEqual '2'


fix(changelog): Fixed referencing invalid object

--- changelog.coffee
+++ changelog.coffee
@@ -37,7 +37,7 @@
   newCommit = []
   for line in commit
     if match = line.match /(?:Closes|Fixes)\s#(\d+)/
-      msg.issues.push parseInt(match[1])
+      commitObj.issues.push parseInt(match[1])
     else
       newCommit.push line
   commit = newCommit


fix formula

fix formula

--- mechanics/001-binary-stars/csmain.coffee
+++ mechanics/001-binary-stars/csmain.coffee
@@ -27,10 +27,10 @@
         m2 = parseFloat(ps['m2'])
         a = parseFloat(ps['a'])
         e = parseFloat(ps['e'])
-        b = a * Math.sqrt(1 - e * e)
+        l = a * (1 - e)
         M = m1 + m2
-        y1 = m2 / M * b
-        y2 = - m1 / M * b
+        y1 = m2 / M * l
+        y2 = - m1 / M * l

         v  = Math.sqrt(au.G * M / a * (1 + e) / (1 - e))
         v1 = v / M * m2


Refactor: Rename function getBillList() to getRepsList

--- app/scripts/controllers/words.coffee
+++ app/scripts/controllers/words.coffee
@@ -33,7 +33,7 @@
       ).error (data, status) ->
         console.log "Error #{status}: #{data}"

-    $scope.getBillList = ->
+    $scope.getRepList = ->
       $http(
         method: "GET"
         url: "http://congress.api.sunlightfoundation.com/legislators?apikey=3cdfa27b289e4d4090fd1b176c45e6cf&chamber=senate&per_page=50&page=2"
@@ -73,5 +73,5 @@
         i++
       return count

-    $scope.getBillList()
+    $scope.getRepList()
   ])


mochacompat: Take files as arguments, not directory

More compatible with regular mocha

--- src/mochacompat.coffee
+++ src/mochacompat.coffee
@@ -64,18 +64,6 @@
       callback && callback failures

   return runner.run done
-
-testFilesInDirectory = (testDir) ->
-
-  files = fs.readdirSync(testDir)
-  .filter (filename) ->
-    isJs = filename.substr(-3) == '.js'
-    isCoffee = filename.substr(-7) == '.coffee'
-    return isJs or isCoffee
-  .map (filename) ->
-    return path.join testDir, filename
-
-  return files

 testId = (fullname) ->
   crypto = require 'crypto'
@@ -310,8 +298,9 @@

   # TODO: take list of files as input instead, to be more mocha compatible
   program
-    .arguments('<test directory>')
-    .action( (dir) -> program.directory = dir )
+    .arguments('<files...>')
+    .action (args) ->
+      program.files = args
     .option('--ide <URL>', 'FBP IDE to use for live-url', String, 'http://app.flowhub.io')
     .option('--host <hostname>', 'Hostname we serve on, for live-url', String, 'autodetect')
     .option('--port <PORT>', 'Command to launch runtime under test', Number, 3333)
@@ -322,8 +311,7 @@
 exports.setup = setup = (options, callback) ->
   options = normalizeOptions options

-  files = testFilesInDirectory options.directory
-  mocha = loadTests files
+  mocha = loadTests options.files
   specs = buildFbpSpecs mocha

   state =


<debug> Te... or why not rimraf?

<debug> Te... or why not rimraf?

--- tests/helpers.coffee
+++ tests/helpers.coffee
@@ -10,7 +10,7 @@
 os = require 'os'
 fs = require 'fs'
 mkdirp = require 'mkdirp'
-async = require 'async'
+rimraf = require 'rimraf'
 fixtures = require 'cozy-fixtures'
 if process.env.USE_JS
     prefix = path.join __dirname, '../build/'
@@ -46,25 +46,7 @@
         @server.close()

     clearFiles: (done) ->
-        fs.readdir DRIVERS_DIR, (err, directories) ->
-            return done 'Could not delete files' if err
-
-            async.each directories, (dir, cb) ->
-                dir = path.resolve DRIVERS_DIR, dir
-                fs.readdir dir, (err, files) ->
-                    return cb 'Could not delete files' if err
-
-                    async.each files, (file, cb2) ->
-                        file = path.resolve dir, file
-                        fs.unlink file, (err) ->
-                            console.log 'Could not delete %s', file if err
-                            cb2 null # loop anyway
-                    , ->
-                        fs.rmdir dir, (err) ->
-                            cb err
-            , ->
-                fs.rmdir DRIVERS_DIR, (err) ->
-                    done err
+        rimraf DRIVERS_DIR, done

     clearDB: (done) ->
          fixtures.resetDatabase callback: done


Remove preventDefault on mobile hovers (closes #1296)

--- components/dropdown/client/mobile.coffee
+++ components/dropdown/client/mobile.coffee
@@ -4,8 +4,6 @@
   deactivate = -> # Overwritten by activations

   $hovers.on 'click', (e) ->
-    e.preventDefault()
-
     deactivate()

     document.documentElement.classList.add 'HTML--disabled-scrolling'


Update Ball.coffee

Update Ball.coffee

--- public/js/Ball.coffee
+++ public/js/Ball.coffee
@@ -46,6 +46,5 @@
     super()
     @speed.x = defaultSpeed.x if defaultSpeed
     @speed.y = defaultSpeed.y if defaultSpeed
-    if defaultSpeed
-      console.log "AFINAL HA!!!"
+
     updateScores()


FIX: Remove git merge markers

FIX: Remove git merge markers


div instead of span

div instead of span

--- resources/assets/coffee/react/beatmapset-page/scoreboard-item.coffee
+++ resources/assets/coffee/react/beatmapset-page/scoreboard-item.coffee
@@ -15,7 +15,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 ###
-{div, span, a} = React.DOM
+{div, a} = React.DOM
 el = React.createElement

 class BeatmapsetPage.ScoreboardItem extends React.Component
@@ -45,4 +45,4 @@
             when 'accuracy'
               "#{_.round @props.score.accuracy * 100, 2}%"

-        span className: className, key: m, contents
+        div className: className, key: m, contents


改行するようにして見やすくした 改行が";"と同じみたいな意味になる言語なので"."の後にしたけど、 Javaとかやっている人から見たら気持ち悪いのかな？

--- spec/character-regexp-util-spec.coffee
+++ spec/character-regexp-util-spec.coffee
@@ -3,32 +3,52 @@
 describe "CharacterRegexpUtil", ->
   describe "CharacterRegexpUtil.range2string()", ->
     it "range", ->
-      expect(CharacterRegexpUtil.range2string([0x20..0x40])).toEqual("\\u0020-\\u0040")
-      expect(CharacterRegexpUtil.range2string([0x300..0x600])).toEqual("\\u0300-\\u0600")
-      expect(CharacterRegexpUtil.range2string([0x4000..0x8000])).toEqual("\\u4000-\\u8000")
-      expect(CharacterRegexpUtil.range2string([0x12AB..0x34CD])).toEqual("\\u12AB-\\u34CD")
+      expect(CharacterRegexpUtil.range2string([0x20..0x40])).
+          toEqual("\\u0020-\\u0040")
+      expect(CharacterRegexpUtil.range2string([0x300..0x600])).
+          toEqual("\\u0300-\\u0600")
+      expect(CharacterRegexpUtil.range2string([0x4000..0x8000])).
+          toEqual("\\u4000-\\u8000")
+      expect(CharacterRegexpUtil.range2string([0x12AB..0x34CD])).
+          toEqual("\\u12AB-\\u34CD")
   describe "CharacterRegexpUtil.range2regexp()", ->
     it "range", ->
-      expect(CharacterRegexpUtil.range2regexp([0x20..0x40])).toEqual(/[\u0020-\u0040]/)
-      expect(CharacterRegexpUtil.range2regexp([0x300..0x600])).toEqual(/[\u0300-\u0600]/)
-      expect(CharacterRegexpUtil.range2regexp([0x4000..0x8000])).toEqual(/[\u4000-\u8000]/)
-      expect(CharacterRegexpUtil.range2regexp([0x12AB..0x34CD])).toEqual(/[\u12AB-\u34CD]/)
+      expect(CharacterRegexpUtil.range2regexp([0x20..0x40])).
+          toEqual(/[\u0020-\u0040]/)
+      expect(CharacterRegexpUtil.range2regexp([0x300..0x600])).
+          toEqual(/[\u0300-\u0600]/)
+      expect(CharacterRegexpUtil.range2regexp([0x4000..0x8000])).
+          toEqual(/[\u4000-\u8000]/)
+      expect(CharacterRegexpUtil.range2regexp([0x12AB..0x34CD])).
+          toEqual(/[\u12AB-\u34CD]/)
   describe "CharacterRegexpUtil.string2regexp()", ->
     it "single char", ->
-      expect(CharacterRegexpUtil.string2regexp("a")).toEqual(/[a]/)
-      expect(CharacterRegexpUtil.string2regexp("あ")).toEqual(/[あ]/)
+      expect(CharacterRegexpUtil.string2regexp("a")).
+          toEqual(/[a]/)
+      expect(CharacterRegexpUtil.string2regexp("あ")).
+          toEqual(/[あ]/)
     it "word", ->
-      expect(CharacterRegexpUtil.string2regexp("abc")).toEqual(/[abc]/)
-      expect(CharacterRegexpUtil.string2regexp("あア亜")).toEqual(/[あア亜]/)
+      expect(CharacterRegexpUtil.string2regexp("abc")).
+          toEqual(/[abc]/)
+      expect(CharacterRegexpUtil.string2regexp("あア亜")).
+          toEqual(/[あア亜]/)
     it "char range", ->
-      expect(CharacterRegexpUtil.string2regexp("a-z")).toEqual(/[a-z]/)
+      expect(CharacterRegexpUtil.string2regexp("a-z")).
+          toEqual(/[a-z]/)
     it "unicode range", ->
-      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040")).toEqual(/[\u0020-\u0040]/)
-      expect(CharacterRegexpUtil.string2regexp("\\u0300-\\u0600")).toEqual(/[\u0300-\u0600]/)
-      expect(CharacterRegexpUtil.string2regexp("\\u4000-\\u8000")).toEqual(/[\u4000-\u8000]/)
+      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040")).
+          toEqual(/[\u0020-\u0040]/)
+      expect(CharacterRegexpUtil.string2regexp("\\u0300-\\u0600")).
+          toEqual(/[\u0300-\u0600]/)
+      expect(CharacterRegexpUtil.string2regexp("\\u4000-\\u8000")).
+          toEqual(/[\u4000-\u8000]/)
     it "complex", ->
-      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040abc")).toEqual(/[\u0020-\u0040abc]/)
-      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040a-cあ")).toEqual(/[\u0020-\u0040a-cあ]/)
+      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040abc")).
+          toEqual(/[\u0020-\u0040abc]/)
+      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040a-cあ")).
+          toEqual(/[\u0020-\u0040a-cあ]/)
     it "multi", ->
-      expect(CharacterRegexpUtil.string2regexp("a", "b")).toEqual(/[ab]/)
-      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040", "a-c", "あ")).toEqual(/[\u0020-\u0040a-cあ]/)
+      expect(CharacterRegexpUtil.string2regexp("a", "b")).
+          toEqual(/[ab]/)
+      expect(CharacterRegexpUtil.string2regexp("\\u0020-\\u0040", "a-c", "あ")).
+          toEqual(/[\u0020-\u0040a-cあ]/)


:art:

:art:

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -2377,9 +2377,9 @@

         it "doesn't throw an error when setting the dimensions for a destroyed decoration", ->
           blockDecoration = addBlockDecorationForScreenRow(0)
-          presenter = buildPresenter(explicitHeight: 30, lineHeight: 10, tileSize: 2, scrollTop: 0)
+          presenter = buildPresenter()
+
           blockDecoration.destroy()
-
           presenter.setBlockDecorationDimensions(blockDecoration, 30, 30)

           expect(getState(presenter).content.blockDecorations).toEqual({})


NPM fix for wercker

NPM fix for wercker

--- framer/EventEmitter.coffee
+++ framer/EventEmitter.coffee
@@ -1,6 +1,6 @@
 {_} = require "./Underscore"

-EventEmitter3 = require "EventEmitter3"
+EventEmitter3 = require "eventemitter3"

 EventKey = "_events"


Escape HTML before any substitution

Escape HTML before any substitution

Fixes #55

--- annotran/static/scripts/annotator/plugin/substitution.coffee
+++ annotran/static/scripts/annotator/plugin/substitution.coffee
@@ -88,6 +88,10 @@
     return newEle

   singleSubstitution: (data = {}, substituteText = '') =>
+
+    div = document.createElement('div')
+    div.appendChild(document.createTextNode(substituteText))
+    substituteText = div.innerHTML

     # resolve the passed xpath
     full_range = new xpathRange.SerializedRange(data).normalize(document.body)


Add error handling for 400 responses

Add error handling for 400 responses


fixed routing

fixed routing

--- app/assets/javascripts/main/routing.coffee
+++ app/assets/javascripts/main/routing.coffee
@@ -90,7 +90,7 @@
             )

-    "views.html.admin.task.taskSelectionAlgorithm" : ->
+    "admin.task.taskSelectionAlgorithm" : ->

       $this = $(this)
       $form = $this.find("form")


remove visual template artifacts, in case of an error when receiving data for tracing

--- app/assets/javascripts/main/routing.coffee
+++ app/assets/javascripts/main/routing.coffee
@@ -79,6 +79,7 @@
           error: (xhr, status, error) ->

             console.error("Something went wrong when receiving task data", xhr, status, error)
+            populateTemplate({task : null})

           complete: (task) ->


[master] Flag for suppression when testing

--- app.coffee
+++ app.coffee
@@ -1,7 +1,7 @@
 express = require 'express'
 path = require 'path'
 favicon = require 'serve-favicon'
-logger = require 'morgan'
+morgan = require 'morgan'
 cookieParser = require 'cookie-parser'
 bodyParser = require 'body-parser'
 stylus = require 'stylus'
@@ -16,7 +16,10 @@

 # Middleware
 app.use favicon(path.join(__dirname, '/public/images/favicon.ico'))
-app.use logger('dev')
+app.use morgan('dev',
+  skip: ->
+    return app.enabled 'automatedTesting'
+)
 app.use bodyParser.json()
 app.use bodyParser.urlencoded { extended: true }
 app.use cookieParser()


make network icon redder.

make network icon redder.

--- src/smc-webapp/app_shared.cjsx
+++ src/smc-webapp/app_shared.cjsx
@@ -204,10 +204,10 @@
             icon_style = {marginRight: 8, fontSize: '13pt', display: 'inline'}
             if (@props.mesg_info?.enqueued ? 0) > 5  # serious backlog of data!
                 icon_style.color = 'red'
-            else if (@props.mesg_info?.count ? 0) > 4 # worrisome amount
+            else if (@props.mesg_info?.count ? 0) > 1 # worrisome amount
+                icon_style.color = '#f33'
+            else if (@props.mesg_info?.count ? 0) > 0 # working well but doing something minimal
                 icon_style.color = '#833'
-            else if (@props.mesg_info?.count ? 0) > 0 # working well but doing something.
-                icon_style.color = '#333'
             <div>
                 <Icon name='wifi' style={icon_style}/>
                 {<Tip


Use array comprehensions instead of new Array().map()

--- bokehjs/src/coffee/models/sources/geojson_data_source.coffee
+++ bokehjs/src/coffee/models/sources/geojson_data_source.coffee
@@ -18,15 +18,9 @@

   _update_data: () -> @data = @geojson_to_column_data()

-  _get_new_list_array: (length) ->
-    array = new Array(length)
-    list_array = array.map((x) -> [])
-    return list_array
+  _get_new_list_array: (length) -> ([] for i in [0...length])

-  _get_new_nan_array: (length) ->
-    array = new Array(length)
-    nan_array = array.map((x) -> NaN)
-    return nan_array
+  _get_new_nan_array: (length) -> (NaN for i in [0...length])

   _flatten_function: (accumulator, currentItem) ->
     return accumulator.concat([[NaN, NaN, NaN]]).concat(currentItem)


Fix Turbolinks.Location anchor truncation

--- src/turbolinks/location.coffee
+++ src/turbolinks/location.coffee
@@ -8,14 +8,14 @@
   constructor: (url = "") ->
     linkWithAnchor = document.createElement("a")
     linkWithAnchor.href = url.toString()
+
     @absoluteURL = linkWithAnchor.href

-    if linkWithAnchor.hash.length < 2
+    hashLength = linkWithAnchor.hash.length
+    if hashLength < 2
       @requestURL = @absoluteURL
     else
-      linkWithoutAnchor = linkWithAnchor.cloneNode()
-      linkWithoutAnchor.hash = ""
-      @requestURL = linkWithoutAnchor.href
+      @requestURL = @absoluteURL.slice(0, -hashLength)

   getOrigin: ->
     @absoluteURL.split("/", 3).join("/")


modify bug that retweet own tweet

modify bug that retweet own tweet

--- oretamaBot/scripts/twitter-search.coffee
+++ oretamaBot/scripts/twitter-search.coffee
@@ -46,7 +46,7 @@
     msg.send 'ツイート流すよー'

     stream.on 'tweet', (tweet) ->
-      msg.send "#{query}見つかったよー  https://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id_str}"
+      msg.send "#{query}見つかったよー  https://twitter.com/#{tweet.user.screen_name}/status/#{tweet.id_str}" unless tweet.user.screen_name is "oretamaBot"
     stream.on "disconnect", (disconnectMessage) ->
       msg.send "ツイート流すが切れたー。理由: #{disconnectMessage}"
     stream.on "reconnect", (request, response, connectInterval) ->


Remove some reserved word errors identified by YUICompressor

--- tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
@@ -56,15 +56,15 @@
     throw new Error "Provided value <#{content}> is not valid as DOM element content."

   # _internal_: Currently don't want to rely on Scriptaculous, since our needs are pretty minor.
-  animate = (element, styleName, initial, final, duration, callbacks) ->
+  animate = (element, styleName, initialValue, finalValue, duration, callbacks) ->
     styles = {}
-    range = final - initial
+    range = finalValue - initialValue
     initialTime = Date.now()
     first = true
     animator = ->
       elapsed = Date.now() - initialTime
       if elapsed >= duration
-        styles[styleName] = final
+        styles[styleName] = finalValue
         element.setStyle styles
         window.clearInterval timeoutID
         callbacks.oncomplete and callbacks.oncomplete()
@@ -81,7 +81,7 @@

     timeoutID = window.setInterval animator

-    styles[styleName] = initial
+    styles[styleName] = initialValue
     element.setStyle styles

   # Generic view of an DOM event that is passed to a handler function.
@@ -98,11 +98,10 @@

     constructor: (event) ->
       @nativeEvent = event
-      @memo = event.memo
-      @type = event.type
-
-      @char = event.char
-      @key = event.key
+
+      # This is to satisfy YUICompressor which doesn't seem to like 'char', even
+      # though it doesn't appear to be a reserved word.
+      this[name] = event[name] for name in ["memo", "type", "char", "key"]

     # Stops the event which prevents further propagation of the DOM event,
     # as well as DOM event bubbling.


remove comment

remove comment

--- src/taxonomyViz.coffee
+++ src/taxonomyViz.coffee
@@ -472,6 +472,7 @@
 				vizdata[i][j].vizColInd = j 							# in the viz cols index
 				vizdata[i][j].bioColInd = selected_samples_clone[j]		# in the original biom sample index
 				vizdata[i][j].sampleName = columns_sample_name_array[order]
+
 				# 1 sort
 				if sortIdFlag
 					vizdata[i][j].phinchName = String(selected_samples_clone[j]) # display the original sample ID
@@ -483,7 +484,7 @@
 					vizdata[i][j].sortColInd = sorted_selected_phinchID_array.indexOf(j)
 					vizdata[i][j].phinchName = String(selected_phinchID_array_clone[j])

-				# 2 delete OTU
+				# 2 delete OTU
 				if deleteOTUArr.indexOf(i) == -1 and new_data_matrix_onLayer[i][order]? # not deleted & has value
 					vizdata[i][j].y = new_data_matrix_onLayer[i][order]
 					sumEachTax[i] += new_data_matrix_onLayer[i][order]
@@ -497,7 +498,7 @@
 				for j in [0..new_data_matrix_onLayer.length-1]
 					vizdata[j][i].y0 = sumEachCol[i]
 					if deleteOTUArr.indexOf(j) == -1
-						sumEachCol[i] += new_data_matrix_onLayer[j][order]
+						sumEachCol[i] += new_data_matrix_onLayer[j][order]

 		# 3 draw
 		width = 1200


DevTools: Temporary fix for resize issue with collaborativeWorkspace

--- client/DevTools/AppController.coffee
+++ client/DevTools/AppController.coffee
@@ -12,3 +12,9 @@
       type     : "application"

     super options, data
+
+  # FIXME facet, to make it work I had to call notifyWindowResizeListeners here
+  handleQuery:->
+    {workspace} = @getView()
+    workspace.ready ->
+      KD.getSingleton("windowController").notifyWindowResizeListeners()


Better placeholder for narratives

Better placeholder for narratives

--- client/src/views/section_view.coffee
+++ client/src/views/section_view.coffee
@@ -67,7 +67,7 @@
   addNarrative: =>
     narrative = new Backbone.Models.Narrative(
       section_id: @section.get(Backbone.Models.Section.idAttribute)
-      content: 'Type your narrative here'
+      content: ''
     )
     @section.set('narrative', narrative)


Style.

Style.

--- tests.coffee
+++ tests.coffee
@@ -928,11 +928,11 @@
     ClassyTestCase.addTest new ReactivePublishTestCase()

 if Meteor.isServer
-  Meteor.publish "initial error", () ->
+  Meteor.publish "initial error", ->
     @autorun () =>
       throw new Meteor.Error('triggered error')

-  Meteor.publish "rereun error", () ->
+  Meteor.publish "rereun error", ->
     reactiveError = new ReactiveVar false

     @autorun () =>
@@ -943,23 +943,19 @@
     @ready()

 class ReactivePublishErrorTests extends ClassyTestCase
-  @testName: "reactivepublish - Errors"
-
-  testClientInitialError: () ->
-      sub = Meteor.subscribe("initial error", {
-        onStop: (error) =>
-          @assertEqual(error.message, '[triggered error]')
-      })
-
-  testClientRerunError: () ->
+  @testName: "reactivepublish - errors"
+
+  testClientInitialError: ->
+    @assertSubscribeFails "initial error", @expect()
+
+  testClientRerunError: ->
     isReady = false
-    Meteor.subscribe("rereun error", {
+    @subscribe "rereun error",
       onStop: @expect (error) =>
-        @assertEqual(isReady, true, 'received error on rerun')
-        @assertEqual(error.message, '[triggered error]')
-      onReady: () =>
+        @assertTrue isReady, true, 'received error on rerun'
+        @assertEqual error.message, '[triggered error]'
+      onReady: =>
         isReady = true
-    })

 ClassyTestCase.addTest new ReactivePublishErrorTests


More tests.

More tests.

--- tests.coffee
+++ tests.coffee
@@ -31,6 +31,10 @@

 BlazeComponent.register 'FooComponent', FooComponent

+class @SelfRegisterComponent extends BlazeComponent
+  # Alternative way of registering components.
+  @register 'SelfRegisterComponent'
+
 class SubComponent extends MainComponent
   @calls: []

@@ -47,33 +51,53 @@

 BlazeComponent.register 'SubComponent', SubComponent

+class UnregisteredComponent extends SubComponent
+  foobar: ->
+    "#{ @componentName() }/UnregisteredComponent.foobar/#{ EJSON.stringify @data() }/#{ EJSON.stringify @currentData() }/#{ @currentComponent().componentName() }"
+
+  foobar2: ->
+    "#{ @componentName() }/UnregisteredComponent.foobar2/#{ EJSON.stringify @data() }/#{ EJSON.stringify @currentData() }/#{ @currentComponent().componentName() }"
+
+# Name has to be set manually.
+UnregisteredComponent.componentName 'UnregisteredComponent'
+
+class SelfNameUnregisteredComponent extends UnregisteredComponent
+  # Alternative way of setting the name manually.
+  @componentName 'SelfNameUnregisteredComponent'
+
+  # We do not extend any helper on purpose. So they should all use "UnregisteredComponent".
+
 class BasicTestCase extends ClassyTestCase
   @testName: 'blaze-components - basic'

-  FOO_COMPONENT_CONTENT = """
-
Other component: FooComponent

-    <button>Foo1</button>
-

-    <button>Foo2</button>
-

-

-  """
+  FOO_COMPONENT_CONTENT = ->
+    """
+
Other component: FooComponent

+      <button>Foo1</button>
+

+      <button>Foo2</button>
+

+

+    """

-  SUB_COMPONENT_CONTENT = """
-
Main component: SubComponent

-    <button>Foo1</button>
-
SubComponent/SubComponent.foobar/{"top":"42"}/{"top":"42"}/SubComponent

-    <button>Foo2</button>
-
SubComponent/SubComponent.foobar2/{"top":"42"}/{"a":"1","b":"2"}/SubComponent

-
SubComponent/MainComponent.foobar3/{"top":"42"}/{"top":"42"}/SubComponent

-
Subtemplate

-    <button>Foo1</button>
-
SubComponent/SubComponent.foobar/{"top":"42"}/{"top":"42"}/SubComponent

-    <button>Foo2</button>
-
SubComponent/SubComponent.foobar2/{"top":"42"}/{"a":"3","b":"4"}/SubComponent

-
SubComponent/MainComponent.foobar3/{"top":"42"}/{"top":"42"}/SubComponent

-    #{ FOO_COMPONENT_CONTENT }
-  """
+  COMPONENT_CONTENT = (componentName, helperComponentName) ->
+    helperComponentName ?= componentName
+
+    """
+
Main component: #{ componentName }

+      <button>Foo1</button>
+
#{ componentName }/#{ helperComponentName }.foobar/{"top":"42"}/{"top":"42"}/#{ componentName }

+      <button>Foo2</button>
+
#{ componentName }/#{ helperComponentName }.foobar2/{"top":"42"}/{"a":"1","b":"2"}/#{ componentName }

+
#{ componentName }/MainComponent.foobar3/{"top":"42"}/{"top":"42"}/#{ componentName }

+
Subtemplate

+      <button>Foo1</button>
+
#{ componentName }/#{ helperComponentName }.foobar/{"top":"42"}/{"top":"42"}/#{ componentName }

+      <button>Foo2</button>
+
#{ componentName }/#{ helperComponentName }.foobar2/{"top":"42"}/{"a":"3","b":"4"}/#{ componentName }

+
#{ componentName }/MainComponent.foobar3/{"top":"42"}/{"top":"42"}/#{ componentName }

+      #{ FOO_COMPONENT_CONTENT() }
+    """

   trim: (html) =>
     html = html.replace />\s+/g, '>'
@@ -89,21 +113,9 @@
       top: '42'

     @assertEqual @trim(output), @trim """
-
Main component: MainComponent

-      <button>Foo1</button>
-
MainComponent/MainComponent.foobar/{"top":"42"}/{"top":"42"}/MainComponent

-      <button>Foo2</button>
-
MainComponent/MainComponent.foobar2/{"top":"42"}/{"a":"1","b":"2"}/MainComponent

-
MainComponent/MainComponent.foobar3/{"top":"42"}/{"top":"42"}/MainComponent

-
Subtemplate

-      <button>Foo1</button>
-
MainComponent/MainComponent.foobar/{"top":"42"}/{"top":"42"}/MainComponent

-      <button>Foo2</button>
-
MainComponent/MainComponent.foobar2/{"top":"42"}/{"a":"3","b":"4"}/MainComponent

-
MainComponent/MainComponent.foobar3/{"top":"42"}/{"top":"42"}/MainComponent

-      #{ FOO_COMPONENT_CONTENT }
+      #{ COMPONENT_CONTENT 'MainComponent' }

-      #{ SUB_COMPONENT_CONTENT }
+      #{ COMPONENT_CONTENT 'SubComponent' }
     """

     componentTemplate = BlazeComponent.getComponentTemplate 'FooComponent'
@@ -113,7 +125,7 @@
     output = Blaze.toHTMLWithData componentTemplate,
       top: '42'

-    @assertEqual @trim(output), @trim FOO_COMPONENT_CONTENT
+    @assertEqual @trim(output), @trim FOO_COMPONENT_CONTENT()

     componentTemplate = BlazeComponent.getComponentTemplate 'SubComponent'

@@ -122,7 +134,7 @@
     output = Blaze.toHTMLWithData componentTemplate,
       top: '42'

-    @assertEqual @trim(output), @trim SUB_COMPONENT_CONTENT
+    @assertEqual @trim(output), @trim COMPONENT_CONTENT 'SubComponent'

   testGetComponent: =>
     @assertEqual BlazeComponent.getComponent('MainComponent'), MainComponent
@@ -136,4 +148,27 @@
     @assertEqual SubComponent.componentName(), 'SubComponent'
     @assertEqual BlazeComponent.componentName(), null

+  testSelfRegister: =>
+    @assertTrue BlazeComponent.getComponent 'SelfRegisterComponent'
+
+  testUnregisteredComponent: =>
+    componentTemplate = UnregisteredComponent.getComponentTemplate()
+
+    @assertTrue componentTemplate
+
+    output = Blaze.toHTMLWithData componentTemplate,
+      top: '42'
+
+    @assertEqual @trim(output), @trim COMPONENT_CONTENT 'UnregisteredComponent'
+
+    componentTemplate = SelfNameUnregisteredComponent.getComponentTemplate()
+
+    @assertTrue componentTemplate
+
+    output = Blaze.toHTMLWithData componentTemplate,
+      top: '42'
+
+    # We have not extended any helper on purpose, so they should still use "UnregisteredComponent".
+    @assertEqual @trim(output), @trim COMPONENT_CONTENT 'SelfNameUnregisteredComponent', 'UnregisteredComponent'
+
 ClassyTestCase.addTest new BasicTestCase()


search and replace

search and replace

--- coffee/operations/search_func.coffee
+++ coffee/operations/search_func.coffee
@@ -7,14 +7,15 @@
    @initialize ->
     @elems.inputs = {}

-   operationName: 'Search Rows'
-   @operationName: 'Search Rows'
+   operationName: 'Search & Process'
+   @operationName: 'Search & Process'
    type: 'searchRows'
    @type: 'searchRows'

    json: ->
     table: @table.id
     search: @search
+    func: @func

    setJson: (json) ->
     @search = json.search
@@ -32,6 +33,9 @@
       @$.elems.func = @textarea "#search-func.u-full-width",
        value: '-> false'
        on: {change: @$.on.change}
+
+     @$.elems.btn = @button '.u-full-width.button-primary', 'Apply',
+      on: {click: @$.on.apply}
      @button '.u-full-width', on: {click: @$.on.cancel}, 'Cancel'

     @_setData() if @table?
@@ -81,12 +85,17 @@
      elems.remove._id = id
      @$.elems.inputs[id] = elems

+   @listen 'apply', (e) ->
+    e.preventDefault()
+    @search = {}
+    @search[id] = true for id of @elems.inputs
+    @func = @elems.func.value
+    @callbacks.apply()
+
+
    refresh: ->
     @search = {}
-    n = 0
-    for id, elems of @elems.inputs
-     @search[id] = true
-     n++
+    @search[id] = true for id of @elems.inputs
     @func = @elems.func.value
     try
      text = @func
@@ -105,7 +114,8 @@
     highlight = (true for i in [0...@table.size])
     for r in [0...@table.size]
      args = (@table.data[id][r] for id of @search)
-     if func.apply null, args
+     result = func.apply null, args
+     if result? and result isnt false
       highlight[r] = true
      else
       highlight[r] = false
@@ -116,6 +126,31 @@
     @table.refresh()

    apply: ->
+    try
+     text = @func
+     coffee = CoffeeScript.compile "return (#{text})"
+     func = new Function "return #{coffee}"
+     func = func()
+    catch e
+     func = -> false
+
+    deleteRows = {}
+    for r in [0...@table.size]
+     args = (@table.data[id][r] for id of @search)
+     result = func.apply null, args
+     if result? and result isnt false
+      if result.delete
+       deleteRows[r] = true
+      else if result.replace?
+       c = 0
+       for id of @search
+        @table.data[id][r] = result.replace[c]
+        ++c
+
+    for id, data of @table.data
+     @table.data[id] = (d for d, r in data when not deleteRows[r])
+     @table.size = @table.data[id].length
+


Cache buffer's base digest and full text

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -110,7 +110,6 @@
   fileSubscriptions: null
   backwardsScanChunkSize: 8000
   defaultMaxUndoEntries: 10000
-  changeCount: 0
   nextMarkerLayerId: 0

   ###
@@ -228,11 +227,9 @@
       buffer.nextMarkerLayerId = params.nextMarkerLayerId
       buffer.nextDisplayLayerId = params.nextDisplayLayerId

-      digest = buffer.buffer.baseTextDigest()
-      if digest is params.digestWhenLastPersisted or not params.filePath?
+      if buffer.digestWhenLastPersisted is params.digestWhenLastPersisted or not params.filePath?
         buffer.buffer.deserializeChanges(params.outstandingChanges)
         buffer.history.deserialize(params.history, buffer)
-      buffer.digestWhenLastPersisted = digest

       for layerId, layerState of params.markerLayers
         if layerId is params.defaultMarkerLayerId
@@ -279,8 +276,9 @@
     }

     if filePath = @getPath()
+      @baseTextDigestCache ?= @buffer.baseTextDigest()
       result.filePath = filePath
-      result.digestWhenLastPersisted = @buffer.baseTextDigest()
+      result.digestWhenLastPersisted = @digestWhenLastPersisted
       result.outstandingChanges = @buffer.serializeChanges()
     else
       result.text = @getText()
@@ -627,7 +625,7 @@
   # Public: Get the entire text of the buffer.
   #
   # Returns a {String}.
-  getText: -> @buffer.getText()
+  getText: -> @cachedText ?= @buffer.getText()

   # Public: Get the text in a range.
   #
@@ -857,7 +855,7 @@
       for id, markerLayer of @markerLayers
         markerLayer.splice(oldRange.start, oldExtent, newExtent)

-    @changeCount++
+    @cachedText = null
     @emitDidChangeEvent(changeEvent)
     newRange

@@ -1529,6 +1527,7 @@
         @outstandingSaveCount--
         @setFile(file)
         @fileHasChangedSinceLastLoad = false
+        @digestWhenLastPersisted = @buffer.baseTextDigest()
         @loaded = true
         @emitModifiedStatusChanged(false)
         @emitter.emit 'did-save', {path: filePath}
@@ -1617,9 +1616,8 @@
     return null unless @isAlive() and oldRange? and patch?

     @fileHasChangedSinceLastLoad = false
-
-    serializedChanges = @serializedChanges
-    @serializedChanges = null
+    @digestWhenLastPersisted = @buffer.baseTextDigest()
+    @cachedText = null

     if @loaded and patch.getChangeCount() > 0
       if options?.clearHistory


Admin: Stacks: Added validation step for onboarding pages.

--- client/admin/lib/views/stacks/onboarding/onboardingview.coffee
+++ client/admin/lib/views/stacks/onboarding/onboardingview.coffee
@@ -66,11 +66,26 @@
     @nextButton = new kd.ButtonView
       cssClass  : 'solid green medium next'
       title     : 'Next'
-      callback  : => @emit 'PageNavigationRequested', 'next'
+      callback  : =>
+        @validatePageInteraction =>
+          @emit 'PageNavigationRequested', 'next'

     @skipLink   = new kd.CustomHTMLView
       cssClass  : 'skip-setup'
       partial   : 'Skip setup guide'
+
+
+  validatePageInteraction: (callback) ->
+
+    isCompleted = yes
+
+    switch @currentPage
+      when @providerSelectionView
+        unless @providerSelectionView.selected
+          new kd.NotificationView title: 'Please select a provider'
+          isCompleted = no
+
+    callback()  if isCompleted

   createStackPreview: ->


Added removetokens script

Added removetokens script


numFrames was constant, getting from Subject instead

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -626,12 +626,16 @@
   summarizeKnownObjects: ->
     objectsData = @Subject.current?.metadata?.known_objects
     seenKnowns = new Object
-    for frame in ['0001', '0002', '0003', '0004'] when objectsData[frame] isnt undefined
+    frameLabels = @getFrameLabels(@numFrames)
+    #when known objects were present
+    for frame in frameLabels when objectsData[frame] isnt undefined
+      #iterate
       for knownObject in [objectsData[frame]...]  when knownObject.good_known
         objectName = knownObject.object
+        #update if seen already
         if seenKnowns[objectName]
           @updateSeenObject(seenKnowns[objectName], knownObject)
-        else # first time seen
+        else # or save off the first time seen
           seenKnowns[objectName] = @firstSeen(knownObject)
       #if we found known object show message
       @knownAsteroidMessage.show()
@@ -647,6 +651,10 @@
       @evaluateAnnotations(seenKnown.P_ref)

   #helper method to summarizeKnownObjects
+  getFrameLabels: (numFrames) ->
+    frameLabels = ( "000#{i}" for i in [1..numFrames] by 1 )
+
+  #helper method to summarizeKnownObjects
   firstSeen:(knownObject) ->
     x = Math.round(knownObject.x)
     y = Math.round(knownObject.y)
@@ -663,7 +671,7 @@
   #helper method to summarizeKnownObjects
   averageSeenKnownPoints: (seenKnown) ->
     HALF_WINDOW_HEIGHT = 256
-    HALF_WINDOW_WIDTH =  190
+    SCALING_FACTOR =  190  #aribtirary value less than HALF_WINDOW_HEIGHT
     #average the values of x and y
     seenKnown.x = seenKnown.x_accum /seenKnown.counter
     seenKnown.y = seenKnown.y_accum /seenKnown.counter
@@ -671,8 +679,8 @@
     # grab the point reference before scale
     seenKnown.P_ref = x:  seenKnown.x, y:  seenKnown.y
     #apply scaling
-    seenKnown.x =  seenKnown.x/HALF_WINDOW_HEIGHT * HALF_WINDOW_WIDTH
-    seenKnown.y =  seenKnown.y/HALF_WINDOW_HEIGHT * HALF_WINDOW_WIDTH
+    seenKnown.x =  seenKnown.x/HALF_WINDOW_HEIGHT * SCALING_FACTOR
+    seenKnown.y =  seenKnown.y/HALF_WINDOW_HEIGHT * SCALING_FACTOR
     seenKnown

   appendMetadata: ->


done rotating, looks good in Zone of Danger (#46)

--- app/lib/surface/CocoSprite.coffee
+++ app/lib/surface/CocoSprite.coffee
@@ -255,7 +255,9 @@
     if @thangType.get('name') is 'Arrow'
       # scale the arrow so it appears longer when flying parallel to horizon
       angle = @getRotation()
-      scaleX = 0.5 + 0.5 * angleDiffFactorFrom90(angle)
+      angle = -angle if angle < 0
+      angle = 180 - angle if angle > 90
+      scaleX = 0.5 + 0.5 * (90 - angle) / 90
     scaleFactorX = @thang.scaleFactorX ? @scaleFactor
     scaleFactorY = @thang.scaleFactorY ? @scaleFactor
     @imageObject.scaleX = @originalScaleX * scaleX * scaleFactorX
@@ -273,29 +275,20 @@
     if @options.showInvisible
       @imageObject.alpha = Math.max 0.5, @imageObject.alpha

-  angleDiffFactorFrom90: (angle) ->
-    # how much horizontal is angle, which must be (-180, 180].
-    # 1 for parallel to horizon, 0 for orthogonal, monotonuous in between
-    angle = -angle if angle < 0
-    angle = 180 - angle if angle > 90
-    return (90 - angle) / 90
-
   updateRotation: (imageObject) ->
     rotationType = @thangType.get('rotationType')
     return if rotationType is 'fixed'
     rotation = @getRotation()
-    if False and @thangType.get('name') is 'Arrow'
-      # Makes arrow rotate properly when travelling upwards or downwards with z-coordinate.
-      # If it's travelling parallel to horizon, then the arc must be more visible.
-      # But when it's perpendicular, the arrow must be scaled, not rotated, to pretend
-      # it is getting closer to the camera.
+    if @thangType.get('name') is 'Arrow'
       velocity = @thang.velocity.z
-      originallyHeaded = 360 - @thang.initialRotation / Math.PI * 180
-      originallyHeaded -= 360 if originallyHeaded > 180
-      angleFactor = angleDiffFactorFrom90(originallyHeaded) # the more it is, the more deviation from original direction
-      speedLimit = 7 # for normalization purposes
-      velocityFactor = velocity / speedLimit # same as angleFactor
-      rotation = originallyHeaded + velocityFactor * angleFactor * 45
+      factor = rotation
+      factor = -factor if factor < 0
+      flip = 1
+      if factor > 90
+        factor = 180 - factor
+        flip = -1
+      factor = (90 - factor) / 90
+      rotation += flip * (velocity / 12) * factor * 45
     imageObject ?= @imageObject
     return imageObject.rotation = rotation if not rotationType
     @updateIsometricRotation(rotation, imageObject)


fixing for iOS

fixing for iOS

--- app/coffee/metamenu.coffee
+++ app/coffee/metamenu.coffee
@@ -7,11 +7,18 @@
   settingsPanel = new SettingsPanel('.settingsPanel')

   $.subscribe('toggleSettings', (event) ->
+    appearancePanel.contract()
+    settingsPanel.contract()
+
     settingsPanel.toggleView()
   )
-
-  $('.settingsPanel .trigger').first().click( (event)->
-    $.publish('hidePanels');
+
+  # iOS Device Fix
+  # Checks if the UserAgent is a iOS device
+  ua = navigator.userAgent
+  clickEvent = if (ua.match(/iPad/i)) then "touchstart" else "click"
+
+  $('.settingsPanel .trigger').first().bind( clickEvent, (event)->
     $.publish('toggleSettings', [this])
   )

@@ -19,15 +26,16 @@
   appearancePanel.displayBedsheets()

   $.subscribe('toggleAppearance', (event) ->
+    appearancePanel.contract()
+    settingsPanel.contract()
+
     appearancePanel.toggleView()
   )
-
-  $('.appearancePanel .trigger').first().click( (event)->
-    $.publish('hidePanels');
+
+  $('.appearancePanel .trigger').first().bind( clickEvent, (event)->
     $.publish('toggleAppearance', [this])
   )

-  # close all panels listener
   $.subscribe('hidePanels', (event) ->
     # close all .panel objects
     appearancePanel.contract()


Remove unused /certificates route

Remove unused /certificates route

--- app/core/Router.coffee
+++ app/core/Router.coffee
@@ -228,7 +228,6 @@
     'test(/*subpath)': go('TestView')

     'user/:slugOrID': go('user/MainUserView')
-    'certificates': go('user/CertificatesView')
     'certificates/:slugOrID': go('user/CertificatesView')
     'certificates/:id/anon': go('user/AnonCertificatesView')


new Utils for switching output to markdown or HTML depending on dest ext, and extracting and transforming repo URL from package.json

--- src/utils/utils.coffee
+++ src/utils/utils.coffee
@@ -1,4 +1,5 @@
 Handlebars = require('../helpers/helpers').Handlebars
+
 fs    = require 'fs'
 path  = require 'path'
 grunt = require "grunt"
@@ -14,6 +15,9 @@
 Utils.safeString = (str) ->
   new Handlebars.SafeString str

+Utils.escapeExpression = (str) ->
+  Handlebars.Utils.escapeExpression
+
 Utils.trim = (str) ->
   trim = if /\S/.test("\xA0") then /^[\s\xA0]+|[\s\xA0]+$/g else /^\s+|\s+$/g
   str.toString().replace trim, ''
@@ -24,12 +28,16 @@
     func.apply(this, args)

+# Convenience for extracting repo url from package.json
+Utils.repoUrl = (str) ->
+  pkg = grunt.file.readJSON("./package.json")
+  url = pkg.repository.url
+  str = url.replace(/.*:\/\/github.com\/(.*?)(?:\.git|$)/, str)
+
 ###
 # Detect and return the indentation.
-#
-# @param  {String} string
-#
-# @return {Mixed} Indentation used, or undefined.
+# param  {String} string
+# return {Mixed} Indentation used, or undefined.
 ###
 Utils.detectIndentation = (string) ->
   tabs = string.match(/^[\t]+/g) or []
@@ -57,9 +65,9 @@
   base     = path.basename(base, path.extname(fullName))

 Utils.getRelativePath = (from, to) ->
-  fromDirname = path.normalize(path.dirname(from))
-  toDirname = path.normalize(path.dirname(to))
-  toBasename = path.basename(to)
+  fromDirname  = path.normalize(path.dirname(from))
+  toDirname    = path.normalize(path.dirname(to))
+  toBasename   = path.basename(to)
   relativePath = path.relative(fromDirname, toDirname)
   Utils.urlNormalize(path.join(relativePath, toBasename))

@@ -77,8 +85,21 @@
     else
       "other"

-Utils.readBasedOnType = (ext) ->
-  ext = options.ext
+Utils.toggleOutput = (ext, md, html) ->
+  if ext is ''
+    output = md
+  else
+    output = html
+
+Utils.switchOutput = (ext, md, html) ->
+  switch ext
+    when "", ".md"
+      output = md
+    when ".html", ".htm"
+      output = html
+  output
+
+Utils.switchType = (ext) ->
   reader = grunt.file.readJSON
   switch ext
     when ".json"
@@ -149,6 +170,7 @@
 Utils.normalizelf = (str) ->
   src = grunt.util.normalizelf(str)

+
 # Ensures that a url path is returned instead
 # of a filesystem path.
 Utils.urlNormalize = (filepath) ->
@@ -157,4 +179,3 @@

-


Better admin debugging global

Better admin debugging global

--- app/core/initialize.coffee
+++ app/core/initialize.coffee
@@ -65,7 +65,7 @@
   handleNormalUrls()
   setUpMoment() # Set up i18n for moment
   installVueI18n()
-  window.globalVar = globalVar if me.isAdmin() or !app.isProduction()
+  window.globalVar = globalVar if me.isAdmin() or !app.isProduction() or serverSession?.amActually
   parent.globalVar = globalVar if self != parent

 module.exports.init = init


making the forms actually work...

making the forms actually work...

Also, providing a method call to dynamically create the form

--- src/views/forms.coffee
+++ src/views/forms.coffee
@@ -11,7 +11,11 @@
     if options.renderFunc
       @renderFunc = options.renderFunc

+  getForm: (context) ->
+    return @form
+
   renderForm: (context={}) ->
+    form = @getForm(context)
     html = form.toHTML(renderFunc)
     @$el.html html


Fix internal project links

Fix internal project links

--- app/pages/project.cjsx
+++ app/pages/project.cjsx
@@ -16,26 +16,28 @@
     document.documentElement.classList.remove 'on-project-page'

   render: ->
+    projectRoute = "/projects/#{@props.project.id}"
+

-        <Link href="/projects/#{@props.project.display_name}" root={true} className="home tabbed-content-tab">
+        <Link href={projectRoute} root={true} className="home tabbed-content-tab">

{@props.project.display_name}

         </Link>
-        <Link href="/projects/#{@props.project.display_name}/science" className="tabbed-content-tab">Science</Link>
-        <Link href="/projects/#{@props.project.display_name}/status" className="tabbed-content-tab">Status</Link>
-        <Link href="/projects/#{@props.project.display_name}/team" className="tabbed-content-tab">Team</Link>
-        <Link href="/projects/#{@props.project.display_name}/classify" className="classify tabbed-content-tab">Classify</Link>
-        <Link href="/projects/#{@props.project.display_name}/talk" className="tabbed-content-tab"></Link>
+        <Link href="#{projectRoute}/science" className="tabbed-content-tab">Science</Link>
+        <Link href="#{projectRoute}/status" className="tabbed-content-tab">Status</Link>
+        <Link href="#{projectRoute}/team" className="tabbed-content-tab">Team</Link>
+        <Link href="#{projectRoute}/classify" className="classify tabbed-content-tab">Classify</Link>
+        <Link href="#{projectRoute}/talk" className="tabbed-content-tab"></Link>

-        <Route path="/projects/#{@props.project.display_name}" className="project-home-content">
+        <Route path="#{projectRoute}" className="project-home-content">

             <Markdown className="introduction">{@props.project.introduction}</Markdown>

-              Get started
+              Get started

@@ -44,29 +46,29 @@
           </Markdown>
         </Route>

-        <Route path="/projects/#{@props.project.display_name}/science" className="project-text-content content-container">
+        <Route path="#{projectRoute}/science" className="project-text-content content-container">
           <Markdown>
             {@props.project.science_case}
           </Markdown>
         </Route>

-        <Route path="/projects/#{@props.project.display_name}/status" className="project-text-content content-container">
+        <Route path="#{projectRoute}/status" className="project-text-content content-container">

         </Route>

-        <Route path="/projects/#{@props.project.display_name}/team" className="project-text-content content-container">
+        <Route path="#{projectRoute}/team" className="project-text-content content-container">

Who’s in charge of this project? What organizations are behind it?

         </Route>

-        <Route path="/projects/#{@props.project.display_name}/classify" className="classify-content content-container">
+        <Route path="#{projectRoute}/classify" className="classify-content content-container">
           <ClassifyPage project={@props.project} />
         </Route>

-        <Route path="/projects/#{@props.project.display_name}/talk" className="project-text-content content-container">
+        <Route path="#{projectRoute}/talk" className="project-text-content content-container">

Discussion boards this project


Do not save all preferences when each Ace instance created again and again.

--- client/landing/app/Applications/Ace.kdapplication/ace.coffee
+++ client/landing/app/Applications/Ace.kdapplication/ace.coffee
@@ -39,15 +39,15 @@
     @setSyntax()
     @setEditorListeners()
     @appStorage.fetchStorage (storage)=>
-      @setUseSoftTabs         @appStorage.getValue('useSoftTabs')         ? yes
-      @setShowGutter          @appStorage.getValue('showGutter')          ? yes
-      @setUseWordWrap         @appStorage.getValue('useWordWrap')         ? no
-      @setShowPrintMargin     @appStorage.getValue('showPrintMargin')     ? no
-      @setHighlightActiveLine @appStorage.getValue('highlightActiveLine') ? yes
-      @setShowInvisibles      @appStorage.getValue('showInvisibles')      ? no
-      @setSoftWrap            @appStorage.getValue('softWrap')            or 'off'
-      @setFontSize            @appStorage.getValue('fontSize')            ? 12
-      @setTabSize             @appStorage.getValue('tabSize')             ? 4
+      @setUseSoftTabs         @appStorage.getValue('useSoftTabs')         ? yes    ,no
+      @setShowGutter          @appStorage.getValue('showGutter')          ? yes    ,no
+      @setUseWordWrap         @appStorage.getValue('useWordWrap')         ? no     ,no
+      @setShowPrintMargin     @appStorage.getValue('showPrintMargin')     ? no     ,no
+      @setHighlightActiveLine @appStorage.getValue('highlightActiveLine') ? yes    ,no
+      @setShowInvisibles      @appStorage.getValue('showInvisibles')      ? no     ,no
+      @setSoftWrap            @appStorage.getValue('softWrap')            or 'off' ,no
+      @setFontSize            @appStorage.getValue('fontSize')            ? 12     ,no
+      @setTabSize             @appStorage.getValue('tabSize')             ? 4      ,no

   setEditorListeners:->

@@ -189,13 +189,6 @@

   setContents:(contents)-> @editor.getSession().setValue contents

-  setTheme:(themeName)->
-    themeName or= @appStorage.getValue('theme') or 'merbivore_soft'
-    require ["ace/theme/#{themeName}"], (callback) =>
-      @editor.setTheme "ace/theme/#{themeName}"
-      @appStorage.setValue 'theme', themeName, =>
-        callback
-
   setSyntax:(mode)->

     file = @getData()
@@ -212,49 +205,65 @@
       @editor.getSession().setMode new Mode
       @syntaxMode = mode

-  setUseSoftTabs:(value)->
+  setTheme:(themeName, save = yes)->
+    themeName or= @appStorage.getValue('theme') or 'merbivore_soft'
+    require ["ace/theme/#{themeName}"], (callback) =>
+      @editor.setTheme "ace/theme/#{themeName}"
+      return  unless save
+      @appStorage.setValue 'theme', themeName, =>
+        callback
+
+  setUseSoftTabs:(value, save = yes)->

     @editor.getSession().setUseSoftTabs value
+    return  unless save
     @appStorage.setValue 'useSoftTabs', value

-  setShowGutter:(value)->
+  setShowGutter:(value, save = yes)->

     @editor.renderer.setShowGutter value
+    return  unless save
     @appStorage.setValue 'showGutter', value

-  setShowPrintMargin:(value)->
+  setShowPrintMargin:(value, save = yes)->

     @editor.setShowPrintMargin value
+    return  unless save
     @appStorage.setValue 'showPrintMargin', value

-  setHighlightActiveLine:(value)->
+  setHighlightActiveLine:(value, save = yes)->

     @editor.setHighlightActiveLine value
+    return  unless save
     @appStorage.setValue 'highlightActiveLine', value

   # setHighlightSelectedWord:(value)-> @editor.setHighlightActiveLine value

-  setShowInvisibles:(value)->
+  setShowInvisibles:(value, save = yes)->

     @editor.setShowInvisibles value
+    return  unless save
     @appStorage.setValue 'showInvisibles', value

-  setFontSize:(value, store = yes)->
+  setFontSize:(value, save = yes)->

     @$("#editor#{@getId()}").css 'font-size', "#{value}px"
-    if store
-      @appStorage.setValue 'fontSize', value
-
-  setTabSize:(value)->
+    return  unless save
+    @appStorage.setValue 'fontSize', value
+
+  setTabSize:(value, save = yes)->
+
     @editor.getSession().setTabSize +value
+    return  unless save
     @appStorage.setValue 'tabSize', value

-  setUseWordWrap:(value)->
+  setUseWordWrap:(value, save = yes)->

     @editor.getSession().setUseWrapMode value
+    return  unless save
     @appStorage.setValue 'useWordWrap', value

-  setSoftWrap:(value)->
+  setSoftWrap:(value, save = yes)->
     softWrapValueMap =
       'off'  : [ null, 80 ]
       '40'   : [ 40,   40 ]
@@ -267,6 +276,7 @@
     @editor.renderer.setPrintMarginColumn margin
     @setUseWordWrap no if value is "off"

+    return  unless save
     @appStorage.setValue 'softWrap', value

   focus: -> @editor?.focus()


client: landing: make-top-nav-Login-link-work

--- client/landing/site.landing/coffee/core/topnavigation.coffee
+++ client/landing/site.landing/coffee/core/topnavigation.coffee
@@ -39,8 +39,10 @@
   createItem: (options) ->

     options.cssClass = options.name.toLowerCase()
+    link             = new CustomLinkView options
+    link.setAttribute 'target', '_self'

-    @addSubView @menu[options.name] = new CustomLinkView options
+    @addSubView @menu[options.name] = link

   setActiveItem: (pane) ->


Disable create-workflow button for live projects

--- app/pages/lab/project.cjsx
+++ app/pages/lab/project.cjsx
@@ -83,7 +83,7 @@
                   <ChangeListener key={workflow.id} target={workflow} eventName="save" handler={renderWorkflowListItem.bind this, workflow} />}

                 <li className="nav-list-item">
-                  <button type="button" onClick={@createNewWorkflow} disabled={@state.workflowCreationInProgress} title="A workflow is the sequence of tasks that you’re asking volunteers to perform.">
+                  <button type="button" onClick={@createNewWorkflow} disabled={@props.project.live or @state.workflowCreationInProgress} title="A workflow is the sequence of tasks that you’re asking volunteers to perform.">
                     New workflow{' '}
                     <LoadingIndicator off={not @state.workflowCreationInProgress} />
                   </button>{' '}


Run redeye tests in fibers

Run redeye tests in fibers

--- test/support/redeye_suite.coffee
+++ test/support/redeye_suite.coffee
@@ -77,7 +77,7 @@
         @dispatcher = dispatcher.run @opts
         @dispatcher.connect =>
           @queue.run => @expect.apply this
-          setTimeout (=> @setup.apply this), 100
+          setTimeout (=> (@fiber = Fiber => @setup.apply this).run()), 100
           @timeout = setTimeout (=> @die()), 5000

   # Forcefully quit the test
@@ -91,6 +91,7 @@
     clearTimeout @timeout
     @_kv.end()
     @_pubsub.end()
+    delete @fiber

   # Send a request to the correct `requests` channel
   request: (args...) ->
@@ -99,7 +100,10 @@

   set: (args..., value) ->
     key = args.join consts.arg_sep
-    @_kv.set key, value
+    @_kv.del key, =>
+      @_kv.set key, value, =>
+        @fiber.run()
+    yield()

   # Look up and de-jsonify a value from redis
   get: (args..., callback) ->


Smaller/better poi titles

Smaller/better poi titles

--- storage/lib/poi.coffee
+++ storage/lib/poi.coffee
@@ -9,7 +9,7 @@
         .replace(/ {2,}/g, ' ')
         .replace(/[^a-zA-Z0-9;:?!,.-=_\/@$%*+()<> |\n]/g, '')

-getType = (gc) ->
+type = (gc) ->
     switch gc.CacheType.GeocacheTypeId
         when   2 then 'T'
         when   3 then 'M'
@@ -18,24 +18,28 @@
         when 137 then 'E'
         else '?'

-getCode = (gc) ->
+code = (gc) ->
     gc.Code[2..-1]

-getSkill = (gc) ->
+skill = (gc) ->
     "#{gc.Difficulty}/#{gc.Terrain}"

-getSize = (gc) ->
+size = (gc) ->
     gc.ContainerType.ContainerTypeName[0]

-getName = (gc) ->
+updated = (gc) ->
+    d = gc.meta.updated.toISOString()
+    d[5..6] + d[8..9]
+
+name = (gc) ->
     clean gc.Name

-getHint = (gc) ->
+hint = (gc) ->
     clean gc.EncodedHints ? ''

 module.exports =
     title: (gc) ->
-        "#{getCode gc} #{getSize gc} #{getType gc} #{getSkill gc}"
+        "#{size gc}#{type gc} #{skill gc} #{updated gc}"

     description: (gc) ->
-        "#{getName gc}\n#{getHint gc}"[0...100]
+        "#{code gc} #{name gc}\n#{hint gc}"[0...100]


Fix checking for already existing box on creation

--- code/serv.coffee
+++ code/serv.coffee
@@ -81,7 +81,7 @@
   new User({apikey: req.body.apikey}).save()
   User.findOne {apikey: req.body.apikey}, (err, user) ->
     return res.send {error: "User not found" }, 404 unless user?
-    Box.findOne {name: 'newdatabox'}, (err, box) ->
+    Box.findOne {name: req.params.box_name}, (err, box) ->
       if box
         res.send {error: "Box already exists"}
       else


Update tooltip.coffee

Update tooltip.coffee

Learning coffeescript

--- vendor/assets/javascripts/mice/tooltip.coffee
+++ vendor/assets/javascripts/mice/tooltip.coffee
@@ -8,37 +8,96 @@

 (($) ->

+
   maybeCall = (thing, ctx) ->
     if (typeof thing == 'function') then thing.call(ctx) else thing

+
   isElementInDOM = (element) ->
-    (if element == document then true) while element = element.parentNode
+    (return true if element == document) while element = element.parentNode
     false

-  class Tooltip
+
+  class Tipsy
     constructor: (@element, @options) ->
-      @enabled = true
+      @.$element = $(@element)
+      @.options = @options
+      @.enabled = true
       @.fixTitle()

     show: ->
-      title = @.getTitle
-      if title and @enabled
+      title = @.getTitle()
+      if title and @.enabled
         $tip = @.tip()

         $tip.find('.tipsy-inner')[if @options.html then 'html' else 'text'](title)
+        $tip[0].className = 'tipsy'
+        $tip.remove().css({ top: 0, left:0, visibility: 'hidden', display: 'block'}).prependTo(document.body)
+
+        pos = $.extend({}, @.$element.offset(), {
+          width: @.$element[0].offsetWidth,
+          height: @.$element[0].offsetHeight
+        });
+
+        actualWidth = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+        gravity = maybeCall @.options.gravity, @.$element[0]
+
+        switch gravity.chartAt(0)
+          when 'n'
+            tp = {top: pos.top + pos.height + @.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+          when 's'
+            tp = {top: pos.top - actualHeight - @.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
+          when 'e'
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - @.options.offset};
+          when 'w'
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + @.options.offset};
+
+        if gravity.length == 2
+          if gravity.chartAt(1) == 'w' then (tp.left = pos.left + pos.width / 2 - 15) else (tp.left = pos.left + pos.width / 2 - actualWidth + 15)
+
+        $tip.css(tp).addClass('tipsy-' + gravity)
+        $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.chartAt(0)
+        if @.options.className then $tip.addClass(maybeCall @.options.className, @.$element[0])
+
+        if @.options.fade
+          $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: @.options.opacity});
+        else
+          $tip.css({visibility: 'visible', opacity: @.options.opacity});
+
+      return

     hide: ->
+      if @.options.fade then @.tip().stop().fadeOut(-> $(@).remove()) else @.tip().remove()

     fixTitle: ->
+      if @.$element.attr('title') or typeof(@.$element.attr('original-title')) != 'string'
+        @.$element.attr('original-title', @.$element.attr('title') || '').removeAttr('title')

     getTitle: ->
+      @.fixTitle()
+      if typeof @.options.title == 'string'
+        title = @.$element.attr( if @.options.title == 'title' then 'original-title' else @.options.title)
+      else if typeof @.options.title == 'function'
+        title = @.options.title.call(@.$element[0])
+
+      title = ('' + title).replace(/(^\s*|\s*$)/, "")
+      title or @.options.fallback

     tip: ->
+      if !@.$tip
+        @.$tip = $('

').html('

');
+        @.$tip.data('tipsy-pointee', @.$element[0]);
+      @.$tip;

     validate: ->
+      if !@.$element[0].parentNode
+        @.hide();
+        @.$element = null;
+        @.options = null;

     enable: ->
-      @enabled = true
+      @.enabled = true

     disable: ->
       @.enabled = false
@@ -46,5 +105,102 @@
     toggleEnabled: ->
       @.enabled = !@.enabled

+  $.fn.tipsy = (options) ->
+    if options == true
+      return @.data('tipsy')
+    else if typeof options == 'string'
+      tipsy = @.data('tipsy')
+      if tipsy then tipsy[options]()
+      return @
+
+    options = $.extend({}, $.fn.tipsy.default, options)
+
+    get = (element) ->
+      tipsy = $.data(element, 'tipsy')
+      if !tipsy
+        tipsy = new Tipsy(element, $.fn.tipsy.elementOptions(element, options))
+        $.data(element, tipsy, tipsy)
+      return tipsy
+
+    enter = ->
+      tipsy = get @
+      tipsy.hoverState = 'in'
+      if options.delayIn == 0
+        tipsy.show()
+      else
+        tipsy.fixTitle()
+        setTimeout((-> tipsy.show() if tipsy.hoverState == 'in' ), options.delayIn)
+
+    leave = ->
+      tipsy = get @
+      tipsy.hoverState = 'out'
+      if options.delayOut == 0
+        tipsy.hide()
+      else
+        setTimeout((-> tipsy.hide() if tipsy.hoverState == 'out' ), options.delayOut)
+
+    @.each( -> get @ ) if !options.live
+
+    if options.trigger != 'manual'
+      binder  = if options.live then 'live' else 'bind'
+      eventIn = if options.trigger == 'hover' then 'mouseenter' else 'focus'
+      eventOut= if options.trigger == 'hover' then 'mouseleave' else 'blur'
+      @[binder](eventIn, enter)[binder](eventOut, leave)
+
+    @
+
+
+  $.fn.tipsy.defaults = {
+    className: null
+    delayIn: 0
+    delayOut: 0
+    fade: false
+    fallback: ''
+    gravity: 's'
+    html: false
+    live: false
+    offset: 0
+    opacity: 0.8
+    title: 'title'
+    trigger: 'hover'
+  };
+
+
+  $.fn.tipsy.revalidate = ->
+    $('.tipsy').each ->
+      pointee = $.data(@, 'tipsy-pointee')
+      $(@).remove() if (!pointee || !isElementInDOM(pointee))
+
+
+  $.fn.tipsy.elementOptions = (ele, options) ->
+    if $.metadata then $.extend({}, options, $(ele).metadata()) else options
+
+
+  $.fn.tipsy.autoNS = ->
+    if $(@).offset().top > ($(document).scrollTop() + $(window).height() / 2) then 's' else 'n'
+
+
+  $.fn.tipsy.autoWE = ->
+    if $(@).offset().left > ($(document).scrollLeft() + $(window).width() / 2) then 'e' else 'w'
+
+
+  $.fn.tipsy.autoBounds = (margin, prefer) ->
+    ->
+      dir = { ns: prefer[0], ew:(if prefer.length > 1 then prefer[1] else false)}
+      boundTop = $(document).scrollTop + margin
+      boundLeft = $(document).scrollLeft + margin
+
+      $this = $(@)
+
+      dir.ns = 'n' if ($this.offset().top < boundTop)
+      dir.ew = 'w' if ($this.offset().left < boundLeft)
+      dir.ew = 'e' if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin)
+      dir.ns = 's' if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin)
+
+      dir.ns + (if dir.ew then dir.ew else '')
+
+
+  return
+

 ) jQuery


Remove unused function

Remove unused function

--- plugins/dotstorm/test/export.spec.coffee
+++ plugins/dotstorm/test/export.spec.coffee
@@ -5,19 +5,6 @@
 async  = require("async")
 _ = require("underscore")
 require "better-stack-traces"
-
-difference = (expected, actual) ->
-  ret = {}
-  for name in expected
-    if name in actual
-      if _.isObject(actual[name]) and not _.isArray(actual[name])
-        diff = difference(expected[name], actual[name])
-        if not _.isEmpty(diff)
-          ret[name] = diff
-        else if not _.isEqual expected[name], actual[name]
-          ret[name] = actual[name]
-  return ret
-

 describe "exports", ->
   before (done) ->


add missed translation

add missed translation

--- src/components/widgets/sales-summary/sales-summary.directive.coffee
+++ src/components/widgets/sales-summary/sales-summary.directive.coffee
@@ -20,7 +20,7 @@
     $scope.chartDeferred.promise
   ]

-  $scope.datesPickerTemplate = "<from-date> to <to-date> <apply>"
+  $scope.datesPickerTemplate = "<from-date>" + $translate.instant('impac.widget.sales_summary.to') + " <to-date> <apply>"

   # Widget specific methods
   # --------------------------------------


Remove unused an obsolete reference to leafletData

--- app/scripts/mapctrl.coffee
+++ app/scripts/mapctrl.coffee
@@ -1,6 +1,6 @@
 angular.module('taarifaWaterpointsApp')

-  .controller 'DashMapCtrl', ($scope, $http, $q, leafletData, $timeout, modalSpinner, waterpointStats, Waterpoint) ->
+  .controller 'DashMapCtrl', ($scope, $http, $q, $timeout, modalSpinner, waterpointStats, Waterpoint) ->

     $scope.hoverText = ""
     $scope.choroChoice = "percFun"


utf8 conversion

utf8 conversion

--- gulp/compress-build.coffee
+++ gulp/compress-build.coffee
@@ -1,8 +1,10 @@
 gulp = require 'gulp'
 gzip = require 'gulp-gzip'
+utf8 = require 'gulp-utf8-convert'

 module.exports = (paths, options = {})->
   return (cb) ->
     gulp.src "#{paths.build.root}/**/**/**/**/**/**/**/*.{js,css,html}"
+      .pipe utf8()
       .pipe gzip({ gzipOptions: { level: 9 }})
       .pipe gulp.dest(paths.build.root)


Resolve nan error

Resolve nan error

--- app/assets/javascripts/backend/purchase_invoices.js.coffee
+++ app/assets/javascripts/backend/purchase_invoices.js.coffee
@@ -17,11 +17,11 @@
         vatRate = $(item).find('.total-column label.vat-rate').text().split("%")[0]
         amountIncludingTaxes = $(item).find('.total-column label.amount-including-taxes').text()

-        totalAmountExcludingTaxes += parseFloat(amountExcludingTaxes)
-        totalAmountIncludingTaxes += parseFloat(parseFloat(amountIncludingTaxes).toFixed(2))
+        totalAmountExcludingTaxes = parseFloat(totalAmountExcludingTaxes + amountExcludingTaxes).toFixed(2)
+        totalAmountIncludingTaxes = parseFloat(totalAmountIncludingTaxes + parseFloat(amountIncludingTaxes)).toFixed(2)

         calculVatRate = parseFloat(parseFloat(amountExcludingTaxes) * parseFloat(vatRate) / 100).toFixed(2)
-        totalVatRate += parseFloat(calculVatRate)
+        totalVatRate = parseFloat(totalVatRate + calculVatRate).toFixed(2)

         selectedVatValue = $(item).parent().find('.nested-item-form select.invoice-vat-total option:selected').val()
         $(item).find('.vat-rate').attr('data-selected-value', selectedVatValue)


JSTack: Use client.connection.delegate instead client.context to get username

--- workers/social/lib/social/models/stack.coffee
+++ workers/social/lib/social/models/stack.coffee
@@ -54,6 +54,7 @@
       callback null, stacks or []

   @getStackId = (selector, callback)->
+
     @getStack selector, (err, stack)->
       callback err, stack?.getId()

@@ -79,12 +80,16 @@

     success: (client, callback)->

-      {user, group} = client.context
+      {group} = client.context
+      user    = client.connection.delegate.profile.nickname
+
       @getStack {user, group}, callback

   @getStacks$ = permit 'get stacks',

     success: (client, callback)->

-      {user, group} = client.context
+      {group} = client.context
+      user    = client.connection.delegate.profile.nickname
+
       @getStacks {user, group}, callback


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -1,6 +1,6 @@
 ###*
 The Sprite class provides a way to load images for use in games.
-The Text Effect class
+The Text Effect class provide

 By default, images are loaded asynchronously. A proxy object is
 returned immediately. Even though it has a draw method it will not


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -8,7 +8,7 @@
     centerText: (canvas, options={}) ->
       font = options.font || I.font
       size = options.size || I.fontSize
-      color = options
+      color = options.
       canvas.font "#{size}px #{font}"

       canvas.centerText


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -5,7 +5,7 @@
     fontColor: 'white'

   self = GameState(I).extend
-    centerText: (canvas, option) ->
+    centerText: (canvas, options={}) ->
       canvas.font I.font

       canvas.centerText


Change html margin when the sidebar opens to compensate for change in

--- app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
+++ app/js/jail_iframe/initializers/scrollbarSuppressor.coffee
@@ -20,8 +20,18 @@
 # To check for scrollbars on the window,  use the slightly unusual window.innerHeight
 # rather than  document.documentElement.clientHeight so it works in css compat mode.
 window_has_scrollbar = -> window.innerHeight < document.documentElement.scrollHeight
+
+saved_marginRight = null
+
 FactlinkJailRoot.on 'modalOpened', ->
   if scrollbarWidth > 0 && window_has_scrollbar()
     console.log 'has scrollbar!'
+    saved_marginRight = document.documentElement.style.marginRight
+    document.documentElement.style.marginRight = scrollbarWidth + 'px'
+
   document.documentElement.setAttribute('data-factlink-suppress-scrolling', '')
-FactlinkJailRoot.on 'modalClosed', -> document.documentElement.removeAttribute('data-factlink-suppress-scrolling')
+FactlinkJailRoot.on 'modalClosed', ->
+  if saved_marginRight != null
+    document.documentElement.style.marginRight = saved_marginRight
+
+  document.documentElement.removeAttribute('data-factlink-suppress-scrolling')


Changed document store load to return content of json payload instead of entire stringified payload

--- src/code/providers/document-store-provider.coffee
+++ src/code/providers/document-store-provider.coffee
@@ -171,7 +171,7 @@
       xhrFields:
         withCredentials: true
       success: (data) ->
-        callback null, JSON.stringify data
+        callback null, data.content
       error: ->
         callback "Unable to load "+metadata.name


add destroy method

add destroy method

--- lib/status/php-debug-console-status-view.coffee
+++ lib/status/php-debug-console-status-view.coffee
@@ -9,8 +9,7 @@

   constructor: (statusBar, @phpDebug) ->
     super
-    statusBar.addLeftTile(item: @element, priority: -99)
-
+    @tile = statusBar.addLeftTile(item: @element, priority: -99)

   toggleConsole: ->
     @phpDebug.toggleConsole()
@@ -20,3 +19,6 @@
       @element.className = 'php-debug-console-view active'
     else
       @element.className = 'php-debug-console-view'
+
+  destroy: ->
+    @tile?.destroy?()


fix(bower): get installed bower pkg version from pkg's .bower.json

Bower deprecated specifying version in pkg's bower.json. It now pulls the version from pkg's git tag. This fixes unnecessary bower pkg installs.

--- src/helpers/bower.coffee
+++ src/helpers/bower.coffee
@@ -166,11 +166,9 @@

 			installed:
 				pkg: (pkg, loc='rb') ->
-					_path = path.join(
-								config.src[loc].client.bower.dir
-								pkg
-								config.bower[loc].file
-							)
+					dir   = config.src[loc].client.bower.dir
+					file  = ".#{config.bower[loc].file}"
+					_path = path.join dir, pkg, file
 					fileHelp.exists _path
 		get:
 			json:
@@ -181,13 +179,11 @@
 					pkg: (pkg, loc='rb') ->
 						return unless me.has.installed.pkg pkg, loc
 						# console.log pkg, loc
-						require(
-							path.join(
-								config.src[loc].client.bower.dir
-								pkg
-								config.bower[loc].file
-							)
-						)
+						dir   = config.src[loc].client.bower.dir
+						file  = ".#{config.bower[loc].file}"
+						_path = path.join dir, pkg, file
+						bowerJson = require _path
+
 			pkgs:
 				from:
 					appOrRb: (loc='rb') ->
@@ -240,7 +236,7 @@
 					for own pkg, version of jPkgs
 						missing = true
 						iPkgs.forEach (v) ->
-							missing = false if v.name is pkg
+							missing = false if v.name.toLowerCase() is pkg.toLowerCase()
 						pkgs.push "#{pkg}##{version}" if missing
 					pkgs


[FIX] Remove cruft

[FIX] Remove cruft

--- app/views/main-view.coffee
+++ app/views/main-view.coffee
@@ -13,5 +13,5 @@

   m 'main', {'role': 'main'}, [
     m JumboTron, site
-    m Album, Object.assign site
+    m Album, site
   ]


array pretty

array pretty

--- src/util/pretty.coffee
+++ src/util/pretty.coffee
@@ -164,7 +164,7 @@
     key   = (k) -> if (k == "" + +k) or k.match /^[A-Za-z_][A-Za-z0-9]*$/ then k else JSON.stringify k
     wrap  = (v) -> if v.match '\n*"' then v else "{#{v}}"
     value = (v) ->
-      return "[#{v.map(value).join ','}]" if v instanceof Array
+      return "[#{v.map(value).join ', '}]" if v instanceof Array

       switch typeof v
               when 'string'
@@ -177,11 +177,9 @@
               when 'number'
                 formatNumber v
               else
-                if v?
+                if v? and v != !!v
                   if v._up?     then return value v.map (v) -> v
                   if v.toMarkup then return v.toString()
-                  if v instanceof Array
-                    "[" + (value vv for vv in v).join(", ") + "]"
                   else
                     "{" + ("#{key kk}: #{value vv}" for kk, vv of v).join(", ") + "}"
                 else


new utils for ascyn read, sync read

new utils for ascyn read, sync read

--- src/utils/utils.coffee
+++ src/utils/utils.coffee
@@ -1,24 +1,34 @@
 Handlebars = require('../helpers/helpers').Handlebars
+
+# Node deps
 fs         = require 'fs'
 path       = require 'path'
+
+

 Utils          = module.exports = {}
 Utils.toString = Object.prototype.toString

+Utils.getExtname = (str) ->
+    extname = path.extname(str)
+    str = extname  if extname
+    str = str.substring(1)  if str[0] is "."
+    str

-Utils.read = (source, callback) ->
-  fs.exists source, (exist) ->
-    if exist
-      fs.readFile source, "utf8", (err, result) ->
-        return callback(err)  if err
-        callback null, textProcess(result)
-    else
-      callback null
+Utils.read = (source) ->
+    source = path.normalize(source)
+    result = fs.readFileSync(source, "utf8")
+
+Utils.readFile = (source) ->
+    source = path.normalize(source)
+    result = fs.readFileSync(source, "utf8")

 Utils.readSync = (source) ->
     result = fs.readFileSync(source, "utf8")
-    textProcess result  if result
+
+Utils.readStream = (source) ->
+    result = fs.createReadStream(source, "utf8")

 Utils.isUndefined = (value) ->
     value is 'undefined' or Utils.toString.call(value) is '[object Function]' or value.hash?
@@ -30,5 +40,12 @@
     trim = if /\S/.test("\xA0") then /^[\s\xA0]+|[\s\xA0]+$/g else /^\s+|\s+$/g
     str.toString().replace trim, ''

+Utils.resolvePath = (from, to) ->
+    from = path.resolve(__dirname, from)
+    to = path.resolve(__dirname, to)
+    path.result(from, to)
+
+
 Utils.urlNormalize = (urlString) ->
-  urlString.replace /\\/g, '/'
+    urlString.replace /\\/g, '/'
+


reactivity: embedbox in edit mode: avoid useless embedly requests if url is not changed

--- client/activity/lib/components/chatinputembedextractor/index.coffee
+++ client/activity/lib/components/chatinputembedextractor/index.coffee
@@ -19,8 +19,13 @@

   onChange: (value) ->

+    url = embedlyHelpers.extractUrl value
+
+    # if url is not changed and it is already processed, do nothing
+    return  if url is @previousUrl and not @timer
+
     kd.utils.killWait @timer  if @timer
-    url = embedlyHelpers.extractUrl value
+    @timer = null

     if url is @previousUrl or not url
       @editEmbedPayload url