Fixing Angular Specs for line items controller

--- spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee
+++ spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee
@@ -26,287 +26,304 @@
     order = { id: 9, order_cycle: { id: 4 }, distributor: { id: 5 }, number: "R123456" }
     lineItem = { id: 7, quantity: 3, order: { id: 9 }, supplier: { id: 1 } }

-    httpBackend.expectGET("/admin/orders.json?q%5Bcompleted_at_not_null%5D=true&q%5Bcreated_at_gt%5D=SomeDate&q%5Bcreated_at_lt%5D=SomeDate&q%5Bstate_not_eq%5D=canceled").respond [order]
+    httpBackend.expectGET("/admin/orders.json?q%5Bcompleted_at_gt%5D=SomeDate&q%5Bcompleted_at_lt%5D=SomeDate&q%5Bcompleted_at_not_null%5D=true&q%5Bstate_not_eq%5D=canceled").respond [order]
     httpBackend.expectGET("/admin/enterprises/for_line_items.json?ams_prefix=basic&q%5Bsells_in%5D%5B%5D=own&q%5Bsells_in%5D%5B%5D=any").respond [distributor]
     httpBackend.expectGET("/admin/order_cycles.json?ams_prefix=basic&as=distributor&q%5Borders_close_at_gt%5D=SomeDate").respond [orderCycle]
-    httpBackend.expectGET("/admin/line_items.json?q%5Border%5D%5Bcompleted_at_not_null%5D=true&q%5Border%5D%5Bcreated_at_gt%5D=SomeDate&q%5Border%5D%5Bcreated_at_lt%5D=SomeDate&q%5Border%5D%5Bstate_not_eq%5D=canceled").respond [lineItem]
+    httpBackend.expectGET("/admin/line_items.json?q%5Border%5D%5Bcompleted_at_gt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_lt%5D=SomeDate&q%5Border%5D%5Bcompleted_at_not_null%5D=true&q%5Border%5D%5Bstate_not_eq%5D=canceled").respond [lineItem]
     httpBackend.expectGET("/admin/enterprises/for_line_items.json?ams_prefix=basic&q%5Bis_primary_producer_eq%5D=true").respond [supplier]

+    scope.bulk_order_form = jasmine.createSpyObj('bulk_order_form', ['$setPristine'])
+
     ctrl "LineItemsCtrl", {$scope: scope, Enterprises: Enterprises, Orders: Orders, LineItems: LineItems, OrderCycles: OrderCycles}
-    httpBackend.flush()
   )

-  describe "initialisation", ->
-    it "gets suppliers, adds a blank option as the first in the list", ->
-      expect(scope.suppliers).toDeepEqual [ { id : '0', name : 'All' }, supplier ]
-
-    it "gets distributors, adds a blank option as the first in the list", ->
-      expect(scope.distributors).toDeepEqual [ { id : '0', name : 'All' }, distributor ]
-
-    it "stores enterprises in an list that is accessible by id", ->
-      expect(Enterprises.enterprisesByID[1]).toDeepEqual supplier
-
-    it "gets order cycles, adds a blank option as the first in the list", ->
-      expect(scope.orderCycles).toDeepEqual [ { id : '0', name : 'All' }, orderCycle ]
-
-    it "gets orders, with dereferenced order cycles and distributors", ->
-      expect(scope.orders).toDeepEqual [ { id: 9, order_cycle: orderCycle, distributor: distributor, number: "R123456" } ]
-
-    it "gets line_items, with dereferenced orders and suppliers", ->
-      expect(scope.lineItems).toDeepEqual [ { id: 7, quantity: 3, order: scope.orders[0], supplier: supplier } ]
-
-    it "resets the select filters", ->
-      expect(scope.distributorFilter).toBe '0'
-      expect(scope.supplierFilter).toBe '0'
-      expect(scope.orderCycleFilter).toBe '0'
-      expect(scope.quickSearch).toBe = ""
-
-    it "sets loading to false", ->
-      expect(scope.loading).toBe false
-
-  describe "deleting a line item", ->
-    order = line_item1 = line_item2 = null
-
-    beforeEach inject((LineItemResource) ->
-      spyOn(window,"confirm").andReturn true
-      order = { number: "R12345678" }
-      line_item1 = new LineItemResource({ id: 1, order: order })
-      line_item2 = new LineItemResource({ id: 2, order: order })
-      scope.lineItems= [ line_item1, line_item2 ]
-    )
-
-    describe "where the request is successful", ->
+  describe "before data is returned", ->
+    it "the RequestMonitor will have a state of loading", ->
+      expect(scope.RequestMonitor.loading).toBe true
+
+    it "will not have reset the select filters", ->
+      expect(scope.distributorFilter).toBeUndefined()
+      expect(scope.supplierFilter).toBeUndefined()
+      expect(scope.orderCycleFilter).toBeUndefined()
+      expect(scope.quickSearch).toBeUndefined()
+
+    it "will not have reset the form state to pristine", ->
+      expect(scope.bulk_order_form.$setPristine.calls.length).toEqual 0
+
+  describe "after data is returned", ->
+    beforeEach ->
+      httpBackend.flush()
+
+    describe "initialisation", ->
+      it "gets suppliers, adds a blank option as the first in the list", ->
+        expect(scope.suppliers).toDeepEqual [ { id : '0', name : 'All' }, supplier ]
+
+      it "gets distributors, adds a blank option as the first in the list", ->
+        expect(scope.distributors).toDeepEqual [ { id : '0', name : 'All' }, distributor ]
+
+      it "stores enterprises in an list that is accessible by id", ->
+        expect(Enterprises.enterprisesByID[1]).toDeepEqual supplier
+
+      it "gets order cycles, adds a blank option as the first in the list", ->
+        expect(scope.orderCycles).toDeepEqual [ { id : '0', name : 'All' }, orderCycle ]
+
+      it "gets orders, with dereferenced order cycles and distributors", ->
+        expect(scope.orders).toDeepEqual [ { id: 9, order_cycle: orderCycle, distributor: distributor, number: "R123456" } ]
+
+      it "gets line_items, with dereferenced orders and suppliers", ->
+        expect(scope.lineItems).toDeepEqual [ { id: 7, quantity: 3, order: scope.orders[0], supplier: supplier } ]
+
+      it "the RequestMonitor will have a state of loaded", ->
+        expect(scope.RequestMonitor.loading).toBe false
+
+      it "resets the select filters", ->
+        expect(scope.distributorFilter).toBe '0'
+        expect(scope.supplierFilter).toBe '0'
+        expect(scope.orderCycleFilter).toBe '0'
+        expect(scope.quickSearch).toBe = ""
+
+      it "resets the form state to pristine", ->
+        expect(scope.bulk_order_form.$setPristine.calls.length).toEqual 1
+
+    describe "deleting a line item", ->
+      order = line_item1 = line_item2 = null
+
+      beforeEach inject((LineItemResource) ->
+        spyOn(window,"confirm").andReturn true
+        order = { number: "R12345678" }
+        line_item1 = new LineItemResource({ id: 1, order: order })
+        line_item2 = new LineItemResource({ id: 2, order: order })
+        scope.lineItems= [ line_item1, line_item2 ]
+      )
+
+      describe "where the request is successful", ->
+        beforeEach ->
+          httpBackend.expectDELETE("/admin/orders/R12345678/line_items/1.json").respond "nothing"
+          scope.deleteLineItem line_item1
+          httpBackend.flush()
+
+        it "removes the deleted item from the line_items array", ->
+          expect(scope.lineItems).toEqual [line_item2]
+
+      describe "where the request is unsuccessful", ->
+        beforeEach ->
+          httpBackend.expectDELETE("/admin/orders/R12345678/line_items/1.json").respond 404, "NO CONTENT"
+          scope.deleteLineItem line_item1
+          httpBackend.flush()
+
+        it "does not remove line_item from the line_items array", ->
+          expect(scope.lineItems).toEqual [line_item1, line_item2]
+
+    describe "deleting 'checked' line items", ->
+      line_item1 = line_item2 = line_item3 = line_item4 = null
+
       beforeEach ->
-        httpBackend.expectDELETE("/admin/orders/R12345678/line_items/1.json").respond "nothing"
-        scope.deleteLineItem line_item1
-        httpBackend.flush()
-
-      it "removes the deleted item from the line_items array", ->
-        expect(scope.lineItems).toEqual [line_item2]
-
-    describe "where the request is unsuccessful", ->
+        line_item1 = { name: "line item 1", checked: false }
+        line_item2 = { name: "line item 2", checked: true }
+        line_item3 = { name: "line item 3", checked: false }
+        line_item4 = { name: "line item 4", checked: true }
+        scope.lineItems = [ line_item1, line_item2, line_item3, line_item4 ]
+
+      it "calls deletedLineItem for each 'checked' line item", ->
+        spyOn(scope, "deleteLineItem")
+        scope.deleteLineItems(scope.lineItems)
+        expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item2)
+        expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item4)
+        expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item1)
+        expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item3)
+
+    describe "check boxes for line items", ->
+      line_item1 = line_item2 = null
+
       beforeEach ->
