Fix spec failures caused by events not bubbling in older Chrome versions

We no longer have to attach DOM nodes for events to bubble in
Electron 33.x, but 1.0 and 1.1 still require it.

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -216,6 +216,8 @@

   describe "when a tab is clicked", ->
     it "shows the associated item on the pane and focuses the pane", ->
+      jasmine.attachToDOM(tabBar) # Remove after Atom 1.2.0 is released
+
       spyOn(pane, 'activate')

       event = triggerMouseEvent('mousedown', tabBar.tabAtIndex(0), which: 1)
@@ -234,6 +236,8 @@
       runs -> expect(pane.activate.callCount).toBe 2

     it "closes the tab when middle clicked", ->
+      jasmine.attachToDOM(tabBar) # Remove after Atom 1.2.0 is released
+
       event = triggerMouseEvent('mousedown', tabBar.tabForItem(editor1), which: 2)

       expect(pane.getItems().length).toBe 2
@@ -245,6 +249,8 @@
       expect(event.preventDefault).toHaveBeenCalled()

     it "doesn't switch tab when right (or ctrl-left) clicked", ->
+      jasmine.attachToDOM(tabBar) # Remove after Atom 1.2.0 is released
+
       spyOn(pane, 'activate')

       event = triggerMouseEvent('mousedown', tabBar.tabAtIndex(0), which: 3)
@@ -259,6 +265,8 @@

   describe "when a tab's close icon is clicked", ->
     it "destroys the tab's item on the pane", ->
+      jasmine.attachToDOM(tabBar) # Remove after Atom 1.2.0 is released
+
       $(tabBar.tabForItem(editor1)).find('.close-icon').click()
       expect(pane.getItems().length).toBe 2
       expect(pane.getItems().indexOf(editor1)).toBe -1
@@ -394,6 +402,7 @@
     beforeEach ->
       paneElement = atom.views.getView(pane)
       paneElement.insertBefore(tabBar, paneElement.firstChild)
+      jasmine.attachToDOM(paneElement) # Remove after Atom 1.2.0 is released

     describe "when tabs:close-tab is fired", ->
       it "closes the active tab", ->


Added preview field in local collection

Added preview field in local collection

Stores the dataURL string of the uploaded file in local collection. Useful to show a preview client-side while the file is being uploaded.

--- client/functions.coffee
+++ client/functions.coffee
@@ -85,6 +85,7 @@
 								loaded:event.loaded
 								total:event.total
 								percent_uploaded: Math.floor ((event.loaded / event.total) * 100)
+								preview: file
 					,false

 				Cloudinary.xhr.addEventListener "load", ->


Implement pending step about pure value equality

--- features/step_definitions/steps.coffee
+++ features/step_definitions/steps.coffee
@@ -76,4 +76,6 @@
     callback()

   this.Then /^the result should equal (\d+)$/, (expected, callback) ->
-    callback.pending()
+    unless result == parseInt(expected)
+      callback.fail new Error("#{result} <> #{expected}")
+    callback()


removed and added discount keys

removed and added discount keys

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -13,7 +13,7 @@
       description_store: "World camera with beautiful image quality. We recommend this option when you want aesthetically pleasing footage, and are not concerned with size, weight, or speed."
       tech_specs:
         sensor: "1910x1080 @30fps"
-        fov: "90 degrees diagonal with auto focus lens."
+        fov: "90 degrees diagonal with auto focus lens."
         latency: "127.7ms"
         audio: "Record audio with built-in stereo microphone"
       title_cart: "World camera: high resolution"
@@ -56,7 +56,7 @@

 get_eye_cam_data = ->
-  eye_cam_data =
+  eye_cam_data =
     enone:
       id: 'enone'
       title_store: "none"
@@ -143,7 +143,7 @@
   return (k for k,v of get_eye_cam_data() when k isnt 'e30' and k isnt 'e120b' and k isnt 'e120')

 get_edu_discount_data = ->
-  edu_discount_data =
+  edu_discount_data =
     id: 'edu'
     title_cart: "Academic Discount"
     description_cart: "For use within an academic (non-commercial) context."
@@ -161,14 +161,14 @@
   edu_discount = get_edu_discount_data()
   pupil_img_path = "../media/images/store_pupil/web"
   vr_ar_img_path = "../media/images/store_vr_ar"
-  additional_product_path = "../media/images/store_additional_products"
-
-  product_db =
+  additional_product_path = "../media/images/store_additional_products"
+
+  product_db =
     pupil_wnone_e30:
       cost: 1140
       title_product: 'Pupil Headset'
       sub_products:
-        world_camera: world_cam_data.wnone
+        world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e30
       img: [pupil_img_path,'pupil_wnone_e30.jpg'].join('/')
     pupil_wnone_e120:
@@ -177,28 +177,28 @@
       sub_products:
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e120
-      img: [pupil_img_path,'pupil_wnone_e120.jpg'].join('/')
+      img: [pupil_img_path,'pupil_wnone_e120.jpg'].join('/')
     pupil_wnone_e200:
       cost: 1590
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e200
-      img: [pupil_img_path,'pupil_wnone_e200.jpg'].join('/')
+      img: [pupil_img_path,'pupil_wnone_e200.jpg'].join('/')
     pupil_wnone_e120b:
       cost: 2090
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e120b
-      img: [pupil_img_path,'pupil_wnone_e120b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_wnone_e120b.jpg'].join('/')
     pupil_wnone_e200b:
       cost: 2490
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e200b
-      img: [pupil_img_path,'pupil_wnone_e200b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_wnone_e200b.jpg'].join('/')
     pupil_wnone_e30_edu:
       cost: 550
       title_product: 'Pupil Headset'
@@ -206,7 +206,7 @@
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e30
         discount: edu_discount
-      img: [pupil_img_path,'pupil_wnone_e30.jpg'].join('/')
+      img: [pupil_img_path,'pupil_wnone_e30.jpg'].join('/')
     pupil_wnone_e120_edu:
       cost: 800
       title_product: 'Pupil Headset'
@@ -221,7 +221,8 @@
       sub_products:
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e200
-      img: [pupil_img_path,'pupil_wnone_e200.jpg'].join('/')
+        discount: edu_discount
+      img: [pupil_img_path,'pupil_wnone_e200.jpg'].join('/')
     pupil_wnone_e120b_edu:
       cost: 1500
       title_product: 'Pupil Headset'
@@ -229,14 +230,15 @@
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e120b
         discount: edu_discount
-      img: [pupil_img_path,'pupil_wnone_e120b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_wnone_e120b.jpg'].join('/')
     pupil_wnone_e200b_edu:
       cost: 1900
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.wnone
         eye_camera: eye_cam_data.e200b
-      img: [pupil_img_path,'pupil_wnone_e200b.jpg'].join('/')
+        discount: edu_discount
+      img: [pupil_img_path,'pupil_wnone_e200b.jpg'].join('/')

     pupil_w30_enone:
       cost: 1190
@@ -297,6 +299,7 @@
       sub_products:
         world_camera: world_cam_data.w30
         eye_camera: eye_cam_data.e200
+        discount: edu_discount
       img: [pupil_img_path,'pupil_w30_e200.jpg'].join('/')

     pupil_w120_enone:
@@ -312,7 +315,6 @@
       sub_products:
         world_camera: world_cam_data.w120
         eye_camera: eye_cam_data.e30
-        discount: edu_discount
       img: [pupil_img_path,'pupil_w120_e30.jpg'].join('/')
     pupil_w120_e120:
       cost: 1640
@@ -334,7 +336,7 @@
       sub_products:
         world_camera: world_cam_data.w120
         eye_camera: eye_cam_data.e120b
-      img: [pupil_img_path,'pupil_w120_e120b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w120_e120b.jpg'].join('/')
     pupil_w120_e200b:
       cost: 2740
       title_product: 'Pupil Headset'
@@ -357,7 +359,7 @@
         world_camera: world_cam_data.w120
         eye_camera: eye_cam_data.e30
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w120_e30.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w120_e30.jpg'].join('/')
     pupil_w120_e120_edu:
       cost: 1050
       title_product: 'Pupil Headset'
@@ -365,7 +367,7 @@
         world_camera: world_cam_data.w120
         eye_camera: eye_cam_data.e120
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w120_e120.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w120_e120.jpg'].join('/')
     pupil_w120_e200_edu:
       cost: 1250
       title_product: 'Pupil Headset'
@@ -373,7 +375,7 @@
         world_camera: world_cam_data.w120
         eye_camera: eye_cam_data.e200
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w120_e200.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w120_e200.jpg'].join('/')
     pupil_w120_e120b_edu:
       cost: 1750
       title_product: 'Pupil Headset'
@@ -397,35 +399,35 @@
       sub_products:
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.enone
-      img: [pupil_img_path,'pupil_w3d_enone.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_enone.jpg'].join('/')
     pupil_w3d_e120:
       cost: 1990
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e120
-      img: [pupil_img_path,'pupil_w3d_e120.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e120.jpg'].join('/')
     pupil_w3d_e200:
       cost: 2190
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e200
-      img: [pupil_img_path,'pupil_w3d_e200.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e200.jpg'].join('/')
     pupil_w3d_e120b:
       cost: 2690
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e120b
-      img: [pupil_img_path,'pupil_w3d_e120b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e120b.jpg'].join('/')
     pupil_w3d_e200b:
       cost: 3090
       title_product: 'Pupil Headset'
       sub_products:
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e200b
-      img: [pupil_img_path,'pupil_w3d_e200b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e200b.jpg'].join('/')

     pupil_w3d_enone_edu:
       cost: 950
@@ -434,7 +436,7 @@
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.enone
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w3d_enone.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_enone.jpg'].join('/')
     pupil_w3d_e120_edu:
       cost: 1400
       title_product: 'Pupil Headset'
@@ -442,7 +444,7 @@
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e120
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w3d_e120.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e120.jpg'].join('/')
     pupil_w3d_e200_edu:
       cost: 1600
       title_product: 'Pupil Headset'
@@ -450,7 +452,7 @@
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e200
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w3d_e200.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e200.jpg'].join('/')
     pupil_w3d_e120b_edu:
       cost: 2100
       title_product: 'Pupil Headset'
@@ -458,7 +460,7 @@
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e120b
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w3d_e120b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e120b.jpg'].join('/')
     pupil_w3d_e200b_edu:
       cost: 2500
       title_product: 'Pupil Headset'
@@ -466,7 +468,7 @@
         world_camera: world_cam_data.w3d
         eye_camera: eye_cam_data.e200b
         discount: edu_discount
-      img: [pupil_img_path,'pupil_w3d_e200b.jpg'].join('/')
+      img: [pupil_img_path,'pupil_w3d_e200b.jpg'].join('/')

     oculusdk2_e120:
       cost: 800
@@ -488,119 +490,119 @@
       title_store: 'Epson Moverio BT-200 Binocular Mount Add-on'
       description_store: "Binocular camera mounts for Epson Moverio BT-200 with USBC Clip. Use with the 120hz eye camera upgrades or existing 120hz eye cameras from a Pupil headset."
       description_cart: "Binocular camera mounts for Epson Moverio BT-200 with USBC Clip. Use with the 120hz eye camera upgrades or existing 120hz eye cameras from a Pupil headset."
-      img: [vr_ar_img_path,'epsonbt200b.jpg'].join('/')
+      img: [vr_ar_img_path,'epsonbt200b.jpg'].join('/')
     epsonbt300b:
       cost: 550
       title_product: 'Epson Moverio BT-300 Eye Camera Mounts'
       title_store: 'Epson Moverio BT-300 Binocular Mount Add-on'
       description_store: "Binocular camera mounts for Epson Moverio BT-300 with USBC clip. Use with the 120hz eye camera upgrades or existing 120hz eye cameras from a Pupil headset."
       description_cart: "Binocular camera mounts for Epson Moverio BT-200 with USBC clip. Use with the 120hz eye camera upgrades or existing 120hz eye cameras from a Pupil headset."
-      img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
+      img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
     epsonbt300_w120_e200b:
       cost: 1750
       title_product: 'Epson Moverio BT-300 Add-on'
       title_store: 'Epson Moverio BT-300 Binocular Mount Add-on'
       description_store: "Binocular camera mounts for Epson Moverio BT-300 and world camera mount. Ships with USBC clip, 2 x 200hz eye cameras, and 1 x high speed world camera."
       description_cart: "Binocular camera mounts for Epson Moverio BT-300 and world camera mount. Ships with USBC clip, 2 x 200hz eye cameras, and 1 x high speed world camera."
