Made Sankey beta version based on useful, final and primary demand

--- app/assets/javascripts/d3/sankey.coffee
+++ app/assets/javascripts/d3/sankey.coffee
@@ -1,8 +1,8 @@
 D3.sankey =
   data :
     nodes: [
+      {id: 'coal_and_derivatives',     column: 0},
       {id: 'oil_and_derivatives',        column: 0},
-      {id: 'coal_and_derivatives',     column: 0},
       {id: 'useful_demand',         column: 0},
       {id: 'non_renewable_waste',         column: 0},
       {id: 'geo_solar_wind_water_ambient',     column: 0},
@@ -10,114 +10,109 @@
       {id: 'nuclear',       column: 0},
       {id: 'biomass_products',       column: 0},
       {id: 'mixed_flows',       column: 0},
-
-      {id: 'electricity_prod',     column: 1, label: "electricity production"},
-      {id: 'heat',   column: 1, label: "heat production"},
-      {id: 'distribution',   column: 1, label: "distribution"},
-
-      {id: 'households',  column: 2},
-      {id: 'buildings',   column: 2},
-      {id: 'transport',   column: 2},
-      {id: 'industry',    column: 2},
-      {id: 'agriculture', column: 2},
-      {id: 'other',       column: 2},
-      {id: 'loss',        column: 2},
+      {id: 'electricity',       column: 0},
+
+      {id: 'households',  column: 1},
+      {id: 'buildings',   column: 1},
+      {id: 'agriculture', column: 1},
+      {id: 'transport',   column: 1},
+      {id: 'industry',    column: 1},
+      {id: 'other',       column: 1},
+
+      {id: 'useful_demand_electric',     column: 2, label: "electricity"},
+      {id: 'useful_demand_heat_cold',   column: 2, label: "heat cold"},
+      {id: 'useful_demand_transport',   column: 2, label: "transport"},
+      {id: 'useful_demand_non_energetic',   column: 2, label: "non-energetic"},
+
+
     ]
     links: [
       #Direct links between carrier and sector
-      {left: 'electricity_prod',      right: 'households', gquery: 'electricity_prod_to_households_in_sankey', color: 'blue'},
-      {left: 'heat',      right: 'households', gquery: 'heat_to_households_in_sankey', color: 'orange'},
-      {left: 'electricity_prod',      right: 'buildings', gquery: 'electricity_prod_to_buildings_in_sankey', color: 'blue'},
-      {left: 'heat',      right: 'buildings', gquery: 'heat_to_buildings_in_sankey', color: 'orange'},
-      {left: 'electricity_prod',      right: 'agriculture', gquery: 'electricity_prod_to_agriculture_in_sankey', color: 'blue'},
-      {left: 'heat',      right: 'agriculture', gquery: 'heat_to_agriculture_in_sankey', color: 'orange'},
-      {left: 'electricity_prod',      right: 'transport', gquery: 'electricity_prod_to_transport_in_sankey', color: 'blue'},
-      {left: 'heat',      right: 'transport', gquery: 'heat_to_transport_in_sankey', color: 'orange'},
-      {left: 'electricity_prod',      right: 'industry', gquery: 'electricity_prod_to_industry_in_sankey', color: 'blue'},
-      {left: 'heat',      right: 'industry', gquery: 'heat_to_industry_in_sankey', color: 'orange'},
-      {left: 'electricity_prod',      right: 'other', gquery: 'electricity_prod_to_other_in_sankey', color: 'blue'},
-      {left: 'heat',      right: 'other', gquery: 'heat_to_other_in_sankey', color: 'orange'},
-      {left: 'distribution',      right: 'households', gquery: 'coal_and_derivatives_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'oil_and_derivatives_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'useful_demand_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'non_renewable_waste_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'geo_solar_wind_water_ambient_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'natural_gas_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'nuclear_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'biomass_products_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'households', gquery: 'mixed_flows_to_households_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'coal_and_derivatives_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'oil_and_derivatives_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'useful_demand_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'non_renewable_waste_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'geo_solar_wind_water_ambient_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'natural_gas_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'nuclear_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'biomass_products_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'buildings', gquery: 'mixed_flows_to_buildings_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'coal_and_derivatives_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'oil_and_derivatives_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'useful_demand_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'non_renewable_waste_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'geo_solar_wind_water_ambient_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'natural_gas_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'nuclear_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'biomass_products_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'agriculture', gquery: 'mixed_flows_to_agriculture_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'coal_and_derivatives_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'oil_and_derivatives_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'useful_demand_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'non_renewable_waste_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'geo_solar_wind_water_ambient_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'natural_gas_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'nuclear_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'biomass_products_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'transport', gquery: 'mixed_flows_to_transport_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'coal_and_derivatives_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'oil_and_derivatives_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'useful_demand_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'non_renewable_waste_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'geo_solar_wind_water_ambient_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'natural_gas_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'nuclear_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'biomass_products_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'industry', gquery: 'mixed_flows_to_industry_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'coal_and_derivatives_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'oil_and_derivatives_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'useful_demand_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'non_renewable_waste_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'geo_solar_wind_water_ambient_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'natural_gas_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'nuclear_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'biomass_products_to_other_in_sankey', color: 'black'},
-      {left: 'distribution',      right: 'other', gquery: 'mixed_flows_to_other_in_sankey', color: 'black'},
-      {left: 'coal_and_derivatives',      right: 'electricity_prod', gquery: 'coal_and_derivatives_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'oil_and_derivatives',      right: 'electricity_prod', gquery: 'oil_and_derivatives_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'useful_demand',      right: 'electricity_prod', gquery: 'useful_demand_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'non_renewable_waste',      right: 'electricity_prod', gquery: 'non_renewable_waste_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'geo_solar_wind_water_ambient',      right: 'electricity_prod', gquery: 'geo_solar_wind_water_ambient_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'natural_gas',      right: 'electricity_prod', gquery: 'natural_gas_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'nuclear',      right: 'electricity_prod', gquery: 'nuclear_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'biomass_products',      right: 'electricity_prod', gquery: 'biomass_products_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'mixed_flows',      right: 'electricity_prod', gquery: 'mixed_flows_to_electricity_prod_in_sankey', color: 'black'},
-      {left: 'coal_and_derivatives',      right: 'heat', gquery: 'coal_and_derivatives_to_heat_in_sankey', color: 'black'},
-      {left: 'oil_and_derivatives',      right: 'heat', gquery: 'oil_and_derivatives_to_heat_in_sankey', color: 'black'},
-      {left: 'useful_demand',      right: 'heat', gquery: 'useful_demand_to_heat_in_sankey', color: 'black'},
-      {left: 'non_renewable_waste',      right: 'heat', gquery: 'non_renewable_waste_to_heat_in_sankey', color: 'black'},
-      {left: 'geo_solar_wind_water_ambient',      right: 'heat', gquery: 'geo_solar_wind_water_ambient_to_heat_in_sankey', color: 'black'},
-      {left: 'natural_gas',      right: 'heat', gquery: 'natural_gas_to_heat_in_sankey', color: 'black'},
-      {left: 'nuclear',      right: 'heat', gquery: 'nuclear_to_heat_in_sankey', color: 'black'},
-      {left: 'biomass_products',      right: 'heat', gquery: 'biomass_products_to_heat_in_sankey', color: 'black'},
-      {left: 'mixed_flows',      right: 'heat', gquery: 'mixed_flows_to_heat_in_sankey', color: 'black'},
-      {left: 'coal_and_derivatives',      right: 'distribution', gquery: 'coal_and_derivatives_to_distribution_in_sankey', color: 'black'},
-      {left: 'oil_and_derivatives',      right: 'distribution', gquery: 'oil_and_derivatives_to_distribution_in_sankey', color: 'black'},
-      {left: 'useful_demand',      right: 'distribution', gquery: 'useful_demand_to_distribution_in_sankey', color: 'black'},
-      {left: 'non_renewable_waste',      right: 'distribution', gquery: 'non_renewable_waste_to_distribution_in_sankey', color: 'black'},
-      {left: 'geo_solar_wind_water_ambient',      right: 'distribution', gquery: 'geo_solar_wind_water_ambient_to_distribution_in_sankey', color: 'black'},
-      {left: 'natural_gas',      right: 'distribution', gquery: 'natural_gas_to_distribution_in_sankey', color: 'black'},
-      {left: 'nuclear',      right: 'distribution', gquery: 'nuclear_to_distribution_in_sankey', color: 'black'},
-      {left: 'biomass_products',      right: 'distribution', gquery: 'biomass_products_to_distribution_in_sankey', color: 'black'},
-      {left: 'mixed_flows',      right: 'distribution', gquery: 'mixed_flows_to_distribution_in_sankey', color: 'black'}
+      {left: 'coal_and_derivatives',      right: 'households', gquery: 'coal_and_derivatives_to_households_in_sankey_ufp', color: 'black'},
+      {left: 'oil_and_derivatives',      right: 'households', gquery: 'oil_and_derivatives_to_households_in_sankey_ufp', color: 'brown'},
+      {left: 'useful_demand',      right: 'households', gquery: 'useful_demand_to_households_in_sankey_ufp', color: 'black'},
+      {left: 'non_renewable_waste',      right: 'households', gquery: 'non_renewable_waste_to_households_in_sankey_ufp', color: 'black'},
+      {left: 'geo_solar_wind_water_ambient',      right: 'households', gquery: 'geo_solar_wind_water_ambient_to_households_in_sankey_ufp', color: 'black'},
+      {left: 'natural_gas',      right: 'households', gquery: 'natural_gas_to_households_in_sankey_ufp', color: 'steelblue'},
+      {left: 'nuclear',      right: 'households', gquery: 'nuclear_to_households_in_sankey_ufp', color: 'black'},
+      {left: 'biomass_products',      right: 'households', gquery: 'biomass_products_to_households_in_sankey_ufp', color: 'green'},
+      {left: 'mixed_flows',      right: 'households', gquery: 'mixed_flows_to_households_in_sankey_ufp', color: 'steelblue'},
+      {left: 'electricity',      right: 'households', gquery: 'electricity_to_households_in_sankey_ufp', color: 'black'},
+      {left: 'coal_and_derivatives',      right: 'buildings', gquery: 'coal_and_derivatives_to_buildings_in_sankey_ufp', color: 'black'},
+      {left: 'oil_and_derivatives',      right: 'buildings', gquery: 'oil_and_derivatives_to_buildings_in_sankey_ufp', color: 'brown'},
+      {left: 'useful_demand',      right: 'buildings', gquery: 'useful_demand_to_buildings_in_sankey_ufp', color: 'black'},
+      {left: 'non_renewable_waste',      right: 'buildings', gquery: 'non_renewable_waste_to_buildings_in_sankey_ufp', color: 'black'},
+      {left: 'geo_solar_wind_water_ambient',      right: 'buildings', gquery: 'geo_solar_wind_water_ambient_to_buildings_in_sankey_ufp', color: 'black'},
+      {left: 'natural_gas',      right: 'buildings', gquery: 'natural_gas_to_buildings_in_sankey_ufp', color: 'steelblue'},
+      {left: 'nuclear',      right: 'buildings', gquery: 'nuclear_to_buildings_in_sankey_ufp', color: 'black'},
+      {left: 'biomass_products',      right: 'buildings', gquery: 'biomass_products_to_buildings_in_sankey_ufp', color: 'green'},
+      {left: 'mixed_flows',      right: 'buildings', gquery: 'mixed_flows_to_buildings_in_sankey_ufp', color: 'steelblue'},
+      {left: 'electricity',      right: 'buildings', gquery: 'electricity_to_buildings_in_sankey_ufp', color: 'black'},
+      {left: 'coal_and_derivatives',      right: 'agriculture', gquery: 'coal_and_derivatives_to_agriculture_in_sankey_ufp', color: 'black'},
+      {left: 'oil_and_derivatives',      right: 'agriculture', gquery: 'oil_and_derivatives_to_agriculture_in_sankey_ufp', color: 'brown'},
+      {left: 'useful_demand',      right: 'agriculture', gquery: 'useful_demand_to_agriculture_in_sankey_ufp', color: 'black'},
+      {left: 'non_renewable_waste',      right: 'agriculture', gquery: 'non_renewable_waste_to_agriculture_in_sankey_ufp', color: 'black'},
+      {left: 'geo_solar_wind_water_ambient',      right: 'agriculture', gquery: 'geo_solar_wind_water_ambient_to_agriculture_in_sankey_ufp', color: 'black'},
+      {left: 'natural_gas',      right: 'agriculture', gquery: 'natural_gas_to_agriculture_in_sankey_ufp', color: 'steelblue'},
+      {left: 'nuclear',      right: 'agriculture', gquery: 'nuclear_to_agriculture_in_sankey_ufp', color: 'black'},
+      {left: 'biomass_products',      right: 'agriculture', gquery: 'biomass_products_to_agriculture_in_sankey_ufp', color: 'green'},
+      {left: 'mixed_flows',      right: 'agriculture', gquery: 'mixed_flows_to_agriculture_in_sankey_ufp', color: 'steelblue'},
+      {left: 'electricity',      right: 'agriculture', gquery: 'electricity_to_agriculture_in_sankey_ufp', color: 'black'},
+      {left: 'coal_and_derivatives',      right: 'transport', gquery: 'coal_and_derivatives_to_transport_in_sankey_ufp', color: 'black'},
+      {left: 'oil_and_derivatives',      right: 'transport', gquery: 'oil_and_derivatives_to_transport_in_sankey_ufp', color: 'brown'},
+      {left: 'useful_demand',      right: 'transport', gquery: 'useful_demand_to_transport_in_sankey_ufp', color: 'black'},
+      {left: 'non_renewable_waste',      right: 'transport', gquery: 'non_renewable_waste_to_transport_in_sankey_ufp', color: 'black'},
+      {left: 'geo_solar_wind_water_ambient',      right: 'transport', gquery: 'geo_solar_wind_water_ambient_to_transport_in_sankey_ufp', color: 'black'},
+      {left: 'natural_gas',      right: 'transport', gquery: 'natural_gas_to_transport_in_sankey_ufp', color: 'steelblue'},
+      {left: 'nuclear',      right: 'transport', gquery: 'nuclear_to_transport_in_sankey_ufp', color: 'black'},
+      {left: 'biomass_products',      right: 'transport', gquery: 'biomass_products_to_transport_in_sankey_ufp', color: 'green'},
+      {left: 'mixed_flows',      right: 'transport', gquery: 'mixed_flows_to_transport_in_sankey_ufp', color: 'steelblue'},
+      {left: 'electricity',      right: 'transport', gquery: 'electricity_to_transport_in_sankey_ufp', color: 'black'},
+      {left: 'coal_and_derivatives',      right: 'industry', gquery: 'coal_and_derivatives_to_industry_in_sankey_ufp', color: 'black'},
+      {left: 'oil_and_derivatives',      right: 'industry', gquery: 'oil_and_derivatives_to_industry_in_sankey_ufp', color: 'brown'},
+      {left: 'useful_demand',      right: 'industry', gquery: 'useful_demand_to_industry_in_sankey_ufp', color: 'black'},
+      {left: 'non_renewable_waste',      right: 'industry', gquery: 'non_renewable_waste_to_industry_in_sankey_ufp', color: 'black'},
+      {left: 'geo_solar_wind_water_ambient',      right: 'industry', gquery: 'geo_solar_wind_water_ambient_to_industry_in_sankey_ufp', color: 'black'},
+      {left: 'natural_gas',      right: 'industry', gquery: 'natural_gas_to_industry_in_sankey_ufp', color: 'steelblue'},
+      {left: 'nuclear',      right: 'industry', gquery: 'nuclear_to_industry_in_sankey_ufp', color: 'black'},
+      {left: 'biomass_products',      right: 'industry', gquery: 'biomass_products_to_industry_in_sankey_ufp', color: 'green'},
+      {left: 'mixed_flows',      right: 'industry', gquery: 'mixed_flows_to_industry_in_sankey_ufp', color: 'steelblue'},
+      {left: 'electricity',      right: 'industry', gquery: 'electricity_to_industry_in_sankey_ufp', color: 'black'},
+      {left: 'coal_and_derivatives',      right: 'other', gquery: 'coal_and_derivatives_to_other_in_sankey_ufp', color: 'black'},
+      {left: 'oil_and_derivatives',      right: 'other', gquery: 'oil_and_derivatives_to_other_in_sankey_ufp', color: 'brown'},
+      {left: 'useful_demand',      right: 'other', gquery: 'useful_demand_to_other_in_sankey_ufp', color: 'black'},
+      {left: 'non_renewable_waste',      right: 'other', gquery: 'non_renewable_waste_to_other_in_sankey_ufp', color: 'black'},
+      {left: 'geo_solar_wind_water_ambient',      right: 'other', gquery: 'geo_solar_wind_water_ambient_to_other_in_sankey_ufp', color: 'black'},
+      {left: 'natural_gas',      right: 'other', gquery: 'natural_gas_to_other_in_sankey_ufp', color: 'steelblue'},
+      {left: 'nuclear',      right: 'other', gquery: 'nuclear_to_other_in_sankey_ufp', color: 'black'},
+      {left: 'biomass_products',      right: 'other', gquery: 'biomass_products_to_other_in_sankey_ufp', color: 'green'},
+      {left: 'mixed_flows',      right: 'other', gquery: 'mixed_flows_to_other_in_sankey_ufp', color: 'steelblue'},
+      {left: 'electricity',      right: 'other', gquery: 'electricity_to_other_in_sankey_ufp', color: 'black'},
+      {left: 'households',      right: 'useful_demand_electric', gquery: 'households_to_useful_demand_electric_in_sankey_ufp', color: 'blue'},
+      {left: 'households',      right: 'useful_demand_heat_cold', gquery: 'households_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},
+      {left: 'households',      right: 'useful_demand_transport', gquery: 'households_to_useful_demand_transport_in_sankey_ufp', color: 'green'},
+      {left: 'households',      right: 'useful_demand_non_energetic', gquery: 'households_to_useful_demand_non_energetic_in_sankey_ufp', color: 'black'},
+      {left: 'buildings',      right: 'useful_demand_electric', gquery: 'buildings_to_useful_demand_electric_in_sankey_ufp', color: 'blue'},
+      {left: 'buildings',      right: 'useful_demand_heat_cold', gquery: 'buildings_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},
+      {left: 'buildings',      right: 'useful_demand_transport', gquery: 'buildings_to_useful_demand_transport_in_sankey_ufp', color: 'green'},
+      {left: 'buildings',      right: 'useful_demand_non_energetic', gquery: 'buildings_to_useful_demand_non_energetic_in_sankey_ufp', color: 'black'},
+      {left: 'agriculture',      right: 'useful_demand_electric', gquery: 'agriculture_to_useful_demand_electric_in_sankey_ufp', color: 'blue'},
+      {left: 'agriculture',      right: 'useful_demand_heat_cold', gquery: 'agriculture_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},
+      {left: 'agriculture',      right: 'useful_demand_transport', gquery: 'agriculture_to_useful_demand_transport_in_sankey_ufp', color: 'green'},
+      {left: 'agriculture',      right: 'useful_demand_non_energetic', gquery: 'agriculture_to_useful_demand_non_energetic_in_sankey_ufp', color: 'black'},
+      {left: 'transport',      right: 'useful_demand_electric', gquery: 'transport_to_useful_demand_electric_in_sankey_ufp', color: 'blue'},
+      {left: 'transport',      right: 'useful_demand_heat_cold', gquery: 'transport_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},
+      {left: 'transport',      right: 'useful_demand_transport', gquery: 'transport_to_useful_demand_transport_in_sankey_ufp', color: 'green'},
+      {left: 'transport',      right: 'useful_demand_non_energetic', gquery: 'transport_to_useful_demand_non_energetic_in_sankey_ufp', color: 'black'},
+      {left: 'industry',      right: 'useful_demand_electric', gquery: 'industry_to_useful_demand_electric_in_sankey_ufp', color: 'blue'},
+      {left: 'industry',      right: 'useful_demand_heat_cold', gquery: 'industry_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},
+      {left: 'industry',      right: 'useful_demand_transport', gquery: 'industry_to_useful_demand_transport_in_sankey_ufp', color: 'green'},
+      {left: 'industry',      right: 'useful_demand_non_energetic', gquery: 'industry_to_useful_demand_non_energetic_in_sankey_ufp', color: 'black'},
+      {left: 'other',      right: 'useful_demand_electric', gquery: 'other_to_useful_demand_electric_in_sankey_ufp', color: 'blue'},
+      {left: 'other',      right: 'useful_demand_heat_cold', gquery: 'other_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},
+      {left: 'other',      right: 'useful_demand_transport', gquery: 'other_to_useful_demand_transport_in_sankey_ufp', color: 'green'},
+      {left: 'other',      right: 'useful_demand_non_energetic', gquery: 'other_to_useful_demand_non_energetic_in_sankey_ufp', color: 'black'},
+      {left: 'buildings',      right: 'useful_demand_heat_cold', gquery: 'buildings_to_useful_demand_heat_cold_in_sankey_ufp', color: 'red'},
     ]


finally drinking the implicit object kool-aid. also, utilizing splatted arguments whenever possible

--- Source/FunctionalJS.coffee
+++ Source/FunctionalJS.coffee
@@ -81,7 +81,7 @@

 	# class methods
-	Function.extend {
+	Function.extend
 		_: (_) ->
 			caller = arguments.callee.caller or arguments.caller
 			return unless caller?
@@ -96,10 +96,8 @@
 		context: -> @
 		lambda: (_) -> (-> _)
 		pluck: (property) -> (obj) -> obj[property]
-		invoke: (method) ->
-			defaultArgs = Array::slice.call arguments, 1
-			(obj) ->
-				args = Array::slice.call arguments, 1
+		invoke: (method,defaultArgs...) ->
+			(obj,args...) ->
 				obj[method].apply obj, (if args.length then args else defaultArgs)
 		sequence: ->
 			switch arguments.length
@@ -117,8 +115,7 @@
 				for fn in functions
 					result = fn.apply @, arguments
 				result
-		compose: ->
-			functions = Array::slice.call arguments
+		compose: (functions...) ->
 			->
 				lastReturn = arguments
 				for fn, i in functions.reverse()
@@ -136,7 +133,6 @@
 				fn = funcTable[arguments.length]
 				return undefined unless fn? and fn instanceof Function
 				fn.apply @, arguments
-	}

 	Function.extend 'concat', Function.concatenate

@@ -176,15 +172,14 @@

 	# instance methods
-	Function.implement {
+	Function.implement
 		toFunction: -> @
 		bind: (scope, args...) ->
 			self = @curry args
 			-> self.apply scope, arguments
 		wrap: (fn,bind) ->
 			self = @
-			wrapper = ->
-				args = Array::slice.call arguments
+			wrapper = (args...) ->
 				fn.call @, (if bind? then self.bind bind else self), args
 			wrapper._origin = @
 			wrapper
@@ -288,8 +283,7 @@
 					if exception then throw exception
 					ret
 		)()
-		partial: ->
-			partialArgs = Array::slice.call arguments
+		partial: (partialArgs...) ->
 			@wrap (original,passedArgs) ->
 				collectedArgs = []
 				for arg in partialArgs
@@ -297,12 +291,10 @@
 					# argument given during this call, else use the predefiend argument
 					collectedArgs.push (if !arg? or arg is Function._ then passedArgs.shift() else arg)
 				original.apply @, collectedArgs.concat(passedArgs)
-		curry: ->
-			curriedArgs = Array::slice.call arguments
+		curry: (curriedArgs...) ->
 			@wrap (original,passedArgs) ->
 				original.apply @, curriedArgs.concat(passedArgs)
-		rcurry: ->
-			curriedArgs = Array::slice.call arguments
+		rcurry: (curriedArgs...) ->
 			@wrap (original,passedArgs) ->
 				original.apply @, passedArgs.concat(curriedArgs)
 		not: ->
@@ -337,34 +329,29 @@
 			@wrap (fn,args) ->
 				fn.apply @, args[0...arity]
 		getArity: -> @arity or @length or @getArgs().length
-	}

 	# define Function::getArgs after Function::memoize is committed
 	# to allow for auto-memoization
-	Function.implement {
+	Function.implement
 		getArgs: (->
 			fn = @getOrigin()
 			args = fn.toString().match(/^function\s*[^\s\(]*\((.*?)\)/)[1].split(/\s*,\s*/)
 			args.filter((_) -> _ isnt "")
 		).memoize()
-	}

 	# implement array methods
 	for fnStr in ['forEach','each','every','some','filter','map','reduce','reduceRight','sort']
 		continue unless Array.prototype[fnStr]?
-		fn = ((fnStr) -> (->
-			args = Array::slice.call arguments
-			arr = args.shift()
-			arr[fnStr].apply arr, [@].concat(args)
-		))(fnStr)
+		fn = ((fnStr) ->
+			(arr,args...) -> arr[fnStr].apply arr, [@].concat(args)
+		)(fnStr)
 		fn._origin = Array.prototype[fnStr]
 		Function.implement fnStr, fn

-	Function.implement {
+	Function.implement
 		foldl: Function::reduce
 		foldr: Function::reduceRight
-	}

 	# Array::toFunction, Hash::toFunction, Object.toFunction


Fixed links in popovers

Fixed links in popovers

--- app/assets/javascripts/navbar.js.coffee
+++ app/assets/javascripts/navbar.js.coffee
@@ -1,6 +1,10 @@
 ###global I18n:false###

 'use strict'
+
+# Needed to follow links in popovers
+$(document).on 'click', '.popover a', (e) ->
+  e.stopPropagation()

 $(document).on 'click', '.notifications', (e) ->
   e.preventDefault()


disable sequelize logging by default

disable sequelize logging by default

prevent any leaking of objects to console during debugging

--- app/coffee/db.coffee
+++ app/coffee/db.coffee
@@ -1,11 +1,14 @@
 Sequelize = require("sequelize")
 Settings = require("settings-sharelatex")
+_ = require("underscore")
+
+options = _.extend {logging:false}, Settings.mysql.clsi

 sequelize = new Sequelize(
 	Settings.mysql.clsi.database,
 	Settings.mysql.clsi.username,
 	Settings.mysql.clsi.password,
-	Settings.mysql.clsi
+	options
 )

 module.exports =


refactor coffeescript dealing with nerdamer interaction

--- app/assets/javascripts/questions.coffee
+++ app/assets/javascripts/questions.coffee
@@ -2,23 +2,38 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http://coffeescript.org/

-# highlight 'Ungespeicherte Änderungen' if something is entered in question basics
-
-extractSolution = ->
-  content  = $('#solution-form').serializeArray().reduce(((obj, item) ->
-    obj[item.name] = item.value
-    obj
-  ), {})
-  type = content['question[solution_type]']
-  if type == 'MampfExpression'
-    statement = content['question[solution_content[0]]']
-    nerd = nerdamer(statement)
-    result =
-      nerd: nerd
-      tex: nerd.toTeX()
-      statement: statement
-    return result
-  else if type == 'MampfMatrix'
+class MampfExpression
+  constructor: (@expression) ->
+    @nerd = nerdamer(@expression)
+    @tex = @nerd.toTeX()
+
+  equals: (otherExpression) ->
+    @nerd.expand().eq(otherExpression.nerd.expand())
+
+  @parse: (content) ->
+    expression = content['question[solution_content[0]]']
+    new MampfExpression(expression)
+
+class MampfMatrix
+  constructor: (@expression) ->
+    @nerd = nerdamer(@expression)
+    size = nerdamer('size(' + @expression + ')').text().split(',')
+    @rowCount = parseInt(size[1])
+    @columnCount = parseInt(size[0])
+    @tex = @nerd.toTeX().replace(/vmatrix/g, 'pmatrix')
+
+  equals: (otherMatrix) ->
+    return false unless @rowCount == otherMatrix.rowCount
+    return false unless @columnCount == otherMatrix.columnCount
+    result = true
+    for i in [1..@rowCount]
+      for j in [1..@columnCount]
+        c1 = nerdamer('matget(' + @expression + ',' + (i-1) + ',' + (j-1) + ')')
+        c2 = nerdamer('matget(' + otherMatrix.expression + ',' + (i-1) + ',' + (j-1) + ')')
+        result = false unless c1.expand().eq(c2.expand())
+    result
+
+  @parse: (content) ->
     rowCount = parseInt(content['question[solution_content[row_count]]'])
     columnCount = parseInt(content['question[solution_content[column_count]]'])
     matrix = ''
@@ -30,25 +45,53 @@
       column += ']'
       matrix += column
       matrix += ',' unless i == rowCount
-    statement = 'matrix(' + matrix + ')'
-    nerd = nerdamer(statement)
-    result =
-      nerd: nerd
-      tex: nerd.toTeX().replace(/vmatrix/g, 'pmatrix')
-      statement: statement
-    return result
-  else if type == 'MampfTuple'
+    expression = 'matrix(' + matrix + ')'
+    new MampfMatrix(expression)
+
+class MampfTuple
+  constructor: (@expression) ->
+    @nerd = nerdamer(@expression)
+    @length = parseInt(nerdamer('size(' + @expression + ')'))
+    texRaw = @nerd.toTeX()
+    @tex = '(' + texRaw.substr(1, texRaw.length - 2) + ')'
+
+  equals: (otherTuple) ->
+    return false unless @length == otherTuple.length
+    result = true
+    for i in [1..@length]
+      c1 = nerdamer('vecget(' + @expression + ',' + (i-1) + ')')
+      c2 = nerdamer('vecget(' + otherTuple.expression + ',' + (i-1) + ')')
+      result = false unless c1.expand().eq(c2.expand())
+    result
+
+  @parse: (content) ->
     coeffs = content['question[solution_content[0]]']
-    statement = 'vector(' + coeffs + ')'
-    nerd = nerdamer(statement)
-    texRaw = nerd.toTeX()
-    tex = '(' + texRaw.substr(1, texRaw.length - 2) + ')'
-    result =
-      nerd: nerd
-      tex: tex
-      statement: statement
-    return result
-  else if type == 'MampfSet'
+    expression = 'vector(' + coeffs + ')'
+    new MampfTuple(expression)
+
+class MampfSet
+  # note that we assume here that redundant elements have already been removed
+  # from the expression (as it is done in the parse class method)
+  constructor: (@expression) ->
+    @nerd = nerdamer(@expression)
+    @size = parseInt(nerdamer('size(' + @expression + ')'))
+    texRaw = @nerd.toTeX()
+    @tex = '\\{' + texRaw.substr(1, texRaw.length - 2) + '\\}'
+
+  equals: (otherSet) ->
+    return false unless @size == otherSet.size
+    result = true
+    if @size > 0
+      for i in [1..@size]
+        c1 = nerdamer('vecget(' + @expression + ',' + (i-1) + ')')
+        found = false
+        for j in [1..@size]
+          c2 = nerdamer('vecget(' + otherSet.expression + ',' + (j-1) + ')')
+          found = true if c1.expand().eq(c2.expand())
+        result = false unless found
+    result
+
+  @parse: (content) ->
     elements = content['question[solution_content[0]]'].split(',')
     size = elements.length
     nerds = []
@@ -62,62 +105,45 @@
       unless duplicate
         nerds.push nerd
         set.push element
-    statement = 'vector(' + set.join(',') + ')'
-    nerd = nerdamer(statement)
-    texRaw = nerd.toTeX()
-    tex = '\\{' + texRaw.substr(1, texRaw.length - 2) + '\\}'
-    result =
-      nerd: nerd
-      tex: tex
-      statement: statement
-    return result
-
-compareToSolution = (expression) ->
+    expression = 'vector(' + set.join(',') + ')'
+    new MampfSet(expression)
+
+class MampfSolution
+  constructor: (@type, @details) ->
+
+  equals: (otherSolution) ->
+    return false unless @type == otherSolution.type
+    @details.equals(otherSolution.details)
+
+  @parse: (content) ->
+    type = content['question[solution_type]']
+    details = switch
+      when type == 'MampfExpression' then MampfExpression.parse(content)
+      when type == 'MampfMatrix' then MampfMatrix.parse(content)
+      when type == 'MampfTuple' then MampfTuple.parse(content)
+      when type == 'MampfSet' then MampfSet.parse(content)
+    new MampfSolution(type, details)
+
+  @fromExpression: (type, expression) ->
+    details = switch
+      when type == 'MampfExpression' then new MampfExpression(expression)
+      when type == 'MampfMatrix' then new MampfMatrix(expression)
+      when type == 'MampfTuple' then new MampfTuple(expression)
+      when type == 'MampfSet' then new MampfSet(expression)
+    new MampfSolution(type, details)
+
+extractSolution = ->
+  content  = $('#solution-form').serializeArray().reduce(((obj, item) ->
+    obj[item.name] = item.value
+    obj
+  ), {})
+  MampfSolution.parse(content)
+
+compareToSolution = (solutionInput) ->
   solutionString = $('#question_nerd').val()
-  solutionNerd = nerdamer(solutionString)
-  expressionNerd = nerdamer(expression)
   type = $('#question_solution_type').val()
-  if type == 'MampfExpression'
-    result = solutionNerd.expand().eq(expressionNerd.expand())
-  else if type == 'MampfMatrix'
-    rowCount = parseInt($('#question_row_count').val())
-    columnCount = parseInt($('#question_column_count').val())
-    if parseInt($('#matrixRowCount').data('count')) != rowCount ||
-    parseInt($('#matrixColumnCount').data('count')) != columnCount
-      result = false
-    else
-      result = true
-      for i in [1..rowCount]
-        for j in [1..columnCount]
-          c1 = nerdamer('matget(' + solutionString + ',' + (i-1) + ',' + (j-1) + ')')
-          c2 = nerdamer('matget(' + expression + ',' + (i-1) + ',' + (j-1) + ')')
-          result = false unless c1.expand().eq(c2.expand())
-  else if type == 'MampfTuple'
-    size = parseInt(nerdamer('size(' + solutionString + ')'))
-    if parseInt(nerdamer('size(' + expression + ')')) != size
-      result = false
-    else
-      result = true
-      for i in [1..size]
-        c1 = nerdamer('vecget(' + solutionString + ',' + (i-1) + ')')
-        c2 = nerdamer('vecget(' + expression + ',' + (i-1) + ')')
-        result = false unless c1.expand().eq(c2.expand())
-  else if type == 'MampfSet'
-    size = parseInt(nerdamer('size(' + solutionString + ')'))
-    console.log size
-    console.log parseInt(nerdamer('size(' + expression + ')'))
-    if size != parseInt(nerdamer('size(' + expression + ')'))
-      result = false
-    else
-      result = true
-      if size > 0
-        for i in [1..size]
-          c1 = nerdamer('vecget(' + solutionString + ',' + (i-1) + ')')
-          found = false
-          for j in [1..size]
-            c2 = nerdamer('vecget(' + expression + ',' + (j-1) + ')')
-            found = true if c1.expand().eq(c2.expand())
-          result = false unless found
+  solution = MampfSolution.fromExpression(type, solutionString)
+  result = solutionInput.equals(solution)
   $('#question_result').val(result)
   if result
     $('#quiz_question_crosses').val($('#quiz_question_crosses').data('answer'))
@@ -129,22 +155,22 @@
   $('#solution-error').empty()
   $('#solution-box').hide()
   try
-    expression = extractSolution()
+    solutionInput = extractSolution()
   catch err
-    expression = 'Syntax Error'
-  if expression == 'Syntax Error'
+    solutionInput = 'Syntax Error'
+  if solutionInput == 'Syntax Error'
     $('#solution_input_tex').val('')
-    $('#solution_input_error').val(expression)
+    $('#solution_input_error').val(solutionInput)
     $('#solution_content_nerd').val('')
     $('#question_result').val(false)
   else
-    latex = expression.tex
+    latex = solutionInput.details.tex
     $('#solution_input_tex').val(latex)
     $('#question_quiz_solution_input').val('$' + latex + '$')
     $('#solution_input_error').val('')
-    $('#solution_content_nerd').val(expression.statement)
+    $('#solution_content_nerd').val(solutionInput.details.expression)
     if $('#question_result').length > 0
-      compareToSolution(expression.statement)
+      compareToSolution(solutionInput)
   $('#submit-solution').hide()
   return

@@ -254,15 +280,15 @@
   $(document).on 'click', '#interpretExpression', ->
     cleanSolutionBox()
     try
-      expression = extractSolution()
+      inputSolution = extractSolution()
     catch err
-      expression = 'Syntax Error'
-    if expression == 'Syntax Error'
-      $('#solution-tex').empty().append(expression)
+      inputSolution = 'Syntax Error'
+    if inputSolution == 'Syntax Error'
+      $('#solution-tex').empty().append(inputSolution)
       $('#solution_input_tex').val('')
-      $('#solution_input_error').val(expression)
+      $('#solution_input_error').val(inputSolution)
     else
-      latex = expression.tex
+      latex = inputSolution.details.tex
       $('#solution_input_error').val('')
       $('#solution_input_tex').val(latex)
       $('#solution-tex').empty().append('$$' + latex + '$$')


Test that we get correct index on detach events for addressable ports

--- spec/OutPort.coffee
+++ spec/OutPort.coffee
@@ -37,3 +37,15 @@
       p.attach s2
       p.attach s3
       chai.expect(-> p.send('some-data', 1)).to.throw
+
+    it 'should give correct port index when detaching a connection', (done) ->
+      p = new outport
+        addressable: true
+      p.attach s1
+      p.attach s2
+      p.attach s3
+      p.on 'detach', (socket, index) ->
+        chai.expect(socket).to.equal s2
+        chai.expect(index).to.equal 1
+        done()
+      p.detach s2


Initialize jquery.timeago on ajax partials

--- app/assets/javascripts/stacks.js.coffee
+++ app/assets/javascripts/stacks.js.coffee
@@ -1,5 +1,10 @@
 jQuery ($) ->
   MAX_RETRY = 4
+
+  $timeago = (html) ->
+    $element = $(html)
+    $element.find('time[data-time-ago]').timeago()
+    $element

   retry = (message, callback, retryCount=0) ->
     retryCount += 1
@@ -16,11 +21,11 @@

   onCommitUpdate = (message) ->
     loadFragment message, (id, commit) ->
-      $("#commit-#{id}").html(commit)
+      $("#commit-#{id}").html($timeago(commit))

   onCommitCreate = (message) ->
     loadFragment message, (id, commit) ->
-      $("ul.commit-lst").prepend(commit)
+      $("ul.commit-lst").prepend($timeago(commit))

   onCommitRemove = (message) ->
     json = JSON.parse(message.data)
@@ -33,11 +38,11 @@

   onDeployUpdate = (message) ->
     loadFragment message, (id, deploy) ->
-      $("#deploy-#{id}").html(deploy)
+      $("#deploy-#{id}").html($timeago(deploy))

   onDeployCreate = (message) ->
     loadFragment message, (id, deploy) ->
-      $("ul.deploy-lst").prepend(deploy)
+      $("ul.deploy-lst").prepend($timeago(deploy))

   $('[data-event-stream]').each ->
     url = $(this).data('event-stream')


dropping commented code

dropping commented code

which was only needed with an old version of browserify

--- src/transform.coffee
+++ src/transform.coffee
@@ -19,7 +19,7 @@
       combine.create().addFile(
         {sourceFile: filename, source: buffer}
         {line: prelude.split('\n').length - 1}
-      ).comment() #.replace('//#', '//@')
+      ).comment()
     )
     @queue(null)
   through(write, end)


Revert "Don't store previous fact relation opinion, that feels too unnatural"

This reverts commit 933efd7eb0f8028e3900fce0a4ce8303d107a812.

--- core/app/backbone/views/fact_relations/vote_view.coffee
+++ core/app/backbone/views/fact_relations/vote_view.coffee
@@ -39,12 +39,17 @@
   set_fact_relation_opinion: (opinion) ->
     return if @model.current_opinion() == opinion

+    @_originalFactRelationOpinion = @model.current_opinion()
     @model.setOpinion opinion

   unset_fact_relation_opinion: (opinion) ->
     return unless @model.current_opinion() == opinion

-    @model.removeOpinion()
+    if @_originalFactRelationOpinion?
+      @model.setOpinion @_originalFactRelationOpinion
+      @_originalFactRelationOpinion = null
+    else
+      @model.removeOpinion()

   set_fact_opinion: (opinion) ->
     return if @model.getFact().getFactWheel().isUserOpinion opinion


Add alert when founder leaves

Add alert when founder leaves

--- static/scripts/main.coffee
+++ static/scripts/main.coffee
@@ -207,6 +207,9 @@
                                         @connections.push connection
                                         @listenForMessage connection

+                connection.on "close", =>
+                    @props.addAlert type: "warning", "Founder left the channel!"
+
                 peer.on "connection", (connection) =>
                     connection.on "open", =>
                         @connections.push connection


Add alerts about new connections

Add alerts about new connections

--- static/scripts/main.coffee
+++ static/scripts/main.coffee
@@ -178,9 +178,9 @@
                     @connections.push connection
                     @listenForMessage connection

-                    peerIds = @connectionsToPeerIds @connections
-
                     connection.on "open", =>
+                        @showAlertAboutNewConnection()
+
                         connectionsWithoutNewConnection = _.filter @connections, (c) -> c.peer != connection.peer
                         peerIdsWithoutNewConnection = @connectionsToPeerIds connectionsWithoutNewConnection

@@ -214,6 +214,8 @@

                 peer.on "connection", (connection) =>
                     connection.on "open", =>
+                        @showAlertAboutNewConnection()
+
                         @connections.push connection
                         @listenForMessage connection

@@ -242,6 +244,9 @@
                 messages = @state.messages
                 messages.push data.message
                 @setState messages: messages
+
+    showAlertAboutNewConnection: ->
+        @props.addAlert type: "info", "Someone joined the channel!"

 Settings = React.createClass
     mixins: [React.addons.LinkedStateMixin]


Fix PCE test to account for outflowing messages

--- test/test_pce.coffee
+++ test/test_pce.coffee
@@ -36,22 +36,14 @@
     @_engine.expects('execute_operation').once().withArgs(operation).returns("success")

     onComplete = (result) ->
-      result.should.equal "success"
+      result.retval.should.equal "success"
+      result.operation.should.equal operation
       done()

     @pce.forward_operation(operation).then(onComplete).done()

-  it 'should report an error when the exectution fails', (done) ->
-    deferred = Q.defer()
-    deferred.resolve(undefined)
-
-    @_journal.expects('record').once().returns(deferred.promise)
-    @_replication.expects('send').once().returns(deferred.promise)
-    @_engine.expects('execute_operation').once().throws("failure")
-
-    onError = (error) ->
-      error.name.should.equal "failure"
-      done()
-
-    @pce.forward_operation(null).fail(onError).done()
-
+  it 'should throw an error immediately when the execution fails', (done) ->
+    expect =>
+      @pce.forward_operation(null).done()
+    .to.throw "Invalid Operation"
+    done()


Fix page loader count

Fix page loader count

--- app/coffee/modules/common/loader.coffee
+++ app/coffee/modules/common/loader.coffee
@@ -102,7 +102,8 @@
             requestCount++

         logResponse: () ->
-            requestCount--
+            if requestCount > 0
+                requestCount--
             lastResponseDate = new Date().getTime()
     }


Adding assigned to functionality in issue detail

--- app/coffee/modules/issues/detail.coffee
+++ app/coffee/modules/issues/detail.coffee
@@ -277,7 +277,7 @@
             <% if (assignedTo) { %>
             <%= assignedTo.full_name_display %>
             <% } else { %>
-            --
+            Not assigned
             <% } %>
             </span>
             <% if (editable) { %>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -13,8 +13,11 @@

 test 'it should be able to set one param at a time', ->
   player = GameObject()
-  player.include F
+
+  player.include Flickerable

+
+
 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject
     alpha: 0.9


Add education page text

Add education page text

--- app/lib/en-us.coffee
+++ app/lib/en-us.coffee
@@ -272,6 +272,8 @@

Asteroid Fact vs. Fiction from the ORISIS-REX 321Mission

+
+
Students Discover, a collaboration between the North Carolina Museum of Natural Sciences and the Kenan Fellows Program, has created Asteroid Zoo: Your Meteor Mission. This set of lessons is designed for middle school classes.

     '''

     more_title: 'How can I keep up to date with education and Asteroid Zoo?'


Update da.coffee

Update da.coffee

Fixed wrong indentation of courses.length.

--- app/locale/da.coffee
+++ app/locale/da.coffee
@@ -1188,7 +1188,7 @@
     view_edit: "vis/redigér"
     students_enrolled: "elever tilmeldt"
     students_assigned: "elever tildelt"
-     length: "Varighed:"
+    length: "Varighed:"

   classes:
     archmage_title: "Ærkemager"


Update en.coffee

Update en.coffee

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -1102,7 +1102,7 @@
     daniela_title: "Marketing Manager"
     chelsea_title: "Operations Manager"
     claire_title: "Executive Assistant"
-    bobby_title: "Senior Game Designer"
+    bobby_title: "Game Designer" # {change}
     brian_title: "Lead Game Designer"
     andrew_title: "Software Engineer"
     stephanie_title: "Customer Support Specialist"


Update about/team title

Update about/team title

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -1086,7 +1086,7 @@
     andrew_title: "Software Engineer"
     stephanie_title: "Customer Support Specialist"
     rob_title: "Sales Development Representative"
-    shubhangi_title: "Software Engineer"
+    shubhangi_title: "Senior Software Engineer"
     bryukh_title: "Gameplay Developer"
     retrostyle_title: "Illustration"
     retrostyle_blurb: "RetroStyle Games"


Finish until line: 770

Finish until line: 770

--- app/locale/id.coffee
+++ app/locale/id.coffee
@@ -651,120 +651,122 @@
     goal_collect: "Kumpulkan semua barang-barang"
     goal_collect_amount: "Kumpulkan __amount__ barang."

-#  game_menu:
-#    inventory_tab: "Inventory"
-#    save_load_tab: "Save/Load"
-#    options_tab: "Options"
-#    guide_tab: "Guide"
-#    guide_video_tutorial: "Video Tutorial"
-#    guide_tips: "Tips"
-#    multiplayer_tab: "Multiplayer"
-#    auth_tab: "Sign Up"
-#    inventory_caption: "Equip your hero"
-#    choose_hero_caption: "Choose hero, language"
-#    options_caption: "Configure settings"
-#    guide_caption: "Docs and tips"
-#    multiplayer_caption: "Play with friends!"
-#    auth_caption: "Save your progress."
-
-#  leaderboard:
-#    view_other_solutions: "View Leaderboards"
-#    scores: "Scores"
-#    top_players: "Top Players by"
-#    day: "Today"
-#    week: "This Week"
-#    all: "All-Time"
-#    latest: "Latest"
-#    time: "Win Time"
-#    damage_taken: "Damage Taken"
-#    damage_dealt: "Damage Dealt"
-#    difficulty: "Difficulty"
-#    gold_collected: "Gold Collected"
-#    survival_time: "Survived"
-#    defeated: "Enemies Defeated"
-#    code_length: "Lines of Code"
-#    score_display: "__scoreType__: __score__"
-
-#  inventory:
-#    equipped_item: "Equipped"
-#    required_purchase_title: "Required"
-#    available_item: "Available"
-#    restricted_title: "Restricted"
-#    should_equip: "(double-click to equip)"
-#    equipped: "(equipped)"
-#    locked: "(locked)"
-#    restricted: "(restricted in this level)"
-#    equip: "Equip"
-#    unequip: "Unequip"
-#    warrior_only: "Warrior Only"
-#    ranger_only: "Ranger Only"
-#    wizard_only: "Wizard Only"
-
-#  buy_gems:
-#    few_gems: "A few gems"
-#    pile_gems: "Pile of gems"
-#    chest_gems: "Chest of gems"
-#    purchasing: "Purchasing..."
-#    declined: "Your card was declined"
-#    retrying: "Server error, retrying."
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Do you want to get more?"
-#    prompt_button: "Enter Shop"
-#    recovered: "Previous gems purchase recovered. Please refresh the page."
-#    price: "x{{gems}} / mo"
-#    buy_premium: "Buy Premium"
-#    purchase: "Purchase"
-#    purchased: "Purchased"
-
-#  earn_gems:
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Keep playing to earn more!"
-
-#  subscribe:
-#    best_deal: "Best Deal!"
-#    confirmation: "Congratulations! You now have a CodeCombat Premium Subscription!"
-#    premium_already_subscribed: "You're already subscribed to Premium!"
-#    subscribe_modal_title: "CodeCombat Premium"
-#    comparison_blurb: "Become a Master Coder - subscribe to Premium today!"
-#    must_be_logged: "You must be logged in first. Please create an account or log in from the menu above."
-#    subscribe_title: "Subscribe" # Actually used in subscribe buttons, too
-#    unsubscribe: "Unsubscribe"
-#    confirm_unsubscribe: "Confirm Unsubscribe"
-#    never_mind: "Never Mind, I Still Love You"
-#    thank_you_months_prefix: "Thank you for supporting us these last"
-#    thank_you_months_suffix: "months."
-#    thank_you: "Thank you for supporting CodeCombat."
-#    sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
-#    unsubscribe_feedback_placeholder: "O, what have we done?"
-#    stripe_description: "Monthly Subscription"
-#    buy_now: "Buy Now"
-#    subscription_required_to_play: "You'll need a subscription to play this level."
-#    unlock_help_videos: "Subscribe to unlock all video tutorials."
-#    personal_sub: "Personal Subscription" # Accounts Subscription View below
-#    loading_info: "Loading subscription information..."
-#    managed_by: "Managed by"
-#    will_be_cancelled: "Will be cancelled on"
-#    currently_free: "You currently have a free subscription"
-#    currently_free_until: "You currently have a subscription until"
-#    free_subscription: "Free subscription"
-#    was_free_until: "You had a free subscription until"
-#    managed_subs: "Managed Subscriptions"
-#    subscribing: "Subscribing..."
-#    current_recipients: "Current Recipients"
-#    unsubscribing: "Unsubscribing"
-#    subscribe_prepaid: "Click Subscribe to use prepaid code"
-#    using_prepaid: "Using prepaid code for monthly subscription"
-#    feature_level_access: "Access 300+ levels available"
-#    feature_heroes: "Unlock exclusive heroes and pets"
-#    feature_learn: "Learn to make games and websites"
-#    month_price: "$__price__"
-#    first_month_price: "Only $__price__ for your first month!"
-#    lifetime: "Lifetime Access"
-#    lifetime_price: "$__price__"
-#    year_subscription: "Yearly Subscription"
-#    year_price: "$__price__/year"
-#    support_part1: "Need help with payment or prefer PayPal? Email"
-#    support_part2: "support@codecombat.com"
+  game_menu:
+    inventory_tab: "Inventaris"
+    save_load_tab: "Simpan/Muat"
+    options_tab: "Opsi"
+    guide_tab: "Panduan"
+    guide_video_tutorial: "Panduan Video"
+    guide_tips: "Saran"
+    multiplayer_tab: "Multipemain"
+    auth_tab: "Daftar"
+    inventory_caption: "Pakai jagoanmu"
+    choose_hero_caption: "Memilih jagoan, bahasa"
+    options_caption: "Mengkonfigurasi pengaturan"
+    guide_caption: "Dokumen dan saran"
+    multiplayer_caption: "Bermain bersama teman-teman!"
+    auth_caption: "Simpan perkembanganmu"
+
+  leaderboard:
+    view_other_solutions: "View Leaderboards"
+    view_other_solutions: "Lihat Spanduk Pemain"
+    scores: "Skor"
+    top_players: "Permain teratas dalam"
+    day: "Hari ini"
+    week: "Minggu ini"
+    all: "Sepanjang waktu"
+    latest: "Terakhir"
+    time: "Waktu menang"
+    damage_taken: "Kerusakan yang Diterima"
+    damage_dealt: "Kerusakan yang Diberikan"
+    difficulty: "Tingkat Kesulitan"
+    gold_collected: "Emas yang dikumpulkan"
+    survival_time: "Bertahan Hidup"
+    defeated: "Lawan yang Dikalahkan"
+    code_length: "Baris Kode"
+    score_display: "__scoreType__: __score__"
+
+  inventory:
+    equipped_item: "Terpakai"
+    required_purchase_title: "Wajib"
+    available_item: "Tersedia"
+    restricted_title: "Terlarang"
+    restricted_title: "Restricted"
+    should_equip: "(klik ganda untuk memakai)"
+    equipped: "(terpakai)"
+    locked: "(terkunci)"
+    restricted: "(terlarang di level ini)"
+    equip: "Pakai"
+    unequip: "Lepas"
+    warrior_only: "Kesatria Saja"
+    ranger_only: "Pemanah Saja"
+    wizard_only: "Penyihir Saja"
+
+  buy_gems:
+    few_gems: "Sedikit permata"
+    pile_gems: "Tumpukan permata"
+    chest_gems: "Peti penuh permata"
+    purchasing: "Membeli..."
+    declined: "Kartumu ditolak"
+    retrying: "Terjadi kesalahan di server, mencoba kembali"
+    prompt_title: "Permata Tidak Cukup"
+    prompt_body: "Ingin mendapatkan lebih?"
+    prompt_button: "Masuk Toko"
+    recovered: "Pembelian permata sebelumnya dipulihkan. Silakan perbaharui halaman."
+    price: "x{{gems}} / bln"
+    buy_premium: "Beli Premium"
+    purchase: "Membeli"
+    purchased: "Terbeli"
+
+  earn_gems:
+    prompt_title: "Permata Tidak Cukup"
+    prompt_body: "Tetap bermain untuk mendapatkan lebih!"
+
+  subscribe:
+    best_deal: "Transaksi Terbaik!"
+    confirmation: "Selamat! Kamu telah berlangganan CodeCombat Premium!"
+    premium_already_subscribed: "Kamu telah berlangganan Premium"
+    subscribe_modal_title: "CodeCombat Premium"
+    comparison_blurb: "Menjadi Penguasa Kode - berlangganan Premium hari ini!"
+    must_be_logged: "Kamu harus masuk terlebih dahulu. Silakan buat akun atau masuk dari menu di atas"
+    subscribe_title: "Berlangganan" # Actually used in subscribe buttons, too
+    unsubscribe: "Berhenti Berlangganan"
+    confirm_unsubscribe: "Konfirmasi Berhenti Berlangganan"
+    never_mind: "Tidak masalah, Aku Masih Suka Kamu"
+    thank_you_months_prefix: "Terima kasih telah mendukung kami hingga saat ini"
+    thank_you_months_suffix: "bulan."
+    thank_you: "Terima kasih telah mendukung CodeCombat."
+    sorry_to_see_you_go: "Kami sedih melihat kamu pergi! Tolong beritahu, apa yang bisa kami lakukan untuk menjadi lebih baik."
+    unsubscribe_feedback_placeholder: "Oh, apa yang telah kami lakukan?"
+    stripe_description: "Berlangganan Bulanan"
+    buy_now: "Beli Sekarang"
+    subscription_required_to_play: "Kamu butuh berlangganan untuk memainkan level ini."
+    unlock_help_videos: "Berlangganan untuk membuka semua panduan video."
+    personal_sub: "Langganan Pribadi" # Accounts Subscription View below
+    loading_info: "Memuat informasi langganan..."
+    managed_by: "Diatur oleh"
+    will_be_cancelled: "Akan dibatalkan"
+    currently_free: "Kamu memiliki langganan gratis"
+    currently_free_until: "Kamu saat ini memiliki langganan hingga"
+    free_subscription: "Berlangganan gratis"
+    was_free_until: "Kamu memiliki langganan gratis hingga"
+    managed_subs: "Mengatur Langganan"
+    subscribing: "Berlangganan..."
+    current_recipients: "Penerima Saat Ini"
+    unsubscribing: "Berhenti Berlangganan"
+    subscribe_prepaid: "Klik Berlangganan untuk menggunakan kode prabayar"
+    using_prepaid: "Menggunakan kode prabayar untuk berlangganan bulanan"
+    feature_level_access: "Akses 300+ level tersedia"
+    feature_heroes: "Membuka jagoan dan peliharaan ekslusif"
+    feature_learn: "Belajar membuat permainan dan situs web"
+    month_price: "$__price__"
+    first_month_price: "Hanya $__price__ untuk bulan pertamamu!"
+    lifetime: "Akses Seumur Hidup"
+    lifetime_price: "$__price__"
+    year_subscription: "Berlangganan Tahunan"
+    year_price: "$__price__/year"
+    support_part1: "Membutuhkan bantuan pembayaran atau memilih PayPal? Email"
+    support_part2: "support@codecombat.com"

 #  announcement:
 #    now_available: "Now available for subscribers!"


Update nl.coffee

Update nl.coffee

--- app/locale/nl.coffee
+++ app/locale/nl.coffee
@@ -136,8 +136,8 @@
     change_hero: "Verander held" # Go back from choose inventory to choose hero
     buy_gems: "Edelstenen kopen"
     subscription_required: "Abonnement nodig"
-#    subscribers_only: "Subscribers Only!"
-#    subscribe_unlock: "Subscribe to Unlock!"
+    subscribers_only: "Alleen voor abonnees!"
+    subscribe_unlock: "Abonneer om toegang te krijgen!"
     anonymous: "Anonieme Speler"
     level_difficulty: "Moeilijkheidsgraad: "
     play_classroom_version: "Speel de klassikale versie" # Choose a level in campaign version that you also can play in one of your courses
@@ -663,44 +663,44 @@
     unsubscribing: "Uitschrijven..." # {change}
     subscribe_prepaid: "Klik op inschrijven om een prepaidcode te gebruiken."
     using_prepaid: "Prepaidcode gebruiken voor maandelijkse inschrijving."
-#    feature_levels: "Access __premiumLevelsCount__ levels available"
-#    feature_gems: "Receive __gems__ gems per month"
-#    feature_heroes: "Unlock exclusive heroes"
-#    feature_games: "Make games for your friends"
-#    feature_websites: "Build websites and apps"
-#    feature_items: "Equip more powerful items"
-#    month_price: "$__price__/mo"
-#    lifetime: "Lifetime Subscription"
-#    lifetime_price: "$__price__"
-#    year_subscription: "Yearly Subscription"
-#    year_price: "$__price__/year"
-#    kids_message_1: "Kids! We'll send an email to your parents so they can purchase a subscription for you."
-#    kids_message_2: "Ask Your Parent"
-#    support_part1: "Need help with payment options? Email"
-#    support_part2: "support@codecombat.com"
-#    support_part3: "if you have any questions."
-
-#  premium_features:
-#    get_premium: "Get
CodeCombat
Premium" # Fit into the banner on the /features page
-#    master_coder: "Become a Master Coder by subscribing today!"
-#    subscribe_now: "Subscribe Now"
-#    hero_blurb_1: "Get access to __premiumHeroesCount__ super-charged subscriber-only heroes! Harness the unstoppable power of Okar Stompfoot, the deadly precision of Naria of the Leaf, or summon \"adorable\" skeletons with Nalfar Cryptor."
-#    hero_blurb_2: "Premium Warriors unlock stunning martial skills like Warcry, Stomp, and Hurl Enemy. Or, play as a Ranger, using stealth and bows, throwing knives, traps! Try your skill as a true coding Wizard, and unleash a powerful array of Primordial, Necromantic or Elemental magic!"
-#    hero_caption: "Exciting new heroes!"
-#    pet_blurb_1: "Pets aren't just adorable companions, they also provide unique functionality and methods. The Baby Griffin can fly over obstacles while helping the hero, the Wolf Pup can fetch potions, and the Cougar can speak!"
-#    pet_blurb_2: "Collect all the pets to discover their unique abilities!"
-#    pet_caption: "Adopt pets to accompany your hero!"
-#    game_dev_blurb: "Learn game scripting and build new levels to share with your friends! Place the items you want, write code for unit logic and behavior, and see if your friends can beat the level!"
-#    game_dev_caption: "Design your own games to challenge your friends!"
-#    everything_in_premium: "Everything you get in CodeCombat Premium:"
-#    list_gems: "Receive bonus gems every month to buy gear, pets, and heroes"
-#    list_levels: "Gain access to __premiumLevelsCount__ more levels"
-#    list_heroes: "Unlock exclusive heroes, include Ranger and Wizard classes"
-#    list_game_dev: "Make and share games with friends"
-#    list_web_dev: "Build websites and interactive apps"
-#    list_items: "Equip Premium-only items like pets"
-#    list_support: "Get Premium support to help you debug tricky code"
-#    list_clans: "Create private clans to invite your friends and compete on a group leaderboard"
+    feature_levels: "Krijg toegang tot __premiumLevelsCount__ beschikbare levels"
+    feature_gems: "Ontvang __gems__ edelstenen per maand"
+    feature_heroes: "Krijg toegang tot speciale helden"
+    feature_games: "Maak spellen voor je vrienden"
+    feature_websites: "Bouw websites en apps"
+    feature_items: "Gebruik krachtigere items"
+    month_price: "$__price__/mo"
+    lifetime: "Levenslang abonnement"
+    lifetime_price: "$__price__"
+    year_subscription: "Jaarlijks abonnement"
+    year_price: "$__price__/year"
+    kids_message_1: "Kinderen! We sturen een mail naar jullie ouders zodat ze een abonnement voor jullie kunnen kopen."
+    kids_message_2: "Vraag het aan je ouders"
+    support_part1: "Hulp nodig met betalingsopties? Email"
+    support_part2: "support@codecombat.com"
+    support_part3: "als je vragen hebt."
+
+  premium_features:
+    get_premium: "Neem
CodeCombat
Premium" # Fit into the banner on the /features page
+    master_coder: "Word een Mesteer Coder door je vandaag te abonneren!"
+    subscribe_now: "Abonneer nu"
+    hero_blurb_1: "Krijg toegang tot __premiumHeroesCount__ supersterke helden, alleen voor abonnees! Gebruik de epische kracht van Okar Stompfoot, de dodelijke precisie van Naria of the Leaf, of roep \"schattige\" skeletten op met Nalfar Cryptor."
+    hero_blurb_2: "Premium helden hebben ongelofelijke vecht skills zoals Warcry, Stomp, en Hurl Enemy. Of, speel als Ranger met pijl en boog, werpmessen en vallen! Test je vaardigheden als echte codeermeester en gebruik een arsenaal vam Primordial, Necromantic og Elemental magie!"
+    hero_caption: "Spannende nieuwe helden!"
+    pet_blurb_1: "Huisdieren zijn niet alleen schattige maatjes, ze hebben ook unieke functies en methoden. De Baby Griffin kan over obstakels heen vliegen om de held te helpen, de Wolf Pup kan potions halen en de Cougar kan praten!"
+    pet_blurb_2: "Verzamel alle huisdieren om hun unieke vaardigheden te ontdekken!"
+    pet_caption: "Adopteer huisdieren om je held gezelschap te houden!"
+    game_dev_blurb: "Leer games programmeren en bouw nieuwe levels om met je vrienden te delen! Plaats de items die je wil, schrijf code voor het gedrag van verschillende units en kijk of je vrienden je level kunnen verslaan!"
+    game_dev_caption: "Ontwerp je eigen levels om je vrienden uit te dagen!"
+    everything_in_premium: "Wat je allemaal krijgt in CodeCombat Premium:"
+    list_gems: "Ontvang elke maand extra edelstenen om gear, huisdieren en helden mee te kopen"
+    list_levels: "Krijg toegang tot to __premiumLevelsCount__ extra levels"
+    list_heroes: "Unlock exclusieve helden, zoals de Ranger en Wizard types"
+    list_game_dev: "Maak en deel games met vrienden"
+    list_web_dev: "Bouw websites en interactieve apps"
+    list_items: "Gebruik Premium items zoals huisdieren"
+    list_support: "Krijg Premium hulp om je te helpen met lastige code"
+    list_clans: "Maak privé clans om met je vrienden te strijden voor het leaderboard"

   choose_hero:
     choose_hero: "Kies je held"
@@ -954,8 +954,8 @@
     please_explain: "(licht toe a.u.b.)"
     middle_school: "Middelbare school (onderbouw)"
     college_plus: "Hoger Onderwijs"
-#    referrer: "How did you hear about us?"
-#    referrer_help: "For example: from another teacher, a conference, your students, Code.org, etc."
+    referrer: "Hoe heb je over ons gehoord?"
+    referrer_help: "Bijvoorbeeld: van een andere leraar, een conferentie, je leerlingen, Code.org, etc."
     anything_else: "Is er verder nog iets dat we moeten weten?"
     thanks_header: "Bedankt voor je demo-aanvraag!"
     thanks_sub_header: "Bedankt voor je interesse in CodeCombat."
@@ -1039,7 +1039,7 @@
     saved: "Aanpassingen Opgeslagen"
     password_mismatch: "Het wachtwoord komt niet overeen."
     password_repeat: "Herhaal je wachtwoord."
-#    file_size_limit: "(max file size: 1MB)"
+    file_size_limit: "(max bestandsgrootte: 1MB)"

   keyboard_shortcuts:
     keyboard_shortcuts: "Toetsenboord sneltoetsen"
@@ -1281,8 +1281,8 @@
     current_hero: "Huidige Held:"
     change_hero: "Verander Held"
     web_dev_language_transition: "Alle klassen programmeren in HTML / Javascript voor deze cursus. Klassen die eerder Python hebben gebruikt zullen beginnen met extra JavaScript intro levels om de overgang te vergemakkelijken. Klassen die al met JavaScript werken slaan deze intro lessen over."
-#    course_membership_required_to_play: "You'll need to join a course to play this level."
-#    license_required_to_play: "Ask your teacher to assign a license to you so you can continue to play CodeCombat!"
+    course_membership_required_to_play: "Je moet je bij een cursus aanmelden om dit level te spelen."
+    license_required_to_play: "Vraag een leraar om je een licentie te geven zodat je verder kunt gaan met CodeCombat!"

   teacher:
     assigning_course: "Cursus toewijzen"
@@ -1325,7 +1325,7 @@
     latest_complete: "Laatste voltooide level"
     enroll_student: "Schrijf leerling in"
     apply_license: "Pas licentie toe"
-#    revoke_license: "Revoke License"
+    revoke_license: "Licentie intrekken"
     course_progress: "Cursus voortgang"
     not_applicable: "n.v.t."
     edit: "wijzig"
@@ -1425,8 +1425,8 @@
     progress_journal_desc: "Moedig leerlingen aan hun voortgang bij te houden in hun logboek."
     cs1_curriculum: "Introductie tot programmeren - Curriculum Gids"
     cs1_curriculum_desc: "Cursus 1: leertraject, lesplannen, activiteiten en meer."
-#    arenas_curriculum: "Arena Levels - Teacher Guide"
-#    arenas_curriculum_desc: "Instructions on how to run Wakka Maul, Cross Bones and Power Peak multiplayer arenas with your class."
+    arenas_curriculum: "Arena Levels - Leraren Gids"
+    arenas_curriculum_desc: "Instructies voor het spelen van de Wakka Maul, Cross Bones en Power Peak multiplayer arenas met je klas."
     cs2_curriculum: "Informatica 2 - Curriculum Gids"
     cs2_curriculum_desc: "Cursus 2: leertraject, lesplannen, activiteiten en meer."
     cs3_curriculum: "Informatica 3 - Curriculum Gids"
@@ -1492,8 +1492,8 @@
     hoc_additional_desc1: "Voor meer Codecombat informatie en activiteiten, ga naar"
     hoc_additional_desc2: "Vragen"
     hoc_additional_contact: "Neem contact op"
-#    revoke_confirm: "Are you sure you want to revoke a Full License from {{student_name}}? The license will become available to assign to another student."
-#    revoking: "Revoking..."
+    revoke_confirm: "Weet je zeker dat je de licentie van {{student_name}} wilt intrekken? De licentie kan dan weer gebruikt worden voor een andere leerling."
+    revoking: "Intrekken..."

   sharing:
     game: "Spel"
@@ -2002,16 +2002,16 @@
     advanced_web_scripting: "Geavanceerde Web Scripts"
     input_handling: "Input Behandeling"
     while_loops: "While Loops"
-#    place_game_objects: "Place game objects"
-#    construct_mazes: "Construct mazes"
-#    create_playable_game: "Create a playable, sharable game project"
-#    alter_existing_web_pages: "Alter existing web pages"
-#    create_sharable_web_page: "Create a sharable web page"
-#    basic_input_handling: "Basic Input Handling"
-#    basic_game_ai: "Basic Game AI"
-#    basic_javascript: "Basic JavaScript"
-#    basic_event_handling: "Basic Event Handling"
-#    create_sharable_interactive_web_page: "Create a sharable interactive web page"
+    place_game_objects: "Plaats game objecten"
+    construct_mazes: "Bouw doolhoven"
+    create_playable_game: "Maak een speelbaar game project dat je kunt delen"
+    alter_existing_web_pages: "Verander bestaande webpagina's"
+    create_sharable_web_page: "Maak een webpagina die je kunt delen"
+    basic_input_handling: "Basis Input Afhandelen"
+    basic_game_ai: "Basis Game AI"
+    basic_javascript: "Basis JavaScript"
+    basic_event_handling: "Basis Event Afhandelen"
+    create_sharable_interactive_web_page: "Maak een interactieve webpagina die je kunt delen"

   delta:
     added: "Toegevoegd"


Simplify unpublish usage text

Simplify unpublish usage text

--- src/unpublish.coffee
+++ src/unpublish.coffee
@@ -20,8 +20,7 @@
       Usage: apm unpublish [<package_name>]
              apm unpublish <package_name>@<package_version>

-      Remove a published package from the atom.io registry or unpublish a given
-      version of a package.
+      Remove a published package or package version from the atom.io registry.

       The package in the current working directory will be used if no package
       name is specified.


Pass contentView to HelptextPopoverView

Pass contentView to HelptextPopoverView

--- core/app/backbone/factlink/tooltip_mixin.coffee
+++ core/app/backbone/factlink/tooltip_mixin.coffee
@@ -9,7 +9,7 @@
     if @_tooltips[selector]?
       throw "Cannot call tooltipAdd multiple times with the same selector: #{selector}"

-    view = new HelptextPopoverView(model: new Backbone.Model(title: title, text: text), side: side)
+    view = new HelptextPopoverView(model: new Backbone.Model(title: title, text: text), side: side, view: options.contentView )

     @_tooltips[selector] = new Backbone.Factlink.PositionedRegion _.extend(options, side: side)
     @_tooltips[selector].show view


KDView: unset tooltip method implemented

--- core/KDView.coffee
+++ core/KDView.coffee
@@ -902,6 +902,10 @@
     else
       kallback()

+  unsetTooltip:(o = {})->
+    @tooltip?.destroy()
+    delete @tooltip
+
   setTooltip:(o = {})->

     placementMap =
@@ -930,8 +934,7 @@
     o.delegate  or= this
     o.events    or= ['mouseenter','mouseleave','mousemove']

-    @tooltip?.destroy()
-    delete @tooltip
+    @unsetTooltip()
     @tooltip = new KDTooltip o, {}

   getTooltip:-> @tooltip


Custom metric should be listed as a metric.

--- src/validator.coffee
+++ src/validator.coffee
@@ -446,7 +446,7 @@

 #Custom
 addDimension 'ga:dimension(n)', 'Custom Dimension', 'ga:dimension([0-9]+)'
-addDimension 'ga:metric(n)', 'Custom Metric', 'ga:metric([0-9]+)'
+addMetric 'ga:metric(n)', 'Custom Metric', 'ga:metric([0-9]+)'

 #Time
 addDimension 'ga:date', 'Date'


some magic

some magic

--- src/valueHand.coffee
+++ src/valueHand.coffee
@@ -13,22 +13,37 @@
   .filter (group) -> group.length > 1
   .value()

+  suits = _ hand.all
+  .groupBy (card) -> card.suit
+  .value()
+
   myPairs = pairs.length - cpairs.length

   value = 0

-  if myPairs > 0 and hand.all.length < 4
-    return 1
-
   if myPairs > 0
-    value += 1
+    if hand.all.length < 4
+      return 1
+    else
+      value = 1
   else
     value -= 0.1

-  highCards = _.filter hand.hole, (card) -> card.rank > 10
+  highCards = _.filter hand.hole, (card) -> card.rank > 9

   if highCards.length > 0
-    value = value + (highCards.length / hand.all.length) * 0.5
+    value = value + (highCards.length / hand.all.length - value) * 0.5
+    if hand.community.length == 0
+      return highCards.length / 2
+  else
+    value -= 0.1
+
+  if hand.community.length == 0
+    if hand.hole[0].suit == hand.hole[1].suit
+      return 1
+
+  if suits.length == 1
+    value = value + (1 - value) * 0.5

   if value > 0
     value /= variance


debug

debug

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -46,7 +46,7 @@
                 if (@_userInteractionEnabled == false)
                     return
                 obj = ui.item
-                JSLog(obj)
+                JSLog(obj.html())
                 @selectMenuItem(item)
                 @closeMenu()
             classes:


Stick to @ symbol

Stick to @ symbol

--- app/assets/javascripts/controllers/project_commits_controller.js.coffee
+++ app/assets/javascripts/controllers/project_commits_controller.js.coffee
@@ -16,9 +16,9 @@

   sortNow: (parameter)->
-    this.set('sortProperties', [parameter])
+    @set('sortProperties', [parameter])
     @filteredContent()

   actions:
     sortBy: (parameter)-> @sortNow(parameter)
-    toggleAccepted: -> @.toggleProperty('hideAccepted')
+    toggleAccepted: -> @toggleProperty('hideAccepted')


Fix search paging for hashless urls

Fix search paging for hashless urls

--- app/talk/search.cjsx
+++ app/talk/search.cjsx
@@ -85,7 +85,8 @@

   goToPage: (n) ->
     nextQuery = Object.assign @props.query, {page: n}
-    @transitionTo @props.path, @props.params, nextQuery
+
+    @transitionTo location.pathname, @props.params, nextQuery

   render: ->
     numberOfResults = @state.results.length


Use Consolas

Use Consolas

--- home/.atom/config.cson
+++ home/.atom/config.cson
@@ -10,6 +10,7 @@
   'tabLength': 4
   'showIndentGuide': true
   'fontSize': 12
+  'fontFamily': 'Consolas'
 'core':
   'themes': [
     'atom-dark-ui'


more tree-view stuff

more tree-view stuff

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -29,24 +29,39 @@
 #
 # NOTE: CAPS LOCK should be mapped to Control
 #
+
+###############################################
+# To not forget the joy of NERDTree's shortcuts
+###############################################
+# Tree View
+# ctrl+\         toggle tree view
+# alt+\          toggle focus
+# shift-a        add folder
+# a              add file
+# d              duplicate
+# ctrl+shift+c   copy full path
+# ctrl+shift+\   reveal active file
+# F2 or m        move
+'.tree-view':
+  'd d': 'tree-view:remove'
+  'y':   'tree-view:copy'
+  'p':   'tree-view:paste'
+  'x':   'tree-view:cut'
+  'a f': 'tree-view:add-file'
+  'a d': 'tree-view:add-folder'
+  'i':   'tree-view:open-selected-entry-down'
+  's':   'tree-view:open-selected-entry-right'
+  'c':   'core:move-up'
+  't':   'core:move-down'
+  'h':   'core:move-left'
+  'n':   'core:move-right'
+
+
 'atom-workspace atom-text-editor':
   ###############################################
   # Search and handy macro-y things
   ##############################################
   # alt-enter converts search results into selections
-
-  ###############################################
-  # To not forget the joy of NERDTree's shortcuts
-  ###############################################
-  # Tree View
-  # ctrl+\         toggle tree view
-  # alt+\          toggle focus
-  # shift-a        add folder
-  # a              add file
-  # d              duplicate
-  # ctrl+shift+c   copy full path
-  # ctrl+shift+\   reveal active file
-  # F2+m           move

   ###################################
   # Pane Navigation


extends password params with @data.password to allow app secific password paramerters

--- modules/http/lib/sessions/Sessions.coffee
+++ modules/http/lib/sessions/Sessions.coffee
@@ -4,294 +4,291 @@
 module.exports =

     ##
-    ##
+    ##
     ##
     class HttpSessions extends floyd.auth.AuthContext
-
+
         configure: (config)->
-
+
             @_hiddenKeys.push 'Registry', 'Session'
             super new floyd.Config
-
+
                 data:
                     cookie:
                         name: 'FSID'
-
+
                     registry:
                         type: 'http.sessions.Registry'
                         interval: 60
                         timeout: 3600
-
+
                         sessions:
                             type: 'http.sessions.Session'
-
-            , config
+
+            , config

         ##
         ##
         ##
         boot: (done)->
-
+
             @_registry = new (floyd.tools.objects.resolve @data.registry.type) @data.registry, @
-
+
             super done
-
+
         ##
         ##
         ##
         start: (done)->
-
+
             exclude = @data.find 'no-session-routes', []
-
+
             ## use the next HttpContext (idealy our parent) to connect req handler
             @delegate '_addMiddleware', (req, res, next)=>
-                return next() if @data.disabled
-
+                return next() if @data.disabled
+
                 for expr in exclude
                     return next() if req.url.match expr
-
+
                 @_load @_getSID(req), (err, sess)=>
                     return next(err) if err
-
+
                     #console.log 'session', sess.public
-
+
                     req.session = sess.public
-
+
                     _end = res.end
                     res.end = (args)=>
-                        @_release req, sess, (err)=>
+                        @_release req, sess, (err)=>
                             return next(err) if err
-
+
                             req.session = null 		## <-- remove references to avoid memory leaks
-
+
                             _end.apply res, args
-
-                    ##
+
+                    ##
                     next()
-
-
+
+
             ##
             super done
-
-
+
+
         ##
         ##
         ##
         login: (token, user, pass, _fn)->
-
+
             fn = (err, user)=>
                 if err
                     setTimeout ()=>
                         _fn err
                     , 2500
-
+
                 else _fn null, user
-
+
             sid = token.substr 40 ## TODO - validate token
-
+
             sess = @_registry.get(sid)
-
+
             return fn(new Error 'login failed') if !sess || !user || !pass
-
+
             users = @parent.children.users
-
-            users.get user, (err, data)=>
+
+            users.get user, (err, data)=>

                 return fn(err || new Error 'login failed') if err || !data
-
+
                 if data.active is false || !floyd.tools.crypto.password.verify pass, data.pass
                     console.warn 'access denied for %s@%s', user, sid
-
-                    fn new Error 'login failed'
-
+
+                    fn new Error 'login failed'
+
                 else
                     data.lastlogin = +new Date()
-
+
                     @_checkPasswordHash user, pass, data, (err, data)=>
                         return fn(err) if err
-
+
                         users.set user, data, (err)=>
-
-                            data = floyd.tools.objects.clone data,
+
+                            data = floyd.tools.objects.clone data,
                                 login: user
-
+
                             delete data.pass
-
+
                             sess.public.user = data
-
+
                             ##
                             fn null, data
-
-
+
+
         ##
         ##
         ##
         _checkPasswordHash: (user, pass, data, fn)->
-
-            cfg = floyd.config.crypto.password
+
+            cfg = floyd.tools.objects.extend floyd.config.crypto.password, @data.password

             parts = data.pass.split '-'
-
-            if parts.length is 1 \          ## check for old-style password hash
+
+            if parts.length is 1 \          ## check for old-style password hash
             or cfg.hasher isnt parts[1] \   ## check for new hash config
             or cfg.keySize isnt (parseInt parts[2]) \
             or cfg.iterations isnt (parseInt parts[3])
-
+
                 data.pass = floyd.tools.crypto.password.create pass
-
+
                 console.warn 'replacing password hash', data.pass
-
-
+
+
             ##
             fn null, data
-
+
         ##
         ##
         ##
         logout: (token, fn)->
-
+
             sid = token.substr 40 ## TODO - validate token
-
+
             #console.log 'logout %s@%s', user, sid
-
+
             if (sess = @_registry.get(sid))
-
+
                 delete sess.public.user
-
-
+
+
             fn()
-
-
-        ##
-        ##
-        ##
-        authorize: (token, fn)->
-
+
+
+        ##
+        ##
+        ##
+        authorize: (token, fn)->
+
             if token && (sess = @_registry?.get (sid = token.substr 40))
-
+
                 #console.log 'found session', sess, sess.public.user?.login

                 ## initialize destroy hook on the fly
                 if !sess.destroyHook
                     sess.on 'destroy', ()=>
                         sess.destroyHook new Error 'session destroyed'
-
+
                 ## register the current callback as the destroy hook
                 sess.destroyHook = fn
-
+
                 ## authorize loggedin session
                 if user = sess.public.user?.login
-
+
                     @parent.children.users.get user, (err, data)=>
                         return fn(err) if err
-
-                        data = floyd.tools.objects.clone data,
+
+                        data = floyd.tools.objects.clone data,
                             login: user
-
+
                         delete data.pass
-
+
                         fn null, sess.public.user = data
-
-                ## authorize anonymous session
+
+                ## authorize anonymous session
                 else
                     fn()
-
-
-            else
+
+
+            else
                 super token, fn
-
+
         ##
         ##
         ##
         authenticate: (identity, fn)->
-
+
             @logger.debug 'session authenticate identity %s', identity.id
-
+
             super identity, (err)=>
                 return fn() if !err ## successfully authenticated
-
+
                 identity.token (err, token)=>
                     if err || !token
                         @logger.warning 'session authenticate NO TOKEN', identity.id

-                        return fn(err || new Error 'session authenticate NO TOKEN')
-
-                    sid = token.substr 40
-
+                        return fn(err || new Error 'session authenticate NO TOKEN')
+
+                    sid = token.substr 40
+
                     @logger.fine 'session authenticate session %s', sid

                     @_load sid, (err, sess)=>
                         return fn(err) if err
-
+
                         @logger.finer 'session authenticate hash %s', token.substr 0, 39
-
+
                         if token is sess.token
-
-                            @logger.debug 'session authenticate SUCCESS', identity.id
-
+
+                            @logger.debug 'session authenticate SUCCESS', identity.id
+
                             fn()
-
+
                         else
-                            @logger.debug 'session authenticate FAILURE', identity.id
-
+                            @logger.debug 'session authenticate FAILURE', identity.id
+
                             fn new Error 'session authenticate FAILURE'
-
-
-
+
+
+
         ##
         ##
         ##
         _load: (sid, fn)->
-
+
             #console.log 'load session', sid
-
+
             ## search and create
             if !(sess = @_registry.get sid)
-
+
                 @_registry.add sess = new (floyd.tools.objects.resolve @data.registry.sessions.type) sid, @data.registry.sessions
-
-            sess.resume()
-
+
+            sess.resume()
+
             ## deliver
             fn null, sess
-
-
+
+
         ##
         ##
         ##
         _release: (req, sess, fn)->

             sess.suspend()
-
+
             fn()
-
-
-        ##
-        ##
-        ##
-        _getSID: (req)->
-
+
+
+        ##
+        ##
+        ##
+        _getSID: (req)->
+
             #console.log 'search cookie', @data.cookie.name, req.url, req.headers.cookie
-
+
             if !(sid = req.cookies.get @data.cookie.name)

                 sid = @_createSID()
-
-                #console.log 'create cookie', sid
-
+
+                #console.log 'create cookie', sid
+
                 req.cookies.set @data.cookie.name, sid
-
+
             #console.log 'sid', sid
-
+
             ##
             return sid
-
-
+
+
         ##
         ##
         ##
         _createSID: ()->
             @_registry.createSID()
-
-
-


Fix for adding the user multiple times ot the array

--- src/adapters/generic.coffee
+++ src/adapters/generic.coffee
@@ -15,7 +15,7 @@
   disableNotificationsFor: (user) ->
     @robot.logger.info "Disabling Github notifications for #{user.name}"
     @disabledUsers.push user.id
-    @robot.brain.set GenericAdapter.GITHUB_NOTIFICATIONS_DISABLED, @disabledUsers
+    @robot.brain.set GenericAdapter.GITHUB_NOTIFICATIONS_DISABLED, _(@disabledUsers).unique()
     @robot.brain.save()

   enableNotificationsFor: (user) ->


fixed strings #{}

fixed strings #{}

--- src/api/transactions.coffee
+++ src/api/transactions.coffee
@@ -246,16 +246,16 @@
               if route.metrics?
                 for metric in route.metrics
                   if metric.type == 'counter'
-                    logger.info 'incrementing mediator counter ' + metric.name
-                    sdc.increment domain + '.channels.' + doc.channelID + '.' + route.name + '.mediator_metrics.' + metric.name
+                    logger.info "incrementing mediator counter  #{metric.name}"
+                    sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}"

                   if metric.type == 'timer'
-                    logger.info 'incrementing mediator timer ' + metric.name
-                    sdc.timing domain + '.channels.' + doc.channelID + '.' + route.name + '.mediator_metrics.' + metric.name, metric.value
+                    logger.info "incrementing mediator timer  #{metric.name}"
+                    sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}", metric.value

                   if metric.type == 'gauge'
-                    logger.info 'incrementing mediator gauge ' + metric.name
-                    sdc.gauge domain + '.channels.' + doc.channelID + '.' + route.name + '.mediator_metrics.' + metric.name, metric.value
+                    logger.info "incrementing mediator gauge  #{metric.name}"
+                    sdc.gauge "#{domain}.channels.#{doc.channelID}.#{route.name}.mediator_metrics.#{metric.name}", metric.value

               for orchestration in route.orchestrations
                 do (orchestration) ->
@@ -269,29 +269,29 @@
                   # Update timers
                   ###
                   logger.info 'updating async route timers'
-                  sdc.timing domain + '.channels.' + doc.channelID + '.' + route.name + '.orchestrations.' + orchestrationName, orchestrationDuration
-                  sdc.timing domain + '.channels.' + doc.channelID + '.' + route.name + '.orchestrations.' + orchestrationName + '.statusCodes.' + orchestrationStatus , orchestrationDuration
+                  sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}", orchestrationDuration
+                  sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.statusCodes.#{orchestrationStatus}" , orchestrationDuration

                   ###
                   # Update counters
                   ###
                   logger.info 'updating async route counters'
-                  sdc.increment domain + '.channels.' + doc.channelID + '.' + route.name + '.orchestrations.' + orchestrationName
-                  sdc.increment domain + '.channels.' + doc.channelID + '.' + route.name + '.orchestrations.' + orchestrationName + '.statusCodes.' + orchestrationStatus
+                  sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}"
+                  sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.statusCodes.#{orchestrationStatus}"

                   if orchestration.metrics?
                     for metric in orchestration.metrics
                       if metric.type == 'counter'
-                        logger.info 'incrementing '+ route.name + ' orchestration counter ' + metric.name
-                        sdc.increment   domain + '.channels.' + doc.channelID + '.' + route.name + '.orchestrations.' + orchestrationName + '.' + metric.name, metric.value
+                        logger.info "incrementing #{route.name} orchestration counter #{metric.name}"
+                        sdc.increment "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value

                       if metric.type == 'timer'
-                        logger.info 'incrementing '+ route.name + 'orchestration timer ' + metric.name
-                        sdc.timing      domain + '.channels.' + doc.channelID + '.' + route.name + '.orchestrations.' + orchestrationName + '.' + metric.name, metric.value
+                        logger.info  "incrementing #{route.name} orchestration timer #{metric.name}"
+                        sdc.timing "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value

                       if metric.type == 'gauge'
-                        logger.info 'incrementing '+ route.name + 'orchestration gauge ' + metric.name
-                        sdc.gauge       domain + '.channels.' + doc.channelID + '.' + route.name + '.orchestrations.' + orchestrationName  + '.' + metric.name, metric.value
+                        logger.info  "incrementing #{route.name} orchestration gauge #{metric.name}"
+                        sdc.gauge "#{domain}.channels.#{doc.channelID}.#{route.name}.orchestrations.#{orchestrationName}.#{metric.name}", metric.value

   catch e
     utils.logAndSetResponse this, 'internal server error', "Could not update transaction via the API: #{e}", 'error'


Define the _rv object id as a non-enumerable, non-configurable, non-writable property on the object.

--- src/adapters.coffee
+++ src/adapters.coffee
@@ -8,8 +8,15 @@

   subscribe: (obj, keypath, callback) ->
     unless obj[@id]?
-      obj[@id] = @counter++
-      @weakmap[obj[@id]] = {}
+      id = @counter++
+
+      Object.defineProperty obj, @id,
+        enumerable: false
+        configurable: false
+        writable: false
+        value: id
+
+      @weakmap[id] = {}

     map = @weakmap[obj[@id]]


support searching for local package.json

--- src/aliasify.coffee
+++ src/aliasify.coffee
@@ -11,7 +11,7 @@
             return pkg+fileParts[2]
     return null

-module.exports = transformTools.makeRequireTransform "aliasify", jsFilesOnly: true, (args, opts, done) ->
+module.exports = transformTools.makeRequireTransform "aliasify", {jsFilesOnly: true, fromSourceFileDir: true}, (args, opts, done) ->
     if !opts.config then return done new Error("Could not find configuration for aliasify")
     aliases = opts.config.aliases
     verbose = opts.config.verbose


:speak_no_evil:

--- src/app/atom.coffee
+++ src/app/atom.coffee
@@ -119,9 +119,6 @@
   getAvailableThemePaths: ->
     themePaths = []
     for themeDirPath in config.themeDirPaths
-
-      console.log fsUtils.list(themeDirPath)
-
       themePaths.push(fsUtils.list(themeDirPath, ['', '.tmTheme', '.css', 'less'])...)
     _.uniq(themePaths)


Add some (breaking) tests for optional parameters.

--- tests/parse.spec.coffee
+++ tests/parse.spec.coffee
@@ -147,6 +147,22 @@
 					global: {}
 					options: {}

+			it 'should be able to parse commands with multiple arguments', ->
+				argv = parse.split('transfer <from name> <to name>')
+				result = parse.parse(argv)
+				expect(result).to.deep.equal
+					command: 'transfer <from name> <to name>'
+					global: {}
+					options: {}
+
+			it 'should be able to parse commands with optional arguments', ->
+				argv = parse.split('greet [their name]')
+				result = parse.parse(argv)
+				expect(result).to.deep.equal
+					command: 'greet [their name]'
+					global: {}
+					options: {}
+
 			it 'should not discard single quotes when parsing the command', ->
 				argv = parse.split('hello \'John Doe\'')
 				result = parse.parse(argv)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -30,9 +30,9 @@
   enemy.flicker
     alpha: 0.4

-  equals player.I.flickerAlpha, 0.4
-  equals player.I.flickerFrequency, 3
-  equals player.I.flickerDuration, 30
+  equals enemy.I.flickerAlpha, 0.4
+  equals enemy.I.flickerFrequency, 3
+  equals enemy.I.flickerDuration, 30

 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

   player.update()
-
+  equals player.I.alpha, 0.2


Basic tooltips on map now display district id.

--- app/scripts/controllers/district.coffee
+++ app/scripts/controllers/district.coffee
@@ -36,8 +36,14 @@
       ready = (error, us, congress) ->
         $scope.usMap.append("defs").append("path").attr("id", "land").datum(topojson.feature(us, us.objects.land)).attr "d", path
         $scope.usMap.append("clipPath").attr("id", "clip-land").append("use").attr "xlink:href", "#land"
-        $scope.usMap.append("g").attr("class", "districts").attr("clip-path", "url(#clip-land)").selectAll("path").data(topojson.feature(congress, congress.objects.districts).features).enter().append("path").attr("d", path).append("title").text (d) ->
+        district = $scope.usMap.append("g").attr("class", "districts").attr("clip-path", "url(#clip-land)").selectAll("path").data(topojson.feature(congress, congress.objects.districts).features).enter().append("path").attr("d", path).text (d) ->
           "#{$scope.FIPS_to_state[d.id / 100 | 0]}-#{d.id % 100}"
+        district.on("mouseover", () ->
+          return tooltip.style("visibility", "visible").text(d3.select(this).text())
+        ).on("mousemove", () ->
+          return tooltip.style("top", (event.pageY-10)+"px").style("left", (event.pageX+10+"px"))
+        ).on("mouseout", () ->
+          return tooltip.style("visibility", "hidden"))
         $scope.usMap.append("path").attr("class", "district-boundaries").attr("clip-path", "url(#clip-land)").datum(topojson.mesh(congress, congress.objects.districts, (a, b) ->
           (a.id / 1000 | 0) is (b.id / 1000 | 0)
         )).attr "d", path
@@ -49,6 +55,11 @@
       path = d3.geo.path()
       svg = d3.select("#map_holder").append("svg").attr("width", width).attr("height", height)
       $scope.usMap = svg.append("g").attr("id", "map_with_districts")
+      tooltip = d3.select("#map_holder")
+        .append("div")
+        .style("position", "absolute")
+        .style("z-index", "10")
+        .style("visibility", "hidden")
       queue().defer(d3.json, "views/us.json").defer(d3.json, "views/us-congress-113.json").await ready

     $scope.zoomIn = (d) ->


Remove duplicate variable declaration

Remove duplicate variable declaration

Hey, just noticed that "Project" has already been declared on line 36

--- src/atom-environment.coffee
+++ src/atom-environment.coffee
@@ -42,7 +42,6 @@
 PaneAxis = require './pane-axis'
 Pane = require './pane'
 Dock = require './dock'
-Project = require './project'
 TextEditor = require './text-editor'
 TextBuffer = require 'text-buffer'
 Gutter = require './gutter'


Added test for async option

Added test for async option

--- test/parser.test.coffee
+++ test/parser.test.coffee
@@ -270,6 +270,13 @@
         equ r.sample.chartest[0]._, 'Character data here!'
         test.finish()

+  'test async execution': (test) ->
+    fs.readFile fileName, (err, data) ->
+      xml2js.parseString data, async: true, (err, r) ->
+        equ err, null
+        equ r.sample.chartest[0]._, 'Character data here!'
+        test.finish()
+
   'test validator': skeleton(validator: validator, (r) ->
     console.log 'Result object: ' + util.inspect r, false, 10
     equ typeof r.sample.validatortest[0].stringtest[0], 'string'


Added policy test stubs

Added policy test stubs

--- test/policy.test.coffee
+++ test/policy.test.coffee
@@ -11,8 +11,9 @@
         p.mustWrap("bleh").should.be.false
         p.onGet({}, "", {}, {}, 1234).should.equal 1234

-    it "should work with rules", () =>
-        p = @policy.build()
-        p.mustWrap("bleh").should.be.false
-        p.onGet({}, "", {}, {}, 1234).should.equal 1234
+    it.skip "should modify return values", ->
+    it.skip "should call external functions", ->
+    it.skip "should throw errors", ->
+    it.skip "should sequence actions", ->
+    it.skip "should sequence rules", ->


use new endpoint docupdater get_and_flush_if_old

--- services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
+++ services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee
@@ -128,9 +128,9 @@
 		# docs from redis via the docupdater. Otherwise we will need to
 		# fall back to getting them from mongo.
 		timer = new metrics.Timer("get-project-docs")
-		url = "#{settings.apis.documentupdater.url}/project/#{project_id}/doc?state=#{projectStateHash}"
+		url = "#{settings.apis.documentupdater.url}/project/#{project_id}/get_and_flush_if_old?state=#{projectStateHash}"
 		logger.log project_id:project_id, "getting project docs from document updater"
-		request.get url, (error, res, body)->
+		request.post url, (error, res, body)->
 			timer.done()
 			if error?
 				logger.error err:error, url:url, project_id:project_id, "error getting project docs from doc updater"