-        httpBackend.expectDELETE("/admin/orders/R12345678/line_items/1.json").respond 404, "NO CONTENT"
-        scope.deleteLineItem line_item1
-        httpBackend.flush()
-
-      it "does not remove line_item from the line_items array", ->
-        expect(scope.lineItems).toEqual [line_item1, line_item2]
-
-  describe "deleting 'checked' line items", ->
-    line_item1 = line_item2 = line_item3 = line_item4 = null
-
-    beforeEach ->
-      line_item1 = { name: "line item 1", checked: false }
-      line_item2 = { name: "line item 2", checked: true }
-      line_item3 = { name: "line item 3", checked: false }
-      line_item4 = { name: "line item 4", checked: true }
-      scope.lineItems = [ line_item1, line_item2, line_item3, line_item4 ]
-
-    it "calls deletedLineItem for each 'checked' line item", ->
-      spyOn(scope, "deleteLineItem")
-      scope.deleteLineItems(scope.lineItems)
-      expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item2)
-      expect(scope.deleteLineItem).toHaveBeenCalledWith(line_item4)
-      expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item1)
-      expect(scope.deleteLineItem).not.toHaveBeenCalledWith(line_item3)
-
-  describe "check boxes for line items", ->
-    line_item1 = line_item2 = null
-
-    beforeEach ->
-      line_item1 = { name: "line item 1", checked: false }
-      line_item2 = { name: "line item 2", checked: false }
-      scope.filteredLineItems = [ line_item1, line_item2 ]
-
-    it "keeps track of whether all filtered lines items are 'checked' or not", ->
-      expect(scope.allBoxesChecked()).toEqual false
-      line_item1.checked = true
-      expect(scope.allBoxesChecked()).toEqual false
-      line_item2.checked = true
-      expect(scope.allBoxesChecked()).toEqual true
-      line_item1.checked = false
-      expect(scope.allBoxesChecked()).toEqual false
-
-    it "toggles the 'checked' attribute of all line items based to the value of allBoxesChecked", ->
-      scope.toggleAllCheckboxes()
-      expect(scope.allBoxesChecked()).toEqual true
-      line_item1.checked = false
-      expect(scope.allBoxesChecked()).toEqual false
-      scope.toggleAllCheckboxes()
-      expect(scope.allBoxesChecked()).toEqual true
-      scope.toggleAllCheckboxes()
-      expect(scope.allBoxesChecked()).toEqual false
-
-  describe "unit calculations", ->
-    describe "fulfilled()", ->
-      it "returns '' if selectedUnitsVariant has no property 'variant_unit'", ->
-        expect(scope.fulfilled()).toEqual ''
-
-      it "returns '' if selectedUnitsVariant has no property 'group_buy_unit_size' or group_buy_unit_size is 0", ->
-        scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 0 }
-        expect(scope.fulfilled()).toEqual ''
-        scope.selectedUnitsProduct = { variant_unit: "weight" }
-        expect(scope.fulfilled()).toEqual ''
-
-      it "returns '', and does not call Math.round if variant_unit is 'items'", ->
-        spyOn(Math,"round")
-        scope.selectedUnitsProduct = { variant_unit: "items", group_buy_unit_size: 10 }
-        expect(scope.fulfilled()).toEqual ''
-        expect(Math.round).not.toHaveBeenCalled()
-
-      it "calls Math.round() if variant_unit is 'weight' or 'volume'", ->
-        spyOn(Math,"round")
-        scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 10 }
-        scope.fulfilled()
-        expect(Math.round).toHaveBeenCalled()
-        scope.selectedUnitsProduct = { variant_unit: "volume", group_buy_unit_size: 10 }
-        scope.fulfilled()
-        expect(Math.round).toHaveBeenCalled()
-
-      it "returns the quantity of fulfilled group buy units", ->
-        scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 1000 }
-        expect(scope.fulfilled(1500)).toEqual 1.5
-
-    describe "allFinalWeightVolumesPresent()", ->
-      it "returns false if the unit_value of any item in filteredLineItems does not exist", ->
-        scope.filteredLineItems = [
-          { final_weight_volume: 1000 }
-          { final_weight_volume: 3000 }
-          { final_weight_yayaya: 2000 }
-        ]
-        expect(scope.allFinalWeightVolumesPresent()).toEqual false
-
-      it "returns false if the unit_value of any item in filteredLineItems is not a number greater than 0", ->
-        scope.filteredLineItems = [
-          { final_weight_volume: 0 }
-          { final_weight_volume: 3000 }
-          { final_weight_volume: 2000 }
-        ]
-        expect(scope.allFinalWeightVolumesPresent()).toEqual false
-        scope.filteredLineItems = [
-          { final_weight_volume: 'lalala' }
-          { final_weight_volume: 3000 }
-          { final_weight_volume: 2000 }
-        ]
-        expect(scope.allFinalWeightVolumesPresent()).toEqual false
-
-      it "returns true if the unit_value of all items in filteredLineItems are numbers greater than 0", ->
-        scope.filteredLineItems = [
-          { final_weight_volume: 1000 }
-          { final_weight_volume: 3000 }
-          { final_weight_volume: 2000 }
-        ]
-        expect(scope.allFinalWeightVolumesPresent()).toEqual true
-
-    describe "sumUnitValues()", ->
-      it "returns the sum of the final_weight_volumes line_items", ->
-        scope.filteredLineItems = [
-          { final_weight_volume: 2 }
-          { final_weight_volume: 7 }
-          { final_weight_volume: 21 }
-        ]
-        expect(scope.sumUnitValues()).toEqual 30
-
-    describe "sumMaxUnitValues()", ->
-      it "returns the sum of the product of unit_value and maxOf(max_quantity, pristine quantity) for specified line_items", ->
-        LineItems.pristineByID = { 1: { quantity: 2 }, 2: { quantity: 3 }, 3: { quantity: 7 } }
-        scope.filteredLineItems = [
-          { id: 1, units_variant: { unit_value: 1 }, max_quantity: 5 }
-          { id: 2, units_variant: { unit_value: 2 }, max_quantity: 1 }
-          { id: 3, units_variant: { unit_value: 3 }, max_quantity: 10 }
-        ]
-        sp0 = scope.filteredLineItems[0].units_variant.unit_value * Math.max(LineItems.pristineByID[scope.filteredLineItems[0].id].quantity, scope.filteredLineItems[0].max_quantity)
-        sp1 = scope.filteredLineItems[1].units_variant.unit_value * Math.max(LineItems.pristineByID[scope.filteredLineItems[1].id].quantity, scope.filteredLineItems[1].max_quantity)
-        sp2 = scope.filteredLineItems[2].units_variant.unit_value * Math.max(LineItems.pristineByID[scope.filteredLineItems[2].id].quantity, scope.filteredLineItems[2].max_quantity)
-
-        expect(scope.sumMaxUnitValues()).toEqual (sp0 + sp1 + sp2)
-
-    describe "formatting a value based upon the properties of a specified Units Variant", ->
-      # A Units Variant is an API object which holds unit properies of a variant
-
-      beforeEach ->
-        spyOn(Math,"round").andCallThrough()
-
-      it "returns '' if selectedUnitsVariant has no property 'variant_unit'", ->
-        expect(scope.formattedValueWithUnitName(1,{})).toEqual ''
-
-      it "returns '', and does not call Math.round if variant_unit is 'items'", ->
-        unitsVariant = { variant_unit: "items" }
-        expect(scope.formattedValueWithUnitName(1,unitsVariant)).toEqual ''
-        expect(Math.round).not.toHaveBeenCalled()
-
-      it "calls Math.round() if variant_unit is 'weight' or 'volume'", ->
-        unitsVariant = { variant_unit: "weight" }
-        scope.formattedValueWithUnitName(1,unitsVariant)
-        expect(Math.round).toHaveBeenCalled()
-        scope.selectedUnitsVariant = { variant_unit: "volume" }
-        scope.formattedValueWithUnitName(1,unitsVariant)
-        expect(Math.round).toHaveBeenCalled()
-
-      it "calls Math.round with the quotient of scale and value, multiplied by 1000", ->
-        unitsVariant = { variant_unit: "weight" }
-        spyOn(VariantUnitManager, "getScale").andReturn 5
-        scope.formattedValueWithUnitName(10, unitsVariant)
-        expect(Math.round).toHaveBeenCalledWith 10/5 * 1000
-
-      it "returns the result of Math.round divided by 1000, followed by the result of getUnitName", ->
-        unitsVariant = { variant_unit: "weight" }
-        spyOn(VariantUnitManager, "getScale").andReturn 1000
-        spyOn(VariantUnitManager, "getUnitName").andReturn "kg"
-        expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg"
-
-    describe "updating the price upon updating the weight of a line item", ->
-      beforeEach ->
-        LineItems.pristineByID = { 1: { price: 2.00, quantity: 1, final_weight_volume: 2000 } }
-
-      it "updates the price if the weight is changed", ->
-        scope.filteredLineItems = [
-          { id: 1, price: 2.00, quantity: 1, final_weight_volume: 4000  }
-        ]
-        scope.weightAdjustedPrice(scope.filteredLineItems[0])
-        expect(scope.filteredLineItems[0].price).toEqual 4.00
-
-      it "doesn't update the price if the weight <= 0", ->
-        scope.filteredLineItems = [
-          { id: 1, price: 2.00, quantity: 1, final_weight_volume: 0  }
-        ]
-        scope.weightAdjustedPrice(scope.filteredLineItems[0])
-        expect(scope.filteredLineItems[0].price).toEqual 2.00
-
-      it "doesn't update the price if the weight is an empty string", ->
-        scope.filteredLineItems = [
-          { id: 1, price: 2.00, quantity: 1, final_weight_volume: ""  }
-        ]
-        scope.weightAdjustedPrice(scope.filteredLineItems[0])
-        expect(scope.filteredLineItems[0].price).toEqual 2.00
-
-    describe "updating final_weight_volume upon updating the quantity for a line_item", ->
-      beforeEach ->
-        LineItems.pristineByID = { 1: { price: 2.00, quantity: 1, final_weight_volume: 2000 } }
-        spyOn(scope, "weightAdjustedPrice")
-
-      it "updates the weight if the quantity is changed, then calls weightAdjustedPrice()", ->
-        scope.filteredLineItems = [
-          { id: 1, price: 2.00, quantity: 2, final_weight_volume: 0  }
-        ]
-        scope.updateOnQuantity(scope.filteredLineItems[0])
-        expect(scope.filteredLineItems[0].final_weight_volume).toEqual 4000
-        expect(scope.weightAdjustedPrice).toHaveBeenCalled()
-
-      it "doesn't update the weight if the quantity <= 0", ->
-        scope.filteredLineItems = [
-          { id: 1, price: 2.00, quantity: 0, final_weight_volume: 1000  }
-        ]
-        scope.updateOnQuantity(scope.filteredLineItems[0])
-        expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000
-
-      it "doesn't update the weight if the quantity is an empty string", ->
-        scope.filteredLineItems = [
-          { id: 1, price: 2.00, quantity: "", final_weight_volume: 1000  }
-        ]
-        scope.updateOnQuantity(scope.filteredLineItems[0])
-        expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000
+        line_item1 = { name: "line item 1", checked: false }
+        line_item2 = { name: "line item 2", checked: false }
+        scope.filteredLineItems = [ line_item1, line_item2 ]
+
+      it "keeps track of whether all filtered lines items are 'checked' or not", ->
+        expect(scope.allBoxesChecked()).toEqual false
+        line_item1.checked = true
+        expect(scope.allBoxesChecked()).toEqual false
+        line_item2.checked = true
+        expect(scope.allBoxesChecked()).toEqual true
+        line_item1.checked = false
+        expect(scope.allBoxesChecked()).toEqual false
+
+      it "toggles the 'checked' attribute of all line items based to the value of allBoxesChecked", ->
+        scope.toggleAllCheckboxes()
+        expect(scope.allBoxesChecked()).toEqual true
+        line_item1.checked = false
+        expect(scope.allBoxesChecked()).toEqual false
+        scope.toggleAllCheckboxes()
+        expect(scope.allBoxesChecked()).toEqual true
+        scope.toggleAllCheckboxes()
+        expect(scope.allBoxesChecked()).toEqual false
+
+    describe "unit calculations", ->
+      describe "fulfilled()", ->
+        it "returns '' if selectedUnitsVariant has no property 'variant_unit'", ->
+          expect(scope.fulfilled()).toEqual ''
+
+        it "returns '' if selectedUnitsVariant has no property 'group_buy_unit_size' or group_buy_unit_size is 0", ->
+          scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 0 }
+          expect(scope.fulfilled()).toEqual ''
+          scope.selectedUnitsProduct = { variant_unit: "weight" }
+          expect(scope.fulfilled()).toEqual ''
+
+        it "returns '', and does not call Math.round if variant_unit is 'items'", ->
+          spyOn(Math,"round")
+          scope.selectedUnitsProduct = { variant_unit: "items", group_buy_unit_size: 10 }
+          expect(scope.fulfilled()).toEqual ''
+          expect(Math.round).not.toHaveBeenCalled()
+
+        it "calls Math.round() if variant_unit is 'weight' or 'volume'", ->
+          spyOn(Math,"round")
+          scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 10 }
+          scope.fulfilled()
+          expect(Math.round).toHaveBeenCalled()
+          scope.selectedUnitsProduct = { variant_unit: "volume", group_buy_unit_size: 10 }
+          scope.fulfilled()
+          expect(Math.round).toHaveBeenCalled()
+
+        it "returns the quantity of fulfilled group buy units", ->
+          scope.selectedUnitsProduct = { variant_unit: "weight", group_buy_unit_size: 1000 }
+          expect(scope.fulfilled(1500)).toEqual 1.5
+
+      describe "allFinalWeightVolumesPresent()", ->
+        it "returns false if the unit_value of any item in filteredLineItems does not exist", ->
+          scope.filteredLineItems = [
+            { final_weight_volume: 1000 }
+            { final_weight_volume: 3000 }
+            { final_weight_yayaya: 2000 }
+          ]
+          expect(scope.allFinalWeightVolumesPresent()).toEqual false
+
+        it "returns false if the unit_value of any item in filteredLineItems is not a number greater than 0", ->
+          scope.filteredLineItems = [
+            { final_weight_volume: 0 }
+            { final_weight_volume: 3000 }
+            { final_weight_volume: 2000 }
+          ]
+          expect(scope.allFinalWeightVolumesPresent()).toEqual false
+          scope.filteredLineItems = [
+            { final_weight_volume: 'lalala' }
+            { final_weight_volume: 3000 }
+            { final_weight_volume: 2000 }
+          ]
+          expect(scope.allFinalWeightVolumesPresent()).toEqual false
+
+        it "returns true if the unit_value of all items in filteredLineItems are numbers greater than 0", ->
+          scope.filteredLineItems = [
+            { final_weight_volume: 1000 }
+            { final_weight_volume: 3000 }
+            { final_weight_volume: 2000 }
+          ]
+          expect(scope.allFinalWeightVolumesPresent()).toEqual true
+
+      describe "sumUnitValues()", ->
+        it "returns the sum of the final_weight_volumes line_items", ->
+          scope.filteredLineItems = [
+            { final_weight_volume: 2 }
+            { final_weight_volume: 7 }
+            { final_weight_volume: 21 }
+          ]
+          expect(scope.sumUnitValues()).toEqual 30
+
+      describe "sumMaxUnitValues()", ->
+        it "returns the sum of the product of unit_value and maxOf(max_quantity, pristine quantity) for specified line_items", ->
+          scope.filteredLineItems = [
+            { id: 1, units_variant: { unit_value: 1 }, max_quantity: 5 }
+            { id: 2, units_variant: { unit_value: 2 }, max_quantity: 1 }
+            { id: 3, units_variant: { unit_value: 3 }, max_quantity: 10 }
+          ]
+
+          expect(scope.sumMaxUnitValues()).toEqual 37
+
+      describe "formatting a value based upon the properties of a specified Units Variant", ->
+        # A Units Variant is an API object which holds unit properies of a variant
+
+        beforeEach ->
+          spyOn(Math,"round").andCallThrough()
+
+        it "returns '' if selectedUnitsVariant has no property 'variant_unit'", ->
+          expect(scope.formattedValueWithUnitName(1,{})).toEqual ''
+
+        it "returns '', and does not call Math.round if variant_unit is 'items'", ->
+          unitsVariant = { variant_unit: "items" }
+          expect(scope.formattedValueWithUnitName(1,unitsVariant)).toEqual ''
+          expect(Math.round).not.toHaveBeenCalled()
+
+        it "calls Math.round() if variant_unit is 'weight' or 'volume'", ->
+          unitsVariant = { variant_unit: "weight" }
+          scope.formattedValueWithUnitName(1,unitsVariant)
+          expect(Math.round).toHaveBeenCalled()
+          scope.selectedUnitsVariant = { variant_unit: "volume" }
+          scope.formattedValueWithUnitName(1,unitsVariant)
+          expect(Math.round).toHaveBeenCalled()
+
+        it "calls Math.round with the quotient of scale and value, multiplied by 1000", ->
+          unitsVariant = { variant_unit: "weight" }
+          spyOn(VariantUnitManager, "getScale").andReturn 5
+          scope.formattedValueWithUnitName(10, unitsVariant)
+          expect(Math.round).toHaveBeenCalledWith 10/5 * 1000
+
+        it "returns the result of Math.round divided by 1000, followed by the result of getUnitName", ->
+          unitsVariant = { variant_unit: "weight" }
+          spyOn(VariantUnitManager, "getScale").andReturn 1000
+          spyOn(VariantUnitManager, "getUnitName").andReturn "kg"
+          expect(scope.formattedValueWithUnitName(2000,unitsVariant)).toEqual "2 kg"
+
+      describe "updating the price upon updating the weight of a line item", ->
+        beforeEach ->
+          LineItems.pristineByID = { 1: { price: 2.00, quantity: 1, final_weight_volume: 2000 } }
+
+        it "updates the price if the weight is changed", ->
+          scope.filteredLineItems = [
+            { id: 1, price: 2.00, quantity: 1, final_weight_volume: 4000  }
+          ]
+          scope.weightAdjustedPrice(scope.filteredLineItems[0])
+          expect(scope.filteredLineItems[0].price).toEqual 4.00
+
+        it "doesn't update the price if the weight <= 0", ->
+          scope.filteredLineItems = [
+            { id: 1, price: 2.00, quantity: 1, final_weight_volume: 0  }
+          ]
+          scope.weightAdjustedPrice(scope.filteredLineItems[0])
+          expect(scope.filteredLineItems[0].price).toEqual 2.00
+
+        it "doesn't update the price if the weight is an empty string", ->
+          scope.filteredLineItems = [
+            { id: 1, price: 2.00, quantity: 1, final_weight_volume: ""  }
+          ]
+          scope.weightAdjustedPrice(scope.filteredLineItems[0])
+          expect(scope.filteredLineItems[0].price).toEqual 2.00
+
+      describe "updating final_weight_volume upon updating the quantity for a line_item", ->
+        beforeEach ->
+          LineItems.pristineByID = { 1: { price: 2.00, quantity: 1, final_weight_volume: 2000 } }
+          spyOn(scope, "weightAdjustedPrice")
+
+        it "updates the weight if the quantity is changed, then calls weightAdjustedPrice()", ->
+          scope.filteredLineItems = [
+            { id: 1, price: 2.00, quantity: 2, final_weight_volume: 0  }
+          ]
+          scope.updateOnQuantity(scope.filteredLineItems[0])
+          expect(scope.filteredLineItems[0].final_weight_volume).toEqual 4000
+          expect(scope.weightAdjustedPrice).toHaveBeenCalled()
+
+        it "doesn't update the weight if the quantity <= 0", ->
+          scope.filteredLineItems = [
+            { id: 1, price: 2.00, quantity: 0, final_weight_volume: 1000  }
+          ]
+          scope.updateOnQuantity(scope.filteredLineItems[0])
+          expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000
+
+        it "doesn't update the weight if the quantity is an empty string", ->
+          scope.filteredLineItems = [
+            { id: 1, price: 2.00, quantity: "", final_weight_volume: 1000  }
+          ]
+          scope.updateOnQuantity(scope.filteredLineItems[0])
+          expect(scope.filteredLineItems[0].final_weight_volume).toEqual 1000

 describe "Auxiliary functions", ->