-      img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
+      img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
     htcvive_e120b:
       cost: 1400
       title_product: 'HTC Vive Binocular Add-on'
       title_store: 'HTC Vive Binocular Add-on'
       description_store: "Add binocular eye tracking to your HTC Vive VR Headset. Includes binocular 120hz eye tracking cameras, clip-on attachment rings with IR illuminators and USB connector clip. No tools required!"
       description_cart: "120hz binocular eye tracking add-on for your HTC Vive VR Headset."
-      img: [vr_ar_img_path,'htcviveb.jpg'].join('/')
+      img: [vr_ar_img_path,'htcviveb.jpg'].join('/')
     htcvivepro_e120b:
       cost: 1400
       title_product: 'HTC Vive PRO Binocular Add-on'
       title_store: 'HTC Vive PRO Binocular Add-on'
       description_store: "Add binocular eye tracking to your HTC Vive PRO VR Headset. Includes binocular 120hz eye tracking cameras, clip-on attachment rings with IR illuminators and USB connector clip. No tools required!"
       description_cart: "120hz binocular eye tracking add-on for your HTC Vive PRO VR Headset."
-      img: [vr_ar_img_path,'htcviveb.jpg'].join('/')
+      img: [vr_ar_img_path,'htcviveb.jpg'].join('/')
     hololens_w120_e120b:
       cost: 1750
       title_product: 'HoloLens Binocular Add-on'
       title_store: 'HoloLens Binocular Add-on'
       description_store: "Add binocular eye tracking to your Microsoft HoloLens AR headset. Includes binocular 120hz eye tracking cameras, high speed world camera, clip on attachment mounts, and USB connector clip."
       description_cart: "120hz binocular eye tracking add-on with high speed world camera for your Microsoft HoloLens AR headset."
-      img: [vr_ar_img_path,'addon_hololens.jpg'].join('/')
+      img: [vr_ar_img_path,'addon_hololens.jpg'].join('/')
     hololens_wnone_e120b:
       cost: 1400
       title_product: 'HoloLens Binocular Add-on'
       title_store: 'HoloLens Binocular Add-on'
       description_store: "Add binocular eye tracking to your Microsoft HoloLens AR headset. Includes binocular 120hz eye tracking cameras, clip on attachment mounts, and USB connector clip."
       description_cart: "120hz binocular eye tracking add-on for your Microsoft HoloLens AR headset."
-      img: [vr_ar_img_path,'addon_hololens.jpg'].join('/')
+      img: [vr_ar_img_path,'addon_hololens.jpg'].join('/')
     hololens_w120_e200b:
       cost: 1750
       title_product: 'HoloLens Binocular Add-on'
       title_store: 'HoloLens Binocular Add-on'
       description_store: "Add binocular eye tracking to your Microsoft HoloLens AR headset. Includes binocular 200hz eye tracking cameras, high speed world camera, clip on attachment mounts, and USB connector clip."
       description_cart: "200hz binocular eye tracking add-on with high speed world camera for your Microsoft HoloLens AR headset."
-      img: [vr_ar_img_path,'addon_hololens_e200.jpg'].join('/')
+      img: [vr_ar_img_path,'addon_hololens_e200.jpg'].join('/')
     hololens_wnone_e200b:
       cost: 1400
       title_product: 'HoloLens Binocular Add-on'
       title_store: 'HoloLens Binocular Add-on'
       description_store: "Add binocular eye tracking to your Microsoft HoloLens AR headset. Includes binocular 200hz eye tracking cameras, clip on attachment mounts, and USB connector clip."
       description_cart: "200hz binocular eye tracking add-on for your Microsoft HoloLens AR headset."
-      img: [vr_ar_img_path,'addon_hololens_e200.jpg'].join('/')
+      img: [vr_ar_img_path,'addon_hololens_e200.jpg'].join('/')
     hub_usbc:
       cost: 210
       title_product: 'USB-C Clip Update'
       title_store: 'USB-C Clip Update'
       description_store: 'Update older micro USB Pupil headsets with the new USB-C clip. Comes with USB-C to USB-A cable.'
       description_cart: 'USB-C clip upgrade older micro USB Pupil headsets. Comes with USB-C to USB-A cable.'
-      img: [additional_product_path,'usbcupgrade.jpg'].join('/')
+      img: [additional_product_path,'usbcupgrade.jpg'].join('/')
     camera_e120:
       cost: 450
       title_product: '120hz Eye Camera'
       title_store: "120hz Eye Camera"
       description_store: "Update any existing Pupil headset with our custom made 120hz camera. This is a drop-in replacement. No tools needed. For more info, check out the camera specs in the configurator above."
       description_cart: "Camera upgrade: 120hz eye camera upgrade"
-      img: [additional_product_path,'e120upgrade.jpg'].join('/')
+      img: [additional_product_path,'e120upgrade.jpg'].join('/')
     camera_e200:
       cost: 650
       title_product: '200hz Eye Camera'
       title_store: "200hz Eye Camera"
       description_store: "Upgrade any existing Pupil headset with our custom made 200hz camera. This is a drop-in replacement. No tools needed. For more info, check out the camera specs in the configurator above."
       description_cart: "Camera upgrade: 200hz eye camera upgrade"
-      img: [additional_product_path,'e200upgrade.jpg'].join('/')
+      img: [additional_product_path,'e200upgrade.jpg'].join('/')
     w3dupgrade:
       cost: 550
       title_product: '3d World Camera'
       title_store: "3d World Camera"
       description_store: "Upgrade any existing Pupil headset with a 3d world camera. Intel RealSense R200 RGBD camera. Provides stereo RGB images and depth image. For more info, check out the camera specs in the configurator above. Ships with USB connector cable."
       description_cart: "World camera: Intel RealSense R200 RGBD camera. Provides stereo RGB images and depth image."
-      img: [additional_product_path,'w3dupgrade.jpg'].join('/')
+      img: [additional_product_path,'w3dupgrade.jpg'].join('/')
     pm_nexus5x:
       cost: 350
       title_product: 'Pupil Mobile Bundle'
       title_store: "Pupil Mobile Bundle"
       description_store: "Connect your Pupil headset to the Nexus 5x. Record video locally on the Nexus 5x, or stream video over Wifi. The Nexus 5x comes with a sports arm band, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. "
       description_cart: "Nexus 5x pre-loaded with Pupil Mobile app, sports arm band, and USBC-USBC cable. Connects to Pupil headsets via USBC-USBC cable."
-      img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
+      img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
     pm_motoz2:
       cost: 650
       title_product: 'Pupil Mobile Bundle'
       title_store: "Pupil Mobile Bundle"
       description_store: "Connect your Pupil headset to the Moto Z2 play. Record up to 4 hours of video locally on the phone, or stream video and sensor data over Wifi. The bundle comes with: Moto Z2 Play (black), hot-swappable Moto power pack, 64gb SD card, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. (Note: Pupil headsets with 3d world camera are not compatible with Pupil Mobile)."
       description_cart: "Moto Z2 Play (black), hot-swappable Moto power pack, 64gb SD card, USBC-USBC cable, and is pre-loaded with the Pupil Mobile app. Connects to Pupil headset via USBC cable (included)."
-      img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
+      img: [additional_product_path,'pm_nexus5x.jpg'].join('/')
     support_6:
       cost: 1000
       title_product: 'Support Contract'
       title_store: '6 Month Support Contract'
       description_store: '6 month support contract. 3 one hour video support sessions and email support.'
       description_cart: '6 month support contract. 3 one hour video support sessions and email support.'
-      img: [additional_product_path,'support6.svg'].join('/')
+      img: [additional_product_path,'support6.svg'].join('/')
     support_12:
       cost: 2000
       title_product: 'Support Contract'
       title_store: '12 Month Support Contract'
       description_store: '12 month support contract. 7 one hour video support sessions and email support.'
       description_cart: '12 month support contract. 7 one hour video support sessions and email support.'
-      img: [additional_product_path,'support12.svg'].join('/')
+      img: [additional_product_path,'support12.svg'].join('/')

   return product_db

@@ -608,7 +610,7 @@

 updateLegacyProductIds_ = (ids) ->
   # list of product ids
-  map =
+  map =
     world_none: 'pupil_wnone'
     world_hr: 'pupil_w30'
     world_hs: 'pupil_w120'
@@ -647,4 +649,4 @@

 get_config_images = ->
   db = get_product_database()
-  return (db[k]['img'] for k of db when k.split("_").length == 3)
+  return (db[k]['img'] for k of db when k.split("_").length == 3)


updating product text.

