minor refactoring of test

minor refactoring of test

--- test/spec/datacommunication/producers/ProducerMapper.spec.coffee
+++ test/spec/datacommunication/producers/ProducerMapper.spec.coffee
@@ -16,7 +16,7 @@
   beforeEach ->
     sandbox = sinon.sandbox.create()
     producerMapper = new Vigor.ProducerMapper()
-    producerMapper.addProducerClass DummyProducer
+    do producerMapper.removeAllProducers

   afterEach ->
     do sandbox.restore
@@ -24,24 +24,24 @@

   describe 'given a registered subscription key', ->
     it 'it should return a producerClass', ->
+      producerMapper.addProducerClass DummyProducer
       producer = producerMapper.findProducerClassForSubscription KEY
       assert.equal(producer, DummyProducer)

   describe 'given a unregistered subscription key', ->
     it 'it should throw a "No producer found for subscription" error', ->
+      producerMapper.addProducerClass DummyProducer
       errorFn = -> producerMapper.findProducerClassForSubscription KEY2
       assert.throws (-> errorFn()), /No producer found for subscription dummy2!/

   describe 'when trying to find a producer when no producers are registered', ->
     it 'it should throw a "There are No producers registered" error', ->
-      do producerMapper.removeAllProducers
       errorFn = -> producerMapper.findProducerClassForSubscription KEY
       assert.throws (-> errorFn()), /There are no producers registered - register producers through the DataCommunicationManager/

   describe 'given a producerClass', ->
     describe 'to add', ->
       it 'it should store the producerClass', ->
-        do producerMapper.removeAllProducers
         assert.equal(producerMapper.producers.length, 0)
         producerMapper.addProducerClass DummyProducer
         assert.equal(producerMapper.producers.length, 1)
@@ -49,17 +49,17 @@

     describe 'to remove', ->
       it 'it should remove the producerClass', ->
-        do producerMapper.removeAllProducers
         assert.equal(producerMapper.producers.length, 0)
         producerMapper.addProducerClass DummyProducer
         assert.equal(producerMapper.producers.length, 1)
+
         producerMapper.removeProducerClass DummyProducer
+
         assert.equal(producerMapper.producers.length, 0)
         assert.equal(producerMapper.subscriptionKeyToProducerMap[KEY], undefined)

   describe 'removing all producers', ->
       it 'it should empty storage', ->
-        do producerMapper.removeAllProducers
         assert.equal(producerMapper.producers.length, 0)
         producerMapper.addProducerClass DummyProducer
         assert.equal(producerMapper.producers.length, 1)


TEST: added some failing tests

TEST: added some failing tests

--- test/tests/CaffeineScript/Parser/Working/Literals/Strings.coffee
+++ test/tests/CaffeineScript/Parser/Working/Literals/Strings.coffee
@@ -80,6 +80,12 @@
       ':hi-there':  '"hi-there";'
       ":80%":       '"80%";'
       ':http://cafscript.com': '"http://cafscript.com";'
+
+    regression:
+      'if currentUsersMessage then "right" else "left"':  'if (currentUsersMessage) {"right";} else {"left";};'
+      'if currentUsersMessage then "right" else :left':   'if (currentUsersMessage) {"right";} else {"left";};'
+      'if currentUsersMessage then "right" else (:left)': 'if (currentUsersMessage) {"right";} else {"left";};'
+      'if currentUsersMessage then :right else :left':    'if (currentUsersMessage) {"right";} else {"left";};'

   toEolStrings:
     "single quote exempt so it can be used in implicit arrays":


Do not trigger change on refresh, compat. to spine 1.3.x and lower.

--- app/assets/javascripts/app/lib/spine/spine.coffee
+++ app/assets/javascripts/app/lib/spine/spine.coffee
@@ -416,7 +416,7 @@
     # go to the source and load attributes
     @constructor.irecords[@id].load(atts)
     @trigger('refresh', this)
-    @trigger('change', this, 'refresh')
+    #@trigger('change', this, 'refresh') # do not trigger change on refresh, compat. to spine 1.3.x and lower
     this

   toJSON: ->


add range slider without styles to group page

--- app/assets/javascripts/components/group-page.cjsx
+++ app/assets/javascripts/components/group-page.cjsx
@@ -1,9 +1,10 @@
-React         = require("react")
-GroupBrowser  = require('./group-browser')
-GenericButton   = require('components/buttons/generic-button')
+React              = require("react")
+GroupBrowser       = require('./group-browser')
+GenericButton      = require('components/buttons/generic-button')
 FirstPageThumbnail = require('./first-page-thumbnail')
-SelectDropdown = require('./select-dropdown')
-API           = require('../lib/api')
+SelectDropdown     = require('./select-dropdown')
+ReactSlider        = require('react-slider')
+API                = require('../lib/api')

 GroupPage = React.createClass
   displayName: "GroupPage"
@@ -131,7 +132,10 @@

?

-
Range slider goes here

+

+                  <ReactSlider defaultValue={[0, 100]} withBars />
+
+

                   <SelectDropdown options_array={@state.all_institutions}
@@ -148,10 +152,10 @@

-
+

MARK

-
+

TRANSCRIBE


properly save transcriptions into separate fields

--- app/assets/javascripts/components/text-entry.cjsx
+++ app/assets/javascripts/components/text-entry.cjsx
@@ -15,7 +15,7 @@

     transcription = []
     for step, i in @props.transcribeSteps
-      transcription.push { field_name: "#{step.field_name}", value: $('.transcribe-input').val() }
+      transcription.push { field_name: "#{step.field_name}", value: $(".transcribe-input:eq(#{step.key})").val() }

     console.log 'TRANSCRIPTION:::::::::::::::::: -> ', transcription

@@ -62,19 +62,21 @@

     if @nextStepAvailable()
       next_button = >
-      done_button = Done
+      # done_button = Done
     else
       next_button = >