Fixed #11

Fixed #11

--- src/coffee/anchosen/view_model.coffee
+++ src/coffee/anchosen/view_model.coffee
@@ -157,7 +157,16 @@

 			@createNewText = ko.observable(options.createNewText)
-			@createNewVisible = ko.computed () => @createNewEnabled() && !@alreadySelectedVisible() && @delayedSearchString() != '' && @singleSelectionAllowed()
+			@createNewVisible = ko.computed () =>
+				unless @createNewEnabled() && !@alreadySelectedVisible() && @delayedSearchString() != '' && @singleSelectionAllowed()
+					return false
+
+				search = @delayedSearchString()
+				match = false
+				ko.utils.arrayForEach @availableOptions(), (e) ->
+					match = true if e.label.toLowerCase().indexOf(search) == 0 && e.label.length == search.length
+
+				return !match

 			@createNewHighlighted = ko.computed () => @highlightedIndex() == CREATENEW_HIGHLIGHT_IDX


Remove console.log

Remove console.log

--- src/coffee/portal-sku-selector.coffee
+++ src/coffee/portal-sku-selector.coffee
@@ -99,7 +99,6 @@

 		originalSelection = (dim.selected for dim in @dimensions)

-		console.log lastSelected
 		for dimension, i in @dimensions
 			@resetDimension(dimension)


Removed exception tracking id from Atom settings.

--- atom/config.cson
+++ atom/config.cson
@@ -59,5 +59,3 @@
     ignoreWhitespaceOnCurrentLine: false
   "atom-beautify":
     analytics: false
-  "exception-reporting":
-    userId: "7eb11660-001b-73c2-c963-ba621983fb91"


Latest configuration

Latest configuration

--- atom/config.cson
+++ atom/config.cson
@@ -16,7 +16,7 @@
     'tab': '⇥'
   'tabLength': 2
   'normalizeIndentOnPaste': false
-  'fontSize': 14
+  'fontSize': 15
   'showInvisibles': true
 'core':
   'projectHome': '/Volumes/Data/Users/Lee/Source'
@@ -47,7 +47,6 @@
   ]
 'tree-view':
   'hideIgnoredNames': true
-  'hideVcsIgnoredFiles': true
 'autosave': {}
 'remember-window':
   'storeOutsideMainConfig': true
@@ -67,6 +66,7 @@
   'scopes':
     'source':
       'gfm':
+        'showInvisibles': false
         'softWrap': true
         'tabLength': 4
       'java':
@@ -103,3 +103,6 @@
   'macroBreakIntervalInMicroAmount': 4
 'package-generator':
   'createInDevMode': true
+'atom-lint':
+  'rubocop':
+    'showDisplayCopNames': true


Unpin linter, allowing it to check for updates

--- atom/config.cson
+++ atom/config.cson
@@ -83,9 +83,6 @@
     titleBar: "custom"
     uriHandlerRegistration: "always"
     useTreeSitterParsers: false
-    versionPinnedPackages: [
-      "linter"
-    ]
   docblockr:
     align_tags: "shallow"
     extend_double_slash: false


Ignore all pep8 linter errors

Ignore all pep8 linter errors

--- atom/config.cson
+++ atom/config.cson
@@ -49,6 +49,9 @@
     showInfoMessages: true
     lintOnChange: false
     showHighlighting: false
+    ignoredLinterErrors: [
+      "pep8"
+    ]
   autosave:
     enabled: true
   script:


Revert "Follow symlinks in Atom fuzzy finder"

This reverts commit 539d500c58ba306fdf7adfed3b77e8ce48559cfb.

--- atom/config.cson
+++ atom/config.cson
@@ -33,6 +33,7 @@
       "welcome"
       "whitespace"
     ]