updating product text.

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -161,10 +161,10 @@
   }, {
     id: "product_oculus_mono"
     cost: 800
-    label: "Oculus Rift DK2 Monocular Add-on"
-    specs: "120hz monocular eye tracking add-on for Oculus Rift DK2. Includes 1 x 120hz eye cameras and USB cables."
-    cartlabel: "Oculus Rift DK2 Monocular Add-on"
-    cartSpecs: "1 x 120hz eye tracking add-on for Oculus Rift DK2. Includes 1 x 120hz eye cameras and USB cables."
+    label: "Oculus Rift DK2 Monocular Add-on Cup"
+    specs: "Add eye tracking to your Oculus Rift DK2 with our 120hz monocular eye tracking add-on. Includes one custom lens cup with: IR mirror, IR LEDs, 1 x 120 Hz eye camera, and USB cables."
+    cartlabel: "Oculus Rift DK2 Monocular Add-on Cup"
+    cartSpecs: "1 x 120hz eye tracking add-on cup for Oculus Rift DK2. Includes one custom lens cup with: IR mirror, IR LEDs, 1 x 120 Hz eye camera, and USB cables."
     dimensions: 0
     weight: 20
     img: "../media/images/additional_products/web/addon_oculus_dk2.png"
@@ -172,10 +172,10 @@
   }, {
     id: "product_oculus_bi"
     cost: 1400
-    label: "Oculus Rift DK2 Binocular Add-on"
-    specs: "120hz binocular eye tracking add-on for Oculus Rift DK2. Includes 2 x 120hz eye cameras and USB cables."
+    label: "Oculus Rift DK2 Binocular Add-on Cups"
+    specs: "Add binocular eye tracking to your Oculus Rift DK2 with our 120hz monocular eye tracking add-on. Includes two custom lens cups with: IR mirors, IR LEDs, 2 x 120 Hz eye camera, and USB cables."
     cartlabel: "Oculus Rift DK2 Binocular Add-on"
-    cartSpecs: "2 x 120hz eye tracking add-on for Oculus Rift DK2. Includes 2 x 120hz eye cameras and USB cables."
+    cartSpecs: "2 x 120hz eye tracking add-on cups for Oculus Rift DK2. Includes two custom lens cups with: IR mirors, IR LEDs, 2 x 120 Hz eye camera, and USB cables."
     dimensions: 0
     weight: 20
     img: "../media/images/additional_products/web/addon_oculus_dk2.png"


update checkbox-toggler.js.coffee

update checkbox-toggler.js.coffee

--- app/assets/javascripts/active_admin/lib/checkbox-toggler.js.coffee
+++ app/assets/javascripts/active_admin/lib/checkbox-toggler.js.coffee
@@ -1,7 +1,7 @@
-window.ActiveAdmin.CheckboxToggler = class ActiveAdmin.CheckboxToggler
+class ActiveAdmin.CheckboxToggler
   constructor: (@options, @container)->
     defaults = {}
-    @options = $.extend {}, defaults, options
+    @options = $.extend defaults, options
     @_init()
     @_bind()

@@ -33,5 +33,4 @@
       $(el).prop checked: setting
       @_didChangeCheckbox(el)

-jQuery ($)->
-  $.widget.bridge 'checkboxToggler', ActiveAdmin.CheckboxToggler
+$.widget.bridge 'checkboxToggler', ActiveAdmin.CheckboxToggler


add: console.log into router

add: console.log into router

--- app/assets/javascripts/app/config/marionette/application.js.coffee
+++ app/assets/javascripts/app/config/marionette/application.js.coffee
@@ -3,6 +3,7 @@
   _.extend Backbone.Marionette.Application::,

     navigate: (route, options = {}) ->
+      console.log 'navigating:', route, options
       route = "#" + route if route.charAt(0) is "/"
       Backbone.history.navigate route, options


remove commented code

remove commented code

--- app/assets/javascripts/controllers/paper_edit_controller.js.coffee
+++ app/assets/javascripts/controllers/paper_edit_controller.js.coffee
@@ -5,9 +5,6 @@
   addAuthorsTask: (->
     this.get('tasks').findBy('type', 'AuthorsTask')
   ).property()
-
-  # showPlaceholder: Em.computed ->
-  #   Ember.isBlank $(@get 'model.body').text()

   showPlaceholder: ( ->
     Ember.isBlank $(@get 'model.body').text()


Matt response is now random

Matt response is now random

--- scripts/dr-mat.coffee
+++ scripts/dr-mat.coffee
@@ -13,8 +13,18 @@
 # Author:
 #   Mateusz Kopij <sagasu>

+
+
+MATT_QUOTE = [
+  "Burritos are just tacoids in the category of enchiladafunctors",
+  "Monads are just monoids in the category of endofunctors",
+  "The review is pretty 3rd quadrant",
+  "Let's do multivariate test",
+  "We have asymptotic release cycle"
+]
+
 module.exports = (robot) ->

   robot.respond /what would Dr Matt say/i, (msg)->
-    msg.send "Burritos are just tacoids in the category of enchiladafunctors"
+    msg.send msg.random(MATT_QUOTE)


change english operator to normal operator


minus requests

minus requests

--- client/src/index.coffee
+++ client/src/index.coffee
@@ -16,25 +16,6 @@
             .end (err, res) ->
                 callback err, res.body

-    createHomeData: (homedata, callback) ->
-        request
-            .post('/api/homedata')
-            .send(homedata)
-            .end (err, res) ->
-                callback err, res.body
-
-    deleteHomeData: (homedata, callback) ->
-        request
-            .del('/api/homedata/' + homedata.id)
-            .end callback
-
-    updateHomeData: (homedata, callback) ->
-        request
-            .put('/api/homedata' + homedata.id)
-            .send(homedata) # to fix
-            .end (err, res) ->
-                callback err, res.body
-

 placesdata =
@@ -45,7 +26,9 @@
             .set('Accept', 'application/json')
             .end (err, res) ->
                 callback err, res.body
-# to fix
+
+
+
 data = {
     getHomeBookmarks: homedata
     getPlacesBookmarks: placesdata
@@ -56,11 +39,8 @@
 # C'est le composant principal de l'application.
 App = React.createClass

-    # Ici on assure le rendu de ce composant.
     render: ->
-        # Le div est un composant de base du DOM, représenté ici en React.
         div null,
-            # Ici c'est un composant spécifique que nous avons créé.
             SideBar
                 homedata: @props.homedata
                 placesdata: @props.placesdata


Fixed time component of #112

Fixed time component of #112

--- public/scripts/backbone-schedule/views/scheduler/calendar-max-section.coffee
+++ public/scripts/backbone-schedule/views/scheduler/calendar-max-section.coffee
@@ -32,8 +32,8 @@
 						height_offset = -10.0
 						top_offset = (slot.startTime.getUTCHours() - 6 + (slot.startTime.getUTCMinutes()/60.0)) * scale + offset
 						height = Math.floor(Math.max(1, (Math.abs( slot.endTime.getUTCMinutes() - slot.startTime.getUTCMinutes() )/60.0 + slot.endTime.getUTCHours() - slot.startTime.getUTCHours()) * scale + offset)) + height_offset
-						startHourAdjusted = (slot.startTime.getUTCHours()%12 is 0) ? 12 : (slot.startTime.getUTCHours()%12)
-						endHourAdjusted = (slot.endTime.getUTCHours()%12 is 0) ? 12 : slot.endTime.getUTCHours()%12
+						startHourAdjusted = if (slot.startTime.getUTCHours()%12 is 0) then 12 else (slot.startTime.getUTCHours()%12)
+						endHourAdjusted = if (slot.endTime.getUTCHours()%12 is 0) then 12 else slot.endTime.getUTCHours()%12

 						$el = $ @template
 							section: @model.attributes
@@ -47,13 +47,17 @@
 						@$els.push $el
 						($ '.wk-event-wrapper[data-day="'+day+'"]').append $el

+						startTime = "#{startHourAdjusted}:#{slot.startTime.getUTCMinutes()}"
+						endTime   = "#{endHourAdjusted}:#{slot.endTime.getUTCMinutes()}"
+						timespan  = "#{startTime} - #{endTime}"
+
 						popover_content = @popoverTemplate
 							credits: @model.get('credits')
 							section: @model.get('info')
 							instructor: @model.get('instructor')
 							crn: @model.get('number')
 							seats: (@model.get('steatsAvailable') || "0") + "/" + @model.get('seatsTotal')
-							time: slot.startTime.toLocaleTimeString() + " - " + slot.endTime.toLocaleTimeString()
+							time: timespan

 						$el.popover
 							title: @model.get('name')


Fix group for Atom project

Fix group for Atom project

--- atom/dot-atom/projects.leopard-jkg.cson
+++ atom/dot-atom/projects.leopard-jkg.cson
@@ -5,12 +5,12 @@
     paths: [
       "~/Development/spyns"
     ]
-  }
+  },
   {
     title: "CSI-702: Homework02 Instructor Attempt"
-    group: "Mason Fall 2017"
+    group: "Mason Spring 2017"
     paths: [
       "~/Documents/work/teaching/2017_Spring_Semester/CSI-702_High_Performance_Computing/assignments/homework02-instructor-attempt"
     ]
-  }
+  },
 ]


kostas in mykonos

kostas in mykonos

--- scripts/kostas.coffee
+++ scripts/kostas.coffee
@@ -32,7 +32,8 @@
 kostasInMykonos = [
   "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/KostasInMykonos.jpg",
   "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/KostasMykonos.gif",
-  "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/KostasNextMorningKneel.jpg"
+  "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/KostasNextMorningKneel.jpg",
+  "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/KostasDrunk.jpeg"
 ]

 module.exports = (robot) ->
@@ -44,4 +45,4 @@
     msg.send msg.random ranipics

   robot.respond /how is Kostas doing in Mykonos/i, (msg)->
-    msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/KostasNextMorningKneel.jpg"
+    msg.send "https://raw.githubusercontent.com/sagasu/offshore-team/master/img/KostasDrunk.jpeg"


initialize is only for Backbone.View, we still need the explicit call here

--- app/assets/javascripts/cartilage/application.js.coffee
+++ app/assets/javascripts/cartilage/application.js.coffee
@@ -10,6 +10,7 @@

   @launch: ->
     @sharedInstance = new @
+    @sharedInstance.initialize()
     @hijackLinks()

   @hijackLinks: ->


fix localization

fix localization

--- app/assets/javascripts/chaltron/localization.js.coffee
+++ app/assets/javascripts/chaltron/localization.js.coffee
@@ -14,7 +14,7 @@
           sInfoThousands:  ',',
           sLengthMenu:     'Visualizza _MENU_ elementi',
           sLoadingRecords: 'Caricamento...',