-      done_button = Next Entry
+      # done_button = Next Entry
+
+    done_button = Done

         {
           for step in @props.transcribeSteps
             if step.key is @state.currentStep
+              class_name = 'input-field active'
+            else
               class_name = 'input-field'
-            else
-              class_name = 'input-field hide'

               {step.instruction}


Update monitor_lizard.coffee

Update monitor_lizard.coffee

--- app/lib/field-guide-content/monitor_lizard.coffee
+++ app/lib/field-guide-content/monitor_lizard.coffee
@@ -4,7 +4,7 @@
     All monitor lizards have elongated bodies, strong limbs, muscular tails, and strong claws. Rock monitors have long necks, which support their elongated head. They have external ears, movable eyelids, and a snakelike protrusible tongue. Their body is blackish or gray-brown, with five or six rows of dull yellow blotches. Some individuals in the southern part of the species range have white throats.
   '''

-  scientificName: '(Varanus albigularis)'
+  scientificName: 'Varanus albigularis'
   mainImage: 'assets/fieldguide-content/reptiles/monitor_lizard/monitor-lizard-feature.jpg'

   conservationStatus: 'Least Concern' # Options are Least Concern, Near Threatened, Vulnerable, and Endangered; all have their own little icons as well.
@@ -12,9 +12,6 @@
   information: [{
     label: 'Length'
     value: '0.85-2 m'
-  }, {
-    label: 'Height'
-    value: ''
   }, {
     label: 'Weight'
     value: '6-8 kg'


Added: a related field to alarms so we can bind other objects to it

--- db/schema.coffee
+++ db/schema.coffee
@@ -3,6 +3,8 @@
     property 'action', String, default: 'DISPLAY'
     property 'trigg', String
     property 'description', String
+
+    property 'related', String, default: null

     # Further work to make the doctype iCal compliant
     # email properties


Add post author to prepopulated reply mentions

--- assets/javascripts/views/post/post_reply_form.js.coffee
+++ assets/javascripts/views/post/post_reply_form.js.coffee
@@ -31,10 +31,12 @@

   context: =>
     post = @post()
+    reply_to_entities = (_.map post.postMentions(), (m) => m.entity)
+    reply_to_entities.push(post.entity) unless TentStatus.Helpers.isCurrentUserEntity(post.entity)
     _.extend {}, super,
       max_chars: TentStatus.config.MAX_LENGTH
       id: post.id
       entity: post.entity
       formatted:
-        reply_to_entities: (_.map post.postMentions(), (m) => m.entity)
+        reply_to_entities: reply_to_entities


Fail replace test

Fail replace test

--- src/test/test_world.coffee
+++ src/test/test_world.coffee
@@ -74,16 +74,11 @@
     t.end()

   test 'world can replace children', (t) ->
-    t.ok world.has_children(), "has some"
-    count = world._child_count()
-    t.ok result = world.find_child("root:0"), "child exists"
-    console.log "result", result
-    t.equal result.get('c'), 3, "c is 3"
-
-    replacement = {_LABEL: 'root:0', c: 9}
-    t.equal result.get('c'), 3, "c is 3"
+    label = 'root:0'
+    t.equal world.find_child(label).get('c'), 3
+    replacement = {_LABEL: label, c: 9}
     world.replace_child(replacement)
-
+    t.equal world.find_child(label).get('c'), 9
     t.end()

   # TODO: Break this up into smaller tests somehow


Tune health settings

Tune health settings

--- app/scripts/components/entities/LargeDrone.coffee
+++ app/scripts/components/entities/LargeDrone.coffee
@@ -34,22 +34,23 @@
         mine.absorbDamage(300) # Mine collision on LargeDrone triggers explosion of mine
     @updatedHealth()
     @bind 'Hit', (data) =>
-      @shiftedX += 2
-      Crafty.e('LaserHit').explode(
-        x: data.projectile.x
-        y: data.projectile.y
-        radius: 4
-        duration: 50
-      ) if data.projectile.has('Bullet')
+      if data.projectile.has('Bullet')
+        @shiftedX += 2
+        Crafty.e('LaserHit').explode(
+          x: data.projectile.x
+          y: data.projectile.y
+          radius: 4
+          duration: 50
+        )
     this

   updatedHealth: ->
     sprite = 0
+    if @health < 350000
+      sprite = 1
     if @health < 325000
-      sprite = 1
-    if @health < 285000
       sprite = 2
-    if @health < 245200
+    if @health < 285200
       sprite = 3

     @sprite(sprite, 0)


Update team mention

Update team mention

--- scripts/mobile-mention.coffee
+++ scripts/mobile-mention.coffee
@@ -11,12 +11,13 @@
     nameWithDot = user.match(/(.+)@inin.com/i)[1]
     name = nameWithDot.split('.').join(' ')
     nameCap = name.replace(/(^|\s)[a-z]/g, (f) -> f.toUpperCase())
+    cleanMessage = message.replace /@mobile/gi, ''

     str = """
           > ###### [@#{nameCap}](#/person/abcdefghijklmnopqrstuv) said
-          > #{message}
+          > #{cleanMessage}

-          [](#/person/57350c17a6a8301c3de8b941) [](#/person/54f07909d96de71e0a552740)
+          [  ](#/person/53a2ecf39cfde35e232982a9) [  ](#/person/50c26f9b3711784291c6807a) [  ](#/person/54f07909d96de71e0a552740) [  ](#/person/53a2eceb9cfde35e2329822a) [  ](#/person/53a2ed209cfde35e23298300) [  ](#/person/53a2ed4a9cfde35e23298437) [  ](#/person/50c26fa43711784290c68026) [  ](#/person/561fcf4e3e0d3317a49d846f) [  ](#/person/5361a63e64a211297b74a391) [  ](#/person/53a2ecec9cfde35e23298236) [  ](#/person/53a2ed3e9cfde35e2329840e)
           Yo mobile team! #{nameCap} said something that you all should read.
           """


Always cancel scrolling after additional (non-repeat) keydowns

This fixes #1596.

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -132,6 +132,7 @@
         handlerStack.alwaysContinueBubbling =>
           @keyIsDown = true
           @lastEvent = event
+          @time += 1 unless event.repeat
       keyup: =>
         handlerStack.alwaysContinueBubbling =>
           @keyIsDown = false


Fix scrolling issue.

Fix scrolling issue.

On this page:
  - http://www.steephill.tv/dubai-tour/

I was seeing a scrolling problem in my live browser (j/k didn't work
until I clicked on the page), but couldn't reproduce it directly in my
test browser.  That's worrying.

Nevertheless, what was happening was that we succeeded in scrolling a
test amount in one direction, but the scroll to revert failed.  The
consequence was that we concluded (incorrectly) that the element doesn't
scroll.  And j/k scrolling is broken as a result.

How can it be that a scroll in one direction succeeds, but the reverse
does not?

This fixes the problem by not checking whether we are able to undo the
scroll of our test amount.

--- content_scripts/scroller.coffee
+++ content_scripts/scroller.coffee
@@ -75,7 +75,11 @@
   # definitely scrolling backwards, so a delta of -1 will do.  For absolute scrolls, factor is always 1.
   delta = factor * getDimension(element, direction, amount) || -1
   delta = getSign delta # 1 or -1
-  performScroll(element, direction, delta) and performScroll(element, direction, -delta)
+  for change in [ delta, -delta ]
+    if performScroll element, direction, change
+      performScroll element, direction, -change
+      return true
+  false

 # From element and its parents, find the first which we should scroll and which does scroll.
 findScrollableElement = (element, direction, amount, factor) ->


Update hooks.coffee

Update hooks.coffee

--- app/tests/cucumber/features/_support/hooks.coffee
+++ app/tests/cucumber/features/_support/hooks.coffee
@@ -6,7 +6,7 @@
 		#server.call 'emailStub/stub'
 		#server.call 'fixtures/stubCloudFrontClient'
 		# go to the page first so we have access to the Meteor object
-		client.url process.env.ROOT_URL
+		@client.url process.env.ROOT_URL
 		###client.executeAsync (done) ->
 			customer =
 				id: 'randomId'


Stop hiding spell palette view when play changes

This was hiding the spell palette when you type anything in the
Ace editor. Which is annoying when you're trying to type something
based on the documentation!

--- app/views/play/level/tome/SpellPaletteView.coffee
+++ app/views/play/level/tome/SpellPaletteView.coffee
@@ -24,7 +24,6 @@
     'tome:change-language': 'onTomeChangedLanguage'
     'tome:palette-clicked': 'onPalleteClick'
     'surface:stage-mouse-down': 'hide'
-    'level:set-playing': 'hide'

   events:
@@ -364,6 +363,8 @@
     @hide()

   hide: () =>
+    console.log('hide!', arguments)
+    console.trace()
     @$el.find('.left .selected').removeClass 'selected'
     @$el.removeClass('open')


[➠] May I have your attention please? Will the real Shady please stand up?.

--- vendor/assets/javascripts/jquery/plugins/shadify/shady.js.coffee
+++ vendor/assets/javascripts/jquery/plugins/shadify/shady.js.coffee
@@ -1,49 +1,346 @@
 class @Shady
+  # === Constants ==
+
+  @MESH:
+    width: 1.2
+    height: 1.2
+    depth: 10
+    segments: 20
+    slices: 16
+    xRange: 0.8
+    yRange: 0.1
+    zRange: 1.0
+    ambient: '#555555'
+    diffuse: '#FFFFFF'
+    speed: 0.002
+
+  @LIGHT:
+    count: 2
+    xyScalar: 1
+    zOffset: 100
+    ambient: '#880066'
+    diffuse: '#FF8800'
+    speed: 0.001
+    gravity: 1200
+    dampening: 0.95
+    minLimit: 10
+    maxLimit: null
+    minDistance: 20
+    maxDistance: 400
+    autopilot: true
+    draw: true
+    bounds: do FSS.Vector3.create
+    step: FSS.Vector3.create Math.randomInRange(0.2, 1.0), Math.randomInRange(0.2, 1.0), Math.randomInRange(0.2, 1.0)
+
+  @WEBGL: 'webgl'
+  @CANVAS: 'canvas'
+  @SVG:'svg'
+
   # === Variables ===

   defaults:
     debug: false

+  rendering:
+    renderer: @WEBGL
+
   # === Public ===

   constructor: (element, options) ->
-    @element   = $ element
+    @element = $ element
+    @options = $.extend @defaults, options
+
+    initialize.call @
+
+    return
+
+  animate: =>
+    @now = do Date.now - @start
+
+    do @update
+
+    do @render
+
+    requestAnimationFrame @animate
+
+    return
+
+  update: ->
+    offset = Shady.MESH.depth / 2
+
+    # Update bounds
+    FSS.Vector3.copy Shady.LIGHT.bounds, @center
+    FSS.Vector3.multiplyScalar Shady.LIGHT.bounds, Shady.LIGHT.xyScalar
+
+    # Update attractor
+    FSS.Vector3.setZ @attractor, Shady.LIGHT.zOffset
+
+    # Overwrite the attractor position
+    if Shady.LIGHT.autopilot
+      ox = Math.sin Shady.LIGHT.step[0] * @now * Shady.LIGHT.speed
+      oy = Math.cos Shady.LIGHT.step[1] * @now * Shady.LIGHT.speed
+
+      FSS.Vector3.set(
+        @attractor
+        Shady.LIGHT.bounds[0] * ox
+        Shady.LIGHT.bounds[1] * oy
+        Shady.LIGHT.zOffset
+      )
+
+    # Animate lights
+    for light in @scene.lights
+      # Reset the z-position of the light
+      FSS.Vector3.setZ light.position, Shady.LIGHT.zOffset
+
+      # Calculate the force Luke!
+      d = Math.clamp FSS.Vector3.distanceSquared(light.position, @attractor), Shady.LIGHT.minDistance, Shady.LIGHT.maxDistance
+
+      f = Shady.LIGHT.gravity * light.mass / d
+
+      FSS.Vector3.subtractVectors light.force, @attractor, light.position
+      FSS.Vector3.normalise light.force
+      FSS.Vector3.multiplyScalar light.force, f
+
+      # Update the light position
+      FSS.Vector3.set light.acceleration
+      FSS.Vector3.add light.acceleration, light.force
+      FSS.Vector3.add light.velocity, light.acceleration
+      FSS.Vector3.multiplyScalar light.velocity, Shady.LIGHT.dampening
+      FSS.Vector3.limit light.velocity, Shady.LIGHT.minLimit, Shady.LIGHT.maxLimit
+      FSS.Vector3.add light.position, light.velocity
+
+    # Animate vertices
+    for vertex in @geometry.vertices
+      ox = Math.sin vertex.time + vertex.step[0] * @now * Shady.MESH.speed
+      oy = Math.cos vertex.time + vertex.step[1] * @now * Shady.MESH.speed
+      oz = Math.sin vertex.time + vertex.step[2] * @now * Shady.MESH.speed
+
+      FSS.Vector3.set (
+        vertex.position
+        Shady.MESH.xRange * @geometry.segmentWidth * ox
+        Shady.MESH.yRange * @geometry.sliceHeight * oy
+        Shady.MESH.zRange * offset * oz - offset
+      )
+
+      FSS.Vector3.add vertex.position, vertex.anchor
+
+    # Set the geometry to dirty
+    # @geometry.dirty = true
+
+    return
+
+  render: ->
+    @renderer.render @scene
+
+    # Draw Lights
+    if Shady.LIGHT.draw
+      for light in @scene.lights
+        lx = light.position[0]
+        ly = light.position[1]
+
+        switch @rendering.renderer
+          when Shady.CANVAS
+            @renderer.context.lineWidth = 0.5
+
+            do @renderer.context.beginPath
+
+            @renderer.context.arc lx, ly, 10, 0, Math.PIM2
+            @renderer.context.strokeStyle = light.ambientHex
+
+            do @renderer.context.stroke
+            do @renderer.context.beginPath
+
+            @renderer.context.arc lx, ly, 4, 0, Math.PIM2
+            @renderer.context.fillStyle = light.diffuseHex
+
+            do @renderer.context.fill
+          when Shady.SVG
+            lx += @renderer.halfWidth
+            ly = @renderer.halfHeight - ly
+
+            light.core.setAttributeNS null, 'fill', light.diffuseHex
+            light.core.setAttributeNS null, 'cx', lx
+            light.core.setAttributeNS null, 'cy', ly
+
+            @renderer.element.appendChild light.core
+
+            light.ring.setAttributeNS null, 'stroke', light.ambientHex
+            light.ring.setAttributeNS null, 'cx', lx
+            light.ring.setAttributeNS null, 'cy', ly
+
+            @renderer.element.appendChild light.ring
+
+    return
+
+  setRenderer: (index) ->
+    if (@renderer)
+      @container.removeChild @renderer.element
+
+    switch index
+      when Shady.WEBGL
+        @renderer = @webglRenderer
+      when Shady.CANVAS
+        @renderer = @canvasRenderer
+      when Shady.SVG
+        @renderer = @svgRenderer
+
+    @renderer.setSize @container.offsetWidth, @container.offsetHeight
+
+    # Add the renderer's element to the DOM (`canvas`)
+    @container.appendChild @renderer.element
+
+    return
+
+  resize: (width, height) ->
+    @renderer.setSize width, height
+
+    FSS.Vector3.set @center, @renderer.halfWidth, @renderer.halfHeight
+
+    createMesh.call @
+
+    return
+
+  # === Events ===
+
+  onWindowResize: (event) =>
+    @resize @container.offsetWidth, @container.offsetHeight
+
+    do @render
+
+    return
+
+  onMouseMove: (event) =>
+    FSS.Vector3.set @attractor, event.x, @renderer.height - event.y
+    FSS.Vector3.subtract @attractor, @center
+
+    return
+
+  onMouseClick: (event) =>
+    FSS.Vector3.set @attractor, event.x, @renderer.height - event.y
+    FSS.Vector3.subtract @attractor, @center
+
+  # === Private ===
+
+  initialize = ->
     @container = @element[0]
-    @options   = $.extend @defaults, options
-
-    @renderer = new FSS.WebGLRenderer
-    @scene    = new FSS.Scene
-    @light    = new FSS.Light '#111122', '#FF0022'
-    @geometry = new FSS.Plane @container.offsetWidth, @container.offsetHeight, 10, 5
-    @material = new FSS.Material '#FFFFFF', '#FFFFFF'
-    @mesh     = new FSS.Mesh @geometry, @material

     @now   = do Date.now
     @start = do Date.now

-    initialize.call @
-
-    @renderer.setSize @container.offsetWidth, @container.offsetHeight
+    @center    = do FSS.Vector3.create
+    @attractor = do FSS.Vector3.create
+
+    createRenderer.call @
+
+    createScene.call @
+
+    createMesh.call @
+
+    createLights.call @
+
+    addEventListeners.call @
+
+    @resize @container.offsetWidth, @container.offsetHeight

     do @animate

     return

-  animate: =>
-    @now = do Date.now - @start
-    @light.setPosition 300 * Math.sin(@now * 0.001), 200 * Math.cos(@now * 0.0005), 60
-    @renderer.render @scene
-
-    requestAnimationFrame @animate
-
-    return
-
-  # === Private ===
-
-  initialize = ->
+  createRenderer = ->
+    @webglRenderer  = new FSS.WebGLRenderer
+    @canvasRenderer = new FSS.CanvasRenderer
+    @svgRenderer    = new FSS.SVGRenderer
+
+    @setRenderer @rendering.renderer
+
+    return
+
+  createScene = ->
+    @scene = new FSS.Scene
+
+    return
+
+  createMesh = ->
+    @scene.remove @mesh if @mesh
+
+    do @renderer.clear
+
+    # Create some geometry (`width`, `height`, `segments`, `slices`) & a material, pass them to mesh constructor, and
+    # add the mesh to the scene
+
+    @geometry = new FSS.Plane(
+      Shady.MESH.width * @renderer.width
+      Shady.MESH.height * @renderer.height
+      Shady.MESH.segments
+      Shady.MESH.slices
+    )
+
+    @material = new FSS.Material(
+      Shady.MESH.ambient
+      Shady.MESH.diffuse
+    )
+
+    @mesh = new FSS.Mesh(
+      @geometry
+      @material
+    )
+
     @scene.add @mesh
-    @scene.add @light
-
-    @container.appendChild @renderer.element
+
+    # Augment vertices for animation
+    for vertex in @geometry.vertices
+      vertex.anchor = FSS.Vector3.clone vertex.position
+
+      vertex.step = FSS.Vector3.create(
+        Math.randomInRange 0.2, 1.0
+        Math.randomInRange 0.2, 1.0
+        Math.randomInRange 0.2, 1.0
+      )
+
+      vertex.time = Math.randomInRange 0, Math.PIM2
+
+    return
+
+  createLights = ->
+    for light in @scene.lights
+      @scene.remove light
+
+    do @renderer.clear
+
+    for i in [0...Shady.LIGHT.count]
+      light = new FSS.Light Shady.LIGHT.ambient, Shady.LIGHT.diffuse
+
+      light.ambientHex = do light.ambient.format
+      light.diffuseHex = do light.diffuse.format
+
+      @scene.add light
+
+      # Augment light for animation
+      light.mass         = Math.randomInRange 0.5, 1
+      light.velocity     = do FSS.Vector3.create
+      light.acceleration = do FSS.Vector3.create
+      light.force        = do FSS.Vector3.create
+
+      # Ring SVG Circle
+      light.ring = document.createElementNS FSS.SVGNS, 'circle'
+
+      light.ring.setAttributeNS null, 'stroke', light.ambientHex
+      light.ring.setAttributeNS null, 'stroke-width', '0.5'
+      light.ring.setAttributeNS null, 'fill', 'none'
+      light.ring.setAttributeNS null, 'r', '10'
+
+      # Core SVG Circle
+      light.core = document.createElementNS FSS.SVGNS, 'circle'
+
+      light.core.setAttributeNS null, 'fill', light.diffuseHex
+      light.core.setAttributeNS null, 'r', '4'
+
+    return
+
+  addEventListeners = ->
+    window.addEventListener 'resize', @onWindowResize
+
+    @container.addEventListener 'click', @onMouseClick
+    @container.addEventListener 'mousemove', @onMouseMove

     return


removed unneccesary parameter from notifyOptions

--- workers/social/lib/social/models/computeproviders/machine.coffee
+++ workers/social/lib/social/models/computeproviders/machine.coffee
@@ -610,7 +610,7 @@

     , (client, label, callback) ->

-      { r: { user, group }, connection: { delegate } } = client
+      { r: { user, group } } = client

       unless isOwner user, this
         return callback new KodingError 'Access denied'
@@ -626,7 +626,6 @@
         return callback new KodingError 'Nickname cannot be empty'

       notifyOptions =
-        account : delegate
         group   : client?.context?.group
         target  : 'group'

@@ -635,7 +634,6 @@
           return callback err  if err?
           @updateAndNotify notifyOptions, { $set: { slug , label } }, (err) -> kallback err, slug
       else
-        console.log 'label ', label
         @updateAndNotify notifyOptions, { $set: { label } }, (err) -> kallback err, slug


Resizing without pointer-events close #65 close #59

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -31,6 +31,18 @@
         # @subscribe atom.packages.once 'activated', =>
         atom.packages.onDidActivatePackage =>
           @subscribeToFilePath(filePath)
+
+    # Disable pointer-events while resizing
+    handles = $("atom-pane-resize-handle")
+    handles.on 'mousedown', => @onStartedResize()
+
+  onStartedResize: ->
+    @css 'pointer-events': 'none'
+    document.addEventListener 'mouseup', @onStoppedResizing.bind this
+
+  onStoppedResizing: ->
+    @css 'pointer-events': 'all'
+    document.removeEventListener 'mouseup', @onStoppedResizing

   serialize: ->
     deserializer : 'AtomHtmlPreviewView'


Adds a version number to login url

Adds a version number to login url

--- src/api/auth.coffee
+++ src/api/auth.coffee
@@ -7,7 +7,7 @@
   catch e
     console.warn("Error: " + e)

-define ['underscore', 'lib/utils/promises'], (_, promises)->
+define ['underscore', 'lib/utils/promises', 'lib/utils/version'], (_, promises, version)->

   (apiFn, config, authServices=[]) ->
     # Holds the state of the authentication process
@@ -61,6 +61,7 @@
       auth_params.callback_url  = config.callback_url || module.location.toString()
       auth_params.auth_referer  = module.location.toString()
       auth_params.callback_name = module.createCallback()
+      auth_params.version       = version

       "#{config.orgUrl}/auth/#{provider}?#{$.param(auth_params)}"


offer to open cleaned images upon failure

--- exifexodus.coffee
+++ exifexodus.coffee
@@ -103,6 +103,7 @@
   form     = if isEvent then e.target else @
   inputs   = form.querySelectorAll 'input'
   jpgs     = []
+  cleaned  = []
   formData = new FormData

   for input in inputs when input.value
@@ -121,12 +122,13 @@
     e.stopImmediatePropagation()

   if !form.action
-    return reportErr 'Can\'t proceed, the upload form has no action URL.'
+    return reportErr 'Can\'t proceed, the upload form has no action URL.', cleaned

   toClean = jpgs.length

   for jpg in jpgs then do (jpg) ->
     cleanImage jpg.file, (blob) ->
+      cleaned.push blob
       fdAppend.call formData, jpg.name, blob, jpg.file.name
       unless --toClean
         xhr = new XMLHttpRequest
@@ -135,24 +137,29 @@
           if xhr.readyState is 4
             unless 200 >= xhr.status < 300
               return reportErr "
-                Attempted upload of EXIF-free image but received an error
-                response from the server (code #{ xhr.status }).
-                "
+                Attempted upload of EXIF-free images but received an error
+                response from the server (code #{ xhr.status }).", cleaned

             if xhr.responseType is 'document' or /<[\w\W]*>/.test xhr.responseText
               document.write xhr.response
             else
-              reportErr 'Uploaded image but received ambiguous response from server.'
+              reportErr 'Uploaded image but received ambiguous response from server.', cleaned

         xhr.onerror = ->
-          reportErr 'Something went wrong submitting the upload form.'
+          reportErr 'Something went wrong submitting the upload form.', cleaned

         xhr.open form.method or 'GET', form.action
         xhrSend.call xhr, formData

-reportErr = (msg) -> alert 'ExifExodus: ' + msg
+reportErr = (msg, imgSet) ->
+  if imgSet
+    if confirm "ExifExodus: #{ msg } Click OK to open your EXIF-free images in new tabs."
+      open URL.createObjectURL(blob), '_blank' for blob in imgSet
+      null
+  else
+    alert 'ExifExodus: ' + msg

 init = -> addEventListener 'submit', onSubmit, true


Added extra options to emulated rquire for getting stuff from the window object and added try/catch to loading of modules that want to access browser specific objects

--- inject.coffee
+++ inject.coffee
@@ -24,8 +24,15 @@
     window.boiler={main:{}};
     var idModuleMap={};
     function emulateRequire(aliasIdMap){
-      function require(alias){
-        return idModuleMap[aliasIdMap[alias]]
+      function require(alias, windowName){
+        var exports = idModuleMap[aliasIdMap[alias]];
+        if(typeof windowName==='function'){
+          return windowName(exports);
+        }else if(typeof windowName==='string'){
+          return window[windowName];
+        }else{
+          return exports;
+        }
       }
       return require;
     }
@@ -67,7 +74,8 @@
             }(require);
             #{content}
             """, filename
-        func module, filename
+        try
+          func module, filename
         aliasIdMap = toDict([alias, pathToId path] for alias, path of module.__required)
         everything += boil pathToId(filename), aliasIdMap, code, filename
         updateExtensions()


Compile .less files.

Compile .less files.

--- lib/Watchers/Style/Style.coffee
+++ lib/Watchers/Style/Style.coffee
@@ -63,7 +63,6 @@
             Q.reject err

     css: (content, path)->
-        defer = Q.defer()
         source = file = @pathpart path

         generator = new Generator({file})
@@ -78,5 +77,29 @@

         Q {content, sourceMap, path}

+    less: (code, path)->
+        defer = Q.defer()
+        file = @pathpart path
+        parser = new (require('less').Parser)
+
+        parser.parse code, (err, tree)->
+            return defer.reject err if err
+            content = ''
+            sourceMap = ''
+
+            writeSourceMap = (sourceMapContent)->
+                sourceMap = JSON.parse(sourceMapContent)
+
+            content = tree.toCSS({
+                sourceMap: yes
+                writeSourceMap
+            })
+
+            sourceMap.sources[0] = file
+            sourceMap.sourcesContent = [ content ]
+
+            defer.resolve { content, sourceMap, path }
+
+        defer.promise

 module.exports = StyleWatcher


don't need this anymore

don't need this anymore

--- lib/atom-ternjs-provider.coffee
+++ lib/atom-ternjs-provider.coffee
@@ -54,10 +54,6 @@
         that.manager.client.completions(editor.getURI(), {line: bufferPosition.row, ch: bufferPosition.column}).then (data) =>
           that.clearSuggestions()
           if !data.completions.length
-            resolve([])
-            that.manager.documentation.hide()
-            return
-          if data.completions.length is 1 and data.completions[0].name.replace('$', '') is prefix
             resolve([])
             that.manager.documentation.hide()
             return


DOM: Added a check in the method 'exitFullscreen' to prevent a js error when the method is invoked without fullscreen mode.

--- src/base/dom.coffee
+++ src/base/dom.coffee
@@ -1519,6 +1519,9 @@
 		dfr.promise()

 	@exitFullscreen: ->
+		if not CUI.dom.isFullscreen()
+			return CUI.resolvedPromise()
+
 		dfr = new CUI.Deferred()

 		if document.exitFullscreen
@@ -1530,7 +1533,7 @@
 		else if (document.webkitExitFullscreen)
 			document.webkitExitFullscreen()

-		fullscreenEvent = CUI.Events.listen
+		CUI.Events.listen
 			type: "fullscreenchange"
 			node: window
 			only_once: true


添加按钮描述

添加按钮描述

--- lib/utils/text-description.coffee
+++ lib/utils/text-description.coffee
@@ -6,6 +6,8 @@
   upload : '上传'
   back : '返回'
   finish : '完成'
+  save : '保存'
+  recovery : '还原'
   login : '登录'
   logout : '退出登录'
   email : '邮箱'
@@ -40,8 +42,9 @@
   moduleConfigFileName: 'package.json'
   ProjectConfigFileName: 'appConfig.json'

+  projectConfig : '应用配置'
+
   # 设置模块
   panelTitle: '设置'
   menuAccount: '开发者账号'
   menuCode: '框架、模版'
-


Remove extraneous comment

Remove extraneous comment

--- tether.coffee
+++ tether.coffee
@@ -369,11 +369,6 @@

       moved = true

-      # If two of the dimentions are consistently the same % of
-      # the width of the offsetParent or body
-      #
-      #
-
     else
       css.position = 'absolute'
       css.top = "#{ position.page.top }px"


fix promise status when promise is not pending to start

--- src/builtins.coffee
+++ src/builtins.coffee
@@ -191,6 +191,9 @@
           else
             text = "Failed after #{duration}"
         React.DOM.div {className: 'promise-status'}, text
+      getInitialState: ->
+        unless @props.promise.isPending()
+          return duration: 0
       finished: ->
         @setState duration: new Date - @props.start_time
       componentWillMount: ->


Remove unused require

Remove unused require

--- src/packages/command-palette/src/command-palette.coffee
+++ src/packages/command-palette/src/command-palette.coffee
@@ -1,4 +1,4 @@
-{View, $$} = require 'space-pen'
+{$$} = require 'space-pen'
 SelectList = require 'select-list'
 $ = require 'jquery'
 _ = require 'underscore'


tests(credentials): add test for loading credentials from env

--- src/spec/helpers/project-credentials-config.spec.coffee
+++ src/spec/helpers/project-credentials-config.spec.coffee
@@ -1,6 +1,6 @@
 _ = require 'underscore'
 Promise = require 'bluebird'
-{ProjectCredentialsConfig} = require '../../lib/main'
+{ProjectCredentialsConfig} = require '../../../lib/main'

 describe 'ProjectCredentialsConfig', ->

@@ -41,6 +41,23 @@
     .catch (error) -> done(error)
     .done()

+  it "should load credentials from environment variables", (done) ->
+    process.env.CTP_PROJECT_KEY = 'project-key'
+    process.env.CTP_CLIENT_ID = 'client-id'
+    process.env.CTP_CLIENT_SECRET = 'client-secret'
+
+    ProjectCredentialsConfig.create()
+    .then (config) ->
+      actual = config.forProjectKey('project-key')
+      expected = {
+        project_key: 'project-key'
+        client_id: 'client-id'
+        client_secret: 'client-secret'
+      }
+      expect(actual).toEqual(expected)
+      done()
+    .catch (err) -> done(err)
+
   it "should throw an error if credentials are not defined", (done) ->
     ProjectCredentialsConfig.create
       lookupFiles: ['data/config/config3']


Reset currentSentence on click

Reset currentSentence on click

--- annotran/static/scripts/annotator/plugin/sentenceselection.coffee
+++ annotran/static/scripts/annotator/plugin/sentenceselection.coffee
@@ -120,6 +120,7 @@
     this.storedEvent = event

     this.currentIndex = 0
+    this.currentSentence = 0
     this.selectSentence event.target

     selection = Annotator.Util.getGlobal().getSelection()


Add callback for re-querying options on update or save

--- app/assets/javascripts/angular/controllers/options_ctrl.js.coffee
+++ app/assets/javascripts/angular/controllers/options_ctrl.js.coffee
@@ -25,10 +25,15 @@
         option.instrument_id = $scope.instrument_id
         option.question_id = $scope.question_id
         if option.id
-          option.$update()
+          option.$update({},
+            (data, headers) -> $scope.options = $scope.queryOptions(),
+            (result, headers) -> alert "Error updating option"
+          )
         else
-          option.$save()
-      $scope.options = $scope.queryOptions()
+          option.$save({},
+            (data, headers) -> $scope.options = $scope.queryOptions(),
+            (result, headers) -> alert "Error updating option"
+          )
   )

   $scope.$on('CANCEL_QUESTION', ->


feature: add a query indicator

feature: add a query indicator

the indicator is active as long as  query is running for an element. the indicator is on a per-field basis and not safe for multiple parallel queries. it indicates that a field is being fetched, either with or without pagination

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- coffee/controllers/main.coffee
+++ coffee/controllers/main.coffee
@@ -17,6 +17,7 @@
     $scope.node = {}
     $scope.package = {}
     $scope.packageByName = {}
+    $scope.querying = {}

     $scope.show = {}
     $scope.nodeVisible = {}
@@ -62,21 +63,30 @@
       x instanceof Array and x.length == 0

     fetchAllUnpaginated = (field, action, opts = {}) ->
+      # make sure to add the querying indicator
+      $scope.querying[field] = true
       # fetch the field
       Restangular.all(field).getList( opts.query || {} ).then(
         (data) ->
           # if we have a result, process it
           console.log "fetched #{field} (no pagination)"
+          # end the querying indicator
+          delete $scope.querying[field]
+          # process the action
           action(data)
         , () ->
           # otherwise we have a fetch error
           fetchError("fetch #{field} (no pagination)")
+          # end the querying indicator
+          delete $scope.querying[field]
         )

     fetchAllPaginated = (field, action, opts = {})->
       stop_when = opts.stop_when || isEmptyArray
       page = opts.page || 1
       limit = opts.limit || 50
+      # make sure to add the querying indicator
+      $scope.querying[field] = true
       # construct a query for pagination
       query = _.merge(
         {page: page, limit: limit},
@@ -98,6 +108,9 @@
               'query': (opts.query || {})
             }
             fetchAllPaginated(field, action, o)
+          else
+            # end the querying indicator
+            delete $scope.querying[field]
         # error handling
         , () ->
           # if we got an error on the first fetch, try again without pagination
@@ -105,6 +118,8 @@
             return fetchAllUnpaginated(field, action, opts)
           # otherwise we have a fetch error
           fetchError("fetch #{field} on page #{page}, limit #{limit}")
+          # end the querying indicator
+          delete $scope.querying[field]
         )

     $scope.fetchNodes = (page = 1)->


Require auth on all non-idempontent requrests

--- coffees/src/application.coffee
+++ coffees/src/application.coffee
@@ -53,6 +53,12 @@
   app.use defaultHeaders
   app.use jsonParser
   app.use authMiddleWare.jwtAuthentication
+  router = express.Router()
+  router.post '*', authMiddleWare.requireAuth
+  router.put '*', authMiddleWare.requireAuth
+  router.patch '*', authMiddleWare.requireAuth
+  router.delete '*', authMiddleWare.requireAuth
+  app.use(router)
   app.use(require('./streaming_api')())
   app.use(require('./api')())
   app.use(require('./controllers/frame_controller')())


Fixed an async case that failed a test only sometimes

--- server/plugins/plugins.coffee
+++ server/plugins/plugins.coffee
@@ -258,16 +258,14 @@
       )
     )

-  # Assume ever save is a new version, hence an edit
+  # Assume every save is a new version, hence an edit
   schema.pre 'save', (next) ->
     User = require '../users/User'  # Avoid mutual inclusion cycles
     userID = @get('creator')?.toHexString()
     return next() unless userID?

     statName = User.statsMapping.edits[@constructor.modelName]
-    User.incrementStat userID, statName
-
-    next()
+    User.incrementStat userID, statName, next

 module.exports.SearchablePlugin = (schema, options) ->
   # this plugin must be added only after the others (specifically Versioned and Permissions)


fix another linter error

fix another linter error

--- spec/editor-spec.coffee
+++ spec/editor-spec.coffee
@@ -149,13 +149,13 @@

   describe "path", ->
     it "notifies ::onDidChangePath observers when the underlying buffer path changes", ->
-       observed = []
-       editor.onDidChangePath (filePath) -> observed.push(filePath)
-
-       buffer.setPath('/foo/bar/baz.txt')
-       buffer.setPath(undefined)
-
-       expect(observed).toEqual ['/foo/bar/baz.txt', undefined]
+      observed = []
+      editor.onDidChangePath (filePath) -> observed.push(filePath)
+
+      buffer.setPath('/foo/bar/baz.txt')
+      buffer.setPath(undefined)
+
+      expect(observed).toEqual ['/foo/bar/baz.txt', undefined]

   describe "cursor", ->
     describe ".getLastCursor()", ->


replace the /gc endpoint with a automatic gc every 3 minutes

--- services/filestore/app.coffee
+++ services/filestore/app.coffee
@@ -85,18 +85,6 @@
 app.put "/project/:project_id/public/:public_file_id", keyBuilder.publicFileKey, fileController.copyFile
 app.del "/project/:project_id/public/:public_file_id", keyBuilder.publicFileKey, fileController.deleteFile

-app.get "/gc", (req, res)->
-	if global.gc?
-		memory_before = process.memoryUsage()
-		global.gc()
-		memory_after = process.memoryUsage()
-		delta = {}
-		for k of memory_after
-			delta[k] = memory_after[k] - memory_before[k]
-		res.send { memory_before, memory_after, delta }
-	else
-		res.send(501) # need --expose-gc, send "not implemented"
-
 app.get "/heapdump", (req, res)->
 	require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.filestore.heapsnapshot', (err, filename)->
 		res.send filename
@@ -151,3 +139,10 @@
 process.on 'SIGTERM', () ->
 	logger.log("filestore got SIGTERM, shutting down gracefully")
 	beginShutdown()
+
+if global.gc?
+	gcTimer = setInterval () ->
+		global.gc()
+		logger.log process.memoryUsage(), "global.gc"
+	, 3 * oneMinute = 60 * 1000
+	gcTimer.unref()


components-gtk: ListenSignal: rename ports

--- components/ListenSignal.coffee
+++ components/ListenSignal.coffee
@@ -6,19 +6,21 @@
   icon: 'bolt'
   constructor: ->
     @inPorts = new noflo.InPorts
-      in:
+      object:
         datatype: 'object'
         description: 'Object instance to listen at'
+        required: yes
       signal:
         datatype: 'string'
         description: 'Signal to listen to'
+        required: yes
     @outPorts = new noflo.OutPorts
-      out:
-        datatype: 'bang'
-        description: 'Signal output'
+      object:
+        datatype: 'object'
+        description: 'Object instance to listen at'
         required: no

-    @inPorts.in.on 'data', (object) =>
+    @inPorts.object.on 'data', (object) =>
       @object = object
       @updateListener()
     @inPorts.signal.on 'data', (signal) =>
@@ -29,8 +31,8 @@
     return unless @object? and @signal?
     @disconnectListener()
     @listener = @object.connect @signal, Lang.bind @, () =>
-      @outPorts.out.send true
-      @outPorts.out.disconnect()
+      @outPorts.object.send true
+      @outPorts.object.disconnect()

   disconnectListener: () ->
     if @listener


Output deprecation message

Output deprecation message

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -141,7 +141,7 @@
     Error.prepareStackTrace = (error, stack) ->
       output = []
       for deprecation in deprecations
-        output.push "#{deprecation.originName} is deprecated"
+        output.push "#{deprecation.originName} is deprecated. #{deprecation.message}"
         output.push _.multiplyString("-", output[output.length - 1].length)
         for stack in deprecation.getStacks()
           for {functionName, location} in stack


Bandango.NumberField should be of type `number`

--- app/assets/javascripts/components/number_field.js.coffee
+++ app/assets/javascripts/components/number_field.js.coffee
@@ -2,6 +2,7 @@
   Number input.toString().replace(/[^\d.]/g, "")

 Bandango.NumberField = Ember.TextField.extend
+  type: "number"
   _cleanValue: (->
     @set "value", onlyNumberHelper(@get("value"))
   ).observes("value")


Remove width/height options from image dialog

--- app/assets/javascripts/effective_ckeditor/init.js.coffee
+++ app/assets/javascripts/effective_ckeditor/init.js.coffee
@@ -143,3 +143,23 @@
     dropdown.on 'click.bs.dropdown', (event) -> event.preventDefault()
     dropdown.on 'click', (event) ->
       $(this).removeClass('open') and event.stopPropagation() if $(this).hasClass('open')
+
+
+# This removes the width and height attributes from being set on an image
+# The attributes will come back if you drag & drop resize the image
+CKEDITOR.on 'dialogDefinition', (ev) ->
+  dialogName = ev.data.name
+  dialogDefinition = ev.data.definition
+
+  if (dialogName == 'image2')
+    try
+      dialogDefinition.contents['0'].elements['2'].style = 'display: none;'  # Hide width/height fields entirely
+
+      widthDefinition = dialogDefinition.contents['0'].elements['2'].children['0']
+      heightDefinition = dialogDefinition.contents['0'].elements['2'].children['1']
+
+      if widthDefinition['label'] == 'Width'
+        widthDefinition.commit = (widget) -> widget.setData('width', null)
+
+      if heightDefinition['label'] == 'Height'
+        heightDefinition.commit = (widget) -> widget.setData('height', null)


OX-189 removed geometryfactory

OX-189 removed geometryfactory

--- app/assets/javascripts/geometries/geometryfactory.coffee
+++ app/assets/javascripts/geometries/geometryfactory.coffee
@@ -1,77 +0,0 @@
-
-GeometryFactory =
-
-  createGeometries : (nodeList) ->
-
-    # Apparently, we need to pass this to the geometry...
-    nodesIndexArray = new Int16Array(nodeList.length*3)
-    for i in [0...nodesIndexArray.length]
-      nodesIndexArray[i] = i
-
-    edgesIndexArray = new Int16Array((nodeList.length - 1)*3*2)
-    for i in [0...edgesIndexArray.length]
-      edgesIndexArray[i] = i
-
-    # [nodes|edges]PosArray contains 3 values per vertex, for the x, y and z coordinates.
-    nodesPosArray = new Float32Array(nodesIndexArray.length)
-    for i in [0...nodeList.length]
-      nodesPosArray[i * 3]     = nodeList[i].pos[0]
-      nodesPosArray[i * 3 + 1] = nodeList[i].pos[1]
-      nodesPosArray[i * 3 + 2] = nodeList[i].pos[2]
-
-    edgesPosArray = new Float32Array(edgesIndexArray.length)
-    for i in [0...nodeList.length]
-      if nodeList[i].parent?
-        edgesPosArray[ 2 * i * 3]     = nodeList[i].pos[0]
-        edgesPosArray[ 2 * i * 3 + 1] = nodeList[i].pos[1]
-        edgesPosArray[ 2 * i * 3 + 2] = nodeList[i].pos[2]
-        edgesPosArray[(2 * i + 1) * 3]     = nodeList[i].parent.pos[0]
-        edgesPosArray[(2 * i + 1) * 3 + 1] = nodeList[i].parent.pos[1]
-        edgesPosArray[(2 * i + 1) * 3 + 2] = nodeList[i].parent.pos[2]
-
-    # Create the BufferGeometry, and assign attributes to the instance.
-    nodesGeometry = new THREE.BufferGeometry()
-
-    # The attributes object contains information about how to treat the indexes, positions, normals arrays etc.
-    # For example, indexes have an itemSize of 1, in other words, 1 array value per vertex index.
-    # Position has 3 array values per vertex, hence, itemSize: 3.
-    # array and numItems simply contain the typed array and its length.
-    nodesGeometry.attributes =
-      index:
-        itemSize: 1
-        array: nodesIndexArray
-        numItems: nodesIndexArray.length
-      position:
-        itemSize: 3
-        array: nodesPosArray
-        numItems: nodesPosArray.length
-
-    edgesGeometry = new THREE.BufferGeometry()
-    edgesGeometry.attributes =
-      index:
-        itemSize: 1
-        array: edgesIndexArray
-        numItems: edgesIndexArray.length
-      position:
-        itemSize: 3
-        array: edgesPosArray
-        numItems: edgesPosArray.length
-
-    nodesGeometry.offsets = [{
-      start: 0
-      count: nodesIndexArray.length
-      index: 0
-    }]
-    edgesGeometry.offsets = [{
-      start: 0
-      count: edgesIndexArray.length
-      index: 0
-    }]
-
-    # These are strictly not needed, but they're available, like on any normal Geometry.
-    #for geometry in [nodesGeometry, edgesGeometry]
-    #  geometry.computeBoundingBox()
-    #  geometry.computeBoundingSphere()
-    #  geometry.computeVertexNormals()
-
-    return {nodesGeometry : nodesGeometry, edgesGeometry : edgesGeometry}


Eliminate application outlet weirdness.

Eliminate application outlet weirdness.

It's not necessary to specify the application outlet. Bad stuff
happens.

--- app/assets/javascripts/routes/paper_task_route.js.coffee
+++ app/assets/javascripts/routes/paper_task_route.js.coffee
@@ -25,11 +25,11 @@

   renderTemplate: ->
-    @render(@controllerFor('application').get('overlayBackground'), into: 'application')
     @render @get('baseObjectName'),
       into: 'application'
       outlet: 'overlay'
       controller: @get('taskController')
+    @render(@controllerFor('application').get('overlayBackground'))

   deactivate: ->


Better support uploading files in bounties

--- app/assets/javascripts/views/markdown_editor_view.coffee
+++ app/assets/javascripts/views/markdown_editor_view.coffee
@@ -34,17 +34,22 @@
     @addUploadPlaceholder(file.name)

   onSuccess: (file) =>
-    @replaceUploadPlaceholder(file.name, file.attachment.get('href'))
+    @replaceUploadPlaceholder(file.type, file.name, file.attachment.get('href'))

   onComplete: (file) =>
-    @textarea.trigger('autosize.resize')
+    @textarea.trigger('autosize.resize').trigger('change')

   addUploadPlaceholder: (key) ->
     insertTextAtCursor(@textarea[0], uploadPlaceholder(key))

-  replaceUploadPlaceholder: (key, href) ->
+  replaceUploadPlaceholder: (type, key, href) ->
     placeholder = uploadPlaceholder(key)
-    @textarea.val(@textarea.val().replace(placeholder, "![#{key}](#{href})"))
+    replacement = if type.match(/image|pdf|png|psd/)
+      "![#{key}](#{href})"
+    else
+      "[#{key}](#{href})"
+
+    @textarea.val(@textarea.val().replace(placeholder, replacement))

 $(document).ready ->


add Grunt task to run without parallel watch

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -381,6 +381,7 @@
 	grunt.registerTask 'test:modules:unit', 'Run the unit tests for the modules', ['compile:modules:server', 'compile:modules:unit_tests'].concat(moduleUnitTestTasks)

 	grunt.registerTask 'run', "Compile and run the web-sharelatex server", ['compile', 'env:run', 'parallel']
+	grunt.registerTask 'runq', "Compile and run the web-sharelatex server", ['compile', 'env:run', 'exec']

 	grunt.registerTask 'default', 'run'


Fix minification process

Fix minification process

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -165,7 +165,7 @@
 	grunt.registerTask 'wrap_sharejs', 'Wrap the compiled ShareJS code for AMD module loading', () ->
 		content = fs.readFileSync "public/js/libs/sharejs.js"
 		fs.writeFileSync "public/js/libs/sharejs.js", """
-			define(["ace/range"], function() {
+			define(["ace/ace"], function() {
 				#{content}
 				return window.sharejs;
 			});


Better sample data

Better sample data

--- src/trix/controllers/abstract_editor_controller.coffee
+++ src/trix/controllers/abstract_editor_controller.coffee
@@ -7,10 +7,12 @@
   constructor: (@config) ->
     {@textElement, @toolbarElement, @textareaElement, @inputElement, @delegate} = @config

-    texts = for i in [1..3]
-      text = @createText()
-      text.attributes = text.attributes.add("quote", true) if i is 2
-      text
+    piece1 = new Trix.Piece """CoffeeScript is a little language that compiles into JavaScript. Underneath that awkward Java-esque patina, JavaScript has always had a gorgeous heart. CoffeeScript is an attempt to expose the good parts of JavaScript in a simple way."""
+    piece2 = new Trix.Piece """The golden rule of CoffeeScript is: "It's just JavaScript". The code compiles one-to-one into the equivalent JS, and there is no interpretation at runtime. You can use any existing JavaScript library seamlessly from CoffeeScript (and vice-versa). The compiled output is readable and pretty-printed, will work in every JavaScript runtime, and tends to run as fast or faster than the equivalent handwritten JavaScript."""
+
+    texts =  [@createText()]
+    texts.push new Trix.Text [piece1], quote: true
+    texts.push new Trix.Text [piece2]

     @document = new Trix.Document texts
     @initialize()


Requires an active dismissal

Requires an active dismissal

--- components/message/view.coffee
+++ components/message/view.coffee
@@ -10,7 +10,6 @@

   events:
     'click .js-close': 'remove'
-    'click a': 'remove'

   initialize: ({ @persist = true }) ->
     if @persist


compute/ctrl: ignore machine after perms are fixed

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -865,6 +865,14 @@
     else @emit 'GroupStacksConsistent'

+  ignoreMachine: (machine) ->
+
+    ignoredMachines = @appStorage.getValue('ignoredMachines') ? {}
+    ignoredMachines[machine.uid] = yes
+
+    @appStorage.setValue 'ignoredMachines', ignoredMachines
+
+
   fixMachinePermissions: (machine, dontAskAgain = no) ->

     { groupsController } = kd.singletons
@@ -876,10 +884,7 @@

       if state.dontAskAgain is yes

-        ignoredMachines = @appStorage.getValue('ignoredMachines') ? {}
-        ignoredMachines[machine.uid] = yes
-
-        @appStorage.setValue 'ignoredMachines', ignoredMachines
+        @ignoreMachine machine

         new kd.NotificationView
           title    : "We won't bother you again for this machine"
@@ -899,8 +904,9 @@
       kloud.addAdmin { machineId: machine._id }
         .finally ->
           notification.destroy()
-        .then (shared) ->
+        .then (shared) =>
           debug 'fixed permissions', shared
+          @ignoreMachine machine
           new kd.NotificationView { title: 'Permissions fixed' }
         .catch (err) ->
           showError err


added md rendering

added md rendering

--- demi/index.coffee
+++ demi/index.coffee
@@ -1,12 +1,9 @@
-yaml = require 'js-yaml'
 marked = require 'marked'
 moment = require 'moment'
-fs = require 'fs'

 class Demi
-  constructor: (fcontent, today, separator = '--') ->
-    @_doc = yaml.safeLoad fcontent
+  constructor: (today, separator = '--') ->
     @_now = moment(today)
     @_sep = separator


Fixed snapshot modal wait time

Fixed snapshot modal wait time

--- client/test/lib/helpers/environmenthelpers.coffee
+++ client/test/lib/helpers/environmenthelpers.coffee
@@ -84,7 +84,7 @@

     @attemptCreateSnapshot(browser)

-    browser.pause 2000 #Wait for the modal for upgrading to be displayed or not
+    browser.pause 6000 #Wait for the modal for upgrading to be displayed or not

     browser.isVisible upgradeSelector, (result) ->


Implements coverage server

Implements coverage server

--- src/coverage.coffee
+++ src/coverage.coffee
@@ -18,7 +18,7 @@
     (ctx, inst) =>
       instance = inst
       context = ctx
-    { globals: 'node', quiet: true }
+    { globals: 'node', quiet: true, useServer: app.coverageServer }
     app.noop
   )


fix tests

fix tests

--- components/auth_modal/test/view.coffee
+++ components/auth_modal/test/view.coffee
@@ -2,10 +2,10 @@
 sd = require('sharify').data
 benv = require 'benv'
 sinon = require 'sinon'
+rewire = require 'rewire'
 Backbone = require 'backbone'
 mediator = require '../../../lib/mediator'
-LoggedOutUser = require '../../../models/logged_out_user'
-rewire = require 'rewire'
+LoggedOutUser = rewire '../../../models/logged_out_user'
 jade = require 'jade'
 path = require 'path'
 fs = require 'fs'
@@ -26,6 +26,7 @@
       @AuthModalView.__set__ 'Cookies',
         set: sinon.stub()
         get: sinon.stub()
+      LoggedOutUser.__set__ 'sd', AP: {}
       sinon.stub @AuthModalView::, 'initialize'
       done()


set default app shortcuts implemented

set default app shortcuts implemented

--- components/buttons/KDButtonMenu.coffee
+++ components/buttons/KDButtonMenu.coffee
@@ -39,6 +39,8 @@

     @$(".chevron-ghost-wrapper").css ghostCss

+    log "bwit " + buttonWidth
+    log "mwit " + menuWidth
     @$().css
       top       : top
       left      : button.getX() + buttonWidth - menuWidth


chaging deleting opinions to not delete accounts  as well

--- server/app/bongo/models/messages/discussion.coffee
+++ server/app/bongo/models/messages/discussion.coffee
@@ -13,12 +13,32 @@

   @share()

+  schema = _.extend {}, jraphical.Message.schema, {
+    isLowQuality  : Boolean
+    counts        :
+      followers   :
+        type      : Number
+        default   : 0
+      following   :
+        type      : Number
+        default   : 0
+    originType  :
+      type      : String
+      required  : yes
+    originId    :
+      type      : ObjectId
+      required  : yes
+    deletedAt   : Date
+    deletedBy   : ObjectRef
+    meta        : require 'bongo/bundles/meta'
+  }
+
   @set
     emitFollowingActivities: yes
     taggedContentRole : 'reply'
     tagRole           : 'tag'
     sharedMethods : JPost.sharedMethods
-    schema        : JPost.schema
+    schema        : schema
     relationships     :
       comment         : JComment
       participant     :
@@ -57,12 +77,42 @@
       meta        : data.meta
     JPost.create.call @, client, codeSnip, callback

+  delete: secure ({connection:{delegate}}, callback)->
+    originId = @getAt 'originId'
+    unless delegate.getId().equals originId
+      callback new KodingError 'Access denied!'
+    else
+      id = @getId()
+      {getDeleteHelper} = Relationship
+      queue = [
+        getDeleteHelper {
+          targetId    : id
+          sourceName  : /Activity$/
+        }, 'source', -> queue.fin()
+        # getDeleteHelper {
+        #   targetName  : {$ne : 'JAccount'}
+        #   sourceId    : id
+        #   sourceName  : 'JOpinion'
+        # }, 'target', -> queue.fin()
+        ->
+          Relationship.remove {
+            targetId  : id
+            as        : 'opinion'
+          }, -> queue.fin()
+        => @remove -> queue.fin()
+      ]
+      dash queue, =>
+        log "deleted opinion"
+        @emit 'OpinionIsDeleted', 1
+        callback null
+
+
   modify: secure (client, data, callback)->
-    codeSnip =
+    opinion =
       title       : data.title
       body        : data.body
       meta        : data.meta
-    JPost::modify.call @, client, codeSnip, callback
+    JPost::modify.call @, client, opinion, callback

   #?>


ING Direct, not Chase.

ING Direct, not Chase.

--- application/chrome/content/wesabe/fi-scripts/com/ingdirect.coffee
+++ application/chrome/content/wesabe/fi-scripts/com/ingdirect.coffee
@@ -1,11 +1,11 @@
 wesabe.download.CompoundPlayer.register
   fid: 'com.ingdirect'
-  org: 'Chase'
+  org: 'ING Direct'

   players: [
     wesabe.download.OFXPlayer.create
       fid: 'com.ingdirect'
-      org: 'Chase'
+      org: 'ING Direct'

       fi:
         ofxFid: '031176110'
@@ -14,7 +14,7 @@

     wesabe.download.Player.create
       fid: 'com.ingdirect'
-      org: 'Chase'
+      org: 'ING Direct'

       dispatchFrames: false
       afterDownload: 'nextGoal'


Test instance variable for checking status

Otherwise it doesn't work and starts up a new runner everytime a file is modified

--- test/config/test-runner.coffee
+++ test/config/test-runner.coffee
@@ -15,8 +15,8 @@
   pendingSpecs: []

   runKarma: ->
-    return if isKarmaRunning or _.isEmpty(@pendingSpecs)
-    isKarmaRunning = true
+    return if @isKarmaRunning or _.isEmpty(@pendingSpecs)
+    @isKarmaRunning = true
     specs = _.unique @pendingSpecs
     gutil.log("[specs]", gutil.colors.green("testing #{specs.join(' ')}"))
     @pendingSpecs = []
@@ -28,7 +28,7 @@
     ], {stdio: 'inherit'} )

     child.on('exit', (exitCode) =>
-      isKarmaRunning = false
+      @isKarmaRunning = false
       duration = moment.duration(moment().diff(startAt))
       elapsed = duration.minutes() + ':' + duration.seconds()
       gutil.log("[test]", gutil.colors.green("done. #{specs.length} specs in #{elapsed}"))


Fix bug in comment construction

Fix bug in comment construction

--- examples/react-tut/main.coffee
+++ examples/react-tut/main.coffee
@@ -28,7 +28,7 @@
   $form.submit ->
     [name, text] = ($x.val().trim() for $x in [$name, $text])
     return false if name == '' or text == ''
-    args.onCommentSubmit({name, text})
+    args.onCommentSubmit({author: name, text: text})
     $x.val('') for $x in [$name, $text]
     false
   $form


Hydrator: unskip tests.

Hydrator: unskip tests.

--- test/OVirtResponseHydrator.test.coffee
+++ test/OVirtResponseHydrator.test.coffee
@@ -271,7 +271,7 @@
         expect(hydrator.exportCollections).to.be.called.once
         expect(hydrator.exportCollections).to.be.called.with 'collections'

-      it.skip "should populate collections for not root node", ->
+      it "should populate collections for not root node", ->
         hydrator = getCollectionsHydrator _isRootElememntXPath: no
         hydrator._collections = xpath: instances: 'collections'
         hydrator.hydrateCollections 'xpath', node = {}
@@ -649,7 +649,7 @@
       attrs = rel: "SPAM"
       hash[ATTRKEY] = attrs

-      it.skip "should return true if is link with rel attribute, href " +
+      it "should return true if is link with rel attribute, href " +
       "doesn't point to resource and isn't a search option", ->
         hydrator = getHydrator.withSpies.andStubs
           isLink: yes
@@ -668,7 +668,7 @@
         expect(hydrator._getAttributes).to.have.been.called.twice
         expect(hydrator._isResourceHref).to.have.been.called.once

-      it.skip "should return false for other cases", ->
+      it "should return false for other cases", ->
         hydrator = getHydrator.withSpies.andStubs
           isLink: yes
           isSearchOption: no


Look up JSON environment file path in the same directory as run script

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- config/generateShellEnv.coffee
+++ config/generateShellEnv.coffee
@@ -11,7 +11,7 @@
     return  if typeof node is 'object'
     add "KONFIG_#{path.join('_').replace(/\./g, "_")}".toUpperCase(), node

-  add 'ENV_JSON_FILE', "$KONFIG_PROJECTROOT/#{options.envFileName}.json"
+  add 'ENV_JSON_FILE', "$(dirname $0)/#{options.envFileName}.json"
   add 'KONFIG_JSON', '$(cat $ENV_JSON_FILE)'

   add 'KITE_HOME', '$KONFIG_KITEHOME'


grid: dont use RIO

grid: dont use RIO

--- app/_src/components/grid.coffee
+++ app/_src/components/grid.coffee
@@ -243,7 +243,8 @@
 		hasChangedBreakpoint = false

 	isUsingRIO: () ->
-		typeof ResponsiveIO is 'object'
+		# typeof ResponsiveIO is 'object'
+		return false

 	###
 		Callback for when it's exiting the page view
@@ -680,7 +681,6 @@
 		numAvailable.cols = Math.ceil( totalAvailableWidth / @getCellWidth('1') )
 		numAvailable.cells = numAvailable.cols * numAvailable.rows

-
 	###
 		Reads our data from the HTML
 		given it's keys.
@@ -695,6 +695,7 @@
 		IS_GRID_REPEATABLE = parseInt( @$context.data( DATA_IS_REPEATABLE ), 10 ) || 1
 		GRID_PATTERN = @getObjFromKey patternKey
 		data = @getObjFromKey modelKey
+

 	getObjFromKey: (key) =>
 		if ( !key )
@@ -704,4 +705,4 @@
 				throw new Error('Key for the model is incorrect: ' + key)
 			else
 				return FOURTEEN[ key ]
-		return false+		return false


fixed browsertest

fixed browsertest

--- test/browsertest/library2config.coffee
+++ test/browsertest/library2config.coffee
@@ -8,7 +8,7 @@
 				hashDigestLength: 5
 			module:
 				rules: [
-					{ test: /extra2?\.js/, loader: "raw!./node_modules/extra.loader.js!val?cacheable", enforce: "left" }
+					{ test: /extra2?\.js/, loader: "raw!./node_modules/extra.loader.js!val?cacheable", enforce: "post" }
 				]
 			amd:
 				fromOptions: true


round out tests for `traverseDependenciesSync`

--- test/dependency-resolution-sync.coffee
+++ test/dependency-resolution-sync.coffee
@@ -28,3 +28,17 @@
       '/b.js': 'require("./c")'
       '/c.js': ''
     arrayEq ['/a.js', '/b.js', '/c.js'], @deps '/a.js'
+
+  test 'circular dependencies', ->
+    fixtures
+      '/a.js': 'require("./b");'
+      '/b.js': 'require("./a")'
+    arrayEq ['/a.js', '/b.js'], @deps '/a.js'
+
+  test 'missing dependencies', ->
+    fixtures '/a.js': 'require("./b")'
+    throws -> @deps '/a.js'
+
+  test 'ignoreMissing option ignores missing dependencies', ->
+    fixtures '/a.js': 'require("./b")'
+    arrayEq ['/a.js'], @deps '/a.js', ignoreMissing: yes


reply more concisely

reply more concisely

--- src/diabetes.coffee
+++ src/diabetes.coffee
@@ -81,7 +81,7 @@

     reply = 'an average of ' + mgdl + ' mg/dL or '
     reply = reply + mmol + ' mmol/L'
-    reply = reply + ' is approximately equivalent to '
+    reply = reply + ' is about '
     reply = reply + dcct.toFixed(1) + '% (DCCT) or '
     reply = reply + dcctToIfcc(dcct).toFixed(0) + ' mmol/mol (IFCC)'
     msg.send reply
@@ -102,7 +102,7 @@

     reply = 'an a1c of ' + dcct + '% (DCCT) or '
     reply = reply + ifcc + ' mmol/mol (IFCC)'
-    reply = reply + ' is approximately equivalent to '
+    reply = reply + ' is about '
     reply = reply + mgdl.toFixed(0) + 'mg/dL or '
     reply = reply + mgdlToMmol(mgdl).toFixed(1) + ' mmol/L'
     msg.send reply


cannot add subdomains to shared.* domains

--- client/landing/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
@@ -128,9 +128,9 @@
     paymentController = KD.getSingleton('paymentController')
     group             = KD.getSingleton("groupsController").getCurrentGroup()
     domainOptions     = [
-      { title : "Create a #{nickname}.kd.io subdomain", value : "subdomain" }
-      { title : "I want to register a domain",          value : "new" }
-      { title : "I already have a domain",              value : "existing" }
+      { title : "Create a subdomain",          value : "subdomain" }
+      { title : "I want to register a domain", value : "new" }
+      { title : "I already have a domain",     value : "existing" }
     ]

     super
@@ -205,8 +205,6 @@
                     regYears.hide()
                     @needBilling no
                     "#{KD.utils.slugify firstName}s-subdomain"
-
-
             domainName                :
               placeholder             : "#{KD.utils.slugify firstName}s-new-domain.com"
               validate                :
@@ -243,7 +241,8 @@
       warn err  if err
       domainList = []
       for domain in userDomains
-        domainList.push {title:domain.domain, value:domain.domain}  unless domain.regYears > 0
+        if not domain.regYears > 0 and domain.domain.indexOf("shared") is -1
+          domainList.push {title:domain.domain, value:domain.domain}
       @forms["Domain Address"].inputs.domains.setSelectOptions domainList

   needBilling:(paymentRequired)->


Corrected the end point calculations

Corrected the end point calculations

This was a bad issue, and we should release again asap

--- src/document.coffee
+++ src/document.coffee
@@ -23,7 +23,7 @@
       piece = pieces[i]
       xstart = if i == startPiece then start - piece.position else 0
       xend = if i == endPiece then end - piece.position else piece.endPosition
-      result.push piece.text.substring(xstart, xend - xstart)
+      result.push piece.text.substring(xstart, xend)

     result.join("")


Fixed an issue where the filters weren’t working

--- src/document.coffee
+++ src/document.coffee
@@ -34,7 +34,7 @@

     replacer = (match, rest...) ->
       if match.length == 1
-        replaced = filters[match.charPointAt(0)]
+        replaced = filters[match.charCodeAt(0)]
         if replaced == 0
           ""
         else
@@ -45,7 +45,7 @@
         rest[0]

     matcher = /(?:[\x02\x05\x07\x08\x0a\x0d\u2018\u2019\u201c\u201d\u2002\u2003\u2012\u2013\u2014]|\x13(?:[^\x14]*\x14)?([^\x15]*)\x15)/g
-    text.replace replacer
+    text.replace(matcher, replacer)

   getBody: (shouldFilter) ->


dotchart: remove some extraneous code

dotchart: remove some extraneous code

--- src/dotchart.coffee
+++ src/dotchart.coffee
@@ -122,7 +122,6 @@
             scaledPoints = ({x:xscale(y[i]),y:yscale(x[i])} for i of x)
         else
             scaledPoints = ({x:xscale(x[i]),y:yscale(y[i])} for i of x)
-        scaledPoints = ({x:p.x, y:p.y} for p in scaledPoints)

         pointGroup = svg.append("g").attr("id", "points")
         points =


Fixed zapper coordinates computation.

Fixed zapper coordinates computation.

--- emulator/frontend/devices/adapters/zapper-adapter.coffee
+++ emulator/frontend/devices/adapters/zapper-adapter.coffee
@@ -20,8 +20,9 @@

     stateChanged: (state) ->
         rect = @videoManager.getCanvasRect()
-        x = (state.cursorX or 0) - rect.left
-        y = (state.cursorY or 0) - rect.top
+        scale = @videoManager.getScale()
+        x = ~~(((state.cursorX or 0) - rect.left) / scale)
+        y = ~~(((state.cursorY or 0) - rect.top) / scale)
         @zapper.setBeamPosition x, y

 module.exports = ZapperAdapter


cleaned up invitation test file

cleaned up invitation test file

--- workers/social/lib/social/models/invitation.test.coffee
+++ workers/social/lib/social/models/invitation.test.coffee
@@ -1,93 +1,99 @@
-{ argv }                    = require 'optimist'
-{ expect }                  = require "chai"
-{ env : { MONGO_URL } }     = process
-KONFIG                      = require('koding-config-manager').load("main.#{argv.c}")
-mongo                       = MONGO_URL or "mongodb://#{ KONFIG.mongo }"
-Bongo                       = require 'bongo'
-{ daisy }                   = Bongo
-JUser                       = require './user'
-JGroup                      = require './group'
-JAccount                    = require './account'
-JInvitation                 = require './invitation'
-JSession                    = require './session'
-{ generateRandomString
-  generateDummyClientData
+{ argv }                      = require 'optimist'
+{ expect }                    = require "chai"
+{ env : { MONGO_URL } }       = process
+KONFIG                        = require('koding-config-manager').load("main.#{argv.c}")
+mongo                         = MONGO_URL or "mongodb://#{ KONFIG.mongo }"
+Bongo                         = require 'bongo'
+{ daisy }                     = Bongo
+JUser                         = require './user'
+JGroup                        = require './group'
+JAccount                      = require './account'
+JSession                      = require './session'
+JInvitation                   = require './invitation'
+{ generateRandomEmail
+  generateDummyClient
+  generateRandomString
   generateDummyUserFormData } = require '../../../testhelper'

-bongo             = null
-adminClient       = null
-group             = null
+registerUserGenerateGroup = (adminClient, adminUserFormData, callback) ->

+  # create our admin user
+  JUser.convert adminClient, adminUserFormData, (err, data) ->
+    expect(err).to.not.exist
+    { account, newToken }   = data
+
+    # set credentials
+    adminClient.sessionToken        = newToken
+    adminClient.connection.delegate = account
+
+    # generate group creation data
+    groupData =
+      slug           : generateRandomString()
+      visibility     : 'visible'
+      title          : generateRandomString()
+      allowedDomains : [ 'koding.com' ]
+
+    # create the group
+    JGroup.create adminClient, groupData, account, (err, data) ->
+      group = data
+      adminClient.context.group = group.slug # set our new group's name
+      callback err, group
+
+
+# making sure we have mongo connection before tests
+beforeTests = -> before (done) ->
+
+  bongo = new Bongo
+    root   : __dirname
+    mongo  : mongo
+    models : ''
+
+  bongo.once 'dbClientReady', ->
+    done()
+
+
+# here we have actual tests
 runTests = ->

-  before (done) ->
-    adminClient = generateDummyClientData()
-    adminUserFormData = generateDummyUserFormData()
-
-    bongo = new Bongo
-      root   : __dirname
-      mongo  : mongo
-      models : '../../models'
-
-    bongo.once 'dbClientReady', ->
-
-      # creating a session before running tests
-      JSession.createSession (err, { session, account }) ->
-
-        adminClient.sessionToken        = session.token
-        adminClient.connection.delegate = account
-
-        # create our admin user
-        JUser.convert adminClient, adminUserFormData, (err, data) ->
-          expect(err).to.not.exist
-          { account, newToken }   = data
-
-          # set credentials
-          adminClient.sessionToken        = newToken
-          adminClient.connection.delegate = account
-
-          # generate group creation data
-          groupData =
-            slug           : generateRandomString()
-            visibility     : 'visible'
-            title          : generateRandomString()
-            allowedDomains : [ 'koding.com' ]
-
-          # create the group
-          JGroup.create adminClient, groupData, account, (err, data) ->
-            expect(err).to.not.exist
-
-            group = data
-            adminClient.context.group = group.slug # set our new group's name
-            done()
-
-
   describe 'workers.social.invitation.create', ->
-    client       = {}
-    userFormData = {}
-
-    beforeEach (done) ->
-      # create session data before each test
-      client = generateDummyClientData()
-      client.context.group = group.slug
-      userFormData = generateDummyUserFormData()
-
-      JSession.createSession (err, { session, account }) ->
-        expect(err).to.not.exist
-
-        client.sessionToken        = session.token
-        client.connection.delegate = account
-
-        done()
-

     describe 'if user is not a member yet', ->

       it 'should receive the invitation', (done) ->
+
+        group             = {}
+        client            = {}
+        adminClient       = {}
+        userFormData      = generateDummyUserFormData()
+        adminUserFormData = generateDummyUserFormData()
+
+
         queue = [

           ->
+            generateDummyClient { group : 'koding' }, (err, client_) ->
+              expect(err).to.not.exist
+              adminClient = client_
+              queue.next()
+
+          ->
+            # registering admin and generating a new group
+            registerUserGenerateGroup adminClient, adminUserFormData, (err, group_) ->
+              expect(err).to.not.exist
+              group = group_
+              queue.next()
+
+          ->
+            # generating new client object and setting it's group context
+            generateDummyClient { group : 'koding' }, (err, client_) ->
+              expect(err).to.not.exist
+              client = client_
+              client.context.group = group.slug
+              queue.next()
+
+          ->
+            # creating an invitation for the unregistered user
             invitationReq = {
               invitations:[
                 { email: userFormData.email }
@@ -99,10 +105,11 @@
               queue.next()

           ->
-              JInvitation.one { email: userFormData.email, groupName: client.context.group }, (err, invitation) ->
-                expect(err).to.not.exist
-                expect(invitation).to.exist
-                queue.next()
+            # expecting invitation to exist
+            JInvitation.one { email: userFormData.email, groupName: client.context.group }, (err, invitation) ->
+              expect(err).to.not.exist
+              expect(invitation).to.exist
+              queue.next()

           -> done()

@@ -115,14 +122,44 @@

       it 'should not receive the invitation', (done) ->

+        group             = {}
+        client            = {}
+        adminClient       = {}
+        userFormData      = generateDummyUserFormData()
+        adminUserFormData = generateDummyUserFormData()
+
         queue = [

           ->
+            generateDummyClient { group : 'koding' }, (err, client_) ->
+              expect(err).to.not.exist
+              client = client_
+              client.context.group = group.slug
+              queue.next()
+
+          ->
+            generateDummyClient { group : 'koding' }, (err, client_) ->
+              expect(err).to.not.exist
+              adminClient = client_
+              queue.next()
+
+          ->
+            # registering admin and generating a new group
+            registerUserGenerateGroup adminClient, adminUserFormData, (err, group_) ->
+              expect(err).to.not.exist
+              group = group_
+              # setting client's group slug as newly generated group's slug
+              client.context.group = group.slug
+              queue.next()
+
+          ->
+            # registering user
             JUser.convert client, userFormData, (err) ->
               expect(err).to.not.exist
               queue.next()

           ->
+            # trying to create an invitation for user's email
             invitationReq = {
               invitations:[
                 { email: userFormData.email }
@@ -134,6 +171,7 @@
               queue.next()

           ->
+            # expecting invitation not to be created
             JInvitation.one { email: userFormData.email, groupName: group.slug }, (err, invitation) ->
               expect(err).to.not.exist
               expect(invitation).to.not.exist
@@ -145,4 +183,7 @@

         daisy queue

+
+beforeTests()
+
 runTests()


indentation and comment fixes

indentation and comment fixes

--- workers/social/lib/social/models/user/index.test.coffee
+++ workers/social/lib/social/models/user/index.test.coffee
@@ -69,11 +69,11 @@

     it 'should be to able login user when data is valid', (done) ->

-      session           = null
-      lastLoginDate     = null
+      session                = null
+      lastLoginDate          = null
       # generating user info with random username and password
-      { username, password }    = userInfo = generateUserInfo()
-      loginCredentials          = generateCredentials { username, password }
+      { username, password } = userInfo = generateUserInfo()
+      loginCredentials       = generateCredentials { username, password }

       queue = [

@@ -91,18 +91,20 @@
             queue.next()

         ->
+          # keeping last login date
           JUser.one { username : loginCredentials.username }, (err, user) ->
             expect(err).to.not.exist
             lastLoginDate = user.lastLoginDate
             queue.next()

         ->
+          # expecting another successful login
           JUser.login clientId, loginCredentials, (err) ->
             expect(err).to.not.exist
             queue.next()

         ->
-          # expecting login date to be saved successfully
+          # expecting last login date to be changed
           JUser.one { username : loginCredentials.username }, (err, user) ->
             expect(err)           .to.not.exist
             expect(lastLoginDate) .not.to.be.equal user.lastLoginDate
@@ -178,8 +180,8 @@

     it 'should handle groups correctly', (done) ->

-      loginCredentials            = generateCredentials()
-      loginCredentials.groupName  = 'invalidgroupName'
+      loginCredentials = generateCredentials
+        groupName : 'invalidgroupName'

       queue = [

@@ -197,8 +199,8 @@

     it 'should pass error if account is not found', (done) ->

-      { username, password }    = userInfo = generateUserInfo()
-      loginCredentials          = generateCredentials { username, password }
+      { username, password } = userInfo = generateUserInfo()
+      loginCredentials       = generateCredentials { username, password }

       queue = [

@@ -251,13 +253,13 @@

         ->
           # blocking user for 1 day
-          untilDate = new Date(Date.now() + 1000 * 60 * 24)
+          untilDate = new Date(Date.now() + 1000 * 60 * 60 * 24)
           user.block untilDate, (err) ->
             expect(err).to.not.exist
             queue.next()

         ->
-          # expecting login attempt to fail
+          # expecting login attempt to fail and return blocked message
           JUser.login clientId, loginCredentials, (err)->
             toDate = user.blockedUntil.toUTCString()
             expect(err.message).to.be.equal JUser.getBlockedMessage toDate
@@ -305,6 +307,7 @@
           setUserPasswordStatus queue, username, 'valid'

         ->
+          # expecting successful login
           JUser.login clientId, loginCredentials, (err)->
             expect(err).to.not.exist
             queue.next()
@@ -313,6 +316,7 @@
           setUserPasswordStatus queue, username, 'needs reset'

         ->
+          # expecting unsuccessful login attempt
           JUser.login clientId, loginCredentials, (err)->
             expect(err).to.exist
             queue.next()
@@ -331,6 +335,7 @@

       loginCredentials = generateCredentials()

+      # when client id is not specified, a new session should be created
       JUser.login null, loginCredentials, (err) ->
         expect(err).to.not.exist
         done()


Work around clever not having an email

Work around clever not having an email

--- server/middleware/auth.coffee
+++ server/middleware/auth.coffee
@@ -133,9 +133,10 @@
     user = yield User.findOne({cleverID: userInfo.data.id})
     unless user
       email = lookup.data.email
-      existingUserWithEmail = yield User.findOne({emailLower: email.toLowerCase()})
-
-      if existingUserWithEmail
+      if email?
+        existingUserWithEmail = yield User.findOne({emailLower: email.toLowerCase()})
+
+      if existingUserWithEmail or not email?
         email = "#{userInfo.data.id}@clever.user"
         existingUserWithEmail = yield User.findOne({emailLower: email.toLowerCase()})
       if existingUserWithEmail


Fixed: byState request should be done only on unarchived tasks

--- server/models/requests.coffee
+++ server/models/requests.coffee
@@ -5,7 +5,9 @@
 module.exports =
     tasky:
         all: americano.defaultRequests.all
-        byState: (doc) -> emit doc.state, doc
+        byState: (doc) ->
+            unless doc.isArchived
+                emit doc.state, doc
         byArchiveState: (doc) -> emit doc.isArchived, doc
         byOrder: (doc) ->
             unless doc.isArchived


add missing request for folder listing

add missing request for folder listing

--- server/models/requests.coffee
+++ server/models/requests.coffee
@@ -9,3 +9,6 @@
     bankoperation:
         byDate: (doc) ->
             emit doc.date, doc
+
+    folder:
+        byFullPath: (doc) -> emit "#{doc.path}/#{doc.name}", doc


Refactored

Refactored

--- server/tools/paypalapi.coffee
+++ server/tools/paypalapi.coffee
@@ -22,12 +22,7 @@
           cvv2: creditCardInfo.cvv2.toString()
           first_name: creditCardInfo.first_name.toString()
           last_name: creditCardInfo.last_name.toString()
-          billing_address:
-            line1: billing_address.line1
-            city: billing_address.city
-            state: billing_address.state
-            postal_code: billing_address.postal_code
-            country_code: billing_address.country_code
+          billing_address: billing_address
         ]

       transactions: [


Move plugin loading over to promises

Move plugin loading over to promises

--- src/generate.coffee
+++ src/generate.coffee
@@ -586,59 +586,72 @@
   ], callback

 loadBundledPlugins = (callback) ->
-  loadPlugins [path.join(__dirname, 'plugins')], (err, plugins) ->
-    if err then return callback err
-
-    bundledPlugins = plugins
-    # Copy default filters, but don't overwrite
-    for key, filter of tinyliquid.filters
-      unless key of bundledPlugins.filters
-        bundledPlugins.filters[key] = filter
-
-    callback()
+  loadPlugins([path.join __dirname, 'plugins'])
+    .then (plugins) ->
+      bundledPlugins = plugins
+      # Copy default filters, but don't overwrite
+      for key, filter of tinyliquid.filters
+        unless key of bundledPlugins.filters
+          bundledPlugins.filters[key] = filter
+      callback()
+    .fail (err) ->
+      callback err

 loadSitePlugins = (config, callback) ->
   # Resolve directories relative to source
   dirs = config.plugins.map (dir) ->
     path.resolve config.source, dir
   async.filter dirs, fs.exists, (results) ->
-    loadPlugins results, callback
-
-loadPlugins = (dirs, callback) ->
-  plugins =
-    filters: {}
-    tags: {}
-    converters: []
-    generators: []
-
-  unless dirs.length
-    return callback null, plugins
-
+    loadPlugins(results)
+      .then (plugins) ->
+        callback null, plugins
+      .fail (err) -> callback err
+
+loadPlugins = (dirs) ->
   log.verbose "generate", "Looking for plugins in: %s", dirs.join ', '
-  async.map dirs, fs.readdir, (err, results) ->
-    allFiles = []
-    for result, i in results
-      continue unless result
-      # Map relative paths
-      allFiles = allFiles.concat(result.map (f) -> path.join dirs[i], f)
-
-    for file in allFiles
-      ext = path.extname file
-      if (ext is '.js' or ext is '.coffee') or fs.statSync(file).isDirectory()
+  Q.all(dirs.map (dir) -> Q.nfcall fs.readdir, dir)
+    .then (dirListings) ->
+      plugins =
+        filters: {}
+        tags: {}
+        converters: []
+        generators: []
+
+      # Make a single list of eligible files
+      allFiles = dirListings
+        # Resolve Paths
+        .map (listing, i) ->
+          listing.map (f) -> path.join dirs[i], f
+        # Merge lists
+        .reduceRight(((prev, listing) -> prev.concat listing), [])
+        # Remove non-code files
+        .filter (f) ->
+          ext = path.extname f
+          ext in ['.js','.coffee'] or fs.statSync(f).isDirectory()
+
+      log.silly "generate", "Found plugins %s in %s",
+        allFiles.map(path.basename), dirs
+
+      for file in allFiles
         log.verbose "generate", "Loading plugin: %s", file
         # Load file
         plugin = require file
+
         if 'filters' of plugin
           for key, filter of plugin['filters']
+            log.silly "generate", "Found filter for %s", key
             plugins.filters[key] = filter
         if 'tags' of plugin
           for key, tag of plugin['tags']
+            log.silly "generate", "Found tag for %s", key
             plugins.tags[key] = tag
         if 'converters' of plugin
           for key, converter of plugin['converters']
+            log.silly "generate", "Found converter for %s", key
             plugins.converters.push converter
         if 'generators' of plugin
           for key, generator of plugin['generators']
+            log.silly "generate", "Found generator for %s", key
             plugins.generators.push generator

-    callback null, plugins
+      plugins


minify more assets

minify more assets

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -98,10 +98,15 @@
 							exclude: ["libs"]
 						}, {
 							name: "ide",
-							exclude: ["libs", "libs/jquery-layout"]
+							exclude: ["libs"]
 						}, {
 							name: "libs"
+						},{
+							name: "libs/pdf.worker"
+						},{
+							name: "ace/mode-latex"
 						}
+
 					]

 		clean:


Fix javascript compiling when there are no modules

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -244,11 +244,11 @@
 	grunt.registerTask 'compile:modules:client', 'Compile all the module client side code', moduleCompileClientTasks
 	grunt.registerTask 'compile:modules:inject_clientside_includes', () ->
 		content = fs.readFileSync("public/js/ide.js").toString()
-		content = content.replace(/"__IDE_CLIENTSIDE_INCLUDES__"/g, moduleIdeClientSideIncludes.map((i) -> "\"#{i}\"").join(", "))
+		content = content.replace(/, "__IDE_CLIENTSIDE_INCLUDES__"/g, moduleIdeClientSideIncludes.map((i) -> ", \"#{i}\"").join(""))
 		fs.writeFileSync "public/js/ide.js", content

 		content = fs.readFileSync("public/js/main.js").toString()
-		content = content.replace(/"__MAIN_CLIENTSIDE_INCLUDES__"/g, moduleMainClientSideIncludes.map((i) -> "\"#{i}\"").join(", "))
+		content = content.replace(/, "__MAIN_CLIENTSIDE_INCLUDES__"/g, moduleMainClientSideIncludes.map((i) -> ", \"#{i}\"").join(""))
 		fs.writeFileSync "public/js/main.js", content

 	grunt.registerTask 'compile:server', 'Compile the server side coffee script', ['clean:app', 'coffee:app', 'coffee:app_dir', 'compile:modules:server']


fix comprehension (array, not object)

fix comprehension (array, not object)

--- app/assets/javascripts/components/dynamic-router.cjsx
+++ app/assets/javascripts/components/dynamic-router.cjsx
@@ -24,7 +24,10 @@

   render: ->
     return null unless @state.pages? # do nothing until project loads from API
-    workflows = @state.project.workflows
+    workflows = @state.project.workflows
+
+    # for workflow in [workflows...]
+    #   if workflow.key == 'transcribe' then console.log 'BALS: ', workflow else console.log 'BLAH'

     <div className="panoptes-main">
       <MainHeader pages={@state.pages} />
@@ -39,12 +42,12 @@
             path='/mark'
             handler={Mark}
             name='mark'
-            workflow={workflow for workflow, key of workflows when key is 'mark'} />
+            workflow={workflow for workflow in workflows when workflow.key is 'mark'} />
           <Route
             path='/transcribe'
             handler={Transcribe}
             name='transcribe'
-            workflow={workflow for workflow, key of workflows when key is 'transcribe'} />
+            workflow={workflow for workflow in workflows when workflow.key is 'transcribe'} />

           { @state.pages?.map (page, key) =>
               <Route


Fix mirroring of the grid

Fix mirroring of the grid

--- src/gridview.coffee
+++ src/gridview.coffee
@@ -48,7 +48,7 @@
             [sx, sy, sw, sh] = sprites[element] ? sprites['unknown']
             context.drawImage spritesheet,
                               sx, sy, sw, sh,
-                              r*CELL_SIZE, c*CELL_SIZE,
+                              c*CELL_SIZE, r*CELL_SIZE,
                               CELL_SIZE, CELL_SIZE

   clicks


EDSC-604 remove the autocomplete text from placename keywords

--- app/assets/javascripts/modules/autocomplete.js.coffee
+++ app/assets/javascripts/modules/autocomplete.js.coffee
@@ -15,7 +15,7 @@
     engine.initialize()

     uiOpts =
-      hint: true
+      hint: false
       highlight: true
       minLength: 1
     dsOpts =


Set participants based on select.

Set participants based on select.

--- app/assets/javascripts/overlays/new_message.js.coffee
+++ app/assets/javascripts/overlays/new_message.js.coffee
@@ -3,21 +3,29 @@
 Tahi.overlays.newMessage =
   overlay: React.createClass
     componentDidMount: ->
+      $.getJSON '/users/chosen_options', (data) =>
+        @setState userModels: data.users

     getInitialState: ->
       participants: [Tahi.currentUser]
-      addableUsers: []
+      userModels: [Tahi.currentUser]

     chosenParticipants: ->
-      _.map @state.addableUsers, (p) ->
-        [p.id, p.name]
+      _.map @selectableUsers(), (p) ->
+        [p.id, p.fullName]
+
+
+    selectableUsers: ->
+      pIds = @participantIds()
+      _.reject @state.userModels, (u) ->
+        _.contains pIds, u.id

     renderParticipants: ->
       {UserThumbnail} = Tahi.overlays.components
       {li} = React.DOM
       _.map @state.participants, (p) ->
         (li {className: 'participant'},
-          (UserThumbnail {name: p.name}))
+          (UserThumbnail {name: p.fullName}))

     participantIds: ->
       _.pluck @state.participants, 'id'
@@ -30,7 +38,8 @@
       @refs.form.submit()

     addParticipant:(e) ->
-      debugger
+      newParticipant = _.findWhere @state.userModels, {id: parseInt(e.target.value)}
+      @setState participants: (@state.participants.concat(newParticipant))

     render: ->
       {div, button, footer, option, header, a, h2, main, ul, li, input, textarea, img, label} = React.DOM


Set default volume to 0.5 for Audio resources

--- app/assets/javascripts/plugins/mediaelement.js.coffee
+++ app/assets/javascripts/plugins/mediaelement.js.coffee
@@ -10,6 +10,7 @@
         alwaysShowControls: true,
         features: ['playpause','progress', 'current', 'duration', 'volume', 'logo'],
         audioVolume: 'horizontal'
+        startVolume: 0.5
         logo:
           text: gon.site_title,
           link: gon.root_url


Remove expiration info from submitting with the form

Fixes #113

--- app/assets/javascripts/store/gateway/stripe.js.coffee
+++ app/assets/javascripts/store/gateway/stripe.js.coffee
@@ -33,8 +33,4 @@
     # insert the token into the form so it gets submitted to the server
     paymentMethodId = Spree.stripePaymentMethod.prop('id').split("_")[2]
     Spree.stripePaymentMethod.append("<input type='hidden' class='stripeToken' name='payment_source[" + paymentMethodId  + "][gateway_payment_profile_id]' value='" + token + "'/>");
-    # insert additional card info to save to the payment source
-    expiration = $('.cardExpiry:visible').payment('cardExpiryVal')
-    Spree.stripePaymentMethod.append("<input type='hidden' name='payment_source[" + paymentMethodId + "][month]' value='" + expiration.month + "'/>");
-    Spree.stripePaymentMethod.append("<input type='hidden' name='payment_source[" + paymentMethodId + "][year]' value='" + expiration.year + "'/>");
     Spree.stripePaymentMethod.parents("form").get(0).submit();


fixing an issue with history in firefox

fixing an issue with history in firefox

--- vendor/assets/javascripts/mercury/page_editor.js.coffee
+++ vendor/assets/javascripts/mercury/page_editor.js.coffee
@@ -43,7 +43,7 @@
       iframeWindow = @iframe.get(0).contentWindow
       jQuery.globalEval = (data) -> (iframeWindow.execScript || (data) -> iframeWindow["eval"].call(iframeWindow, data))(data) if (data && /\S/.test(data))
       iframeWindow.Mercury = Mercury
-      iframeWindow["eval"].call(iframeWindow, "history.pushState = function(obj, title, url) { top.history.pushState(obj, title, url) }")
+      iframeWindow["eval"].call(iframeWindow, "try{ (History ? History.prototype : history).pushState = function(args, title, url) { top.history.pushState(args, title, url) } }catch(e){}")

       @bindEvents()
       @resize()


Return correct error on fs read error

Return correct error on fs read error

--- src/imgcache.coffee
+++ src/imgcache.coffee
@@ -83,7 +83,7 @@
                 console.log(info)
                 request(url).pipe(fs.createWriteStream(info.path)).on 'close', ->
                   fs.readFile info.path, (err, file) ->
-                    callback error, file, info
+                    callback err, file, info

     iscached: (url) ->
       imagepath = cachedir + '/' + getrelativepath(url)


do not refresh when short-op is busy

do not refresh when short-op is busy

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -150,6 +150,7 @@
   $ document.createElement 'i'
     .addClass "fa fa-#{icon}"
 @shortOperation = (title, operation)=>
+  @shortOperationBusy = true
   $ '#loadingText'
     .text title||''
   $ 'body'
@@ -160,6 +161,7 @@
     .text '取消'
     .hide()
   operationDone = (e)=>
+    @shortOperationBusy = false
     $ 'body'
       .removeClass 'loading'
     if e
@@ -621,7 +623,7 @@
     .messenger()
   @async.forever (doneForever)=>
       setTimeout =>
-          if @m_active
+          if @m_active && !@shortOperationBusy
             @refresh_filelist (e)=>
               if e
                 msg = Messenger().post


Logout click

Logout click

--- public/javascripts/main_script.coffee
+++ public/javascripts/main_script.coffee
@@ -113,6 +113,14 @@
   $('button').click (e) ->
     e.preventDefault()

+  $('#userActions .logout').click ->
+    submitUserData({
+      method: 'logout'
+    }).then (res) ->
+      res = JSON.parse res
+      if res.status == 'success'
+        location.reload()
+
   return
 # ---
 # generated by js2coffee 2.2.0


Environment: DomainContainer: Let the Domain container works

--- client/Environments/views/scene/environmentdomaincontainer.coffee
+++ client/Environments/views/scene/environmentdomaincontainer.coffee
@@ -6,30 +6,47 @@
     options.title     = 'Domains'
     super options, data

+    # Plus button on domainsContainer opens up the domainCreateModal
+    @on 'PlusButtonClicked', =>
+      return unless KD.isLoggedIn()
+        new KDNotificationView title: "You need to login to add a new domain."
+
+      domainCreateForm = @getDomainCreateForm()
+
+      new KDModalView
+        title          : "Add Domain"
+        view           : domainCreateForm
+        width          : 700
+        buttons        :
+          createButton :
+            title      : "Create"
+            style      : "modal-clean-green"
+            callback   : =>
+              domainCreateForm.createSubDomain()
+
   loadItems:->

     KD.whoami().fetchDomains (err, domains)=>
-      if err or domains.length is 0
+
+      if err or not domains or domains.length is 0
         @emit "DataLoaded"
         return warn "Failed to fetch domains", err  if err

-      addedCount = 0
+      @removeAllItems()

-      KD.singletons.vmController.fetchGroupVMs yes, (err, vms)=>
+      domains.forEach (domain, index)=>

-        @removeAllItems()
+        @addItem
+          title       : domain.domain
+          description : $.timeago domain.createdAt
+          activated   : yes
+          aliases     : domain.hostnameAlias
+          domain      : domain

-        domains.forEach (domain)=>
+        if index is domains.length - 1 then @emit "DataLoaded"

-          if domain
-            @addItem
-              title       : domain.domain
-              description : $.timeago domain.createdAt
-              activated   : yes
-              aliases     : domain.hostnameAlias
-              domain      : domain
-
-          addedCount++
-
-          if addedCount is domains.length
-            @emit "DataLoaded"
+  getDomainCreateForm: ->
+    domainCreateForm = new DomainCreateForm
+    @on "itemRemoved", domainCreateForm.bound "updateDomains"
+    domainCreateForm.on "DomainSaved", @bound "loadItems"
+    return domainCreateForm