+    followSymlinks: false
     ignoredNames: [
       "._*"
       ".DS_Store"


Update atom config

Update atom config

--- atom/config.cson
+++ atom/config.cson
@@ -1,4 +1,7 @@
 "*":
+  "atom-beautify":
+    js:
+      default_beautifier: "ESLint Fixer"
   core:
     autoHideMenuBar: true
     destroyEmptyPanes: false
@@ -16,13 +19,15 @@
     userId: "08af8d6a-7537-40e4-b365-15d7ece4d098"
   "line-ending-selector":
     defaultLineEnding: "LF"
+  "linter-ui-default":
+    showPanel: true
   minimap:
     plugins:
       pigments: true
       pigmentsDecorationsZIndex: 0
-  "one-dark-ui":
-    layoutMode: "Compact"
+  "one-dark-ui": {}
   "project-viewer":
+    customWidth: 188
     disclaimer:
       v1014: false
   "tool-bar":


Add new test helper subject and given (waiting is waiting an implementation)

--- test/test_helper.coffee
+++ test/test_helper.coffee
@@ -23,6 +23,18 @@
 options = {}
   # stderr: (data)-> print data
   # stdout: (data)-> print data
+
+global.subject = (name, block) ->
+  [name, block] = [block, name] if typeof name is 'function'
+  beforeEach ->
+    @subject = block.call this
+    @[name] = @subject if name?
+
+global.given = (name, block) ->
+  beforeEach -> @[name] = block.call this
+
+global.waiting = ->
+

 cursor = 0
 global.progress = (f) ->


KodingAppsController: cleanup

KodingAppsController: cleanup

--- client/Main/kodingappscontroller.coffee
+++ client/Main/kodingappscontroller.coffee
@@ -404,6 +404,7 @@
     .then ->
       FSHelper.createFileFromPath("#{appPath}/manifest.json")
               .save manifestStr
+
     .then ->
       FSHelper.createFileFromPath("#{appPath}/ChangeLog")
               .save changeLogStr
@@ -480,12 +481,12 @@
       type     : "mini"

     {vmController} = KD.singletons
-    vmController.run {
+    vmController.run
       withArgs : "kdc #{app.path}"
       vmName   : app.vm
-    }, (err, response)->
-
-      if err
+    , (err, response)->
+
+      if err or not response

         loader.notificationSetTitle "An unknown error occured"
         loader.notificationSetTimer 2000


Client: Activity: Added markParticipantMayBeDeleted and unmarkParticipantMayBeDeleted actions to set states of selected dropdown users before delete

--- client/app/lib/flux/actions/user.coffee
+++ client/app/lib/flux/actions/user.coffee
@@ -155,6 +155,25 @@
     impersonate owner.profile.nickname

+###*
+ * Action to set delete flag for create channel modal dropdown selected users to delete
+###
+markParticipantMayBeDeleted = (accountId) ->
+
+  { SET_CREATE_CHANNEL_PARTICIPANT_DELETE_FLAG } = actions
+  dispatch SET_CREATE_CHANNEL_PARTICIPANT_DELETE_FLAG, { accountId }
+
+
+###*
+ * Action to unset delete flag for create channel modal dropdown selected users
+###
+unmarkParticipantMayBeDeleted = (accountId) ->
+
+  { UNSET_CREATE_CHANNEL_PARTICIPANT_DELETE_FLAG } = actions
+  dispatch UNSET_CREATE_CHANNEL_PARTICIPANT_DELETE_FLAG, { accountId }
+
+
+
 module.exports = {
   loadAccount
   searchAccounts
@@ -162,5 +181,7 @@
   unmarkUserAsTroll
   blockUser
   impersonateUser
+  markParticipantMayBeDeleted
+  unmarkParticipantMayBeDeleted
 }


geoip: post event is rewritten to send data as including location

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -25,11 +25,12 @@
   url = "/account"
   post url, {oldId: id, nick: nickname}, callback

-updateAccount = ({id, nick}, callback)->
+updateAccount = (data, callback)->
+  {id, nick} = data
   if not id or not nick
     return callback {message:"Request is not valid for updating account"}
   url = "/account/#{id}"
-  post url, {id, nick}, callback
+  post url, data , callback

 createChannel = (data, callback)->
   unless data.name or data.creatorId


Improved: 'send emails' confirmation modal

--- client/app/models/scheduleitem.coffee
+++ client/app/models/scheduleitem.coffee
@@ -219,21 +219,17 @@

     confirmSendEmails: (callback) ->
         attendees = @get('attendees') or []
-        if attendees.length is 0
+        guestsToInform = attendees.filter (guest) ->
+            return guest.status is 'INVITATION-NOT-SENT' or \
+                   (guest.status is 'ACCEPTED' and dateChanged)
+        .map (guest) -> guest.email
+
+        if guestsToInform.length is 0
             callback false
         else
-            text = t('send mails question')
-            first = true
-            attendees.forEach (guest) ->
-                if guest.status is 'INVITATION-NOT-SENT' or (
-                    guest.status is 'ACCEPTED' and dateChanged)
-                    if not first
-                        text += ', '
-                    else
-                        first = false
-                    text += guest.email
-
-            Modal.confirm t('modal send mails'), text, \
+            guestsList = guestsToInform.join ', '
+            content = "#{t 'send mails question'} #{guestsList}"
+            Modal.confirm t('modal send mails'), content, \
                 t('yes'), t('no'), callback

         @startDateChanged = false


Use $interval now that Angular implemented it!

--- client/modules/packages/ui/title.coffee
+++ client/modules/packages/ui/title.coffee
@@ -27,8 +27,8 @@
 ]

 exports.$service = -> [
-	'$rootScope', '$timeout'
-	($rootScope, $timeout) ->
+	'$rootScope', '$interval'
+	($rootScope, $interval) ->

 # Get and set the page title.
 		_page = ''
@@ -71,26 +71,21 @@
 # Don't queue up a million timeouts.
 			return if flashInProgress?

-			(flashTimeout = ->
+			flashInProgress = $interval(
+				->

-				if _windowWrapper is _flashUpWrapper
-					_windowWrapper = _flashDownWrapper
-				else
-					_windowWrapper = _flashUpWrapper
-
-# It seems Angular has a memory leak in $timeout, so we'll use setTimeout
-# unless it isn't available for some reason.
-				flashInProgress = (setTimeout ? $timeout)(
-					-> $rootScope.$apply -> flashTimeout()
-					1200
-				)
-
-			)()
+					if _windowWrapper is _flashUpWrapper
+						_windowWrapper = _flashDownWrapper
+					else
+						_windowWrapper = _flashUpWrapper
+
+				600
+			)

 # Restore the window/tab title to normal again.
 		@stopFlashing = ->

-			(clearTimeout ? $timeout.cancel) flashInProgress
+			$interval.cancel flashInProgress if flashInProgress?
 			flashInProgress = null

 			_windowWrapper = angular.identity


Removed noCaret: yes and added dateGlyph: no

--- example.coffee
+++ example.coffee
@@ -38,11 +38,12 @@
         help: "You can to change date and time here."
       createElement DateTime,
         label: "Custom display format date"
-        help: "Link styte without caret."
+        help: "Link style without icon."
         datePart: "LL"
         timePart: no
         bsStyle: "link"
-        noCaret: yes
+        dateGlyph: no
+        locale: locale
       createElement DateTime,
         label: "Custom display format time"
         help: "With warning style applied. Current time #{time or "now"}"
@@ -51,6 +52,7 @@
         timePart: "hh:mm A"
         ref: "time"
         onChange: @handleChange
+        locale: locale
       createElement DateTime,
         label: "Default 13 Jan 2008"
         help: "Drop up with succes style."
@@ -60,5 +62,6 @@
         datePart: "MM/DD/YY"
         timePart: no
         dropup: yes
+        locale: locale

 render (createElement Example), document.getElementById "example"


something changed after updated atom

something changed after updated atom

--- linked_files/.atom/config.cson
+++ linked_files/.atom/config.cson
@@ -1,8 +1,9 @@
-'editor': {}
-'core': {}
 'exception-reporting':
   'userId': 'adaafeb8-bc23-4548-6cbd-b1ee8b7f45bb'
 'welcome':
   'showOnStartup': false
 'metrics':
   'userId': 'c5c6463055fca50078f6817afca15adeb1bd4545'
+'core': {}
+'editor':
+  'invisibles': {}


Add some specs for the default path.

Add some specs for the default path.

--- spec/package-generator-spec.coffee
+++ spec/package-generator-spec.coffee
@@ -12,19 +12,23 @@
     activationPromise = atom.packages.activatePackage("package-generator")

   describe "when package-generator:generate-package is triggered", ->
-    it "displays a miniEditor with the correct text selected", ->
-      atom.workspaceView.trigger("package-generator:generate-package")
-
-      waitsForPromise ->
-        activationPromise
-
-      runs ->
-        packageGeneratorView = atom.workspaceView.find(".package-generator").view()
-        themeName = packageGeneratorView.miniEditor.editor.getSelectedText()
-        expect(themeName).toEqual 'my-package'
+    it "displays a miniEditor with the correct text and selection", ->
+      atom.workspaceView.trigger("package-generator:generate-package")
+
+      waitsForPromise ->
+        activationPromise
+
+      runs ->
+        packageGeneratorView = atom.workspaceView.find(".package-generator").view()
+        packageName = packageGeneratorView.miniEditor.editor.getSelectedText()
+        expect(packageName).toEqual 'my-package'
+
+        fullPath = packageGeneratorView.miniEditor.editor.getText()
+        base = atom.config.get 'core.projectHome'
+        expect(fullPath).toEqual path.join(base, 'my-package')

   describe "when package-generator:generate-theme is triggered", ->
-    it "displays a miniEditor with correct text selected", ->
+    it "displays a miniEditor with correct text and selection", ->
       atom.workspaceView.trigger("package-generator:generate-syntax-theme")

       waitsForPromise ->
@@ -34,6 +38,10 @@
         packageGeneratorView = atom.workspaceView.find(".package-generator").view()
         themeName = packageGeneratorView.miniEditor.editor.getSelectedText()
         expect(themeName).toEqual 'my-theme'
+
+        fullPath = packageGeneratorView.miniEditor.editor.getText()
+        base = atom.config.get 'core.projectHome'
+        expect(fullPath).toEqual path.join(base, 'my-theme-syntax')

   describe "when core:cancel is triggered", ->
     it "detaches from the DOM and focuses the the previously focused element", ->


Remove jQuery usage in project-find-view-spec

--- spec/project-find-view-spec.coffee
+++ spec/project-find-view-spec.coffee
@@ -1,9 +1,7 @@
 os = require 'os'
 path = require 'path'
 temp = require 'temp'
-
 _ = require 'underscore-plus'
-{$, View} = require 'atom-space-pen-views'
 fs = require 'fs-plus'

 ResultsPaneView = require '../lib/project/results-pane'
@@ -12,7 +10,7 @@
 waitsForPromise = (fn) -> window.waitsForPromise timeout: 30000, fn

 describe 'ProjectFindView', ->
-  [activationPromise, editor, editorView, projectFindView, searchPromise, resultsPane, workspaceElement, mainModule, stoppedChangingDelay] = []
+  [activationPromise, editor, editorView, projectFindView, searchPromise, workspaceElement, mainModule, stoppedChangingDelay] = []

   getAtomPanel = ->
     workspaceElement.querySelector('.project-find').parentNode
@@ -36,7 +34,6 @@
       stoppedChangingDelay = projectFindView.findEditor.getModel().getBuffer().stoppedChangingDelay
       spy = spyOn(projectFindView, 'search').andCallFake ->
         searchPromise = spy.originalValue.apply(projectFindView, arguments)
-        resultsPane = $(workspaceElement).find('.preview-pane').view()
         searchPromise

   describe "when project-find:show is triggered", ->
@@ -179,39 +176,42 @@
   describe "when project-find:show-in-current-directory is triggered", ->
     [nested, tree, projectPath] = []

-    class DirElement extends View
-      @content: (path) ->
-        @div class: 'directory', =>
-          @div class: 'nested-thing', =>
-            @span outlet: 'name', class: 'name', 'data-path': path, path
-            @ul outlet: 'files', class: 'files'
-      initialize: (@path) ->
-      createFiles: (names) ->
-        for name in names
-          @files.append(new FileElement(path.join(@path, name), name))
-
-    class FileElement extends View
-      @content: (path, name) ->
-        @li class: 'file', 'data-path': path, =>
-          @span outlet: 'name', class: 'name', name
-      initialize: (path) ->
-        fs.writeFileSync(path, '')
-
     beforeEach ->
       projectPath = temp.mkdirSync("atom")
       atom.project.setPaths([projectPath])
-      p = atom.project.getPaths()[0]
-      tree = new DirElement(p)
-      tree.createFiles(['one.js', 'two.js'])
-
-      nested = new DirElement(path.join(p, 'nested'))
-      nested.createFiles(['another.js'])
-
-      tree.files.append(nested)
-      workspaceElement.appendChild(tree[0])
+
+      tree = document.createElement('div')
+      tree.className = 'directory'
+      tree.innerHTML = """
+

+          #{projectPath}
+

+

+              one.js
+

+

+              two.js
+

+

+

+                nested
+

+

+                    another.js
+

+

+

+

+

+

+      """
+
+      nested = tree.querySelector('.directory')
+
+      workspaceElement.appendChild(tree)

     it "populates the pathsEditor when triggered with a directory", ->
-      atom.commands.dispatch nested.name[0], 'project-find:show-in-current-directory'
+      atom.commands.dispatch nested.querySelector('.name'), 'project-find:show-in-current-directory'

       waitsForPromise ->
         activationPromise
@@ -221,11 +221,11 @@
         expect(projectFindView.pathsEditor.getText()).toBe('nested')
         expect(projectFindView.findEditor).toHaveFocus()

-        atom.commands.dispatch tree.name[0], 'project-find:show-in-current-directory'
+        atom.commands.dispatch tree.querySelector('.name'), 'project-find:show-in-current-directory'
         expect(projectFindView.pathsEditor.getText()).toBe('')

     it "populates the pathsEditor when triggered on a directory's name", ->
-      atom.commands.dispatch nested[0], 'project-find:show-in-current-directory'
+      atom.commands.dispatch nested, 'project-find:show-in-current-directory'

       waitsForPromise ->
         activationPromise
@@ -235,11 +235,11 @@
         expect(projectFindView.pathsEditor.getText()).toBe('nested')
         expect(projectFindView.findEditor).toHaveFocus()

-        atom.commands.dispatch tree.name[0], 'project-find:show-in-current-directory'
+        atom.commands.dispatch tree.querySelector('.name'), 'project-find:show-in-current-directory'
         expect(projectFindView.pathsEditor.getText()).toBe('')

     it "populates the pathsEditor when triggered on a file", ->
-      atom.commands.dispatch nested.files.find('> .file:eq(0)').view().name[0], 'project-find:show-in-current-directory'
+      atom.commands.dispatch nested.querySelector('.file .name'), 'project-find:show-in-current-directory'

       waitsForPromise ->
         activationPromise
@@ -249,7 +249,7 @@
         expect(projectFindView.pathsEditor.getText()).toBe('nested')
         expect(projectFindView.findEditor).toHaveFocus()

-        atom.commands.dispatch tree.files.find('> .file:eq(0)').view().name[0], 'project-find:show-in-current-directory'
+        atom.commands.dispatch tree.querySelector('.file .name'), 'project-find:show-in-current-directory'
         expect(projectFindView.pathsEditor.getText()).toBe('')

     describe "when there are multiple root directories", ->
@@ -257,7 +257,7 @@
         atom.project.addPath(temp.mkdirSync("another-path-"))

       it "includes the basename of the containing root directory in the paths-editor", ->
-        atom.commands.dispatch nested.files.find('> .file:eq(0)').view().name[0], 'project-find:show-in-current-directory'
+        atom.commands.dispatch nested.querySelector('.file .name'), 'project-find:show-in-current-directory'

         waitsForPromise ->
           activationPromise


Reorg some specs for clarity

Reorg some specs for clarity

--- spec/project-find-view-spec.coffee
+++ spec/project-find-view-spec.coffee
@@ -1054,28 +1054,6 @@
             expect(projectFindView.descriptionLabel.text()).toContain "13 results found in 2 files for items"

     describe "when the project-find:replace-all is triggered", ->
-      describe "when there are no results", ->
-        beforeEach ->
-          projectFindView.findEditor.setText('nopenotinthefile')
-          atom.commands.dispatch(projectFindView[0], 'core:confirm')
-
-          waitsForPromise ->
-            searchPromise
-
-        it "doesnt replace anything", ->
-          projectFindView.replaceEditor.setText('sunshine')
-
-          spyOn(atom.workspace, 'scan').andCallThrough()
-          spyOn(atom, 'beep')
-          atom.commands.dispatch(projectFindView[0], 'project-find:replace-all')
-
-          # The replacement isnt even run
-          expect(replacePromise).toBeUndefined()
-
-          expect(atom.workspace.scan).not.toHaveBeenCalled()
-          expect(atom.beep).toHaveBeenCalled()
-          expect(projectFindView.descriptionLabel.text().replace(/(  )/g, ' ')).toContain "No results"
-
       describe "when no search has been run", ->
         it "does nothing", ->
           projectFindView.findEditor.setText('items')
@@ -1089,7 +1067,29 @@
           expect(atom.beep).toHaveBeenCalled()
           expect(projectFindView.descriptionLabel.text()).toContain "Find in Project"

-      describe "when a search has been run", ->
+      describe "when a search with no results has been run", ->
+        beforeEach ->
+          projectFindView.findEditor.setText('nopenotinthefile')
+          atom.commands.dispatch(projectFindView[0], 'core:confirm')
+
+          waitsForPromise ->
+            searchPromise
+
+        it "doesnt replace anything", ->
+          projectFindView.replaceEditor.setText('sunshine')
+
+          spyOn(atom.workspace, 'scan').andCallThrough()
+          spyOn(atom, 'beep')
+          atom.commands.dispatch(projectFindView[0], 'project-find:replace-all')
+
+          # The replacement isnt even run
+          expect(replacePromise).toBeUndefined()
+
+          expect(atom.workspace.scan).not.toHaveBeenCalled()
+          expect(atom.beep).toHaveBeenCalled()
+          expect(projectFindView.descriptionLabel.text().replace(/(  )/g, ' ')).toContain "No results"
+
+      describe "when a search with results has been run", ->
         beforeEach ->
           projectFindView.findEditor.setText('items')
           atom.commands.dispatch(projectFindView[0], 'core:confirm')


update markdown_to_json converter

update markdown_to_json converter

added stats, tags part to output json

--- markdown_to_json.coffee
+++ markdown_to_json.coffee
@@ -1,10 +1,25 @@
-fs = require 'fs'
+md_file_path = process.argv[2]
+if md_file_path is undefined then process.exit(0)
+
+fs   = require 'fs'
 util = require 'util'

+OBJECT     = {}
 JSONSTRING = []
+TAGS       = []
+STATS      = {}
+STATS.tags =
+  count: 0
+STATS.links =
+  count: 0
+STATS.part =
+  count: 0

 showJson = ->
-  console.log JSON.stringify(JSONSTRING)
+  OBJECT.items = JSONSTRING
+  OBJECT.tags = TAGS
+  OBJECT.stats = STATS
+  console.log JSON.stringify(OBJECT)
   return

 findTag = (rows, index, tag) ->
@@ -12,6 +27,10 @@
   row = rows[index]
   if /^\*\s/.test(row)
     tag_name = row.replace /^\*\s/, ""
+    if TAGS.indexOf(tag_name) is -1
+      TAGS.push(tag_name)
+      STATS.tags.count += 1
+
     tag.push tag_name
     findTag rows, index, tag

@@ -31,6 +50,7 @@
 findItem = (rows, index, item) ->
   if index + 1 >= rows.length
     JSONSTRING.push item
+    STATS.links.count += 1
     findHead rows, index
     return

@@ -51,10 +71,12 @@
     index = tags.index

     item.links.push link
+    STATS.links.count += 1
     findItem rows, index, item

   else if /^#\s[A-Za-zА-Яа-я0-9]/.test(row)
     JSONSTRING.push item
+    STATS.part.count += 1
     index -= 1
     findHead rows, index

@@ -82,6 +104,6 @@
   findHead rows, -1
   return

-fs.readFile 'copy.md', 'utf8', (err, content) ->
+fs.readFile md_file_path, 'utf8', (err, content) ->
   startFind content
   return


Fix the double callback call on docker timeout.

 When the timeout handler is called, disable the callback in the close handler so that the success of the stop command doesnt produce two calls to the callback, the first one meaning success.

--- app/coffee/DockerRunner.coffee
+++ app/coffee/DockerRunner.coffee
@@ -20,10 +20,13 @@

     proc = spawn DockerRunner._docker, docker_cmd, stdio: "inherit", cwd: directory
     timer = setTimeout () ->
+      # Too late, don't call the callback when the process exits
+      _callback = callback
+      callback = ->
       logger.warn "timeout achieved, stopping docker instance"
-      proc = spawn 'docker', ['stop', "texlive-#{project_id}"]
+      proc = spawn DockerRunner._docker, ['stop', "texlive-#{project_id}"]
       proc.on "close", ->
-        callback timedout: true
+        _callback timedout: true
     , timeout
     proc.on "close", () ->
       clearTimeout timer


don't log docLines when document removed from redis

they can now be binary gzipped data which messes up the logs

--- app/coffee/RedisManager.coffee
+++ app/coffee/RedisManager.coffee
@@ -32,7 +32,6 @@
 	removeDocFromMemory : (project_id, doc_id, callback)->
 		logger.log project_id:project_id, doc_id:doc_id, "removing doc from redis"
 		multi = rclient.multi()
-		multi.get keys.docLines(doc_id:doc_id)
 		multi.del keys.docLines(doc_id:doc_id)
 		multi.del keys.projectKey(doc_id:doc_id)
 		multi.del keys.docVersion(doc_id:doc_id)
@@ -43,8 +42,7 @@
 				logger.err project_id:project_id, doc_id:doc_id, err:err, "error removing doc from redis"
 				callback(err, null)
 			else
-				docLines = replys[0]
-				logger.log project_id:project_id, doc_id:doc_id, docLines:docLines, "removed doc from redis"
+				logger.log project_id:project_id, doc_id:doc_id, "removed doc from redis"
 				callback()

 	getDoc : (doc_id, callback = (error, lines, version) ->)->


tokenized input improvements

tokenized input improvements

--- components/inputs/tokenizedinput.coffee
+++ components/inputs/tokenizedinput.coffee
@@ -28,30 +28,33 @@
     for rule of o.match
       @registeredTokens[rule] = []

-    @input.on "keydown", (event)=> @decorateLayer()
+    @input.on "keydown", @keyDownOnInput.bind @
+    @input.on "keyup", @keyUpOnInput.bind @

-    @input.on "keyup", (event)=>
-      matchRules = @getOptions().match
-      val = @input.getValue()
-      @decorateLayer()
-      {input} = @
-      if matchRules
-        for rule, ruleSet of matchRules
-          val = val.slice(0, input.getCaretPosition())
-          matches = val.match ruleSet.regex
-          # log matches, rule, ruleSet.regex
-          if matches
-            matches.forEach (match,i)->
-              # log match,_oldMatches[i],i
-              unless _oldMatches[i] is match
-                _oldMatches[i] = match
+  keyDownOnInput:(event)->
+    @decorateLayer()
+    # @layer.setClass "hide-tokens"

-                if ruleSet.throttle
-                  do _.throttle ->
-                    ruleSet.dataSource match
-                  , ruleSet.throttle
-                else
+  keyUpOnInput:(event)->
+    matchRules = @getOptions().match
+    val = @input.getValue()
+    @decorateLayer()
+    # @layer.unsetClass "hide-tokens"
+    {input} = @
+    if matchRules
+      for rule, ruleSet of matchRules
+        val = val.slice(0, input.getCaretPosition())
+        matches = val.match ruleSet.regex
+        if matches
+          matches.forEach (match,i)->
+            unless _oldMatches[i] is match
+              _oldMatches[i] = match
+              if ruleSet.throttle
+                do _.throttle ->
                   ruleSet.dataSource match
+                , ruleSet.throttle
+              else
+                ruleSet.dataSource match

   showMenu:(options, data)->

@@ -62,10 +65,8 @@
       y : @input.getY() + @input.getHeight()
     @input.setBlur()
     @menu = new KDTokenizedMenu o, data
-    # log @menu, ">>>>"
     @menu.on "ContextMenuItemReceivedClick", (menuItem)=>
       @registerSelectedToken {rule, token}, menuItem.getData()
-      # @getOptions().match[rule].callback token, menuItem.getData()

   registerSelectedToken:({rule, token}, data)->


Fix dashboard for not found submit

Fix dashboard for not found submit

--- server/api/dashboard.coffee
+++ server/api/dashboard.coffee
@@ -15,7 +15,7 @@
     res.fullUser = res.fullUser.toObject()
     res.fullTable = (await Problem.findById(result.table)).toObject()
     if res.lastSubmitId
-        res.fullSubmit = (await Submit.findById(result.lastSubmitId))?.toObject()
+        res.fullSubmit = (await Submit.findById(result.lastSubmitId))?.toObject() or {}
         delete res.fullSubmit.source
         delete res.fullSubmit.sourceRaw
         delete res.fullSubmit.results


Fix some keyboard shortcuts for layouts with AltGr

In many keyboard layouts, such as German, French and Swedish, some
characters have to be typed using the AltGr key. If such a key is the
non-first key of a VMP keyboard shortcut _sequence,_ the sequence fails
to trigger. This is because Atom receive an 'altgraph' key press
inbetween. For example, the shortcut `g ~` is seen as `g altgraph ~`.
'altgraph' seems to be handled just like any other key, so just like
`g a ~` does not exist, neither does `g altgraph ~`.

This commit duplicates shortcuts containing problematic AltGr keys. For
example, in addition to the `g ~` shortcut I've added `g altgraph ~`.

The keys I did this for are: `~ $ | [ ] { }`.

Fixes #661.

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -103,6 +103,7 @@
   'g 0': 'vim-mode-plus:move-to-beginning-of-screen-line'
   'g ^': 'vim-mode-plus:move-to-first-character-of-screen-line'
   'g $': 'vim-mode-plus:move-to-last-character-of-screen-line'
+  'g altgraph $': 'vim-mode-plus:move-to-last-character-of-screen-line'

   # scroll
   'ctrl-f': 'vim-mode-plus:scroll-full-screen-down'
@@ -154,6 +155,7 @@
   # String transformation
   '~': 'vim-mode-plus:toggle-case-and-move-right'
   'g ~': 'vim-mode-plus:toggle-case'
+  'g altgraph ~': 'vim-mode-plus:toggle-case'
   'g U': 'vim-mode-plus:upper-case'
   'g u': 'vim-mode-plus:lower-case'
   'g C': 'vim-mode-plus:camel-case'
@@ -164,6 +166,7 @@
   'g q': 'vim-mode-plus:reflow'
   'g w': 'vim-mode-plus:reflow-with-stay'
   'g |': 'vim-mode-plus:trim-string'
+  'g altgraph |': 'vim-mode-plus:trim-string'
   'g cmd-d': 'vim-mode-plus:select-occurrence'
   'g ,': 'vim-mode-plus:split-arguments'
   # 'g _': 'vim-mode-plus:snake-case'
@@ -270,9 +273,13 @@
   ']': 'vim-mode-plus:move-down-to-edge'

   # '[ [': 'vim-mode-plus:move-to-previous-fold-start'
+  # '[ altgraph [': 'vim-mode-plus:move-to-previous-fold-start'
   # '] [': 'vim-mode-plus:move-to-next-fold-start'
+  # '] altgraph [': 'vim-mode-plus:move-to-next-fold-start'
   # '[ ]': 'vim-mode-plus:move-to-previous-fold-end'
+  # '[ altgraph ]': 'vim-mode-plus:move-to-previous-fold-end'
   # '] ]': 'vim-mode-plus:move-to-next-fold-end'
+  # '] altgraph ]': 'vim-mode-plus:move-to-next-fold-end'
   # '(': 'vim-mode-plus:move-to-previous-fold-start-with-same-indent'
   # ')': 'vim-mode-plus:move-to-next-fold-start-with-same-indent'

@@ -567,9 +574,13 @@
   'a `': 'vim-mode-plus:a-back-tick'

   'i {': 'vim-mode-plus:inner-curly-bracket'
+  'i altgraph {': 'vim-mode-plus:inner-curly-bracket'
   'i }': 'vim-mode-plus:inner-curly-bracket'
+  'i altgraph }': 'vim-mode-plus:inner-curly-bracket'
   'a {': 'vim-mode-plus:a-curly-bracket'
+  'a altgraph {': 'vim-mode-plus:a-curly-bracket'
   'a }': 'vim-mode-plus:a-curly-bracket'
+  'a altgraph }': 'vim-mode-plus:a-curly-bracket'

   'i B': 'vim-mode-plus:inner-curly-bracket'
   'a B': 'vim-mode-plus:a-curly-bracket'
@@ -581,11 +592,15 @@

   # allow-forwarding family
   # 'i }':  'vim-mode-plus:inner-curly-bracket-allow-forwarding'
+  # 'i altgraph }':  'vim-mode-plus:inner-curly-bracket-allow-forwarding'
   # 'a }':  'vim-mode-plus:a-curly-bracket-allow-forwarding'
+  # 'a altgraph }':  'vim-mode-plus:a-curly-bracket-allow-forwarding'
   # 'i >':  'vim-mode-plus:inner-angle-bracket-allow-forwarding'
   # 'a >':  'vim-mode-plus:a-angle-bracket-allow-forwarding'
   # 'i ]':  'vim-mode-plus:inner-square-bracket-allow-forwarding'
+  # 'i altgraph ]':  'vim-mode-plus:inner-square-bracket-allow-forwarding'
   # 'a ]':  'vim-mode-plus:a-square-bracket-allow-forwarding'
+  # 'a altgraph ]':  'vim-mode-plus:a-square-bracket-allow-forwarding'
   # 'i )':  'vim-mode-plus:inner-parenthesis-allow-forwarding'
   # 'a )':  'vim-mode-plus:a-parenthesis-allow-forwarding'

@@ -593,9 +608,13 @@
   'a t': 'vim-mode-plus:a-tag'

   'i [': 'vim-mode-plus:inner-square-bracket'
+  'i altgraph [': 'vim-mode-plus:inner-square-bracket'
   'i ]': 'vim-mode-plus:inner-square-bracket'
+  'i altgraph ]': 'vim-mode-plus:inner-square-bracket'
   'a [': 'vim-mode-plus:a-square-bracket'
+  'a altgraph [': 'vim-mode-plus:a-square-bracket'
   'a ]': 'vim-mode-plus:a-square-bracket'
+  'a altgraph ]': 'vim-mode-plus:a-square-bracket'

   'i (': 'vim-mode-plus:inner-parenthesis'
   'i )': 'vim-mode-plus:inner-parenthesis'


Fix telnet server shutdown from crashing

--- src/03_telnet_server/server.coffee
+++ src/03_telnet_server/server.coffee
@@ -128,6 +128,7 @@
 		return
 	on_close: () ->

+server = undefined
 server_socket = undefined

 exports.test = ->


horizontal species histogram

horizontal species histogram

--- app/controllers/profile.coffee
+++ app/controllers/profile.coffee
@@ -193,31 +193,25 @@
     width = 900 - margin.left - margin.right
     height = 350 - margin.top - margin.bottom

-
     x = d3.scale.linear()
           .range([0, width])
+          .domain([0, d3.max(data, (d) -> return d.value)])
     y = d3.scale.ordinal()
-          .rangeRoundBands([height, 0], .1, 1)
-
-    # x = d3.scale.ordinal()
-    #       .rangeRoundBands([0, width], .1, 1)
-    # y = d3.scale.linear()
-    #       .range([height, 0])
-
+          .rangeBands([0, height])
+          .domain(data.map((d) -> return d.key))
+
     xAxis = d3.svg.axis()
               .scale(x)
               .orient('bottom')
     yAxis = d3.svg.axis()
               .scale(y)
               .orient('left')
+
     svg = d3.select('.map-container .plot').append('svg')
             .attr('width', width + margin.left + margin.right)
             .attr('height', height + margin.top + margin.bottom)
           .append('g')
             .attr('transform', "translate(#{margin.left}, #{margin.top})")
-
-    x.domain([0, d3.max(data, (d) -> return d.value)])
-    y.domain(data.map((d) -> return d.key))

     svg.append('g')
         .attr('class', 'x axis')
@@ -237,8 +231,7 @@
         .data(data)
       .enter().append('rect')
         .attr('class', 'bar')
-        .attr('x', (d) -> return x(d.value))
-        .attr('width', (d) -> return width - x(d.value))
+        .attr('width', (d) -> return x(d.value))
         .attr('y', (d) -> return y(d.key))
         .attr('height', (d) -> y.rangeBand())


Use XMLSerializer and DOMParser instead of jquery.

Jquery often changes a file into an html file instead. EPUB wants xhtml.

--- scripts/gh-book/xhtml-file.coffee
+++ scripts/gh-book/xhtml-file.coffee
@@ -49,6 +49,13 @@
       base64Encoded: bits
     }