-          sProcessing:     'PIPPO...',
+          sProcessing:     'Elaborazione...',
           sSearch:         'Cerca:',
           sZeroRecords:    'La ricerca non ha portato alcun risultato.',
           oPaginate: {


dont overwrite local changes

dont overwrite local changes

--- app/assets/javascripts/controllers/bill_form.js.coffee
+++ app/assets/javascripts/controllers/bill_form.js.coffee
@@ -161,8 +161,8 @@
 #   merge local changes
     mergePreviousIncluded(bill.billItems)
     mergeCategory(bill.billItems)
-    bill.billedOn ||= $scope.bill.billedOn
-    bill.number ||= $scope.bill.number
+    bill.billedOn = $scope.bill.billedOn if $scope.bill.billedOn
+    bill.number = $scope.bill.number if $scope.bill.number

     $scope.billItemsByEventId = createHash(bill.billItems, 'eventId')


fix deprecation warning for @data.permissions

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -51,7 +51,7 @@

             @logger = @_createLogger @ID

-            if @data.permissions
+            if @data.permissions || @data.permissions is false
                 @logger.warning '@data.permissions is deprecated use config.permissions instead'
                 config.permissions = @data.permissions


Regex corrected.

Regex corrected.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -105,7 +105,7 @@

     # Get a list of the top X users for point totals.
     # Work in progress. Winging it.
-    robot.respond /^top (\d*)$/i, (msg) ->
+    robot.respond /top (\d*)$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
             pointcount = msg.match[1]
             leaders = pointcount([username + " with " + points[username] + " points."])


Even more data checks.

Even more data checks.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -132,11 +132,11 @@
             # Also only returns active users at the moment
             # score = ["#{username} has #{points[username]}" for points in robot.brain.data.points]

-            output1 = points[username]
+            output1 = robot.brain.data
             output2 = robot.brain.data('points')
             output3 = robot.brain.data('points.username')
             output4 = robot.brain.data('data.points.username')

-            outputs = ["Output 1: #{output1}, Output 2: #{output2}, Output 3: #{output3}, Output 4: #{output4}"]
+            outputs = ["Output 1: #{output1}, Output 2: #{output2}, Output 3: #{output3}, Output 4: #{output4}, Output 5: #{points[username]}"]

             msg.send "Brain output tests: #{outputs}"


senpai who is this

senpai who is this

--- scripts/senpai.coffee
+++ scripts/senpai.coffee
@@ -4,6 +4,7 @@
 # Commands:
 #   hubot よろしくお願いします！ - hubot に挨拶しよう！
 #   hubot 誰知ってるんですか? - hubot が知ってる人を教えてくれるよ！
+#   hubot {アダ名 or nickname} って誰ですか？ - 誰かよくわからない場合は hubot に聞いてみよう！
 #   hubot 後輩の {nickname} - hubot に面通ししよう！
 #   hubot {nickname} のアダ名何ですか?  - ものしりな hubot にアダ名を教えてもらおう！
 #   hubot {アダ名} は {nickname} のアダ名です  - hubot にアダ名を教えるよ！
@@ -121,6 +122,20 @@
     checkKeigo robot, msg
 # }}}

+  robot.respond /([^ ]+)[ ]*って(誰|だれ)/, (msg) -># {{{
+    name = msg.match[1]
+    user = whoIsThis robot, msg, name
+    unless user?
+      msg.send "#{user}？俺もしらねーなぁ"
+      return
+
+    if name is user
+      msg.send "#{name} は #{name} だろ。大丈夫か？"
+    else
+      msg.send "#{name} は #{user} だのことだ。"
+    checkKeigo robot, msg
+# }}}
+
   robot.respond /後輩の[ ]*([^ ]+)/, (msg) -># {{{
     name = msg.match[1]
     name = trimKeigo name


Update JApp.approve method for new mechanism

 - If the app identifier is starts with "waits.for.approve:" so we call it approval_app
   - Search for the real app, with identifier without "waits.for.approve:" string
   - If found
      - check versions if already approved, if its fail
      - otherwise merge approval_app data to real_app
      - and set approved:yes

- otherwise just update approved field with given state

--- server/app/bongo/models/app.coffee
+++ server/app/bongo/models/app.coffee
@@ -246,8 +246,50 @@
       unless delegate.checkFlag 'super-admin'
         callback new KodingError 'Only Koding Application Admins can approve apps.'
       else
-        @update ($set: approved: state), (err)=>
-          callback err
+        if @getAt('identifier').indexOf('waits.for.approve:') is 0
+          identifier = @getAt('identifier').replace 'waits.for.approve:', ''
+
+          JApp.one
+            identifier:identifier
+          , (err, target)=>
+            if err
+              callback err
+            else
+
+              if target
+                newVersion = @getAt 'manifest.version'
+
+                if not newVersion
+                  callback new KodingError 'Version is not provided.'
+                else
+                  curVersions = target.data.versions
+                  versionExists = (curVersions[i] for i in [0..curVersions.length] when curVersions[i] is newVersion).length
+
+                  if versionExists
+                    callback new KodingError 'Version already approved, update version to reapprove.'
+                  else
+                    target.update
+                      $set:
+                        title     : @getAt 'title'
+                        body      : @getAt 'body'
+                        manifest  : @getAt 'manifest'
+                        approved  : yes
+                      $addToSet   :
+                        versions  : @getAt 'manifest.version'
+                    , (err)->
+                      if err
+                        console.log err
+                        callback err
+                      else
+                        # @delete We can delete the temporary JApp (@) here.
+                        callback null, target
+
+              else
+                callback new KodingError "Target (already approved application) not found!"
+
+        else
+          @update ($set: approved: state), (err)=>
+            callback err

   @someWithRelationship: secure (client, selector, options, callback)->
     {delegate} = client.connection


Ace: Added extra checks before resizing ace.

--- client/Ace/acefindandreplaceview.coffee
+++ client/Ace/acefindandreplaceview.coffee
@@ -89,10 +89,13 @@
     @show()

   resizeEditor: (height) ->
-    {ace} = @getDelegate()
-    ace.setHeight ace.getHeight() + @lastHeightTakenFromAce - height
-    ace.editor.resize yes
-    @lastHeightTakenFromAce = height
+    if @getDelegate()?.ace?
+      {ace} = @getDelegate()
+      return unless ace.editor
+
+      ace.setHeight ace.getHeight() + @lastHeightTakenFromAce - height
+      ace.editor.resize yes
+      @lastHeightTakenFromAce = height

   lastHeightTakenFromAce: 0


MachineSettingsModal: Added validation for updated domain name.

--- client/Main/machinesettingsmodal.coffee
+++ client/Main/machinesettingsmodal.coffee
@@ -254,7 +254,11 @@
   linkDomain: ->

     domainSuffix = ".#{KD.nick()}.#{KD.config.userSitesDomain}"
-    domain = @modalTabs.forms.Settings.inputs.domain.getValue() + domainSuffix
+    domainValue  = @modalTabs.forms.Settings.inputs.domain.getValue()
+    domain       = "#{domainValue}#{domainSuffix}"
+
+    unless KD.utils.domainWithTLDPattern.test domain
+      return new KDNotificationView title: 'Invalid domain name'

     @machine.jMachine.setDomain domain, (err) =>
       unless err


fixes popState

fixes popState

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -25,16 +25,14 @@
         replaceState      : yes
     @startListening()

-  popState:do->
-    # TODO: don't think this will work in FF: (C.T.)
-    (event)=> # fat-arrow binding makes this handler easier to remove.
-      revive event.state, (err, state)=>
-        if err?
-          new KDNotificationView title: 'An unknown error has occurred.'
-        else
-          @handleRoute "#{location.pathname}#{location.search}",
-            shouldPushState   : no
-            state             : state
+  popState:(event)=> # fat-arrow binding makes this handler easier to remove.
+    revive event.state, (err, state)=>
+      if err?
+        new KDNotificationView title: 'An unknown error has occurred.'
+      else
+        @handleRoute "#{location.pathname}#{location.search}",
+          shouldPushState   : no
+          state             : state

   clear:(route = '/', replaceState = yes)-> @handleRoute route, {replaceState}


added a symmetrical comment about the onpopstate listener

--- core/KDRouter.coffee
+++ core/KDRouter.coffee
@@ -29,6 +29,7 @@
   stopListening:->
     return no  unless @isListening # make this action idempotent
     @isListening = no
+    # we need to remove the listener from the window's popstate event:
     window.removeEventListener 'popstate', @onpopstate
     return yes


pass in text box appearance times

pass in text box appearance times

--- app/assets/javascripts/strut_builder/textbox.js.coffee
+++ app/assets/javascripts/strut_builder/textbox.js.coffee
@@ -5,11 +5,11 @@
 StrutBuilder.Textbox.build = (data, i) ->
   {
     TextBox: {},
-    x: 341.3333333333333,
+    x: 341,
     y: 65 * (i+1),
     scale: {"x":1, "y":1},
     type: "TextBox",
-    text: "" + data.text + "",
+    text: "" + data.text + "",
     size: 72,
     selected:false
   }


メニューのRowの組み立て処理のコード見通しわるいのでリファクタリング

メニューのRowの組み立て処理のコード見通しわるいのでリファクタリング

--- coffee/menuTable.coffee
+++ coffee/menuTable.coffee
@@ -21,8 +21,7 @@
       left:0
       top:0

-    makeConfigSection = ->
-      configRows = []
+    makeConfigRow = ->
       configBtn = Ti.UI.createImageView
         image:"ui/image/light_gear.png"
         left:5
@@ -35,17 +34,70 @@
       configAccountLabel.top = 8
       configAccountLabel.left = 35

-      configTitleRow = Ti.UI.createTableViewRow
+      configRow = Ti.UI.createTableViewRow
         width: 158
         height:40
         left:1
         backgroundColor:backgroundColorSub
-
-      configTitleRow.add configBtn
-      configTitleRow.add configAccountLabel
-      configRows.push configTitleRow
-
-      return configRows
+
+      configRow.addEventListener('click',(e) ->
+
+        slideEvent()
+        controller.moveToConfigWindow()
+      )
+
+
+      configRow.add configBtn
+      configRow.add configAccountLabel
+      return configRow
+
+    makeStockRow = ->
+      stockBtn = Ti.UI.createImageView
+        image:"ui/image/light_list.png"
+        left:5
+        top:5
+        backgroundColor:"transparent"
+
+      stockLabel = Ti.UI.createLabel(fontThemeWhite)
+
+      stockLabel.text = "ストック投稿を見る"
+      stockLabel.top = 8
+      stockLabel.left = 35
+
+      stockRow = Ti.UI.createTableViewRow
+        width: 158
+        height:40
+        left:1
+        backgroundColor:backgroundColorSub
+
+      stockRow.add stockBtn
+      stockRow.add stockLabel
+
+      return stockRow
+
+    makeTagRow = ->
+      tagRow = Ti.UI.createTableViewRow
+        width: 158
+        height:40
+        left:1
+        backgroundColor:backgroundColorSub
+
+      tagLabel = Ti.UI.createLabel(fontThemeWhite)
+
+      tagLabel.text = "タグを見る"
+      tagLabel.top = 8
+      tagLabel.left = 35
+
+      tagBtn = Ti.UI.createImageView
+        image:"ui/image/light_tag.png"
+        left:5
+        top:10
+        backgroundColor:"transparent"
+
+      tagRow.add tagLabel
+      tagRow.add tagBtn
+      return tagRow
+

     matchTag = (items,tagName) ->
       for i in [0..items.length-1]
@@ -73,25 +125,22 @@

     table.addEventListener('click',(e) ->
-
       curretRowIndex = e.index
-
       resetBackGroundColor(table.data[0].rows)
-
       # クリックされたrowの色を'#59BB0C'に設定
       table.data[0].rows[curretRowIndex].backgroundColor = '#59BB0C'
-
-      # サブメニューで選択されたタグにマッチする
-      # 投稿を非表示にする
-

       items = JSON.parse(Ti.App.Properties.getString('storedStocks'))
       result = []
+
       allLabelIndexPosition = 3
       if curretRowIndex is allLabelIndexPosition
         result.push(t.createRow(json)) for json in items
       else
-        result.push(matchTag(items,e.rowData.className))
+        tagName = e.rowData.className
+        result.push(matchTag(items,tagName))
+
+

       # row.classの値が allLabel の場合にのみ過去の投稿を
       # 読み込むためのラベルを配置する
@@ -101,59 +150,10 @@
         result.push(t.createRowForLoadOldEntry())

       mainTable.setData result
-
-
     )

     qiita.getFollowingTags( (result,links)->
-
-      configRows = makeConfigSection()
-      stockBtn = Ti.UI.createImageView
-        image:"ui/image/light_list.png"
-        left:5
-        top:5
-        backgroundColor:"transparent"
-
-      stockLabel = Ti.UI.createLabel(fontThemeWhite)
-
-      stockLabel.text = "ストック投稿を見る"
-      stockLabel.top = 8
-      stockLabel.left = 35
-
-      stockRow = Ti.UI.createTableViewRow
-        width: 158
-        height:40
-        left:1
-        backgroundColor:backgroundColorSub
-
-      stockRow.add stockBtn
-      stockRow.add stockLabel
-      configRows.push stockRow
-
-
-      tagRow = Ti.UI.createTableViewRow
-        width: 158
-        height:40
-        left:1
-        backgroundColor:backgroundColorSub
-
-      tagLabel = Ti.UI.createLabel(fontThemeWhite)
-
-      tagLabel.text = "タグを見る"
-      tagLabel.top = 8
-      tagLabel.left = 35
-
-      tagBtn = Ti.UI.createImageView
-        image:"ui/image/light_tag.png"
-        left:5
-        top:10
-        backgroundColor:"transparent"
-
-      tagRow.add tagLabel
-      tagRow.add tagBtn
-      configRows.push tagRow
-
-
+      rows = [makeConfigRow(),makeStockRow(),makeTagRow()]

       allLabelRow = Ti.UI.createTableViewRow
         width:158
@@ -179,11 +179,10 @@
           fontWeight:'bold'
         text:"ALL"

-
       allLabelRow.className = "allLabel"
       allLabelRow.add allLabel

-      configRows.push allLabelRow
+      rows.push allLabelRow

       for json in result
@@ -216,9 +215,9 @@
         menuRow.add textLabel
         menuRow.className = json.url_name

-        configRows.push menuRow
-
-      table.data = configRows
+        rows.push menuRow
+
+      table.setData rows

     )
     return table


features: fixed coffeelint errors

features: fixed coffeelint errors

--- client/features/lib/featuresview.coffee
+++ client/features/lib/featuresview.coffee
@@ -29,7 +29,7 @@
       "
     'IDE'       :
       image     : 'ide-ss.png'
-      text      : "
+      text      : '

Multiple language support

Go, NodeJS, Ruby, Python, PHP, Java, C, C++, Javascript, Coffeescript,
         etc. whatever your language of choice, our IDE supports it and gives you
@@ -43,10 +43,10 @@

Themes

Easily switch the default color theme and font to one from a growing
         list of choices. We are sure that you will find a favorite within minutes.

-      "
+      '
     'Terminal'  :
       image     : 'terminal-ss.png'
-      text      : "
+      text      : '

Themes

Terminal offers multiple color themes and font selections so that
         you can change the default to one that makes it easier for you to
@@ -60,10 +60,10 @@

Multi-tab

Open as many Terminal tabs as you would like. Each Terminal tab lives
         inside its own independent process. Multitask like there’s no tomorrow!

-      "
+      '
     'Community' :
       image     : 'community-ss.png'
-      text      : "
+      text      : '

Diverse global community

Our developer community is comprised of users from
         all over the globe so no matter what language you speak,
@@ -78,90 +78,90 @@

Not all conversations are publick, so if you want to take something private,
         we offer the ability to easily host private one-on-one and private group
         conversations.

-      "
+      '

   BOTTOMFEATURES =
     'VMs'                  :
       'VM Specs'           :
-        text               : "1 GB RAM, 3GB Storage, 1 Core CPU (burstable). So much power!"
-        iconClass          : "vms-specs"
+        text               : '1 GB RAM, 3GB Storage, 1 Core CPU (burstable). So much power!'
+        iconClass          : 'vms-specs'
       'SSH Access'         :
-        text               : "Full SSH access to your box so you can still use your localhost to connect to your Koding VM."
-        iconClass          : "vms-access"
+        text               : 'Full SSH access to your box so you can still use your localhost to connect to your Koding VM.'
+        iconClass          : 'vms-access'
       'sudo Access'        :
-        text               : "Unrestricted root access to your box. Delete anything, update anything. You are in control."
-        iconClass          : "vms-access"
+        text               : 'Unrestricted root access to your box. Delete anything, update anything. You are in control.'
+        iconClass          : 'vms-access'
       'Install anything'   :
-        text               : "No restrictions on what you can and can not install. Try anything. Explore everything!"
-        iconClass          : "vms-install"
+        text               : 'No restrictions on what you can and can not install. Try anything. Explore everything!'
+        iconClass          : 'vms-install'
       'Public IPs'         :
-        text               : "VMs come with publicly accessible IPs making it super easy to work with them."
-        iconClass          : "vms-publicip"
+        text               : 'VMs come with publicly accessible IPs making it super easy to work with them.'
+        iconClass          : 'vms-publicip'
       'Multiple VMs'       :
-        text               : "Upgrading will get you the ability to create up to 5 VMs."
-        iconClass          : "vms-multiple"
+        text               : 'Upgrading will get you the ability to create up to 5 VMs.'
+        iconClass          : 'vms-multiple'
       'Always-on'          :
-        text               : "If you want to, you can now have an always-on VM to run your blog, database or anything!"
-        iconClass          : "vms-alwayson"
+        text               : 'If you want to, you can now have an always-on VM to run your blog, database or anything!'
+        iconClass          : 'vms-alwayson'
       'Great for'         :
-        text               : "Development environments, code repositories, experimentation and small databases."
-        iconClass          : "vms-install"
+        text               : 'Development environments, code repositories, experimentation and small databases.'
+        iconClass          : 'vms-install'
       'Custom domains and nicknames' :
         text               : "Don’t like the machine name we picked, change it to something meaningful like 'app-box-1'!"
-        iconClass          : "vms-specs"
+        iconClass          : 'vms-specs'

     'IDE'                  :
       'Workspaces'         :
-        text               : "Use workspaces to organize your project. Workspaces keeps everything neat and tidy."
-        iconClass          : "ide-workspaces"
+        text               : 'Use workspaces to organize your project. Workspaces keeps everything neat and tidy.'
+        iconClass          : 'ide-workspaces'
       'Previews'           :
-        text               : "Easily preview your work using the built-in browser. No more tab switching."
-        iconClass          : "ide-preview"
+        text               : 'Easily preview your work using the built-in browser. No more tab switching.'
+        iconClass          : 'ide-preview'
       'Collapsable Panes'  :
-        text               : "Collapse the IDE main frame to get more screen real estate."
-        iconClass          : "ide-collapse"
+        text               : 'Collapse the IDE main frame to get more screen real estate.'
+        iconClass          : 'ide-collapse'
       'Open Source'        :
         text               : "Don't like something? Change it and make the IDE better for everyone!"
-        iconClass          : "ide-opensource"
+        iconClass          : 'ide-opensource'
       'Common editor settings' :
-        text               : "Autocomplete, soft tabs, line numbers, word wrap, tab size, auto-indenting..are all there."
-        iconClass          : "ide-settings"
+        text               : 'Autocomplete, soft tabs, line numbers, word wrap, tab size, auto-indenting..are all there.'
+        iconClass          : 'ide-settings'
       'Resize and Split panes' :
-        text               : "Open several IDE tabs and resize them the way you want."
-        iconClass          : "terminal-split"
+        text               : 'Open several IDE tabs and resize them the way you want.'
+        iconClass          : 'terminal-split'
       'Graphical permissions' :
         text               : "Don't like setting permissions on the command line, do it via a handy GUI."
-        iconClass          : "ide-permissions"
+        iconClass          : 'ide-permissions'
       'multi-cursor support' :
-        text               : "Create multiple cursors and selections in order to make lots of similar edits at once."
-        iconClass          : "ide-multiplecursor"
+        text               : 'Create multiple cursors and selections in order to make lots of similar edits at once.'
+        iconClass          : 'ide-multiplecursor'
       'Code folding'       :
         text               : "Easily fold away fragments of code that you don't need to look at."
-        iconClass          : "ide-fold"
+        iconClass          : 'ide-fold'

     'Terminal'             :
       'Fonts'              :
-        text               : "Terminal has a nice selection of monospaced fonts to choose from."
-        iconClass          : "terminal-fonts"
+        text               : 'Terminal has a nice selection of monospaced fonts to choose from.'
+        iconClass          : 'terminal-fonts'
       'Split views'        :
-        text               : "Split terminals into other Terminals...so many Terminals!"
-        iconClass          : "terminal-split"
+        text               : 'Split terminals into other Terminals...so many Terminals!'
+        iconClass          : 'terminal-split'
       'Resizeable'         :
-        text               : "Resize the Terminal frame to make it perfect for your tasks."
-        iconClass          : "terminal-resize"
+        text               : 'Resize the Terminal frame to make it perfect for your tasks.'
+        iconClass          : 'terminal-resize'

     'Community'            :
       'Support for Markdown':
-        text               : "Full support for markdown in posts and comments. Say it with flair!"
-        iconClass          : "com-markdown"
+        text               : 'Full support for markdown in posts and comments. Say it with flair!'
+        iconClass          : 'com-markdown'
       'Follow/Unfollow anytime':
-        text               : "Like a topic, follow it... No longer interested, unfollow. One click!"
-        iconClass          : "com-diverse"
+        text               : 'Like a topic, follow it... No longer interested, unfollow. One click!'
+        iconClass          : 'com-diverse'
       'Search to get to what you want' :
-        text               : "Scan through topic histories with our robust topic search."
-        iconClass          : "com-search"
-
-  constructor:(options = {}, data)->
+        text               : 'Scan through topic histories with our robust topic search.'
+        iconClass          : 'com-search'
+
+  constructor: (options = {}, data) ->

     super options, data

@@ -196,7 +196,7 @@

   selectTab : (name) ->
-    {text, image}   = TABS[name]
+    { text, image } = TABS[name]

     tabView         = new KDCustomHTMLView
       tagName       : 'article'
@@ -212,7 +212,7 @@
       attributes    :
         'src'       : "#{IMAGEPATH}/#{image}"

-    tabView.addSubView new KDCustomHTMLView cssClass: 'clearfix'
+    tabView.addSubView new KDCustomHTMLView { cssClass: 'clearfix' }

     tabView.addSubView @createBottomFeatures name

@@ -237,7 +237,7 @@
       cssClass     : 'bottom-features'

     for title, content of BOTTOMFEATURES[tabName]
-      do (title, content) =>
+      do (title, content) ->
         view = new KDCustomHTMLView
           cssClass    : 'feature-item'
           partial     : "


<tests> Measure - Correcting typo in fixtures.

--- tests/fixtures/measure.coffee
+++ tests/fixtures/measure.coffee
@@ -8,23 +8,23 @@
 module.exports =
 	measureMovement1:
 		sensorId:	null
-		time:		Date.UTC(2015, 05, 24, 17, 31, 0)
+		time:		Date.UTC 2015, 5, 24, 17, 31, 0
 		value: 		"1"
 		type: 		"movement"

 	measureMovement2:
 		sensorId:	null
-		time:		Date.UTC(2015, 05, 24, 17, 32, 0)
+		time:		Date.UTC 2015, 5, 24, 17, 32, 0
 		value: 		"6"
 		type: 		"movement"

 	measureMovement3:
 		sensorId:	null
-		time:		Date.UTC(2015, 05, 24, 17, 33, 0)
+		time:		Date.UTC 2015, 5, 24, 17, 33, 0
 		value: 		"0"
 		type: 		"movement"

 	intervalGetRequest:
-		minTime: 		new Date(2015, 05, 24, 17, 31, 0)
-		maxTime: 		new Date(2015, 05, 24, 17, 33, 0)
+		minTime: 		new Date 2015, 5, 24, 17, 31, 0
+		maxTime: 		new Date 2015, 5, 24, 17, 33, 0


dont require helpers, and dont blow up if none are defined

--- lib/builder.coffee
+++ lib/builder.coffee
@@ -57,7 +57,7 @@
     meta.config = Config
     meta._ = _
     meta.h = _.reduce(
-      _.keys( Config.helpers ),
+      _.keys( Config.helpers||{} ),
       (
         (uh, key) ->
           uh[key] = _.bind( Config.helpers[key], meta )


Only show overview edit options as admin.

--- app/assets/javascripts/app/controllers/ticket_overview.coffee
+++ app/assets/javascripts/app/controllers/ticket_overview.coffee
@@ -250,7 +250,9 @@

     # render init page
     checkbox = true
-    edit     = true
+    edit     = false
+    if @isRole('Admin')
+      edit = true
     if @isRole('Customer')
       checkbox = false
       edit     = false
@@ -271,7 +273,6 @@
     html = App.view('agent_ticket_view/content')(
       overview:   @overview
       view_modes: view_modes
-      checkbox:   checkbox
       edit:       edit
     )
     html = $(html)


Add listener & saveLog in data manager

Add listener & saveLog in data manager

--- lib/data-manager.coffee
+++ lib/data-manager.coffee
@@ -2,6 +2,12 @@

 log = (msg) ->
   console.log "[Akashic Records][Data Manager] #{msg}" if process.env.DEBUG is 1
+
+warn = (msg) ->
+  console.warn "[Akashic Records][Data Manager] #{msg}" if process.env.DEBUG is 1
+
+error = (msg) ->
+  console.error "[Akashic Records][Data Manager] #{msg}" if process.env.DEBUG is 1

 typeList = ['attack', 'mission', 'createship', 'createitem', 'resource']

@@ -11,6 +17,7 @@
     for type in typeList
       @data[type] = []
     @nickNameId = 0
+    @listeners = {}

   _getDataAccordingToNameId: (id, type) ->
     testNum = /^[1-9]+[0-9]*$/
@@ -45,4 +52,37 @@
     for type in typeList
       @data[type] = @_getDataAccordingToNameId @nickNameId, type

+  addListener: (type, method) ->
+    if not @listeners[type]?
+      @listeners[type] = []
+    @listeners[type].push method
+
+  _fireEvent: (type) ->
+    if @listeners[type]
+      for method in @listeners[type]
+        method.call(this)
+
+  saveLog: (type, log) ->
+    if type in log
+      @data[type].push log
+      fs.ensureDirSync(path.join(APPDATA_PATH, 'akashic-records', @nickNameId.toString(), type))
+      if type is 'attack'
+        date = new Date(log[0])
+        year = date.getFullYear()
+        month = date.getMonth() + 1
+        if month < 10
+          month = "0#{month}"
+        day = date.getDate()
+        if day < 10
+          day = "0#{day}"
+        fs.appendFile(path.join(APPDATA_PATH, 'akashic-records', @nickNameId.toString(), type, "#{year}#{month}#{day}"), "#{log.join(',')}\n", 'utf8', (err)->
+          error "Write attack-log file error!" if err)
+      else
+        fs.appendFile(path.join(APPDATA_PATH, 'akashic-records', @nickNameId.toString(), type, "data"), "#{log.join(',')}\n", 'utf8', (err)->
+          error "Write #{type}-log file error!" if err)
+      log "save one #{type} log"
+      _fireEvent(type)
+    else
+      error "saveLog arguments error!"
+


Tweak of Header Size

Tweak of Header Size

--- app/pages/lab/glossary.cjsx
+++ app/pages/lab/glossary.cjsx
@@ -5,7 +5,7 @@
 counterpart.registerTranslations 'en',
   policiesPage:
     content: '''
-      # Glossary
+      ## Glossary
       A collection of definitions for terms that are used across the Zooniverse. The terms are split into three different categories:

       [General Terms](#general-terms)
@@ -14,7 +14,7 @@

       If you'd like to see a definition that currently isn't on this page, [get in touch](/about/contact) and we'll look into adding it.

-      ## General Terms
+      ### General Terms

       + **Classification** -  A classification is all the data associated with a  volunteer’s response to an item of data (or subject) they’re presented with whilst going through a project. In essence, a classification is the core unit of human effort produced by the Zooniverse community.
@@ -44,9 +44,7 @@
       + **Zooniverse** - the Zooniverse encompasses the team, website, projects and our codebase. The Zooniverse is an open web-based platform for large scale citizen science research projects, capable of supporting tens of thousands of simultaneous users. The Zooniverse hosts the largest collection of online citizen science projects in the world, supporting over 1.5 million registered users and containing projects in astronomy, ecology, humanities, physics, and beyond.

-
-
-      ## People
+      ### People

       + **Collaborator** - Collaborators are people with permission on a project with full access to edit workflows and project content, including deleting some or all of the project. You can add a collaborator to a project you own through the Collaborators section of the Project Builder.
@@ -80,10 +78,7 @@

-
-
-
-      ## Project-Specific Terms
+      ### Project-Specific Terms

       + **Active workflow** - these are workflows that are currently being presented to volunteers. They can’t be edited whilst they are active. You can make a workflow active and inactive by ticking the relevant box on the Visibility tab on the Project Builder page


line height 1.2 looks much better to me...

--- files/home/.atom/config.cson
+++ files/home/.atom/config.cson
@@ -3,7 +3,7 @@
     fontFamily: "Consolas"
     showInvisibles: true
     fontSize: 17
-    lineHeight: 1.3
+    lineHeight: 1.2
   core:
     themes: [
       "atom-dark-ui"


Added logic to save order when confirmation button clicked

--- app/assets/javascripts/sprangular/controllers/checkout.coffee
+++ app/assets/javascripts/sprangular/controllers/checkout.coffee
@@ -1,6 +1,16 @@
 Sprangular.controller 'CheckoutCtrl', ($scope, $location, countries, order, Account, Cart, Checkout) ->
   $scope.countries = countries
   $scope.order = order
+  $scope.errored = false
+  $scope.processing = false

   $scope.submit = ->
-    $location.path('/checkout/confirm')
+    $scope.processing = true
+
+    Checkout.update()
+      .success ->
+        $scope.processing = false
+        $location.path('/checkout/confirm')
+      .error ->
+        $scope.processing = false
+        $scope.errored = true


pluginClass improvements

pluginClass improvements

--- app/assets/javascripts/ultimate/jquery-plugin-class.js.coffee
+++ app/assets/javascripts/ultimate/jquery-plugin-class.js.coffee
@@ -18,22 +18,25 @@
 # 1 _.string.startsWith

 class Ultimate.Plugin
+  cid: null
   el: null
   $el: null
-  nodes: {}
-  events: {}
+  nodes: null
+  events: null

-  options: {}
+  options: null

   # @defaultLocales: { en: {} }
   locale: 'en'
-  translations: {}
+  translations: null

   constructor: (options) ->
+    # TODO
     throw new Error('Property `el` must be specified at first argument of Ultimate.Plugin.constructor')  unless options?.el
     @cid = _.uniqueId('ultimatePlugin_')
     @_configure options
-    @$el = $(@el)
+    @$el = $(@el).first()
+    @el = @$el[0]
     @findNodes()
     @initialize? arguments...
     @delegateEvents()
@@ -99,7 +102,8 @@
       events = normalizedEvents
     events

-  _configure: (options) ->
+  _configure: (options = {}) ->
+    @options ||= null
     _.extend @options, options
     #cout '@options', @options
     @_reflectOptions()


[grunt] added renaming mechanism for `grunt-nunjucks-2-html` for painless human readable urls everywhere

Example:

`/posts/2015-10-12-article.nj` -> `/posts/2015-10-12-article.nj/index.html`

See details in comment before code

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -168,4 +168,17 @@
         src: ['{,**/}*.nj', '{,**/}*.html', '!{,**/}_*.nj']
         dest: '<%= path.build.root %>/'
         ext: '.html'
+        # Rename all except matching `exclude` pattern files to `index.html`
+        # and move them to directory named after basename of the file
+        # @example `/posts/2015-10-12-article.nj` -> `/posts/2015-10-12-article.nj/index.html`
+        rename: (dest, src) =>
+          exclude  = /^(index|\d\d\d)$/
+
+          ext      = grunt.task.current.data.files[0].ext
+          basename = path.basename(src, ext)
+
+          if !exclude.test(basename)
+            src = src.replace(basename + ext, basename + '/index' + ext)
+
+          path.join(dest, src);
       ]


refs #2. fix a bug.

refs #2. fix a bug.

--- lib/emp-debugger.coffee
+++ lib/emp-debugger.coffee
@@ -83,7 +83,7 @@
     empDebuggerSettingViewState: @empDebuggerSettingView.serialize()

   live_preview: ->
-    editor = atom.workspace.activePaneItem
+    editor = atom.workspace.getActiveEditor()
     if editor
       text_path = editor.getPath()
       text_ext  = undefined
@@ -107,10 +107,14 @@
         if debug_script
           debug_script.script_con = debug_text
           @emp_socket_server.live_preview_view(debug_view, debug_text, debug_script_name)
-        else
+        else if debug_view
           # if the script isn't exist , then live preview the view
           debug_view.view = debug_text
           @emp_socket_server.live_preview_view(debug_text)
+        else
+          atom.confirm
+            message:"Error"
+            detailedMessage:"No Content to live preview~"
     else
       atom.confirm
         message:"Error"


Prevent feed_item from skipping setting up a save button for its first artwork.

--- components/feed/client/feed_item.coffee
+++ components/feed/client/feed_item.coffee
@@ -42,14 +42,13 @@

   setupArtworkSaveControls: (artworks=@model.artworks().models)->
     listItems =
-      for artwork, index in artworks
-        if artwork and index
-          overlay = @$(".artwork-item[data-artwork='#{artwork.get('id')}']").find('.overlay-container')
-          if overlay.length
-            new SaveControls
-              el               : overlay
-              model            : artwork
-              artworkCollection: @artworkCollection
+      for artwork in artworks
+        overlay = @$(".artwork-item[data-artwork='#{artwork.get('id')}']").find('.overlay-container')
+        if overlay.length
+          new SaveControls
+            el               : overlay
+            model            : artwork
+            artworkCollection: @artworkCollection

     if @artworkCollection
       @artworkCollection.addRepoArtworks @model.artworks()


CLI making the server to serve packages by versions as well

--- cli/commands/server.coffee
+++ cli/commands/server.coffee
@@ -33,14 +33,21 @@
       console.log(" Compiling: ".cyan+ "/#{module}.js (#{size}Kb #{if minify then 'minified' else 'source'})".grey)

     else
-      src = "/#{module}/current/build.js"
+
+      if match = module.match(/^(.+?)\-(\d+\.\d+\.\d+.*?)$/)
+        module  = match[1]
+        version = match[2]
+      else
+        version = 'active'
+
+      src = "/#{module}/#{version}/build.js"
       console.log(" Serving:   ".magenta + "/#{module}.js -> ~/.lovely/packages#{src}".grey)
       src = fs.readFileSync("#{base}/#{src}").toString()

     res.charset = 'utf-8'
     res.header('Cache-Control', 'no-cache')
-    res.contentType('text/javascript')
+    res.header('Content-Type', 'text/javascript')
     res.send src

   # just a dummy favicon response
@@ -66,6 +73,7 @@
       switch extension[extension.length - 1]
         when 'css' then return 'text/css'
         when 'js'  then return 'text/javascript'
+        when 'ico' then return 'image/icon'
         else            return 'text/html'

@@ -84,7 +92,7 @@
       data = fs.readFileSync("#{shared}/404.html")

     res.charset = 'utf-8'
-    res.contentType(content_type(filename))
+    res.header('Content-Type', content_type(filename))
     res.send data


remove hellobar handling code

remove hellobar handling code

--- server/cluster.coffee
+++ server/cluster.coffee
@@ -75,19 +75,6 @@
       console.log "Sent request to start instance. Response from the agent is", result.content.toString()
       Instances.update {name: instance}, $set: {'logs.bootstrapLog': "#{result.content}"}
     ""
-  setHellobarMessage: (instanceName, message) ->
-    asyncFunc = (instanceName, message, callback) ->
-      instance = Instances.findOne name: instanceName
-      HTTP.put "http://www.#{instanceName}.#{Settings.get('project')}.ictu/api/v1/hellobar/", params: value: message, (err, result) ->
-        if not err and result and result.statusCode == 200
-          EtcdClient.set "instances/#{Settings.get('project')}/#{instance.meta.appName}/#{instance.name}/meta_/hellobar", message
-          callback null,result
-        else
-          console.log err
-          callback err, result
-
-    syncFunc = Meteor.wrapAsync asyncFunc
-    syncFunc instanceName, message

   stopInstance: (instanceName) ->
     unless Instances.findOne {name: instanceName}


Rename is_property() to is_property_of_object().

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -53,7 +53,7 @@
   _check_expects_usage = (object, method_name) ->
     _throw_expects_usage() unless method_name?
     _throw_reserved_word(method_name) if _is_reserved_word(method_name)
-    _throw_pre_existing_property(method_name) if is_property(object, method_name)
+    _throw_pre_existing_property(method_name) if is_property_of_object(object, method_name)
     _throw_not_an_existing_method(method_name) if is_class(object) and not is_method_in_prototype(object, method_name)

   _is_reserved_word = (word) ->
@@ -275,5 +275,5 @@
 is_method_in_prototype = (object, method_name) ->
    object.prototype[ method_name ]?

-is_property = (object, method_name) ->
+is_property_of_object = (object, method_name) ->
   object[ method_name ]? and (typeof object[ method_name ]) != 'function'


move check_for_duplicate_expectations into class AllExpectations.

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -83,6 +83,14 @@
 	  for expectation in @_expectations when expectation.matches(object, method_name, args...)
 	    return expectation
 	  undefined
+
+	@check_for_duplicate_expectations: ->
+	  # TODO: use each with index and slice to avoid last element
+	  return if @_expectations.length < 2
+	  for outer in [0..@_expectations.length-2]
+	    for inner in [outer+1..@_expectations.length-1]
+	      if @_expectations[outer].equals( @_expectations[inner] )
+	        throw_duplicate_expectation("#{@_expectations[outer]._method_name}(#{@_expectations[outer]._args})")

 	@verify_all_expectations: ->
 		errors = @_find_all_errors()
@@ -110,19 +118,10 @@
   (args...) ->
     expectation = AllExpectations.find_expectation(@, method_name, args...)
     throw_unknown_expectation("#{method_name}(#{args})") unless expectation?
-    check_for_duplicate_expectations()
+    AllExpectations.check_for_duplicate_expectations()
     expectation._called = yes
     throw expectation._throws if expectation._throws?
     expectation._returns
-
-# TODO: move into AllExpectations
-check_for_duplicate_expectations = ->
-  # TODO: use each with index and slice to avoid last element
-  return if AllExpectations._expectations.length < 2
-  for outer in [0..AllExpectations._expectations.length-2]
-    for inner in [outer+1..AllExpectations._expectations.length-1]
-      if AllExpectations._expectations[outer].equals( AllExpectations._expectations[inner] )
-        throw_duplicate_expectation("#{AllExpectations._expectations[outer]._method_name}(#{AllExpectations._expectations[outer]._args})")

 throw_unknown_expectation = (expectation) ->
   throw new Error( "#{expectation} does not match any expectations" )


Rename state_new_expectation() to create_expectation().

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -37,7 +37,7 @@
 #
 expects = (method_name) ->
   check_expects_usage(@, method_name)
-  start_new_expectation(@, method_name)
+  create_expectation(@, method_name)

 check_expects_usage = (object, method_name) ->
   throw_expects_usage() unless method_name?
@@ -57,7 +57,7 @@
 prototype_has_method = (object, method_name) ->
    object.prototype[ method_name ]?

-start_new_expectation = (object, method_name) ->
+create_expectation = (object, method_name) ->
   new Expectation(object, method_name)

 throw_expects_usage = ->


:new: Add onDidUpdateMessages method

:new: Add onDidUpdateMessages method

--- lib/linter-plus.coffee
+++ lib/linter-plus.coffee
@@ -58,8 +58,12 @@
   getMessages: ->
     @messages.publicMessages

+  onDidUpdateMessages: (callback) ->
+    @messages.onDidUpdateMessages(callback)
+
   onDidChangeMessages: (callback) ->
-    @messages.onDidUpdateMessages(callback)
+    deprecate("Linter::onDidChangeMessages is deprecated, use Linter::onDidUpdateMessages instead")
+    @onDidUpdateMessages(callback)

   onDidChangeProjectMessages: (callback) ->
     deprecate("Linter::onDidChangeProjectMessages is deprecated, use Linter::onDidChangeMessages instead")


Don't pass vlan to plumber if not set

Don't pass vlan to plumber if not set

--- lib/compose.coffee
+++ lib/compose.coffee
@@ -3,7 +3,8 @@
 resolvep  = require 'resolve-path'

 module.exports = (config) ->
-  networkValue = "#{config.host_if} -i eth0 @CONTAINER_NAME@ dhclient @#{config.vlan}"
+  vlan = if config.vlan then " @#{config.vlan}" else  ''
+  networkValue = "#{config.host_if} -i eth0 @CONTAINER_NAME@ dhclient#{vlan}".
   networkEnv = 'eth0_pipework_cmd'

   augmentCompose: (instance, options, doc) ->


Use default for options argument

Use default for options argument

--- lib/promiseSftp.coffee
+++ lib/promiseSftp.coffee
@@ -295,9 +295,8 @@
           group: file.attrs.gid
           target: null   # TODO
           sticky: null   # TODO
-
-    @get = (sourcePath, opts) -> Promise.try () ->
-      options = opts || {}
+
+    @get = (sourcePath, options = {}) -> Promise.try () ->
       if restartOffset != null
         options.start = restartOffset
         options.flags = 'r+'


Minor code cleanup

Minor code cleanup

--- lib/debugger.coffee
+++ lib/debugger.coffee
@@ -112,9 +112,8 @@
       @process.once 'exit', onProcessEnd
       @process.once 'close', onProcessEnd

-class BreakpointManager extends EventEmitter
+class BreakpointManager
   constructor: (@debugger) ->
-    super()
     log "BreakpointManager.constructor"
     self = this
     @breakpoints = []
@@ -287,13 +286,10 @@
     @client.once 'ready', @bindEvents

     @client.on 'unhandledResponse', (res) => @emit 'unhandledResponse', res
-    @client.on 'break', (res) =>
-      @onBreakEvent.broadcast(res.body)
-      @emit 'break', res.body
+    @client.on 'break', (res) => @onBreakEvent.broadcast(res.body); @emit 'break', res.body
     @client.on 'exception', (res) => @emit 'exception', res.body
     @client.on 'error', onConnectionError
-    @client.on 'close', () ->
-      logger.info 'client', 'client closed'
+    @client.on 'close', () -> logger.info 'client', 'client closed'

     attemptConnect()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/State.coffee
+++ src/State.coffee
@@ -142,16 +142,16 @@

   return self

-  add_methods_to_object: (name, object) ->
+  add_methods_to_object = (name, object) ->
     object[name] = self.state()
     object[name+'_events'] = events
     object[name+'_states'] = states

-  add_event_methods: (name, object, event) ->
+  add_event_methods = (name, object, event) ->
     object[name] = -> event.fire(arguments)
     object['can_'+name] = -> event.can_fire()

-  set_state: (state) ->
+  set_state = (state) ->
     internal_state = state
     object[machine_name] = state


Remove the test method

Remove the test method

--- src/alias.coffee
+++ src/alias.coffee
@@ -44,6 +44,3 @@
         msg.send "I removed alias #{alias}"
         delete table[alias]
         robot.brain.set ALIAS_TABLE_KEY, table
-
-  robot.respond /hello/i, (msg)->
-    msg.reply 'hello'


Only require platform stylesheet if it exists

Make requireStylesheet throw exception if the stylesheet does not exist

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -55,8 +55,8 @@
     $(@rootViewParentSelector).append @rootView

   requireStylesheet: (path) ->
-    fullPath = require.resolve(path)
-    return unless fs.isFile(fullPath)
+    unless fullPath = require.resolve(path)
+      throw new Error("requireStylesheet could not find a file at path '#{path}'")
     window.applyStylesheet(fullPath, fs.read(fullPath))

   applyStylesheet: (id, text) ->
@@ -107,4 +107,6 @@

 requireStylesheet 'reset.css'
 requireStylesheet 'atom.css'
-requireStylesheet "#{$native.getPlatform()}.css"
+
+if nativeStylesheetPath = require.resolve("#{platform}.css")
+  requireStylesheet(nativeStylesheetPath)


allows for null selectedAssignment with AssignnmentMuter

fixes CNVS-10874

test plan:
- in screenreader gradebook
- navigate through assignments
- mute and unmute them
- set assignment back to null
- verify that you dont get errors in console
- verify that other srgb functionality works (prev/next navigation, grading)

Change-Id: I8021d0c120638149082ac8b0ee9b3ce703a122a3
Reviewed-on: https://gerrit.instructure.com/29519
Reviewed-by: Cameron Sutter <853bcc0cb8f5834b7805e8eb9a762054be43c731@instructure.com>
QA-Review: Amber Taniuchi <ef4bfd1e6d278f6bc7d4a478f02be2cb9581a0a5@instructure.com>
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Product-Review: Liz Abinante <ce3305c9be70e1a5a9ff49df19e1a5b24d5a47f8@instructure.com>

--- app/coffeescripts/ember/screenreader_gradebook/components/assignment_muter_component.coffee
+++ app/coffeescripts/ember/screenreader_gradebook/components/assignment_muter_component.coffee
@@ -31,7 +31,7 @@
     ).property('assignment.muted')

     setup: (->
-      assignment = this.get('assignment')
-      url = "#{ENV.GRADEBOOK_OPTIONS.context_url}/assignments/#{assignment.id}/mute"
-      @muter = new AssignmentMuter(null, assignment, url, Em.set)
+      if assignment = this.get('assignment')
+        url = "#{ENV.GRADEBOOK_OPTIONS.context_url}/assignments/#{assignment.id}/mute"
+        @muter = new AssignmentMuter(null, assignment, url, Em.set)
     ).observes('assignment').on('init')


adjust automatic state poller

adjust automatic state poller

state poller should now be functional again.


initial halstead counting

initial halstead counting

--- src/chuck.coffee
+++ src/chuck.coffee
@@ -39,6 +39,67 @@
                calculateExpressionComplexity expression.falseValue
   0

+countExpressionHalstead = (expression) ->
+  if expression?.negative?
+    hal = countExpressionHalstead expression.negative
+    hal.operators.push '-'
+    return hal
+  if expression?.inverse?
+    hal = countExpressionHalstead expression.inverse
+    hal.operators.push '!'
+    return hal
+  if expression?.parenthesized?
+    hal = countExpressionHalstead expression.parenthesized
+    hal.operators.push '()'
+    return hal
+  if expression?.receiver?
+    hals = countExpressionHalstead expression.receiver
+    hals.push countExpressionHalstead expression.index if expression.index?
+    hal = combineHalsteads hals
+    hal.operators.push '[]'
+    return hal
+  if expression?.argv?
+    hal = combineHalsteads (countExpressionHalstead arg for arg in expression.argv)
+    hal.operands.push expression.callee
+    hal.operators.push 'new' if expression.newAllocation?
+    return hal
+  if expression?.callee? and expression?.initializer?
+    hal = countExpressionHalstead expression.initializer
+    hal.operators.push 'new'
+    hal.operators.push '{}'
+    return hal
+  if comparators.test expression?.operator
+    hal = combineHalsteads [
+      countExpressionHalstead expression.left
+      countExpressionHalstead expression.right
+    ]
+    hal.operators.push expression.operator
+    return hal
+  if assigners.test expression?.operator
+    hal = countExpressionHalstead expression.value
+    hal.operators.push expression.operator
+    hal.operands.push expression.name
+    return hal
+  if operators.test expression?.operator
+    hal = combineHalsteads [
+      countExpressionHalstead expression.left
+      countExpressionHalstead expression.right
+    ]
+    hal.operators.push expression.operator
+    return hal
+  if expression?.condition?
+    hal = combineHalsteads [
+      countExpressionHalstead expression.condition
+      countExpressionHalstead expression.trueValue
+      countExpressionHalstead expression.falseValue
+    ]
+    hal.operators.push '?:'
+    return hal
+  return {
+    operators: []
+    operands: []
+  }
+
 calculateStatementComplexity = (statement, options) ->
   cnf = options.conditionalNestFactor
   lnf = options.loopNestFactor
@@ -81,11 +142,93 @@
     else 0
   return cc

+countStatementHalstead = (statement) ->
+  switch statement.statement
+    when "return"
+      countExpressionHalstead statement.returns
+    when "throw"
+      countExpressionHalstead statement.throws
+    when "declaration"
+      hal = if statement.initializer? then countExpressionHalstead statement.initializer else
+        operators: []
+        operands: []
+      hal.operands.push statement.name
+      if statement.type.container?
+        if statement.type.container is '[]'
+          hal.operands.push "#{statement.type.contains}[]"
+        else
+          hal.operands.push "#{statement.type.container}<#{statement.type.contains}>"
+      else
+        hal.operands.push statement.type
+      hal.operators.push '=' if statement.initializer?
+      hal
+    when "assignment", "prefix", "postfix", "methodCall"
+      countExpressionHalstead statement.expression
+    when "if"
+      hals = [
+        countExpressionHalstead statement.condition
+        countExpressionHalstead statement.block
+      ]
+      hals.push countExpressionHalstead statement.elseBlock if statement.elseBlock?
+      hal = combineHalsteads hals
+      hal.operators.push 'if'
+      hal.operators.push 'else' if statement.elseBlock?
+      hal
+    when "while", "do_while"
+      hal = combineHalsteads [
+        countExpressionHalstead statement.condition
+        countExpressionHalstead statement.block
+      ]
+      hal.operators.push statement.statement
+      hal
+    when "for"
+      if statement.initializer?
+        hals = [
+          countExpressionHalstead statement.intializer
+          countExpressionHalstead statement.condition
+          countExpressionHalstead statement.increment
+        ]
+      else
+        hals = [
+          countExpressionHalstead statement.domain
+        ]
+      hals.push countExpressionHalstead statement.block
+      hal = combineHalsteads hals
+      hal.operators.push 'for'
+      hal
+    when "dml"
+      if statement.operation is 'merge'
+        hal = combineHalsteads [
+          countExpressionHalstead statement.left
+          countExpressionHalstead statement.right
+        ]
+      else
+        hal = countExpressionHalstead statement.expression
+      hal.operators.push statement.operation
+      hal
+    when "block"
+      countBlockHalstead statement.block
+    else
+        operators: []
+        operands: []
+
 calculateBlockComplexity = (block, options) ->
- cc = 0
- for statement in block
-   cc += calculateStatementComplexity statement, options
- cc
+  cc = 0
+  for statement in block
+    cc += calculateStatementComplexity statement, options
+  cc
+
+combineHalsteads = (hals) ->
+  hal =
+    operators: []
+    operands: []
+  for oneHal in hals
+    hal.operators = hal.operators.concat oneHal.operators
+    hal.operands = hal.operands.concat oneHal.operands
+  hal
+
+countBlockHalstead = (block) ->
+  combineHalsteads (countStatementHalstead statement for statement in block)

 calculateComplexity = (methodBody, options) ->
   options ?= {}
@@ -94,6 +237,9 @@
   cc = calculateBlockComplexity methodBody, options
   cc += 1 if methodBody[methodBody.length-1].statement isnt 'return'
   cc
+
+countHalstead = (methodBody) ->
+  countBlockHalstead methodBody

 lineCount = (pos) ->
   pos.last_line - pos.first_line + 1
@@ -114,6 +260,7 @@
         conditionalNestFactor: NEST_FACTOR
         loopNestFactor: NEST_FACTOR
       )
+      halstead: countHalstead m.body
   metrics.methodCount = metrics.methods.length
   metrics.propertyCount = (m for m in cls.body when m.member is 'property').length
   metrics.innerClassCount = (m for m in cls.body when m.member is 'inner_class').length


:bug: Remove bubble on hide

:bug: Remove bubble on hide

--- lib/views/panel.coffee
+++ lib/views/panel.coffee
@@ -11,6 +11,7 @@
     @decorations = []
   hide: ->
     @removeDecorations()
+    @linter.views.bubble?.remove()
     @innerHTML = ''
     @linter.views.panelWorkspace.hide() if @linter.views.panelWorkspace.isVisible()
   render: (messages) ->


Trim '(London, England)' from borough names

--- app/choose-subject-set.cjsx
+++ app/choose-subject-set.cjsx
@@ -21,7 +21,7 @@

Choose a report to work on

-        {
{subject_set.display_name}
{subject_set.metadata.BOROUGH}
{subject_set.metadata.Date}
 for subject_set in @state.subject_sets}
+        {
{subject_set.display_name}
{subject_set.metadata.BOROUGH.replace(' (London, England)', '')}
{subject_set.metadata.Date}
 for subject_set in @state.subject_sets}


Don't use deprecated api_key

Don't use deprecated api_key

--- lib/generators/nas_extjs/templates/public/app/model/Message.coffee
+++ lib/generators/nas_extjs/templates/public/app/model/Message.coffee
@@ -19,10 +19,8 @@
         { name:'created_by_id', type: 'int', useNull: true }
     ]

-    api_key: 'messages'
+    proxy: App.Util.makeProxy( 'messages' )

-    toString:->
-        'Message id ' + this.getId()

     associations: [
         { associatedName: 'attachments',  type: 'hasMany',   model: 'App.model.Attachment', foreignKey: 'message_id', primary_key: 'id' }


Documents now have styles on node

Documents now have styles on node

--- cli/tasks/build/parse/documents.coffee
+++ cli/tasks/build/parse/documents.coffee
@@ -36,13 +36,11 @@
         parseFile name

     Document.onBeforeParse onBeforeParseListener = (file) ->
-        if platform isnt 'node'
-            Document.Style.applyStyleQueriesInDocument file
+        Document.Style.applyStyleQueriesInDocument file
         return

     Document.onParse onParseListener = (file) ->
-        if platform isnt 'node'
-            Document.Style.createStylesInDocument file
+        Document.Style.createStylesInDocument file
         return

     parseFile = (path) ->


Activity: fix embedded content is not visible after page load bug

--- client/Main/socialapicontroller.coffee
+++ client/Main/socialapicontroller.coffee
@@ -45,7 +45,6 @@
     plain._id = plain.id

     {payload} = plain
-    delete plain.payload

     m = new KD.remote.api.SocialMessage plain
     m.account = mapAccounts(accountOldId)[0]


Add LikeViewClean for comments

Add LikeViewClean for comments

--- client/app/CommonViews/LikeView.coffee
+++ client/app/CommonViews/LikeView.coffee
@@ -2,8 +2,9 @@

   constructor:(options={}, data)->

-    options.tagName  or= 'span'
-    options.cssClass or= 'like-view'
+    options.tagName         or= 'span'
+    options.cssClass        or= 'like-view'
+    options.tooltipPosition or= 'se'

     super options, data

@@ -12,7 +13,7 @@

     @likeCount    = new ActivityLikeCount
       tooltip     :
-        gravity   : "se"
+        gravity   : options.tooltipPosition
         title     : ""
       bind        : "mouseenter"
       mouseenter  : => @fetchLikeInfo()
@@ -85,3 +86,19 @@

   pistachio:->
     """{{> @likeLink}}{{> @likeCount}}"""
+
+class LikeViewClean extends LikeView
+
+  constructor:(options={ tooltipPosition : 'w' }, data)->
+
+    @seperator = new KDCustomHTMLView "span"
+    super options, data
+
+    @seperator.updatePartial if @getData().meta.likes then ' · ' else ''
+
+    @likeCount.on "countChanged", (count) =>
+      @seperator.updatePartial if count then ' · ' else ''
+
+  pistachio:->
+    """{{> @likeLink}}{{> @seperator}}{{> @likeCount}}"""
+


Providers: enable DO, disable Rackspace, coming soon Softlayer

--- client/app/lib/providers/config.coffee
+++ client/app/lib/providers/config.coffee
@@ -129,6 +129,22 @@
           { title: 'Eastern Asia (asia-east1)',     value: 'asia-east1' }
         ]

+  digitalocean             :
+    name                   : 'Digital Ocean'
+    link                   : 'https://digitalocean.com'
+    title                  : 'Digitalocean'
+    color                  : '#7abad7'
+    supported              : yes
+    slug                   : 'do'
+    enabled                : 'beta'
+    defaultTemplate        : require './templates/digitalocean'
+    instanceTypes          : require './instance-types/do'
+    description            : 'Digital Ocean droplets'
+    credentialFields       :
+      access_token         :
+        label              : 'Personal Access Token'
+        placeholder        : 'Digital Ocean access token'
+
   azure                    :
     name                   : 'Azure'
     link                   : 'https://azure.microsoft.com/'
@@ -147,29 +163,12 @@
         placeholder        : 'azure secret'
         type               : 'password'

-  digitalocean             :
-    name                   : 'Digital Ocean'
-    link                   : 'https://digitalocean.com'
-    title                  : 'Digitalocean'
-    color                  : '#7abad7'
-    supported              : yes
-    enabled                : no
-    description            : 'Digitalocean droplets'
-    instanceTypes          : require './instance-types/do'
-    credentialFields       :
-      clientId             :
-        label              : 'Client Id'
-        placeholder        : 'client id in digitalocean'
-      apiKey               :
-        label              : 'API Key'
-        placeholder        : 'digitalocean api key'
-
   rackspace                :
     name                   : 'Rackspace'
     link                   : 'http://www.rackspace.com'
     title                  : 'Rackspace'
     color                  : '#d8deea'
-    supported              : yes
+    supported              : no
     enabled                : no
     description            : 'Rackspace machines'
     credentialFields       :
@@ -185,7 +184,7 @@
     link                   : 'http://www.softlayer.com'
     title                  : 'Softlayer'
     color                  : '#B52025'
-    supported              : no
+    supported              : yes
     enabled                : no
     description            : 'Softlayer resources'
     credentialFields       :


canPrivate bug fix: handle null message case

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -491,7 +491,7 @@
       false
     else
       root = findMessageRoot message
-      root.threadPrivacy? and 'public' in root.threadPrivacy and 'private' in root.threadPrivacy
+      root?.threadPrivacy? and 'public' in root.threadPrivacy and 'private' in root.threadPrivacy

 @canAdmin = (group, message = null) ->
   messageRoleCheck group, message, 'admin'


update round after publishing

update round after publishing

--- src/node_modules/group-rounds/group-rounds-controller.coffee
+++ src/node_modules/group-rounds/group-rounds-controller.coffee
@@ -1,6 +1,7 @@
 _ = require('lodash')

 roundModal = require('round-modal')
+roundPublishModal = require('round-publish-modal')

 ### @ngInject ###
 module.exports = ($scope, $modal, group, AuthService) ->
@@ -39,3 +40,16 @@
       # TODO rounds.sort()

     , handleError
+
+  $scope.publishRound = (round, index) ->
+    $modal.open(
+      roundPublishModal(
+        round: round
+        group: group
+      )
+    ).result.then (round) ->
+
+      rounds.at(index).set(round)
+      # TODO rounds.sort()
+
+    , handleError


Give a name to the amd export

Give a name to the amd export

--- pace.coffee
+++ pace.coffee
@@ -311,7 +311,7 @@
 shouldTrack = (method='GET') ->
   if ignoreStack[0] is 'track'
     return 'force'
-
+
   if not ignoreStack.length and options.ajax
     if method is 'socket' and options.ajax.trackWebSockets
       return true
@@ -739,7 +739,7 @@

 if typeof define is 'function' and define.amd
   # AMD
-  define -> Pace
+  define ['pace'], -> Pace
 else if typeof exports is 'object'
   # CommonJS
   module.exports = Pace


Provide an additional filter for super-admins to fetch not approved apps

--- client/landing/app/Applications/Apps.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Apps.kdapplication/AppController.coffee
@@ -21,7 +21,7 @@
     @createFeed()

   createFeed:(view)->
-    appManager.tell 'Feeder', 'createContentFeedController', {
+    options =
       subItemClass          : AppsListItemView
       limitPerPage          : 10
       filter                :
@@ -32,21 +32,15 @@
         kodingAddOns        :
           title             : "Koding Add-ons"
           dataSource        : (selector, options, callback)=>
-            setTimeout =>
-              callback null,@dummy
-            ,200
+            callback 'Coming soon!'
         serverStacks        :
           title             : "Server Stacks"
           dataSource        : (selector, options, callback)=>
-            setTimeout =>
-              callback null,@dummy
-            ,200
+            callback 'Coming soon!'
         frameworks          :
           title             : "Frameworks"
           dataSource        : (selector, options, callback)=>
-            setTimeout =>
-              callback null,@dummy
-            ,200
+            callback 'Coming soon!'
       sort                  :
         'counts.followers'  :
           title             : "Most popular"
@@ -67,8 +61,14 @@
           html      : yes
           animate   : yes

-
-    }, (controller)=>
+    if KD.checkFlag 'super-admin'
+      options.filter.waitsForApprove =
+        title             : "New Apps"
+        dataSource        : (selector, options, callback)=>
+          selector.approved = no
+          bongo.api.JApp.someWithRelationship selector, options, callback
+
+    appManager.tell 'Feeder', 'createContentFeedController', options, (controller)=>
       # @getSingleton("kodingAppsController").fetchAppsFromDb (err, apps)=>
       #   log "Installed Apps:", apps
       for own name,listController of controller.resultsController.listControllers


--- client/landing/app/Applications/Apps.kdapplication/Views/appview.coffee
+++ client/landing/app/Applications/Apps.kdapplication/Views/appview.coffee
@@ -134,8 +134,23 @@
         diameter: 30
         color   : "#ffffff"
       callback  : ->
-        appsController.installApp app, app.manifest.version, (err)=>
+
+        _installApp = =>
+          @showLoader()
+          appsController.installApp app, app.manifest.version, (err)=>
+            @hideLoader()
+
+        vmController = KD.getSingleton("vmController")
+        unless vmController.vms.length
           @hideLoader()
+          vmController.askToTurnOn app.title, -> _installApp()
+        else
+          vmController.info (err, vm, info)=>
+            if info?.state is "RUNNING"
+              _installApp()
+            else
+              @hideLoader()
+              vmController.askToTurnOn app.title, -> _installApp()

     @runButton = new KDButtonView
       title     : "Run"


cleanup

cleanup

--- client/landing/app/MainApp/maincontroller/mainviews/contentpanel.coffee
+++ client/landing/app/MainApp/maincontroller/mainviews/contentpanel.coffee
@@ -4,16 +4,15 @@

     super options, data

-    @registerSingleton "contentPanel", @, yes
+    @registerSingleton "contentPanel", this, yes
     @bindTransitionEnd()
     @listenWindowResize()
-    @state = 'full'
-    @chatMargin = 0
+
+    @state              = 'full'
+    @chatMargin         = 0
     @windowController or= KD.getSingleton 'windowController'
-
-    @navOpenedOnce = yes #if KD.isLoggedIn() then yes else no
-
-    mainViewController = KD.getSingleton "mainViewController"
+    @navOpenedOnce      = yes #if KD.isLoggedIn() then yes else no
+    mainViewController  = KD.getSingleton "mainViewController"
     mainViewController.on "UILayoutNeedsToChange", @bound "changeLayout"
     mainViewController.on "browseRequested", @bound "browseRequested"

@@ -25,10 +24,13 @@
   nameMap =
     Home    : 'adjustForFullWidth'

+
   browseRequested:->
+
     @navOpenedOnce = yes
     @adjustForSocial()
     KD.getSingleton("mainView").mainTabView.changeLayout hideTabs : no
+

   changeLayout:(options)->

@@ -40,36 +42,49 @@

     @adjustLayoutHelper name, type

+
   resetToCurrentState:->
+
     switch @state
       when 'develop' then @adjustForDevelop()
       when 'social'  then @adjustForSocial()
       when 'full'    then @adjustForFullWidth()

+
   adjustShadow:(hideTabs)->
+
     @[if hideTabs then 'setClass' else 'unsetClass'] "no-shadow"

+
   adjustLayoutHelper:(name, type)->
+
     if @navOpenedOnce # KD.isLoggedIn() or
     then @[nameMap[name] or typeMap[type] or typeMap[@state]]?()
     else @adjustForFullWidth()

+
   adjustForFullWidth:->
+
     @setX 0
     @setWidth @windowController.winWidth - @chatMargin
     @setClass 'full'
     @state = 'full'

+
   adjustForDevelop:->
+
     @setX 260
     @setWidth @windowController.winWidth - 260 - @chatMargin
     @setClass 'develop'
     @state = 'develop'

+
   adjustForSocial:->
+
     offset = if @windowController.winWidth < 768 then 50 else 160
     @setWidth @windowController.winWidth - offset - @chatMargin
     @setClass 'social'
     @state = 'social'

+
   _windowDidResize: @::adjustLayoutHelper


Remove dangling log

Remove dangling log

--- server/app/bongo/abstractions/taggable.coffee
+++ server/app/bongo/abstractions/taggable.coffee
@@ -28,7 +28,6 @@
                   as: taggedContentRole
                   respondWithCount: yes
                 }, (err, count)=>
-                  console.log "Here the error:", err, count
                   if err then callback err
                   else queue.next()
               =>