+  xhtmlToDocument = (html) ->
+    parser = new DOMParser()
+    doc = parser.parseFromString(html, 'text/xml')
+    # Chrome litters the entire dom with duplicate ns attributes
+    $(doc).find('html *[xmlns="http://www.w3.org/1999/xhtml"]').removeAttr('xmlns')
+    return doc
+
   # The `Content` model contains the following members:
   #
   # * `title` - an HTML title of the content
@@ -85,34 +92,42 @@
       #   @set 'head', $head.html(), options

       @on 'change:body', (model, value, options) =>
-        $body = jQuery("

")
-        $body.append(value)
+        return if options.imageupdate # Avoid loops
+
+        doc = xhtmlToDocument(
+            '' + value + '')
+        $html = $(doc)
+
+        $error = $html.find('parsererror')
+        $images = $html.find('body img[src^="data:"]:not([data-src])')

         # "1. When the body text is changed" (see above)
         # -------------
         # For newly added images (ones without a `data-src` attribute)
         # Create a new BinaryFile
-        $body.find('img[src^="data:"]:not([data-src])').each (i, img) =>
-          $img = $(img)
-
-          # 'data:image/png;base64,.....'
-          src = $img.attr('src')
-          attrs = imgDataSrcToAttributes(src)
-
-          # If the resource is not already in allContent then add it
-          if not allContent.get(attrs.id)
-            imageModel = new BinaryFile(attrs)
-            # Make sure the mediaType is set (This may be redundant)
-            imageModel.mediaType = attrs.mediaType
-            # Set the bits here so isDirty is set to true
-            imageModel._markDirty({}, true)
-
-            allContent.add(imageModel)
-
-          # $img.attr('data-src', attrs.id) TODO: Aloha keeps stripping this attribute off.
-
-        @set('body', $body.html().trim())
-
+        if $error.length == 0 and $images.length > 0
+          $images.each (i, img) =>
+            $img = $(img)
+
+            # 'data:image/png;base64,.....'
+            src = $img.attr('src')
+            attrs = imgDataSrcToAttributes(src)
+
+            # If the resource is not already in allContent then add it
+            if not allContent.get(attrs.id)
+              imageModel = new BinaryFile(attrs)
+              # Make sure the mediaType is set (This may be redundant)
+              imageModel.mediaType = attrs.mediaType
+              # Set the bits here so isDirty is set to true
+              imageModel._markDirty({}, true)
+
+              allContent.add(imageModel)
+
+            # $img.attr('data-src', attrs.id) TODO: Aloha keeps stripping this attribute off.
+
+          @set 'body',
+            (new XMLSerializer).serializeToString($html.find('body')[0]),
+            imageupdate: true

     # Since the titles are purely cosmetic do not mark the model as dirty
     # TODO: Remove this after the sprint
@@ -126,64 +141,32 @@
       return $.when(@_imagesLoaded, originalPromise)

     parse: (json) ->
-      html = json.content
-
       # If the parse is a result of a write then update the sha.
       # The parse is a result of a GitHub.write if there is no `.content`
       return {} if not json.content

-      # Rename elements before jQuery parses and removes them
-      # (because they are not valid children of a div)
-
+      html = json.content
       html = "#{html}" if not /<body/.test html
       html = "#{html}" if not /<html/.test html

-      # Do **not** use dashes in element names because browsers will ignore the '/'
-      # in "<*-img/>" and stuff the rest of the HTML into the "<prefix-img>" element.
-      html = html.replace(/html>/g, "prefixhtml>")
-      html = html.replace(/<\/head>/g, "</prefixhead>")
-
-      # **Note:** There is a `<tbody>` element so we have to match start of the tag
-      html = html.replace(/<body/g, "<prefixbody")
-      html = html.replace(/<\/body/g, "</prefixbody")
-
-      html = html.replace(/<html/g, "<prefixhtml")
-      # Search for the exact element `<head>` since otherwise it could be
-      # confused with `<header>`.
-      html = html.replace(//g, "<prefixhead>")
-      html = html.replace(/<body/g, "<prefixbody")
-
-      # When an `<img src="...">` is parsed by jQuery the src attribute is fetched
-      # even if the image hasn't been added to the DOM yet.
-      # Instead of letting that silently fail,
-      # replace the `img` tag with another element until the bytes are retrieved
-      # via the github API.
-      html = html.replace(/]*)>/g, '<prefiximg$1 />')
-
-      $html = jQuery(html)
-
-      $head = $html.find('prefixhead')
-      $body = $html.find('prefixbody')
+      # Parse to DOM tree
+      doc = xhtmlToDocument(html)
+      $html = $(doc)
+
+      $head = $html.find('head')
+      $body = $html.find('body')

       # Change the `src` attribute to be a `data-src` attribute if the URL is relative
-      $body.find('prefiximg').each (i, img) ->
+      $body.find('img').each (i, img) ->
         $imgHolder = jQuery(img)
         src = $imgHolder.attr 'src'

         keepSrc = /^https?:/.test(src) or /^data:/.test(src)
-
-        $imgHolder.removeAttr 'src' if not keepSrc
-
-        # Replace the `<prefiximg>` with a real `<img>` and set the `src` attribute
-        $img = jQuery('')
-        $img.attr 'data-src', src if not keepSrc
-        # Transfer all the attributes to `$img`
-        $img.attr(Utils.elementAttributes $imgHolder)
-
-        $imgHolder.replaceWith $img
-
-
-      @loadImages($body)
+        if not keepSrc
+          $imgHolder.attr 'data-src', src
+          $imgHolder.removeAttr 'src'
+
+      @loadImages($html)

       attributes =
         head: $head[0]?.innerHTML.trim()
@@ -199,12 +182,12 @@
     # Called both by `.parse()` and when making a clone.
     # Done when making a clone because the clone `isNew` so it will not fetch
     # the images.
-    loadImages: ($body=null) ->
-      if not $body
-        $body = $('

')
-        $body.append(@get('body'))
-
-      $images = $body.find('img[data-src]')
+    loadImages: ($html=null) ->
+      if not $html
+        doc = xhtmlToDocument(@get('body'))
+        $html = $(doc)
+
+      $images = $html.find('body img[data-src]')
       counter = $images.length
       allImages = []

@@ -219,7 +202,10 @@
           console.error "ERROR: Manifest missing image file #{path}"
           counter--
           # Set `parse:true` so the dirty flag for saving is not set
-          @set 'body', $body.html().trim(), {parse:true, loading:true} if counter == 0
+          if counter == 0
+            @set 'body',
+              (new XMLSerializer).serializeToString($html.find('body')[0]),
+              parse:true, loading:true
           deferred.resolve()
           return

@@ -234,7 +220,10 @@

           counter--
           # Set `parse:true` so the dirty flag for saving is not set
-          @set 'body', $body.html().trim(), {parse:true, loading:true} if counter == 0
+          if counter == 0
+            @set 'body',
+              (new XMLSerializer).serializeToString($html.find('body')[0]),
+              parse:true, loading:true
           deferred.resolve()
         .fail ->
           counter--
@@ -247,69 +236,41 @@
     serialize: ->
       head = @get 'head'
       body = @get 'body'
-      $head = jQuery("
#{head}
")
-      $body = jQuery("
#{body}
")
-
-
-      # Do **not** use dashes in element names because browsers will ignore the '/'
-      # in "<*-img/>" and stuff the rest of the HTML into the "<prefix-img>" element.
+
+      xml = """
+        <?xml version="1.0" encoding="UTF-8"?>
+
+          #{head}
+          #{body}
+        """
+
+      doc = xhtmlToDocument(xml)
+      $html = $(doc)

       # "2. When the model is serialized" (see above)
       # -------------
-      $body.find('img[src^="data:"]:not([data-src])').each (i, img) =>
+      $html.find('body img[src^="data:"]:not([data-src])').each (i, img) =>
         $img = $(img)
         src = $img.attr('src')
         attrs = imgDataSrcToAttributes(src)
         imgAbsolutePath = attrs.id
         # Create a relative path to src
         src = Utils.relativePath(@id, imgAbsolutePath)
-
-        # To prevent a 404 and trying to load a bunch of images, do not
-        # change the image source.
-        # Instead, use a dummy element and then replace it from the resulting HTML
-        $imgHolder = jQuery('<prefiximg></prefiximg>')
-        $imgHolder.attr(Utils.elementAttributes $img)
-        $imgHolder.attr 'src', src
-
-        $img.replaceWith $imgHolder
+        $img.attr 'src', src

       # Replace all the `img[data-src]` attributes with `img[src]`
-      $body.find('img[data-src]').each (i, img) ->
+      $html.find('body img[data-src]').each (i, img) ->
         $img = jQuery(img)
         src = $img.attr('data-src')
         $img.removeAttr('data-src')
-        $img.removeAttr('src')
-
-        # To prevent a 404 and trying to load a bunch of images, do not
-        # change the image source.
-        # Instead, use a dummy element and then replace it from the resulting HTML
-        $imgHolder = jQuery('<prefiximg></prefiximg>')
-        $imgHolder.attr(Utils.elementAttributes $img)
-        $imgHolder.attr 'src', src
-
-        $img.replaceWith $imgHolder
-
-      headHtml = $head.html().trim()
-      bodyHtml = $body.html().trim()
-
-      # The text currently contains `<prefiximg ... ></prefiximg>`
-      # To make the `<img>` valid XHTML (self-closing like `<img/>`)
-      # replace "></prefiximg" with "/". Note the missing '>' ; )
-      bodyHtml = bodyHtml.replace(/><\/prefiximg/g, '/')
-      bodyHtml = bodyHtml.replace(/<prefiximg/g, '</g, '>\n<')
       # bodyHtml = bodyHtml.replace(/></g, '>\n<')

-      return """
-        <?xml version="1.0" encoding="UTF-8"?>
-
-          #{headHtml?.trim() or ''}
-          #{bodyHtml?.trim() or ''}
-
-        """
+      return (new XMLSerializer).serializeToString(doc)

     # Hook to merge local unsaved changes into the remotely-updated model
     onReloaded: (oldContent) ->


Remove the GET request by the browser when jQuery parses HTML with an IMG tag

--- scripts/gh-book/xhtml-file.coffee
+++ scripts/gh-book/xhtml-file.coffee
@@ -41,20 +41,36 @@
       html = html.replace(//g, "<prefix-head>")
       html = html.replace(/<body/g, "<prefix-body")

+      # When an `<img src="...">` is parsed by jQuery the src attribute is fetched
+      # even if the image hasn't been added to the DOM yet.
+      # Instead of letting that silently fail,
+      # replace the `img` tag with another element until the bytes are retrieved
+      # via the github API.
+      html = html.replace(/<img/g, '<prefix-img')
+      html = html.replace(/<\/img>/g, '</prefix-img>')
+
       $html = jQuery(html)

       $head = $html.find('prefix-head')
       $body = $html.find('prefix-body')

       # Change the `src` attribute to be a `data-src` attribute if the URL is relative
-      $html.find('img').each (i, img) ->
-        $img = jQuery(img)
-        src = $img.attr 'src'
+      $html.find('prefix-img').each (i, img) ->
+        $imgHolder = jQuery(img)
+        src = $imgHolder.attr 'src'
         return if /^https?:/.test src
         return if /^data:/.test src

-        $img.removeAttr 'src'
+        $imgHolder.removeAttr 'src'
+
+        # Replace the `<prefix-img>` with a real `<img>` and set the `src` attribute
+        $img = jQuery('')
         $img.attr 'data-src', src
+        # Transfer all the attributes to `$img`
+        jQuery.each $imgHolder[0].attributes, (index, attr) =>
+          $img.attr attr.name, attr.value
+
+        $imgHolder.replaceWith $img

       $images = $html.find('img[data-src]')


Fix form validation bug

Fix form validation bug

--- views/app.coffee
+++ views/app.coffee
@@ -22,7 +22,7 @@
 sanitize_form = ->
   $('input[type=text]').each ->
     $(this).val $(this).val().trim()
-  $('#uid').val $('#uid').val().replace /@.*/, ''
+  $('#uid').val $('#uid').val().replace /@.*/, '' if $('#uid').length is 1

 validate_form = ->


fix bad checks for fixed font values

fix bad checks for fixed font values

--- bokehjs/src/coffee/common/properties.coffee
+++ bokehjs/src/coffee/common/properties.coffee
@@ -344,7 +344,7 @@

   cache_select: (name, i) ->
     if name == "font"
-      if @font.value? and @font_size.value? and @font_style.value?
+      if @font.fixed_value? and @font_size.fixed_value? and @font_style.fixed_value?
         @cache.font = @font_value()
       else
         @cache.font = @cache.font_array[i]


Sim: Transform sets boosts for client.

Sim: Transform sets boosts for client.

--- server/bw/attachment.coffee
+++ server/bw/attachment.coffee
@@ -1180,10 +1180,10 @@
     @pokemon.weight = target.weight
     # The rest aren't.
     @pokemon.moves     = _.clone(target.moves)
-    @pokemon.stages    = _.clone(target.stages)
     @pokemon.types     = _.clone(target.types)
     @pokemon.evs       = _.extend({}, target.evs, hp: @pokemon.evs.hp)
     @pokemon.baseStats = _.extend({}, target.baseStats, hp: @pokemon.baseStats.hp)
+    @pokemon.setBoosts(target.stages)
     @pokemon.resetAllPP(5)
     # Send updated information to the client
     @pokemon.changeSprite(target.name, target.forme)


remove unused code

remove unused code

--- app/lib/experiments.coffee
+++ app/lib/experiments.coffee
@@ -10,4 +10,8 @@
     dataType: 'json'
   }).promise()

+getCohort = (data) ->
+  data.cohort
+
+exports.getCohort = getCohort
 exports.getExperiment = getExperiment


Pets should only have one name

Pets should only have one name

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -149,32 +149,14 @@
     'Ofgar'
     'Randall'
   ]
-  'Raven Pet': [
-    # Animal
-    'Nevermore'
-    'Poe'
-  ]
-  'Cougar Pet': [
-    # Animal
-    'Kitty'
-  ]
-  'Frog Pet': [
-    # Animal
-    'Bighead'
-    'Bufo'
-    'Bunda'
-    'Dan\'l Webster'
-    'Freddy'
-    'Frogger'
-    'Froggy'
-    'Hypnotoad'
-    'Mr. Toad'
-    'Slippy'
-    'Toada'
-    'Trevor'
-    'Wart'
-    'Wei Qi'
-  ]
+  # Pets only have one name
+  'Raven Pet': ['Nevermore']
+  'Cougar Pet': ['Kitty']
+  'Frog Pet': ['Hypnotoad']
+  'Griffin Pet': ['']
+  'Pugicorn Pet': ['']
+  'Polar Bear Pet': ['']
+  'Wolf Pet': ['']
   'Horse': [
     # Animal
     'Abby'


fixed initial folder structure to be opened

--- client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -8,7 +8,7 @@
     @kiteController = @getSingleton('kiteController')
     {nickname} = KD.whoami().profile
     treeOptions =
-      treeItemClass     : options.treeItemClass     or NFinderItem
+      treeItemClass     : options.treeItemClass     or NFinderItem
       nodeIdPath        : options.nodeIdPath        or "path"
       nodeParentIdPath  : options.nodeParentIdPath  or "parentPath"
       dragdrop          : options.dragdrop           ? yes
@@ -23,64 +23,78 @@
       delegate          : @

     @treeController = new NFinderTreeController treeOptions, []
-
+
+    @defaultStructureLoaded = no
+
     @treeController.on "file.opened", (file)=> @setRecentFile file.path
     @treeController.on "folder.expanded", (folder)=> @setRecentFolder folder.path
     @treeController.on "folder.collapsed", (folder)=> @unsetRecentFolder folder.path
-
+
+    @listenTo
+      KDEventTypes       : "AppManagerOpensAnApplication"
+      listenedToInstance : appManager
+      callback           : (pubInst, appInst)=>
+        if appInst instanceof StartTab12345 and not @defaultStructureLoaded
+          @loadDefaultStructure()
+
+    @loadDefaultStructureTimer = @utils.wait 5000, =>
+      @loadDefaultStructure()

   loadView:(mainView)->

     mainView.addSubView @treeController.getView()
-
+
   reset:()->
-
+
     delete @_storage
-
-    if KD.whoami() instanceof bongo.api.JAccount
+    {initialPath} = @getOptions() # not used, fix this

-      {initialPath} = @getOptions()
+    mount = if KD.isLoggedIn()
       {nickname}    = KD.whoami().profile
-
-      mount         = FSHelper.createFile
+      FSHelper.createFile
         name        : nickname
         path        : "/Users/#{nickname}"
         type        : "mount"
-
-      @treeController.initTree [mount]
-
-      @fetchStorage (storage)=>
-
-        recentFolders = if storage.bucket?.recentFolders?
-          storage.bucket.recentFolders
-        else
-          [
-            "/Users/#{nickname}"
-            "/Users/#{nickname}/Sites"
-            "/Users/#{nickname}/Sites/#{nickname}.beta.koding.com"
-            "/Users/#{nickname}/Sites/#{nickname}.beta.koding.com/website"
-          ]
-
-        @utils.wait 2000, =>
-
-          a = Date.now()
-          mount.emit "fs.fetchContents.started"
-          KD.getSingleton('kiteController').run
-            withArgs  :
-              command : "ls #{recentFolders.join(" ")} -lpva --group-directories-first --time-style=full-iso"
-          , (err, response)=>
-            if response
-              files = FSHelper.parseLsOutput recentFolders, response
-              @treeController.addNodes files
-            log "#{(Date.now()-a)/1000}sec"
-            mount.emit "fs.fetchContents.finished"
-
     else
-      mount         = FSHelper.createFile
+      FSHelper.createFile
         name        : "guest"
         path        : "/Users/guest"
         type        : "mount"
-      @treeController.initTree [mount]
+    @defaultStructureLoaded = no
+    @treeController.initTree [mount]
+
+  loadDefaultStructure:->
+
+    @defaultStructureLoaded = yes
+    @utils.killWait @loadDefaultStructureTimer
+
+    return unless KD.isLoggedIn()
+    {nickname} = KD.whoami().profile
+
+    @fetchStorage (storage)=>
+      recentFolders = if storage.bucket?.recentFolders? and storage.bucket.recentFolders.length > 0
+        storage.bucket.recentFolders
+      else
+        [
+          "/Users/#{nickname}"
+          "/Users/#{nickname}/Sites"
+          "/Users/#{nickname}/Sites/#{nickname}.beta.koding.com"
+          "/Users/#{nickname}/Sites/#{nickname}.beta.koding.com/website"
+        ]
+
+      timer = Date.now()
+      mount = @treeController.nodes["/Users/#{nickname}"].getData()
+
+      mount.emit "fs.fetchContents.started"
+      KD.getSingleton('kiteController').run
+        withArgs  :
+          command : "ls #{recentFolders.join(" ")} -lpva --group-directories-first --time-style=full-iso"
+      , (err, response)=>
+        if response
+          files = FSHelper.parseLsOutput recentFolders, response
+          @treeController.addNodes files
+        log "#{(Date.now()-timer)/1000}sec"
+        mount.emit "fs.fetchContents.finished"

   fetchStorage:(callback)->
@@ -99,11 +113,11 @@
       unless filePath in recentFiles
         recentFiles.pop() if recentFiles.length is @getOptions().maxRecentFiles
         recentFiles.unshift filePath
-
+
       storage.update {
         $set: 'bucket.recentFiles': recentFiles.slice(0,10)
       }, callback
-
+
   setRecentFolder:(folderPath, callback)->

     @fetchStorage (storage)=>
@@ -114,20 +128,27 @@
         recentFolders.push folderPath

       recentFolders.sort (path)-> if path is folderPath then -1 else 0
-
+
       storage.update {
         $set: 'bucket.recentFolders': recentFolders
-      }, => log "recentFolder set"
+      }, => #log "recentFolder set"

   unsetRecentFolder:(folderPath, callback)->

     @fetchStorage (storage)=>

       recentFolders = if storage.bucket?.recentFolders? then storage.bucket.recentFolders else []
-      if folderPath in recentFolders
-        recentFolders.splice recentFolders.indexOf(folderPath), 1
+
+      splicer = ->
+        recentFolders.forEach (recentFolderPath)->
+          if recentFolderPath.search(folderPath) > -1
+            recentFolders.splice recentFolders.indexOf(recentFolderPath), 1
+            splicer()
+            return
+
+      splicer()
       recentFolders.sort (path)-> if path is folderPath then -1 else 0

       storage.update {
         $set: 'bucket.recentFolders': recentFolders
-      }, => log "recentFolder unset"
+      }, => #log "recentFolder unset"


Fix link in news

Fix link in news


no app references in backoffice

no app references in backoffice

--- app/assets/javascripts/backbone/collections/propositions.js.coffee
+++ app/assets/javascripts/backbone/collections/propositions.js.coffee
@@ -1,11 +1,12 @@
 class window.PropositionsCollection extends Backbone.Collection

   initialize: ->
-    app.models.election.bind 'change', (election) =>
-      @electionId = election.id
-
-    app.models.tag.bind 'change', (tag) =>
-      @tagId = tag.id
+    if app?
+      app.models.election.bind 'change', (election) =>
+       @electionId = election.id
+
+      app.models.tag.bind 'change', (tag) =>
+        @tagId = tag.id

   model: PropositionModel

@@ -13,4 +14,4 @@
     "/api/v1/propositions/search"

   parse: (response) ->
-    response.response.propositions+    response.response.propositions


Repeat sessions call every 30 seconds

Repeat sessions call every 30 seconds

--- views/map.coffee
+++ views/map.coffee
@@ -17,3 +17,4 @@
         map: map,
         title: session.user_last_name
       )
+    setTimeout(loadSessions, 30000)


Added Statement to the grammar.

Added Statement to the grammar.

--- grammar.coffee
+++ grammar.coffee
@@ -55,22 +55,35 @@
   Root: [
     o "",                                       -> new Expressions()
     o "TERMINATOR",                             -> new Expressions()
-    o "Expressions"
+    o "Body"
     o "Block TERMINATOR"
   ]

-  # Any list of expressions or method body, seperated by line breaks or
-  # semicolons.
-  Expressions: [
-    o "Expression",                             -> Expressions.wrap [$1]
-    o "Expressions TERMINATOR Expression",      -> $1.push $3
-    o "Expressions TERMINATOR"
+  # Any list of statements and expressions, seperated by line breaks or semicolons.
+  Body: [
+    o "Line",                                   -> Expressions.wrap [$1]
+    o "Body TERMINATOR Line",                   -> $1.push $3
+    o "Body TERMINATOR"
+  ]
+
+  # Expressions and statements, which make up a line in a body.
+  Line: [
+    o "Expression"
+    o "Statement"
+  ]
+
+  # Pure statements which cannot be expressions.
+  Statement: [
+    o "Return"
+    o "Throw"
+    o "BREAK",                                  -> new LiteralNode yytext
+    o "CONTINUE",                               -> new LiteralNode yytext
   ]

   # All the different types of expressions in our language. The basic unit of
-  # CoffeeScript is the **Expression** -- you'll notice that there is no
-  # "statement" nonterminal. Expressions serve as the building blocks
-  # of many other rules, making them somewhat circular.
+  # CoffeeScript is the **Expression** -- everything that can be an expression
+  # is one. Expressions serve as the building blocks of many other rules, making
+  # them somewhat circular.
   Expression: [
     o "Value"
     o "Call"
@@ -80,8 +93,6 @@
     o "Assign"
     o "If"
     o "Try"
-    o "Throw"
-    o "Return"
     o "While"
     o "For"
     o "Switch"
@@ -97,7 +108,7 @@
   # will convert some postfix forms into blocks for us, by adjusting the
   # token stream.
   Block: [
-    o "INDENT Expressions OUTDENT",             -> $2
+    o "INDENT Body OUTDENT",             -> $2
     o "INDENT OUTDENT",                         -> new Expressions()
     o "TERMINATOR Comment",                     -> Expressions.wrap [$2]
   ]
@@ -120,8 +131,6 @@
     o "AlphaNumeric"
     o "JS",                                     -> new LiteralNode yytext
     o "REGEX",                                  -> new LiteralNode yytext
-    o "BREAK",                                  -> new LiteralNode yytext
-    o "CONTINUE",                               -> new LiteralNode yytext
     o "TRUE",                                   -> new LiteralNode true
     o "FALSE",                                  -> new LiteralNode false
     o "YES",                                    -> new LiteralNode true
@@ -396,7 +405,7 @@
   # where only values are accepted, wrapping it in parentheses will always do
   # the trick.
   Parenthetical: [
-    o "( Expression )",                         -> new ParentheticalNode $2
+    o "( Line )",                         -> new ParentheticalNode $2
   ]

   # A language extension to CoffeeScript from the outside. We simply pass
@@ -415,6 +424,7 @@
   # or postfix, with a single expression. There is no do..while.
   While: [
     o "WhileSource Block",                      -> $1.add_body $2
+    o "Statement WhileSource",                 -> $2.add_body Expressions.wrap [$1]
     o "Expression WhileSource",                 -> $2.add_body Expressions.wrap [$1]
   ]

@@ -422,6 +432,7 @@
   # Comprehensions can either be normal, with a block of expressions to execute,
   # or postfix, with a single expression.
   For: [
+    o "Statement FOR ForVariables ForSource",  -> new ForNode $1, $4, $3[0], $3[1]
     o "Expression FOR ForVariables ForSource",  -> new ForNode $1, $4, $3[0], $3[1]
     o "FOR ForVariables ForSource Block",       -> new ForNode $4, $3, $2[0], $2[1]
   ]
@@ -491,7 +502,9 @@
   # *if* and *unless*.
   If: [
     o "IfBlock"
+    o "Statement IF Expression",                -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true}
     o "Expression IF Expression",               -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true}
+    o "Statement UNLESS Expression",            -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true, invert: true}
     o "Expression UNLESS Expression",           -> new IfNode $3, Expressions.wrap([$1]), null, {statement: true, invert: true}
   ]


Fixed a spelling type in assert.

Fixed a spelling type in assert.

--- src/elements/ListView/ListView.coffee
+++ src/elements/ListView/ListView.coffee
@@ -49,7 +49,7 @@

 		@__maxCols = []
 		for col, col_i in @__cols
-			CUI.util.assert(col in ["auto", "maximize", "fixed", "manual"], "new #{@__cls}", "Unkown type of col: \"#{col}\". opts.cols can only contain \"auto\" and \"maximize\" elements.")
+			CUI.util.assert(col in ["auto", "maximize", "fixed", "manual"], "new #{@__cls}", "Unknown type of col: \"#{col}\". opts.cols can only contain \"auto\" and \"maximize\" elements.")
 			if col == "maximize"
 				# CUI.util.assert(@_maximize, "new CUI.ListView", "maximized columns can only exist inside an maximized ListView", opts: @opts)
 				CUI.util.assert(col_i >= @fixedColsCount, "new CUI.ListView", "maximized columns can only be in the non-fixed side of the ListView.", opts: @opts)


para names changed

para names changed

--- modules/levelcreator/app/assets/javascripts/level_creator/plugins/write_para_data.coffee
+++ modules/levelcreator/app/assets/javascripts/level_creator/plugins/write_para_data.coffee
@@ -33,13 +33,13 @@
     for segment in activeSegments

       segmentPayload =
-        id : segment.id
-        segmentValue : segment.value
+
+        segmentLabel : segment.value

         isEndSegment : segment.isEndSegment
         isStartSegment : segment.isStartSegment

-        outlineLength : segment.path.length
+        perimeter : segment.path.length
         size : segment.size

         probability : segment.probability || -1


Fixes to automatic refresh logic.

Fixes to automatic refresh logic.

--- public/scripts/refresh.coffee
+++ public/scripts/refresh.coffee
@@ -1,14 +1,14 @@

-refreshInterval = 60 * 1000 # 60 Seconds
+refreshInterval = 60 * 5 * 1000 # 5 Minutes

-refresh ->
-  xhr = new XMLHttpRequest
+handleRefresh = ->
+  document.querySelector(".widget").innerHTML = this.response
+
+refresh = ->
+  xhr = new XMLHttpRequest()
   xhr.onload = handleRefresh
   xhr.open("get", window.location.href, true)
   xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest")
-  xhr.send
-
-handleRefresh ->
-  document.querySelector(".widget").innerHTML = this.response
+  xhr.send()

 setInterval(refresh, refreshInterval)


Client: Sidebar: formatting

Client: Sidebar: formatting

--- client/app/lib/flux/environment/stores/activeinvitationmachineidstore.coffee
+++ client/app/lib/flux/environment/stores/activeinvitationmachineidstore.coffee
@@ -16,8 +16,9 @@

   setMachineId: (activeMachineId, { id, forceUpdate }) ->

-    return id  if forceUpdate
-    return null  if activeMachineId is id
-    return id
+    if forceUpdate or activeMachineId isnt id
+      return id
+
+    return null


Less fat arrows in BrainstemExpectation

Less fat arrows in BrainstemExpectation

--- vendor/assets/javascripts/brainstem/brainstem-expectation.coffee
+++ vendor/assets/javascripts/brainstem/brainstem-expectation.coffee
@@ -16,21 +16,21 @@
     @requestQueue = []
     @options.response(@) if @options.response?

-  remove: =>
+  remove: ->
     @disabled = true

-  recordRequest: (loader) =>
+  recordRequest: (loader) ->
     if @immediate
       @handleRequest(loader)
     else
       @requestQueue.push(loader)

-  respond: =>
+  respond: ->
     for request in @requestQueue
       @handleRequest request
     @requestQueue = []

-  handleRequest: (loader) =>
+  handleRequest: (loader) ->
     @matches.push loader.originalOptions

     if @triggerError?
@@ -56,7 +56,7 @@

     loader._onLoadSuccess(returnedModels)

-  optionsMatch: (name, options) =>
+  optionsMatch: (name, options) ->
     @manager.dataLoader._checkPageSettings options
     if !@disabled && @collectionName == name
       _(['include', 'only', 'order', 'filters', 'perPage', 'page', 'limit', 'offset', 'search']).all (optionType) =>


Use ATOM_ACCESS_TOKEN when available

Use ATOM_ACCESS_TOKEN when available

--- src/auth.coffee
+++ src/auth.coffee
@@ -6,6 +6,10 @@
   # * callback: A function to call with an error as the first argument and a
   #             string token as the second argument.
   getToken: (callback) ->
+    if token = process.env.ATOM_ACCESS_TOKEN
+      callback(null, token)
+      return
+
     tokenName = 'GitHub API Token'
     command = "security -q find-generic-password -ws '#{tokenName}'"
     child_process.exec command, (error, stdout='', stderr='') ->


some refactor

some refactor


delete app with empty start at start

delete app with empty start at start

--- config/initializers/resetproxy.coffee
+++ config/initializers/resetproxy.coffee
@@ -16,7 +16,10 @@
             appDict = {}
             if installedApps isnt undefined
                 for installedApp in installedApps
-                    appDict[installedApp.name] = installedApp
+                    if installeApp.name isnt ""
+                        appDict[installedApp.name] = installedApp
+                    else
+                        installedApp.destroy()

             haibuClient.get 'drones/running', (err, res, apps) ->
                 updateApps apps, appDict, resetProxy
@@ -46,4 +49,4 @@
                 console.info 'Something went wrong while reseting proxy.'

     if process.env.NODE_ENV != "test"
-        resetRoutes()+        resetRoutes()


Workspace: Removed settings menu of workspace terminal.

--- client/landing/app/CommonViews/workspace/panes/terminalpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/terminalpane.coffee
@@ -6,9 +6,10 @@

     super options, data

-    @webterm = new WebTermView
-      delegate : @
-      cssClass : "webterm"
+    @webterm           = new WebTermView
+      delegate         : @
+      cssClass         : "webterm"
+      advancedSettings : no

     @webterm.on "WebTermConnected", (@remote)=>
       {command} = @getProperties()


fix(web): Fixing manifest files

fix(web): Fixing manifest files

--- apps/actor-web/gulpfile.coffee
+++ apps/actor-web/gulpfile.coffee
@@ -108,7 +108,7 @@
     .pipe connect.reload()

 gulp.task 'manifest', ->
-  gulp.src ['./dist/*']
+  gulp.src ['./dist/**/*']
     .pipe manifest {
       hash: true,
       preferOnline: true,


Destroy environment to silence leaked editor warning

--- spec/atom-environment-spec.coffee
+++ spec/atom-environment-spec.coffee
@@ -314,9 +314,12 @@
             }
           )
         })
+        atom2.initialize({document, window})
         atom2.deserialize(atom.serialize())

         expect(atom2.textEditors.getGrammarOverride(editor)).toBe('text.plain')
+
+        atom2.destroy()

   describe "openInitialEmptyEditorIfNecessary", ->
     describe "when there are no paths set", ->


stack-editor: redirect user to IDE when a stack is provisioned

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -760,7 +760,7 @@

     @outputView.add 'Generating stack from template...'

-    stackTemplate.generateStack (err) =>
+    stackTemplate.generateStack (err, result) =>
       @generateStackButton.hideLoader()

       return  if @outputView.handleError err
@@ -769,6 +769,7 @@

       computeController.reset yes

+      kd.singletons.router.handleRoute "/IDE/#{result.results.machines[0].obj.label}"
       @emit 'Reload'


De-indented if statements

De-indented if statements

--- client/test/lib/helpers/teamshelpers.coffee
+++ client/test/lib/helpers/teamshelpers.coffee
@@ -326,10 +326,10 @@
       .waitForElementVisible  createChannelModalNameSelector, 20000
       .setValue               createChannelModalNameSelector, channelName

-      if purpose
-        browser
-          .waitForElementVisible  purposeSelector, 20000
-          .setValue               purposeSelector, purpose
+    if purpose
+      browser
+        .waitForElementVisible  purposeSelector, 20000
+        .setValue               purposeSelector, purpose

     browser
       .waitForElementVisible  createChannelModalButtonSelector, 20000
@@ -351,8 +351,9 @@
         .pause                  2000 # wait for side bar channel list
         .waitForElementVisible  channelLinkOnSidebarSelector, 20000
         .assert.containsText    sidebarSectionsSelector, channelName
-        if purpose
-          browser.assert.containsText '.ChannelThreadPane-purposeWrapper .ChannelThreadPane-purpose',purpose
+
+    if purpose
+      browser.assert.containsText '.ChannelThreadPane-purposeWrapper .ChannelThreadPane-purpose',purpose

     return channelName