jupyter: exporting stdin to file

jupyter: exporting stdin to file

--- src/smc-webapp/jupyter/export-to-ipynb.coffee
+++ src/smc-webapp/jupyter/export-to-ipynb.coffee
@@ -28,14 +28,15 @@
 # Return ipynb version of the given cell as Python object
 cell_to_ipynb = (id, opts) ->
     cell = opts.cells.get(id)
+    metadata = {}
     obj =
         cell_type : cell.get('cell_type') ? 'code'
         source    : cell.get('input')
-        metadata  : {}
+        metadata  : metadata
     if cell.get('collapsed')
-        obj.metadata.collapsed = true
+        metadata.collapsed = true
     if cell.get('scrolled')
-        obj.metadata.scrolled = true
+        metadata.scrolled = true

     exec_count = cell.get('exec_count') ? 0
     if obj.cell_type == 'code'
@@ -46,6 +47,13 @@
         obj.outputs = ipynb_outputs(output, exec_count, opts.more_output?[id], opts.blob_store)
     else if not obj.outputs? and obj.cell_type == 'code'
         obj.outputs = [] # annoying requirement of ipynb file format.
+    for n, x of obj.outputs
+        if x.cocalc?
+            # alternative version of cell that official Jupyter doesn' support can only
+            # stored in the **cell-level** metadata, not output.
+            metadata.cocalc ?= {outputs:{}}
+            metadata.cocalc.outputs[n] = x.cocalc
+            delete x.cocalc
     return obj

 ipynb_outputs = (output, exec_count, more_output, blob_store) ->
@@ -99,6 +107,15 @@
         output_n.execution_count = exec_count
     else if output_n.name?
         output_n.output_type = 'stream'
+        if output_n.name == 'input'
+            process_stdin_output(output_n)
     else if output_n.ename?
         output_n.output_type = 'error'
     return
+
+process_stdin_output = (output) ->
+    output.cocalc = misc.deep_copy(output)
+    output.name = 'stdout'
+    output.text = output.opts.prompt + ' ' + (output.value ? '')
+    delete output.opts
+    delete output.value


add i18n support

add i18n support

--- src/module.coffee
+++ src/module.coffee
@@ -26,6 +26,15 @@

   triggerHandler: (args...) ->
     $(@).triggerHandler args...
+
+  _i18n: (key) ->
+    cls = @constructor
+    cls.i18n[cls.locale]?[key]
+
+  @i18n:
+    'zh-CN': {}
+
+  @locale: 'zh-CN'

 class Widget extends Module


Issue #1024.

Issue #1024.

--- nodes.coffee
+++ nodes.coffee
@@ -899,7 +899,9 @@
     top       = o.level is LEVEL_TOP
     {value}   = this
     {objects} = @variable.base
-    return value.compile o unless olen = objects.length
+    unless olen = objects.length
+      code = value.compile o
+      return if o.level >= LEVEL_OP then "(#{code})" else code
     isObject = @variable.isObject()
     if top and olen is 1 and (obj = objects[0]) not instanceof Splat
       # Unroll simplest cases: `{v} = x` -> `v = x.v`


adding a traverse method to the AST, so we can do fancy processing from external scripts.

--- nodes.coffee
+++ nodes.coffee
@@ -94,8 +94,15 @@
 BaseNode::contains: (block) ->
   for node in @children
     return true if block(node)
-    return true if node instanceof BaseNode and node.contains block
+    return true if node.contains and node.contains block
   false
+
+# Perform an in-order traversal of the AST.
+BaseNode::traverse: (block) ->
+  for node in @children
+    block node
+    node.traverse block if node.traverse
+

 # toString representation of the node, for inspecting the parse tree.
 BaseNode::toString: (idt) ->
@@ -670,10 +677,16 @@
   top_sensitive: ->
     true

+  real_children: ->
+    flatten [@params, @body.expressions]
+
+  traverse: (block) ->
+    block this
+    block(child) for child in @real_children()
+
   toString: (idt) ->
     idt ||= ''
-    children: flatten [@params, @body.expressions]
-    '\n' + idt + @type + (child.toString(idt + TAB) for child in children).join('')
+    '\n' + idt + @type + (child.toString(idt + TAB) for child in @real_children()).join('')

 }


Add support for mac and linux with wine

Add support for mac and linux with wine

--- src/rcedit.coffee
+++ src/rcedit.coffee
@@ -20,6 +20,10 @@
       args.push "--set-#{name}"
       args.push options[name]

+  if process.platform != "win32"
+    args.unshift(rcedit)
+    rcedit = "wine"
+
   child = spawn rcedit, args

   stderr = ''


Added @ in front of username in "Reminder for..." message so that the user is pinged.

--- src/remind.coffee
+++ src/remind.coffee
@@ -73,7 +73,7 @@
         robot.brain.data[HUBOT_REMIND_KEY] or= []

     Reminder = new Reminder (user, message, executionDate) ->
-        message = "Reminder for #{user.name}: #{message}"
+        message = "Reminder for @#{user.name}: #{message}"
         robot.send {room: user.room, user: user}, message
         old = [user, message, executionDate]
         BrainReminders().splice BrainReminders().indexOf old, 1


changes to stage config from web0

changes to stage config from web0

--- config/stage.coffee
+++ config/stage.coffee
@@ -43,11 +43,12 @@
     host        : 'localhost'
     protocol    : 'http:'
     defaultFromAddress: 'hello@koding.com'
-  guestCleanup:
+  guests:
      # define this to limit the number of guset accounts
      # to be cleaned up per collection cycle.
     batchSize   : undefined
-    cron        : '*/10 * * * * *'
+    cleanupCron : '*/10 * * * * *'
+    poolSize    : 1e4
   logger        :
     mq          :
       host      : 'localhost'


add in select to total record count

add in select to total record count

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -153,11 +153,15 @@
                 editOk: @collection().editOk?(record)
                 deleteOk: @collection().deleteOk?(record)

+        recordDisplayStop = @skip() + @increment
+        if recordDisplayStop > @_sess("recordCount")
+            recordDisplayStop = @_sess("recordCount")
+
         theData =
             haveData: records? and (records.length > 0 or @_sess("recordCount") > 0)
             tableTitle: @_tableTitle()
             recordDisplayStart: @skip() + 1
-            recordDisplayStop: @skip() + @increment
+            recordDisplayStop: recordDisplayStop
             recordName: @_recordName()
             records: recordData
             headers: @headers


Change player alpha to 240

Change player alpha to 240

--- coffee/models/piece.coffee
+++ coffee/models/piece.coffee
@@ -38,7 +38,7 @@
 ]

 class Tetrus.Piece extends Batman.Object
-  @playerAlpha = 255
+  @playerAlpha = 240
   @peerAlpha = 200

   constructor: (peer, @storage) ->


small fix

small fix

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -157,17 +157,32 @@
         data = if SiriusUtils.is_array(@data) then @data else [@data]
         data = SiriusApplication.adapter.get_property(e, data)
         merge = [].concat([], [e], data)
-        if @guard && @guard.apply(null, merge)
+        if @guard
+          if @guard.apply(null, merge)
+            @before()
+            @action.apply(null, merge)
+            @after()
+        else
           @before()
           @action.apply(null, merge)
           @after()
       else
-        if @guard && @guard.apply(null, args)
+        if @guard
+          if @guard.apply(null, [e])
+            @before()
+            @action.apply(null, [e])
+            @after()
+        else
           @before()
           @action.apply(null, [e])
           @after()
     else
-      if @guard && @guard.apply(null, args)
+      if @guard
+        if @guard.apply(null, args)
+          @before()
+          @action.apply(null, args)
+          @after()
+      else
         @before()
         @action.apply(null, args)
         @after()


Now passed-in data is shown in the view (No action is working)

--- app/assets/javascripts/components/IngredientSuggestions/app.js.coffee
+++ app/assets/javascripts/components/IngredientSuggestions/app.js.coffee
@@ -1,6 +1,4 @@
 window.loadIngredientSuggestionsEditor = (options) ->
-
-  console.log options

   # Constants (Action types)

@@ -11,9 +9,10 @@
   # Stores

   IngredientSuggestionsStore = Fluxxor.createStore
-    initialize: (options) ->
+    initialize: ->

-      @ingredients = options.ingredients || []
+      @ingredients = options || []
+      # console.log @ingredients  # Debug

       @bindActions(constants.UPDATE_INGREDIENT, @onUpdateIngredient,
                    constants.DELETE_INGREDIENT, @onDeleteIngredient)
@@ -93,7 +92,7 @@
       ingredients = @state.ingredients.map (ingredient) ->
         <IngredientSuggestion ingredient={ingredient}
                               key={ingredient.id}
-                              flux={@props.flux} />
+                              flux={flux} />
       <div>
         {ingredients}
       </div>


[improvement] Allowed cancelling drag operations with ESC (5m)

--- app/assets/javascripts/houston/roadmap/views/edit_roadmap_view.coffee
+++ app/assets/javascripts/houston/roadmap/views/edit_roadmap_view.coffee
@@ -9,8 +9,11 @@
     @project = options.project
     super(milestones, options)
     $(document.body).on 'keyup', (e)=>
-      return unless @$newMilestone and @supportsCreate
-      @cancelCreate() if e.keyCode is 27
+      if e.keyCode is 27
+        @cancelCreate() if @$newMilestone and @supportsCreate
+        if @drag
+          @update()
+          @drag = null

   createMilestone: (callback)->
@@ -187,7 +190,7 @@
       start: _.bind(@onStartDrag, @)

       drag: (e, ui)=>
-        return unless @drag
+        return false unless @drag
         @initializeDrag() unless @drag.band is @drag.bandOver

         ui.position.left = Math.max(ui.position.left, @drag.minLeft)
@@ -207,7 +210,8 @@
         $(e.target).resizable 'option', 'grid', false
         $(e.target).draggable 'option', 'grid', false

-      revert: ($target)-> !$target or !$target.is('.roadmap-band')
+      revert: ($target)->
+        !$target or !$target.is('.roadmap-band')

   onStartDrag: (e, ui)->


Split update as per Jason

Split update as per Jason

--- app/lib/splits.coffee
+++ app/lib/splits.coffee
@@ -10,34 +10,39 @@
     a:
       header: -> ''
       body: -> ''
-      isShown: -> true
+      isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 1

     b:
+      header: -> ''
+      body: -> ''
+      isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 3
+
+    c:
       header: -> 'Good job'
       body: (userCount) -> "You and #{ userCount } other Zooniverse volunteers have contributed to Cyclone Center."
       isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 1

-    c:
+    d:
       header: -> 'Good job'
       body: (userCount) -> "You and #{ userCount } other Zooniverse volunteers have contributed to Cyclone Center."
       isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 3

-    d:
+    e:
       header: -> 'Good job'
-      body: -> 'You just successfully finished classifying satellite images of tropical cyclones'
+      body: -> 'You just successfully finished classifying satellite images of tropical cyclones.'
       isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 1

-    e:
+    f:
       header: -> 'Good job'
       body: -> 'You just successfully finished classifying satellite images of tropical cyclones.'
       isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 3

-    f:
+    g:
       header: -> 'Good job'
       body: -> 'Your work is directly helping the science of meteorology to better understand the formation and development of tropical cyclones.'
       isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 1

-    g:
+    h:
       header: -> 'Good job'
       body: -> 'Your work is directly helping the science of meteorology to better understand the formation and development of tropical cyclones.'
       isShown: -> User.current.project.reveal_count is undefined or User.current.project.reveal_count <= 3


Allow views to be appended in elements other than a div

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -9,26 +9,29 @@
       @el = el

     show: (view) ->
-      @clean()
-      @views = null
+      @empty()
       @append(view)

     append: (view) ->
+      @appendAs('div', view)
+
+    appendAs: (type, view) ->
       @$el = @parent.$el.find(@el)
       view.parent = @parent
       @views ?= []
       @views.push(view)
-      view.setElement($('<div>').appendTo(@$el)).render()
+      view.setElement($("<#{type}>").appendTo(@$el)).render()

-    clean: () ->
+    empty: () ->
       _.each @views, (view) ->
         view.close()

       @$el?.empty()
       @$el = null
+      @views = null

     close: () ->
-      @clean()
+      empty()
       delete @[key] for key of @

   class Regions


Make listing headers stay in place

Make listing headers stay in place

--- src/smc-webapp/share/public-paths-browser.cjsx
+++ src/smc-webapp/share/public-paths-browser.cjsx
@@ -7,7 +7,7 @@
 {Space, TimeAgoElement} = require('../r_misc')

 INDEX_STYLE =
-    margin     : '15px 30px'
+    margin     : '0px 30px 15px 30px'
     overflow   : 'auto'
     height     : '100%'
     background : 'white'
@@ -55,7 +55,7 @@

     render_headings: ->
-

+

                 Path

@@ -105,9 +105,8 @@
                 <Space />
                 {@render_next_page()}

-
+            {@render_headings()}

-                {@render_headings()}
                 {@render_index()}


Update screens.coffee

Update screens.coffee

--- src/tasks/build/after/local/ios/screens.coffee
+++ src/tasks/build/after/local/ios/screens.coffee
@@ -30,5 +30,14 @@

     if screens?.ios?.iphone568hx2?
       grunt.file.copy screens.ios.iphone568hx2, path.join(res, 'Default-568h@2x~iphone.png'), encoding: null
-
+
+    if screens?.ios?.iphone667h?
+      grunt.file.copy screens.ios.iphone667h, path.join(res, 'Default-667h.png'), encoding: null
+
+    if screens?.ios?.iphone736h?
+      grunt.file.copy screens.ios.iphone736h, path.join(res, 'Default-736h.png'), encoding: null
+
+    if screens?.ios?.iphoneLandscape736h?
+      grunt.file.copy screens.ios.iphoneLandscape736h, path.join(res, 'Default-Landscape-736h.png'), encoding: null
+
     if fn then fn()


domain: manually apply patch from master

This file was renamed, so it was easier to bring this change over than to merge

--- client/app/Applications/Environments.kdapplication/views/domains/domaincreateform.coffee
+++ client/app/Applications/Environments.kdapplication/views/domains/domaincreateform.coffee
@@ -166,9 +166,10 @@
       warn "Failed to update domains:", err  if err
       domainList = []

-      for domain in userDomains
-        if not domain.regYears > 0
-          domainList.push {title:".#{domain.domain}", value:domain.domain}
+      if userDomains
+        for domain in userDomains
+          if not domain.regYears > 0
+            domainList.push {title:".#{domain.domain}", value:domain.domain}

       {domains, domainName} = @subDomainEntryForm.inputs


server: remove unnecessary partition to chunk

--- servers/lib/server/customers.coffee
+++ servers/lib/server/customers.coffee
@@ -31,23 +31,18 @@
     return res.status(400).send err  if err

     response = []
-    chunk    = []
     queue    = []

-    while (usernames.length > 0)
-      chunk = usernames.splice(0,10)
-      chunk.forEach (username)->
-        queue.push -> JMachine.fetchByUsername username, (err, machines)->
-          if err
-            queue.fin()
-          else
-            slugs = []
-            machines.forEach (machine)->
-              slugs.push  machine.data.slug  if machine.data.meta.alwaysOn
+    usernames.forEach (username)->
+      queue.push -> JMachine.fetchByUsername username, (err, machines)->
+        if err
+          queue.fin()
+        else
+          slugs = []
+          machines.forEach (machine)->
+            slugs.push  machine.data.slug  if machine.data.meta.alwaysOn

-            resp = { "username" : username, "vms" : slugs }
-            response.push resp
-
-            queue.fin()
+          response.push { "username" : username, "vms" : slugs }
+          queue.fin()

     dash queue, -> res.status(200).send response


resultsPerPage=12

--- public/collections/products.coffee
+++ public/collections/products.coffee
@@ -6,7 +6,7 @@
   model: Product

   url: ->
-    url = "#{App.rootUrl}/shops/#{@shopId}/products?resultsPerPage=9"
+    url = "#{App.rootUrl}/shops/#{@shopId}/products?resultsPerPage=12"
     if @categoryId
       url += "&categoryId=#{@categoryId}"
     url


Add an acceptance test for using range header when getting a file.

--- test/acceptence/coffee/SendingFileTest.coffee
+++ test/acceptence/coffee/SendingFileTest.coffee
@@ -32,7 +32,7 @@

 	it "should send a 200 for status endpoing", (done)->
-		request "#{@filestoreUrl}/status", (err, response, body)->
+		request "#{@filestoreUrl}/status", (err, response, body)->
 			response.statusCode.should.equal 200
 			body.indexOf("filestore").should.not.equal -1
 			body.indexOf("up").should.not.equal -1
@@ -54,6 +54,26 @@
 			@timeout(1000 * 10)
 			request.get @fileUrl, (err, response, body)=>
 				body.should.equal @constantFileContent
+				done()
+
+		it "should be able to get back the first 8 bytes of the file", (done) ->
+			@timeout(1000 * 10)
+			options =
+				uri: @fileUrl
+				headers:
+					'Range': 'bytes=0-8'
+			request.get options, (err, response, body)=>
+				body.should.equal 'hello wor'
+				done()
+
+		it "should be able to get back bytes 4 through 10 of the file", (done) ->
+			@timeout(1000 * 10)
+			options =
+				uri: @fileUrl
+				headers:
+					'Range': 'bytes=4-10'
+			request.get options, (err, response, body)=>
+				body.should.equal 'o world'
 				done()

 		it "should be able to delete the file", (done)->
@@ -84,6 +104,3 @@
 					request.get newFileUrl, (err, response, body)=>
 						body.should.equal @constantFileContent
 						done()
-
-
-


:lipstick:

:lipstick:

--- spec/editor-component-spec.coffee
+++ spec/editor-component-spec.coffee
@@ -151,7 +151,6 @@
       expect(region3Rect.left).toBe 0
       expect(region3Rect.width).toBe 10 * charWidth

-
   it "updates the scroll bar when the scrollTop is changed in the model", ->
     node.style.height = 4.5 * lineHeightInPixels + 'px'
     component.updateAllDimensions()


:green_heart: Fix Tokenized Buffer spec

:green_heart: Fix Tokenized Buffer spec

--- spec/tokenized-buffer-spec.coffee
+++ spec/tokenized-buffer-spec.coffee
@@ -235,7 +235,7 @@
             buffer.setTextInRange([[0, 0], [2, 0]], "foo()\n7\n")

             expect(tokenizedBuffer.tokenizedLineForRow(0).tokens[1]).toEqual(value: '(', scopes: ['source.js', 'meta.function-call.js', 'punctuation.definition.arguments.begin.js'])
-            expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.js'])
+            expect(tokenizedBuffer.tokenizedLineForRow(1).tokens[0]).toEqual(value: '7', scopes: ['source.js', 'constant.numeric.decimal.js'])
             # line 2 is unchanged
             expect(tokenizedBuffer.tokenizedLineForRow(2).tokens[2]).toEqual(value: 'if', scopes: ['source.js', 'keyword.control.js'])


Uncomment non-in-progress broadcast test

--- test/integration/broadcast-sent-2-spec.coffee
+++ test/integration/broadcast-sent-2-spec.coffee
@@ -9,7 +9,7 @@
 JobManager     = require 'meshblu-core-job-manager'
 HydrantManager = require 'meshblu-core-manager-hydrant'

-xdescribe 'BroadcastSent(2): send', ->
+describe 'BroadcastSent(2): send', ->
   beforeEach (done) ->
     @db            = mongojs 'meshblu-core-test'
     @devices       = @db.collection 'devices'


fix C2CChoose snippet

fix C2CChoose snippet

fix C2CChoose snippet

--- snippets/language-coursescript.cson
+++ snippets/language-coursescript.cson
@@ -1775,8 +1775,8 @@
       Text: $6
       Text: $7
       Opts:
-      Text(Checked=true): $8
-      Text(Checked=true): $9
+      Text(checked=true): $8
+      Text(checked=true): $9
       Text: $10
       Text: $11
       Text: $12


generictoggler: minor classname update

generictoggler: minor classname update

--- client/home/lib/virtualmachines/components/machineslist/generictoggler.coffee
+++ client/home/lib/virtualmachines/components/machineslist/generictoggler.coffee
@@ -33,7 +33,7 @@
     if button
       buttonClassName = 'GenericToggler-button'
       toggleClassName = 'GenericToggler-toggle hidden'
-      topClassName = 'GenericToggler-top button'
+      topClassName = 'GenericToggler-top build-logs'

     unless machineState
       buttonClassName = "#{buttonClassName} disabled"


terminal: Fixed AltGr key handling.

terminal: Fixed AltGr key handling.

--- client/landing/app/Applications/WebTerm.kdapplication/src/InputHandler.coffee
+++ client/landing/app/Applications/WebTerm.kdapplication/src/InputHandler.coffee
@@ -44,7 +44,7 @@
     @terminal.cursor.resetBlink()

     if event.ctrlKey
-      unless event.shiftKey or event.keyCode < 64
+      unless event.shiftKey or event.altKey or event.keyCode < 64
         @terminal.server.controlSequence String.fromCharCode(event.keyCode - 64)
         event.preventDefault()
       return
@@ -58,7 +58,7 @@
       event.preventDefault()

   keyPress: (event) ->
-    unless event.ctrlKey or event.charCode is 0
+    unless (event.ctrlKey and not event.altKey) or event.charCode is 0
       @terminal.server.input String.fromCharCode(event.charCode)
     event.preventDefault()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp.coffee
+++ test/clamp.coffee
@@ -9,6 +9,6 @@

   ok obj.clamp

-test 'it should clamp value between'
+test 'it should clamp value between', ->

 module()


Add ElementWrapper.children()

Add ElementWrapper.children()

--- tapestry-core/src/main/preprocessed-coffeescript/org/apache/tapestry5/t5-core-dom.coffee
+++ tapestry-core/src/main/preprocessed-coffeescript/org/apache/tapestry5/t5-core-dom.coffee
@@ -445,14 +445,12 @@
 #if jquery
       matches = @$.find selector

-      return [] if matches.length is 0
-
-      for i in [0..(matches.length - 1)]
+      for i in [0...matches.length]
         new ElementWrapper matches.eq i
 #elseif prototype
       matches = @element.select selector

-      _.map matches, (e) -> new ElementWrapper e
+      new ElementWrapper(e) for e in matches
 #endif

     # Find the first container element that matches the selector (wrapped as an ElementWrapper),
@@ -503,6 +501,18 @@
       return null unless parent
 #endif
       new ElementWrapper parent
+
+    # Returns an array of all the immediate child elements of this element, as ElementWrappers.
+    children: ->
+#if jquery
+      children = @$.children()
+
+      for i in [0...matches.length]
+        new ElementWrapper children.eq i
+
+#elseif prototype
+      new ElementWrapper(e) for e in @element.childElements()
+#endif

     # Returns true if this element is visible, false otherwise. This does not check to see if all containers of the
     # element are visible.


Fixed indentation to solve (N) x subscription items bug.

--- client/app/Applications/Account.kdapplication/account/accSettingsSubscriptions.coffee
+++ client/app/Applications/Account.kdapplication/account/accSettingsSubscriptions.coffee
@@ -30,10 +30,10 @@
                 sub.plan = plan
                 cb null, sub

-          async.parallel stack, (err, result)=>
-            result = [] if err
-            @instantiateListItems result
-            @hideLazyLoader()
+        async.parallel stack, (err, result)=>
+          result = [] if err
+          @instantiateListItems result
+          @hideLazyLoader()

   loadView:->
     super


Workspace: Fixed client terminal height on resized.

--- client/landing/app/CommonViews/workspace/panes/collaborativeclientterminalpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativeclientterminalpane.coffee
@@ -24,6 +24,8 @@

     @createElements()
     @blinkCursor()
+
+    @on "PaneResized", => @setHeight @parent.getHeight() - 37

   createElements: ->
     @container  = new KDView


Add test.

Add test.

--- test/index.coffee
+++ test/index.coffee
@@ -628,6 +628,10 @@
         count = MyDoc.find().count()
         count.should.be.gte 1

+      it_ "should work without find", ->
+        count = MyDoc.count()
+        count.should.be.gte 1
+
     describe "#update()", ->
       it_ "should create a new doc with upsert", ->
         starting_count = MyDoc.count()


test fix: 'returns neither a string nor a Buffer' test now supports async error

--- test/index.coffee
+++ test/index.coffee
@@ -32,8 +32,13 @@

     context 'returns neither a string nor a Buffer', ->

-      it 'throws PluginError', ->
-        err -> transform((content) -> 42).write buffered()
+      it 'throws PluginError', (done) ->
+          t = transform((content) -> 42)
+          t.on 'error', (err) ->
+            done()
+          t.on 'data', () ->
+            done new Error('expected PluginError')
+          t.write buffered()

     context 'returns a Buffer or string', ->
       [fn, file] = [null, null]


More tests.

More tests.

--- test/index.coffee
+++ test/index.coffee
@@ -618,6 +618,17 @@
       dest = doc.forJson()
       dest.should.eql my_id: 'reverse', embed: ["array", "of", "vals"]

+    it "should not assume embeds are objects", ->
+      SomeDoc = Db.document 'someDoc',
+        embed: Embed 'e',
+          field: 'f'
+
+      doc = new SomeDoc e: 'value'
+      doc.should.eql e: 'value'
+      doc = doc.forJson()
+      doc.should.eql embed: 'value'
+
+
 describe "Cursor", ->
   before (done) ->
     MyDoc.remove {}, (err) ->


Added test for create_authn_request.

Added test for create_authn_request.

--- test/saml2.coffee
+++ test/saml2.coffee
@@ -31,12 +31,25 @@

   # Auth Request, before it is compressed and base-64 encoded
   describe 'AuthRequest', ->
-    xit 'is valid xml', (done) ->
-      assert false
-      done()
-    xit 'contains expected fields', (done) ->
-      assert false
-      done()
+    it 'contains expected fields', (done) ->
+      saml2.create_authn_request 'https://sp.example.com/metadata.xml', 'https://sp.example.com/assert', 'https://idp.example.com/login', (err, result) ->
+        assert not err?, "Got error: #{err}"
+        dom = (new xmldom.DOMParser()).parseFromString result
+        authn_request = dom.getElementsByTagName('AuthnRequest')[0]
+
+        required_attributes =
+          Version: '2.0'
+          Destination: 'https://idp.example.com/login'
+          AssertionConsumerServiceURL: 'https://sp.example.com/assert'
+          ProtocolBinding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
+
+        _(required_attributes).each (req_value, req_name) ->
+          assert _(authn_request.attributes).some((attr) -> attr.name is req_name and attr.value is req_value)
+          , "Expected to find attribute '#{req_name}' with value '#{req_value}'!"
+
+        assert _(authn_request.attributes).some((attr) -> attr.name is "ID"), "Missing required attribute 'ID'"
+        assert.equal dom.getElementsByTagNameNS('urn:oasis:names:tc:SAML:2.0:assertion', 'Issuer')[0].firstChild.data, 'https://sp.example.com/metadata.xml'
+        done()

   describe 'pretty_assertion_attributes', ->
     it 'creates a correct user object', ->


(fix) Add missing editor instance to addResult.

--- src/javascript-editor-errors.coffee
+++ src/javascript-editor-errors.coffee
@@ -24,4 +24,4 @@
         return r
       return resCorrLine

-    editor.addResult _.flatten results
+    editor.addResult editor, _.flatten results


Add arguments test.

Add arguments test.

--- test/tests.coffee
+++ test/tests.coffee
@@ -625,3 +625,11 @@
         expect(error.args).length.above(0)
         expect(error.args[0]).equal('room1')
         done()
+
+  it 'should validate message arguments', (done) ->
+    chatServer = new ChatService { port : port, enableRoomsManagement : true }
+    socket1 = ioClient.connect url1, makeParams(user1)
+    socket1.on 'loginConfirmed', (u) ->
+      socket1.emit 'roomCreate', null, false, (error, data) ->
+        expect(error).ok
+        done()


set route reqres handler on min.js

set route reqres handler on min.js

--- assets/js/main.coffee
+++ assets/js/main.coffee
@@ -24,7 +24,8 @@
 require ['app', 'marionette', 'router', 'controller'], (App, Marionette, Router, Controller) ->

   App.on 'initialize:after', ->
-    new Router(controller: new Controller)
+    router = new Router(controller: new Controller)
+    App.reqres.setHandler('router', -> router)
     Backbone.history.start
       pushState: true


handle clicking past end of line

handle clicking past end of line

--- assets/js/view.coffee
+++ assets/js/view.coffee
@@ -1152,6 +1152,12 @@
         elLine = virtualDom.h 'div', {
           id: rowDivID row.id
           className: 'node-text'
+          # if clicking outside of text, but on the row, move cursor to the end of the row
+          onclick:  ((row) =>
+            col = if options.cursorBetween then -1 else -2
+            @cursor.set row, col
+            do @render
+          ).bind(@, row)
         }, (@virtualRenderLine row, options)
         rowElements.push elLine

@@ -1203,10 +1209,13 @@

       if options.handle_clicks
         if @mode == MODES.NORMAL or @mode == MODES.INSERT
-          lineoptions.charclick = (column) =>
+          lineoptions.charclick = (column, e) =>
             @cursor.set row, column
             # assume they might click again
             @render {handle_clicks: true}
+            # prevent overall row click
+            do e.stopPropagation
+            return false
       else if not options.no_clicks
         lineoptions.linemouseover = () =>
           @render {handle_clicks: true}


testing changing account settings

testing changing account settings

--- salvus/test/rethink-test.coffee
+++ salvus/test/rethink-test.coffee
@@ -15,7 +15,6 @@

 teardown = (cb) ->
     db?.delete_all(cb:cb, confirm:'yes')
-

 describe 'working with accounts: ', ->
     @timeout(5000)
@@ -390,5 +389,23 @@
                         cb(err)
         ], done)

-
-
+describe 'testing changing account settings: ', ->
+    before(setup)
+    after(teardown)
+    account_id = undefined
+    it 'creates an account', (done) ->
+        db.create_account(first_name:"Sage", last_name:"Math", created_by:"1.2.3.4",\
+                          email_address:"sage@example.com", password_hash:"blah", cb:(err, x) => account_id=x; done(err))
+    it 'creates a second account', (done) ->
+        db.create_account(first_name:"Sage", last_name:"Math", created_by:"1.2.3.4",\
+                          email_address:"sage2@example.com", password_hash:"blah", cb:done)
+    it 'tries to change first email to second, and we check for an error', (done) ->
+        db.update_account_settings(account_id:account_id, set:{email_address:"sage2@example.com"}, cb:(err) ->
+            expect(!!err).toBe(true); done())
+    it 'changes the foobar setting', (done) ->
+        db.update_account_settings(account_id:account_id, set:{foobar:{bar:"none"}}, cb:done)
+    it 'check that the foobar setting was changed', (done) ->
+        db.get_account(account_id:account_id, columns:['foobar'], cb:((err, x) -> expect(x.foobar).toEqual({bar:"none"}); done(err)))
+
+
+


Remove the text field when an empty metadata list is closed

--- app/assets/javascripts/rglossa/react/components/left/metadata_select.jsx.coffee
+++ app/assets/javascripts/rglossa/react/components/left/metadata_select.jsx.coffee
@@ -11,6 +11,18 @@

   componentWillUnmount: ->
     $(@refs.hidden.getDOMNode()).select2('destroy') if @state.isActive
+
+  componentDidUpdate: (prevProps, prevState) ->
+    if prevState.isActive and !@state.isActive
+      # We need setTimeout here because if the dropdown was closed by pressing
+      # the escape key, select2 wants to focus the text field afterwards, and
+      # it will throw an error if we have removed the field. By waiting until
+      # the next run of the JavaScript run loop, we let select2 finish its thing
+      # before removing it.
+      setTimeout((=>
+        $(@refs.hidden.getDOMNode()).select2('destroy')),
+        0)
+

   handleHeaderClick: ->
     node = @refs.hidden.getDOMNode()
@@ -47,6 +59,15 @@
     $(node).on 'change', =>
       @props.handleMetadataSelectionsChanged()

+    $(node).on 'select2-close', (e) =>
+      unless e.target.value
+        # If we close the dropdown (by clicking elsewhere or pressing
+        # the escape key) and no values have been selected, set isActive
+        # to false, which will cause the component to update and the text
+        # field to be removed as well in componentDidUpdate (since we don't
+        # want empty metadata text fields to stay around)
+        @setState(isActive: false)
+

   destroySelect: (node) ->
     $(node).select2('destroy')


KDEventEmitter: wildcard actual event

KDEventEmitter: wildcard actual event

this.event should temporarily hold the actual event.  If you need it asynchronously, you should assign it's value to a local variable

--- core/KDEventEmitter.coffee
+++ core/KDEventEmitter.coffee
@@ -176,13 +176,25 @@

   emit: (eventName, rest...) ->

+    if @hasOwnProperty 'event' then oldEvent = @event
+
+    @event = eventName
+
     listeners = getAllListeners @_e, eventName.split @_delim

     listener.apply this, rest  for listener in listeners
+
+    if oldEvent?
+      @event = oldEvent
+
+    else
+      delete @event
+
     return this

   off: (eventName, listener) ->
-    removeAllListeners @_e, (eventName.split @_delim), listener
+    removeAllListeners @_e, ((eventName ? '*').split @_delim), listener
+    return this

   on: (eventName, listener) ->
     unless 'function' is typeof listener


Fix all the bugs

Fix all the bugs

--- scripts/govuk_app_owners.coffee
+++ scripts/govuk_app_owners.coffee
@@ -3,11 +3,11 @@
 module.exports = (robot) ->
   robot.hear /who owns (.*)\?/i, (res) ->
     # Best guess of the intended application.
-    application = res.match[1].replace(/ /g, '-').toLowerCase()
+    application = res.match[1].replace(/\s/g, '-').toLowerCase()
     console.log("Fetching owner for #{application}")

     robot.http("https://docs.publishing.service.gov.uk/apps/#{application}.json")
       .get() (err, response, body) ->
         if response.statusCode == 200
           data = JSON.parse(body)
-          res.reply "<#{data.links.html_url>}|#{data.app_name}> is owned by <#{data.team}>"
+          res.reply "<#{data.links.html_url}|#{data.app_name}> is owned by <#{data.team}>"


fix comments

fix comments

--- scripts/pagerv2_commands.coffee
+++ scripts/pagerv2_commands.coffee
@@ -25,7 +25,7 @@
 #   hubot pd res|resolve [all]   - resolves any un-resolved incidents
 #   hubot pd res|resolve <#,#,#> - resolves incident <number>
 #
-#   hubot pd snooze [all] [for] [<duration>] [min]   - snoozes any open incidents
+#   hubot pd snooze [all] [for] [<duration>] [min]   - snoozes any open incidents for [<duration>] (default 120m)
 #   hubot pd snooze <#,#,#> [for] [<duration>] [min] - snoozes incident <number>
 #
 #   hubot pd assign [all] to me       - assigns all open incidents to caller
@@ -104,7 +104,7 @@
       res.send e
     res.finish()

-#   hubot pd oncall - returns who is on call
+#   hubot pd [who is] oncall - returns who is on call
   robot.respond /(?:pd )?(?:who(?: is|'s) )?on ?call\s*$/, 'pd_oncall', (res) ->
     pagerv2.getOncall()
     .then (data) ->
@@ -144,7 +144,7 @@
       res.send e
     res.finish()

-#   hubot pd incident <number> - gives more information about incident number <number>
+#   hubot pd incident <#> - gives more information about incident number <number>
   robot.respond /pd (?:inc |incident )#?(\d+|[A-Z0-9]{7})\s*$/, 'pd_incident', (res) ->
     [ _, incident ] = res.match
     pagerv2.getPermission(res.envelope.user, 'pduser')
@@ -183,7 +183,7 @@
       res.send e
     res.finish()

-#   hubot pd ack               - acknowledges any unack incidents
+#   hubot pd ack [all]         - acknowledges any unack incidents
   robot.respond /pd ack(?: all)?\s*$/, 'pd_ack_all', (res) ->
     pagerv2.getPermission(res.envelope.user, 'pduser')
     .then ->
@@ -197,7 +197,7 @@
       res.send e.message or e
     res.finish()

-#   hubot pd ack <#>           - acknowledges incident <number>
+#   hubot pd ack <#,#,#>           - acknowledges incident <number>
   robot.respond /pd ack #?(.+)\s*$/, 'pd_ack_one', (res) ->
     [ _, incidents ] = res.match
     pagerv2.getPermission(res.envelope.user, 'pduser')
@@ -212,7 +212,7 @@
       res.send e.message or e
     res.finish()

-#   hubot pd res|resolve       - acknowledges any unack incidents
+#   hubot pd res|resolve [all]      - resolves any unresolved incidents
   robot.respond /pd res(?:olve)?(?: all)?\s*$/, 'pd_res_all', (res) ->
     pagerv2.getPermission(res.envelope.user, 'pduser')
     .then ->
@@ -226,7 +226,7 @@
       res.send e.message or e
     res.finish()

-#   hubot pd res|resolve <#>   - acknowledges incident <number>
+#   hubot pd res|resolve <#,#,#>   - resolves incident <number>
   robot.respond /pd res(?:olve)? #?(.+)\s*$/, 'pd_res_one', (res) ->
     [ _, incidents ] = res.match
     pagerv2.getPermission(res.envelope.user, 'pduser')
@@ -279,7 +279,7 @@
       res.send e.message or e
     res.finish()

-#   hubot pd snooze [all] [for] [<duration>] [min]  - acknowledges any unack incidents
+#   hubot pd snooze [all] [for] [<duration>] [min]  - snoozes all incidents for [<duration>] (default 120m)
   robot.respond (
     /pd snooze(?: all)?(?: (?:for )(\d+)(?: min(?:utes)?)?)?\s*$/
   ), 'pd_snooze_all', (res) ->
@@ -296,7 +296,7 @@
       res.send e.message or e
     res.finish()

-#   hubot pd snooze <#,#,#> [for] [<duration>] [min] - acknowledges incident <number>
+#   hubot pd snooze <#,#,#> [for] [<duration>] [min] - snoozes incident <number> for [<duration>] (default 120m)
   robot.respond (
     /pd snooze #?(.+)(?: (?:for )(\d+)(?: min(?:utes)?)?)?\s*$/
   ), 'pd_snooze_one', (res) ->


automatically add entity to conversation if any

--- src/ui/app.coffee
+++ src/ui/app.coffee
@@ -12,7 +12,7 @@
 ipc = require 'ipc'

 {applayout}    = require './views'
-{viewstate}    = require './models'
+{viewstate, conv}    = require './models'

 # tie layout to DOM
 document.body.appendChild applayout.el
@@ -32,7 +32,11 @@
 # events from hangupsjs
 require('./events').forEach (n) -> ipc.on n, (e) -> action n, e
 # response from getentity
-ipc.on 'getentity:result', (r) -> action 'addentities', r.entities
+ipc.on 'getentity:result', (r, data) ->
+    action 'addentities', r.entities
+    conv_id = data?.add_to_conv
+    if conv_id then r.entities.forEach (p) -> conv.addParticipant conv_id, p
+
 ipc.on 'resize', (dim) -> action 'resize', dim
 ipc.on 'moved', (pos)  -> action 'moved', pos
 ipc.on 'searchentities:result', (r) ->


:bug: :fire: remaining logs

:bug: :fire: remaining logs

--- lib/csv-editor-form-element.coffee
+++ lib/csv-editor-form-element.coffee
@@ -335,8 +335,6 @@
     else unless rowDelimiter is 'auto'
       options.rowDelimiter = denormalizeValue(rowDelimiter)

-    console.log options
-
     switch trim
       when 'both' then options.trim = true
       when 'left' then options.ltrim = true


hotfix clues

hotfix clues

--- app/assets/javascripts/app/controllers/layout_ref.js.coffee
+++ app/assets/javascripts/app/controllers/layout_ref.js.coffee
@@ -1192,10 +1192,10 @@
     positionDictionary =
       above:
         x: modal.width/2
-        y: modal.height + @transformOriginPadding
+        y: modal.height
       below:
         x: modal.width/2
-        y: -@transformOriginPadding
+        y: 0
       left:
         x: modal.width + @transformOriginPadding
         y: modal.height/2


sample recipe: custom_phrase.

sample recipe: custom_phrase.

--- sample/custom_phrase.recipe.coffee
+++ sample/custom_phrase.recipe.coffee
@@ -15,19 +15,22 @@
   setup: (done) ->
     schemaId = @params['schema']
     s = new Config
-    s.loadFile "#{@rimeDirectory}/#{schemaId}schema.yaml", (s) =>
-      main_translators = [
-        'reverse_lookup_translator'
-        'table_translator'
-        'script_translator'
+    s.loadFile "#{@rimeDirectory}/#{schemaId}.schema.yaml", (s) =>
+      mainTranslators = [
+        /^reverse_lookup_translator/
+        /^script_translator/
+        /^table_translator/
       ]
+      customPhraseTranslator = 'table_translator@custom_phrase'
       translators = s.get 'engine/translators'
-      index = 0
-      for t in translators
-        if t in main_translators  # TODO: handle those with aliases
-          break
-        ++index
-      translators.insert index, 'table_translator@custom_phrase'
+      unless customPhraseTranslator in translators
+        # insert custom phrase translator before main translators
+        index = 0
+        for x in translators
+          if mainTranslators.some((elem) -> elem.test x)
+            break
+          ++index
+        translators.splice index, 0, customPhraseTranslator
       @customize schemaId, done, (c) ->
         c.patch 'engine/translators', translators
         c.patch 'custom_phrase',


Add version option

Add version option

--- src/csonc.coffee
+++ src/csonc.coffee
@@ -14,12 +14,18 @@
     If no output file is specified then the JSON is written to standard out.
   """
   options.alias('h', 'help').describe('help', 'Print this help message')
-  options.alias('r', 'root').boolean('root').describe('root', 'Require that the input file contain an object at the root.').default('root', false)
-  options.alias('o', 'output').string('output').describe('output', 'File path to write the JSON output to.')
+  options.alias('r', 'root').boolean('root').describe('root', 'Require that the input file contain an object at the root').default('root', false)
+  options.alias('o', 'output').string('output').describe('output', 'File path to write the JSON output to')
+  options.alias('v', 'version').describe('version', 'Print the version')

   {argv} = options
   [inputFile] = argv._
   inputFile = path.resolve(inputFile) if inputFile
+
+  if argv.version
+    {version} = require '../package.json'
+    console.log(version)
+    return

   if argv.help
     options.showHelp()


Fixed bug on unacceptable routes.

Fixed bug on unacceptable routes.

--- server/lib/crawler/index.coffee
+++ server/lib/crawler/index.coffee
@@ -97,7 +97,10 @@
                     return res.send 200, content
                 daisy queue
             else
-              createActivityContent JAccount, model, {}, yes, (error, content)=>
+              createFullHTML = no
+              putBody = yes
+              createActivityContent JAccount, model, {}, createFullHTML, putBody, (error, content)=>
+                return res.send 500, error_500()  if error
                 return res.send 200, content
       else
         if /^(Activity)|(Topics)/.test name


Modified the dbpedia client which does simple get requests. to test (is result really JSON? etc)

--- server/lib/dbpediaclient.coffee
+++ server/lib/dbpediaclient.coffee
@@ -1,14 +1,9 @@
-SparqlClient = require 'sparql-client'
+request = require 'request'
 endpoint = 'http://dbpedia.org/sparql'

-module.exports  = (query) ->
-    client = new SparqlClient(endpoint)
-    console.log "Query to " + endpoint
-    console.log "Query: " + query
-    client.query(query)
-    # rest of the javascript on the github example
-    #https://github.com/thomasfr/node-sparql-client
-    #    .bind('city', '<http://dbpedia.org/resource/Vienna>')
-    #.execute(function(error, results) {
-    #process.stdout.write(util.inspect(arguments, null, 20, true)+"\n");
-    #});+module.exports = (query, callback)->
+    request endpoint + '/' + query
+    (error, response, body)->
+        return callback error if error
+        body = JSON.parse body
+        callback null, body.response.title


use script elem instead of eval

use script elem instead of eval

--- editor-app/main.coffee
+++ editor-app/main.coffee
@@ -31,7 +31,10 @@

 evalCode = (code) ->
   window.frameLoaded = (frame) ->
-    frame.eval(code)
+    scriptElem = frame.document.createElement "script"
+    scriptElem.type = "text/javascript"
+    scriptElem.text = code
+    frame.document.body.appendChild scriptElem
   $("#game").attr("src", "game.html")

 evalE.onValue(evalCode)


indentation fix

indentation fix

--- client/home/lib/myteam/components/hometeamsendinvites/alreadymemberinvitationmodal.coffee
+++ client/home/lib/myteam/components/hometeamsendinvites/alreadymemberinvitationmodal.coffee
@@ -7,10 +7,10 @@
   constructor: (options = {}, data) ->

     options = _.assign {}, options,
-      title                   : 'Invites Already Sent'
-      overlay                 : yes
-      height                  : 'auto'
-      cssClass                : 'admin-invite-confirm-modal content-modal'
+      title    : 'Invites Already Sent'
+      overlay  : yes
+      height   : 'auto'
+      cssClass : 'admin-invite-confirm-modal content-modal'
       buttons :
         OK :
           title: 'OK'


Hide autocomplete again when switching to comment

--- core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
+++ core/app/backbone/views/fact_relations/auto_complete_fact_relations_view.coffee
@@ -66,6 +66,7 @@
       created_by: currentUser.toJSON()

   switchCheckboxClicked: (e) ->
+    @$el.removeClass 'active'
     @trigger 'switch_to_comment_view', @model.get('text')
     e.preventDefault()
     e.stopPropagation()


Folder documentation

Folder documentation

--- lib/layervault/nodes/folder.coffee
+++ lib/layervault/nodes/folder.coffee
@@ -1,10 +1,33 @@
 Node = require '../node'

+# Represents a single Folder in LayerVault
 module.exports = class Folder extends Node
+  # Retrieves information about this folder.
+  #
+  # @param [Function] cb The finished callback.
   get: (cb) -> @api.get(@nodePath, {}, cb.bind(@))
+
+  # Creates a folder at this path.
+  #
+  # @param [Function] cb The finished callback.
   create: (cb) -> @api.post(@nodePath, {}, cb.bind(@))
+
+  # Deletes the folder at this path.
+  #
+  # @param [Function] cb The finished callback.
   delete: (cb) -> @api.delete(@nodePath, {}, cb.bind(@))
+
+  # Moves this folder to a new location. The new location must be relative to the
+  # organization root.
   move: (to, cb) -> @api.post("#{@nodePath}/move", { to: to }, cb.bind(@))
+
+  # @see Folder#move
   rename: @move

+  # Changes the color of the folder. At this time, the color is only reflected
+  # on the site for project folders. Possible values include: white, green,
+  # orange, and red.
+  #
+  # @param [String] color The color to change this folder to.
+  # @param [Function] cb The finished callback.
   changeColor: (color, cb) -> @api.put("#{@nodePath}/color", { color: color }, cb.bind(@))


GET /

GET /

--- test/basic.coffee
+++ test/basic.coffee
@@ -1,2 +1,11 @@
 # basic.coffee

+http = require 'http'
+
+describe 'server', ->
+  before (done) ->
+    server = require 'serv'
+    done()
+  it 'can be started', (done) ->
+    http.get {host:'127.0.0.1', port:3000, path:'/'}, (err, res) ->
+      done()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -208,6 +208,15 @@
   equalEnough hsl[3], 1.0

 test "#hue", ->
+  color = Color(20, 45, 49)
+  color.hue(60)
+
+  equalEnough color.r, 51
+  equalEnough color.g, 51
+  equalEnough color.b, 21
+  equalEnough color.a, 1.0
+
+test "#shiftHue", ->
   color = Color(34, 54, 239).shiftHue(20)

   equalEnough color.r, 84
@@ -215,7 +224,7 @@
   equalEnough color.b, 239
   equalEnough color.a, 1.0

-test "#hue$", ->
+test "#shiftHue$", ->
   color = Color(34, 54, 239)

   color.shiftHue$(60)


[test] [fix] front test should now actually test front->api->engine connection

--- test/front.coffee
+++ test/front.coffee
@@ -1,22 +1,16 @@
-Api = require('../lib/api')
-Engine = require('../lib/engine')
-Front = require('../lib/front/front')
 WebSocket = require('ws')
 WebSocketServer = require('ws').Server

+Buttercoin = require('../lib/buttercoin')
+
 describe 'Front', ->

-  it 'should initialize', ->
-    front = new Front
+  it 'can initialize', (done) ->
+    @butter = new Buttercoin()
+    done()

-  describe 'Front.start', ->
-    it 'should start, connect to api, and provide a WS server', ->
-      api = new Api
-      front = new Front
-      engine = new Engine
-      engine.start {port: 3043, host: "0.0.0.0" }, (err, server) =>
-        # console.log err, server
-        api.start {port: 3042, host: "0.0.0.0", engineEndpoint: 'ws://0.0.0.0:3043' }, (err, server) =>
-          # console.log err, server
-          front.start {port: 3044, host: "0.0.0.0", apiEndpoint: 'ws://0.0.0.0:3022' }, (err, server) =>
-            # console.log err, server
+  it 'should start, connect to api, and provide a WS server', (done)->
+    @butter.engine.start {port: 3043, host: "0.0.0.0" }, (err, server) =>
+      @butter.api.start {port: 3042, host: "0.0.0.0", engineEndpoint: 'ws://0.0.0.0:3043' }, (err, server) =>
+        @butter.front.start {port: 3044, host: "0.0.0.0", apiEndpoint: 'ws://0.0.0.0:3042' }, (err, server) =>
+          done(null)


Test sort ascending and descending.

Test sort ascending and descending.

Signed-off-by: Ian Macalinao <08370e385f4778cad8fe504ec5445edd3d45bd9a@gmail.com>

--- test/items.coffee
+++ test/items.coffee
@@ -55,3 +55,11 @@
             items.find().sort items.sorts.byProperty("name")
           catch e
             e.message.should.equal "The property must be a number."
+
+        it "should sort ascending by default", ->
+          res = items.find items.filters.withStats(["ap"]), items.sorts.byProperty("ap")
+          (res[0].ap < res[res.length - 1].ap).should.be.true
+
+        it "should sort descending if needed", ->
+          res = items.find items.filters.withStats(["ap"]), items.sorts.byProperty("ap", no)
+          (res[0].ap > res[res.length - 1].ap).should.be.true


Catch stream error

Catch stream error

--- lib/adapters/hTwitterAdapter.coffee
+++ lib/adapters/hTwitterAdapter.coffee
@@ -45,6 +45,8 @@
       )
       @twit.stream "statuses/filter", track: @properties.tags, (stream) =>
         @stream = stream
+        stream.on "error", (type, code) =>
+          @owner.log "error", "Twitter stream error : #{type} #{code}"
         stream.on "data", (data) =>
           unless data.disconnect
             if @properties.langFilter is undefined or data.user.lang is @properties.langFilter


Fix max items count for list view

Fix max items count for list view

--- lib/simple-select-list-view.coffee
+++ lib/simple-select-list-view.coffee
@@ -7,6 +7,7 @@

 class SimpleSelectListView extends SelectListView
   eventsAttached: false
+  maxItems: 10
   @content: ->
     @div class: "select-list", =>
       @input class: "fake-input", outlet: "fakeInput"


:bug: fix #5 partially

:bug: fix #5 partially

--- lib/atom-ternjs-autocomplete.coffee
+++ lib/atom-ternjs-autocomplete.coffee
@@ -10,12 +10,11 @@
     disposables: null
     documentationView = null
     maxItems = null
-    force = false
     # automcomplete-plus
     autocompletePlus = null
     id: 'atom-ternjs-provider'
     selector: '.source.js'
-    blacklist: '.source.js .comment,source.js .string'
+    blacklist: '.source.js .comment'

     init: (client, documentationView) ->
         atom.packages.activatePackage('autocomplete-plus')
@@ -32,7 +31,7 @@
         return [] unless options?.editor? and options?.buffer? and options?.cursor?
         prefix = options.prefix

-        if (!(/^[a-z0-9.\"\' ]$/i).test(prefix[prefix.length - 1]) or prefix.indexOf('..') != -1) and !@force
+        if prefix.endsWith(';') or prefix.indexOf('..') != -1 or !prefix.length
             @documentationView.hide()
             return []

@@ -54,8 +53,14 @@
                     for obj, index in data.completions
                         if index == maxItems
                             break
+                        if obj.type == 'string'
+                            # remove leading and trailing double quotes since
+                            # they are already typed and won't be replaced by
+                            # the suggestion and who would use double quotes
+                            # anyway duh
+                            obj.name = obj.name.replace /(^"|"$)/g, ''
+
                         that.suggestionsArr.push {
-
                             word: obj.name,
                             prefix: prefix,
                             label: obj.type,
@@ -79,9 +84,7 @@
         @documentationView.show()

     forceCompletion: ->
-        @force = true
         @autocompletePlus.autocompleteManager.runAutocompletion()
-        @force = false

     forceCancel: ->
         @autocompletePlus.autocompleteManager.hideSuggestionList()


Fix error in temporary fix for direct messages

When setting resources via direct messages, the room is "undefined"
Current code attempted to detect direct messages with room == null

--- scripts/watch-web-resources.coffee
+++ scripts/watch-web-resources.coffee
@@ -98,7 +98,7 @@
         robot.brain.set 'web_resources', resources
         if firstPeriodicCheck
           room = res.envelope.room
-          if room is null
+          if typeof room is "undefined"
           # Direct message, we need to find the room where we last saw the user.
             room = robot.brain.userForName(res.message.user.name).room
           robot.brain.set 'room_web_resources', room


Add helper.exec timeout option (Closes #5)

Add an option to set the linter timeout in milliseconds with fallback to
0.

--- lib/linter-perl-check-syntax.coffee
+++ lib/linter-perl-check-syntax.coffee
@@ -29,6 +29,8 @@
         if settings.tainted == true
             parameters.push('-T')

+        execTimeout = parseInt(settings.execTimeout) || 0
+
         sourceCode = textEditor.getText()

         executableCwd = settings.executableCwd
@@ -41,7 +43,9 @@
             {
                 stdin: sourceCode,
                 stream: 'both',
-                cwd: executableCwd}
+                cwd: executableCwd,
+                timeout: execTimeout
+            }
             ).then (result) ->
                 messages = []
                 regex = namedRegExp.named(REGEX)
@@ -129,6 +133,12 @@
             title: "Enable Taint check"
             default: false
             description: "Enable command line taint switch (`perl -T`)"
+        xexecTimeout:
+            type: "integer"
+            title: "Linter Exec Timeout"
+            default: 0
+            minimum: 0
+            description: "This sets the timeout (in milliseconds) for the internal exec command. This is useful if you work with files on network shares."

     activate: ->
         @subscriptions = new CompositeDisposable
@@ -157,6 +167,10 @@
             (tainted) =>
                 @tainted = tainted

+        @subscriptions.add atom.config.observe "#{pkg.name}.xexecTimeout",
+            (execTimeout) =>
+                @execTimeout = execTimeout
+
     deactivate: ->
         @subscriptions.dispose()

@@ -177,5 +191,6 @@
                         includePathsAbsolute: @includePathsAbsolute,
                         warnings: @warnings,
                         tainted: @tainted,
+                        execTimeout: @execTimeout
                     }
                 )


fix syntax error

fix syntax error

--- src/scripts/views/entry-view.coffee
+++ src/scripts/views/entry-view.coffee
@@ -4,13 +4,14 @@
 class EntryView extends React.Component
   @propTypes:
     entry: React.PropTypes.object
+    isOpen: React.PropTypes.bool

   constructor: (props) ->
     super props

   render: ->
     React.createElement 'div', {
-      className: 'entry' + (isOpen ? ' is-open' : '')
+      className: 'entry' + (if @props.isOpen then ' is-open' else '')
     },
     React.createElement('div', { className: 'entry-date' }, @props.entry.date),
     React.createElement('div', {


Give assets time to copy before reloading page.

--- config.coffee
+++ config.coffee
@@ -203,6 +203,8 @@
     assetsmanager:
       copyTo:
         'lib/ace': ['node_modules/ace-builds/src-min-noconflict/*']
+    autoreload:
+      delay: 1000

   modules:
     definition: (path, data) ->


update tests against updated demo district

--- test/query.coffee
+++ test/query.coffee
@@ -162,22 +162,22 @@

     it 'exists true with where works', (done) ->
       @clever.School.find().where('name').exists(true).count().exec (err, count) ->
-        assert.equal count, 4
+        assert.equal count, 3
         done()

     it 'exists without args works', (done) ->
       @clever.School.find().where('name').exists().count().exec (err, count) ->
-        assert.equal count, 4
+        assert.equal count, 3
         done()

     it 'exists true works', (done) ->
       @clever.School.find().exists('name', true).count().exec (err, count) ->
-        assert.equal count, 4
+        assert.equal count, 3
         done()

     it 'exists path works', (done) ->
       @clever.School.find().exists('name').count().exec (err, count) ->
-        assert.equal count, 4
+        assert.equal count, 3
         done()

     it 'exists false with where works', (done) ->


js state machine

js state machine

--- test/state.coffee
+++ test/state.coffee
@@ -1,4 +1,6 @@
 test "State", ->
-  callback = Callback()
+  callback = Callback{}, "Test Machine", ->
+    console.log "testing"
+
   equals color.r(), 0, "default red channel is 0"


Assert Image#resize()

Assert Image#resize()

--- test/suite.coffee
+++ test/suite.coffee
@@ -1,5 +1,8 @@
 assert = require 'assert'
 Upload = require '../src/index.coffee'
+
+fs = require('fs')
+gm = require('gm').subClass imageMagick: true

 hash = require('crypto').createHash
 rand = require('crypto').pseudoRandomBytes
@@ -175,13 +178,104 @@
             done()

       describe '#resize()', ->
-        it 'should return updated properties for original image'
-        it 'should return updated properties for resized image'
-        it 'should write resized image to temp destination'
-        it 'should set hegith and width for reszied image'
-        it 'should set corret orientation for resized image'
-        it 'should set colorspace to RGB for resized image'
-        it 'should set quality for resized image'
+        versions = null
+        beforeEach ->
+          versions = JSON.parse JSON.stringify upload.versions
+          versions[0].suffix = ''
+
+          image.src = __dirname + '/assets/photo.jpg'
+          image.tmpName = 'ed8d8b72071e731dc9065095c92c3e384d7c1e27'
+          image.meta =
+            format: 'jpeg'
+            fileSize: '617KBB'
+            imageSize: width: 1536, height: 2048
+            orientation: 'Undefined'
+            colorSpace: 'RGB'
+            compression: 'JPEG'
+            quallity: '96'
+            exif: undefined
+
+        it 'should return updated properties for original image', (done) ->
+          @timeout 10000
+          image.resize JSON.parse(JSON.stringify(versions[0])), (err, version) ->
+            assert.ifError err
+            assert.deepEqual version,
+              original: true,
+              awsImageAcl: 'private'
+              suffix: ''
+              src: image.src
+              format: image.meta.format
+              size: image.meta.fileSize
+              width: image.meta.imageSize.width
+              height: image.meta.imageSize.height
+            done()
+
+        it 'should return updated properties for resized image', (done) ->
+          @timeout 10000
+          image.resize JSON.parse(JSON.stringify(versions[1])), (err, version) ->
+            assert.ifError err
+            assert.deepEqual version,
+              suffix: versions[1].suffix
+              quality: versions[1].quality
+              format: 'jpeg'
+              src: '/tmp/gm-ed8d8b72071e731dc9065095c92c3e384d7c1e27-large.jpeg'
+              width: versions[1].maxWidth
+              height: versions[1].maxHeight
+
+            done()
+
+        it 'should write resized image to temp destination', (done) ->
+          @timeout 10000
+          image.resize JSON.parse(JSON.stringify(versions[1])), (err, version) ->
+            assert.ifError err
+            fs.stat version.src, (err, stat) ->
+              assert.ifError err
+              assert.equal typeof stat, 'object'
+              done()
+
+        it 'should set hegith and width for reszied image', (done) ->
+          @timeout 10000
+          image.resize JSON.parse(JSON.stringify(versions[1])), (err, version) ->
+            assert.ifError err
+            gm(version.src).size (err, value) ->
+              assert.ifError err
+              assert.deepEqual value,
+                width: 780
+                height: 1040
+              done()
+
+        it 'should set corret orientation for resized image', (done) ->
+          @timeout 10000
+          image.src = __dirname + '/assets/rotate.jpg'
+          image.resize JSON.parse(JSON.stringify(versions[1])), (err, version) ->
+            assert.ifError err
+            gm(version.src).identify (err, value) ->
+              assert.ifError err
+              assert.equal value.Orientation, 'Undefined'
+              assert.equal value.size.width, 585
+              assert.equal value.size.height, 1040
+              done()
+
+        it 'should set colorspace to RGB for resized image', (done) ->
+          @timeout 10000
+          image.src = __dirname + '/assets/cmyk.jpg'
+          image.resize JSON.parse(JSON.stringify(versions[1])), (err, version) ->
+            assert.ifError err
+            gm(version.src).identify (err, value) ->
+              assert.ifError err
+              assert.equal value.Colorspace, 'RGB'
+              done()
+
+        it 'should set quality for resized image', (done) ->
+          @timeout 10000
+          versions[1].quality = 50
+          image.src = __dirname + '/assets/photo.jpg'
+          image.resize JSON.parse(JSON.stringify(versions[1])), (err, version) ->
+            assert.ifError err
+            gm(version.src).identify (err, value) ->
+              assert.ifError err
+              assert.equal value.Quality, versions[1].quality
+              done()

       describe '#upload()', ->
         it 'should not upload original image if keepOriginal is set to false'


Fix AWS cleanup error

Fix AWS cleanup error

--- test/suite.coffee
+++ test/suite.coffee
@@ -206,7 +206,7 @@
           assert.ifError err

           for image in images
-            cleanup.push Key: image.path # clean up in AWS
+            cleanup.push Key: image.path if image.path # clean up in AWS

             if image.original
               assert.equal typeof image.size, 'string'


Debug activity should return not null answer

--- server/controllers/activity.coffee
+++ server/controllers/activity.coffee
@@ -68,10 +68,10 @@
         when 'error'
             log.error activity.data
             log.error activity.data.error?.stack
-            res.send null
+            res.send 'ok'
         when 'debug'
             log.info activity.data.message
-            res.send null
+            res.send 'ok'
         else
             res.status(400).send
                 name: "Unknown activity data type",


Test Indicator.findWhereIndicatorHasData respects filters

--- server/test/units/indicator.coffee
+++ server/test/units/indicator.coffee
@@ -459,3 +459,42 @@
   )

 )
+
+test("#findWhereIndicatorHasData respects the given filters", (done)->
+  indicatorToFind = indicatorToFilterOut = null
+
+  helpers.createIndicatorModels([{},{}]).then((indicators) ->
+    indicatorToFind = indicators[0]
+    indicatorToFilterOut = indicators[1]
+
+    Q.nfcall(
+      helpers.createIndicatorData, {
+        indicator: indicatorToFind
+        data: [{some: 'data'}]
+      }
+    )
+  ).then((indicatorData) ->
+
+    Q.nfcall(
+      helpers.createIndicatorData, {
+        indicator: indicatorToFilterOut
+        data: [{some: 'data'}]
+      }
+    )
+  ).then((indicatorData) ->
+    Indicator.findWhereIndicatorHasData(_id: indicatorToFind._id)
+  ).then((indicators) ->
+
+    assert.lengthOf indicators, 1, "Expected only the one indicator with data to be returned"
+    assert.strictEqual indicators[0]._id.toString(), indicatorToFind._id.toString(),
+      "Expected the returned indicator to be the indicator with data"
+
+    done()
+
+  ).fail((err) ->
+    console.error err
+    console.error err.stack
+    throw err
+  )
+
+)


only mock the Helper http connection if HUBOT_LIVE env var is unset

--- test/tests.coffee
+++ test/tests.coffee
@@ -49,8 +49,9 @@
     user = new Robot.User 1, 'helper'
     super new Robot.TextMessage(user, text)

-class Helper.Response extends Robot.Response
-  # This changes ever HTTP request to hit the danger server above
-  http: (url) ->
-    super(url).host('127.0.0.1').port(9001)
+if not process.env.HUBOT_LIVE
+  class Helper.Response extends Robot.Response
+    # This changes ever HTTP request to hit the danger server above
+    http: (url) ->
+      super(url).host('127.0.0.1').port(9001)


add comments

add comments

--- server/initializers/updates.coffee
+++ server/initializers/updates.coffee
@@ -1,3 +1,4 @@
+# This initializer aims to check
 NotificationsHelper = require 'cozy-notifications-helper'
 log = require('printit')
     prefix: 'application updates'
@@ -9,13 +10,15 @@
 TIME_BETWEEN_UPDATE_CHECKS = 1000 * 60 * 60 * 24 # once a day

+# Check updates download the application manifest and extract version from it.
+# Then it compares it with the actual version number. If it's higher, a
+# *needUpdate* flag is set to true on the application model.
 checkUpdate = (notifier, app) ->
     log.info "#{app.name} - checking for an update..."
     app.checkForUpdate (err, setUpdate) ->
-        log.debug "#{app.version}"
         if err?
             log.error "#{app.name} - Error while checking update."
-            console.log err
+            log.raw err
         else if setUpdate
             log.info "#{app.name} - update required."
             notifier.createTemporary
@@ -25,6 +28,8 @@
             log.info "#{app.name} - no update required."

+# Check if a new version of an application is available for each of application
+# listed in the Cozy.
 checkUpdates = ->
     notifier = new NotificationsHelper 'home'

@@ -32,12 +37,13 @@
     Application.all (err, apps) ->
         if err
             log.error "Error when checking apps versions:"
-            console.log err
+            log.raw err
         else
             for app in apps
                 checkUpdate notifier, app

+# Start check update cron.
 module.exports = ->
     checkUpdates()
     setInterval checkUpdates, TIME_BETWEEN_UPDATE_CHECKS


Attempted fix for clearing hbs cache after updating a template

--- server/routes/api/templates.coffee
+++ server/routes/api/templates.coffee
@@ -28,7 +28,6 @@
         res.send(files)

   .post (req, res) ->
-
     errors = validateTemplate req.body.contents
     return res.status(400).send errors if errors

@@ -73,6 +72,7 @@
       if err
         res.status(500).send err
       else
+        hbs.cache = {} # Reset hbs internal cache
         res.status(201).send
           filename: filename
           contents: contents


Fixed #288 Chrome page reloading

Fixed #288 Chrome page reloading

--- wzk/resource/Client.coffee
+++ wzk/resource/Client.coffee
@@ -256,7 +256,7 @@

     xhr.listenOnce goog.net.EventType.SUCCESS, =>
       if xhr.getResponseHeader('Location')?
-        @xhrFac.getLocation().assign xhr.getResponseHeader('Location')
+        @reload xhr.getResponseHeader('Location')
       else if @isHtmlResponse(xhr)
         onError xhr.getResponseText()
       else if @isJsonResponse(xhr)
@@ -269,6 +269,14 @@
       'Content-Type': 'application/x-www-form-urlencoded'
     headers[wzk.resource.Client.X_HEADERS.REQUESTED_WITH] = 'XMLHttpRequest'
     @send url, 'POST', xhr, content, headers
+
+  ###*
+    @protected
+    @param {string} location
+  ###
+  reload: (location) ->
+    @xhrFac.getLocation().assign location
+    @xhrFac.getLocation().reload true

   ###*
     Posts given form in Iframe
@@ -285,7 +293,7 @@
     iframeIO.listen goog.net.EventType.SUCCESS, (event) =>
       response = event.target.getResponseJson()
       if response['location']?
-        @xhrFac.getLocation().assign response['location']
+        @reload response['location']
       else
         onSuccess response
         @xhrFac.applyJsonResponse response, @xhrConfig


Fix HistoryV2Manager front end tests

Fix HistoryV2Manager front end tests

--- services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee
+++ services/web/test/unit_frontend/coffee/ide/history/HistoryV2ManagerTests.coffee
@@ -130,7 +130,7 @@
 					project_ops: [{
 						remove:
 							pathname: "main.tex"
-							atV: 2
+						atV: 2
 					}]
 					fromV: 1, toV: 2
 				}])
@@ -144,7 +144,7 @@
 					project_ops: [{
 						remove:
 							pathname: "main.tex"
-							atV: 1
+						atV: 1
 					}]
 					fromV: 0, toV: 1
 				}])


Fixed masking for other browsers

Fixed masking for other browsers

--- app/assets/javascripts/blackmailr.js.coffee
+++ app/assets/javascripts/blackmailr.js.coffee
@@ -21,29 +21,33 @@
             if w and h
               unless $t.data 'mask'
                 $t.data mask: $('<div>')
-                                .css(
-                                  background: '#000'
-                                  position: 'absolute'
-                                  width: w
-                                  lineHeight: h + 'px'
-                                  left: l
-                                  top: t
-                                  color: '#fff'
-                                  textAlign: 'center'
-                                  fontSize: 50
-                                ).appendTo $t.parent()
+                  .css(
+                    position: 'absolute'
+                    width: 600
+                    top: t
+                  ).appendTo $t.parent()
+                $('<div>')
+                  .css(
+                    background: '#000'
+                    width: w
+                    lineHeight: h + 'px'
+                    margin: '0 auto'
+                    color: '#fff'
+                    textAlign: 'center'
+                    fontSize: 50
+                  ).appendTo $t.data 'mask'
               $mask = $t.data 'mask'
               if s <= 0
                 $mask.remove()
               else
                 scalar = .75 + (s / (10 * 60)) * .25
                 $mask
+                  .css(top: t + (h - scalar * h) / 2)
+                  .find('> div')
                   .text(text)
                   .css
                     width: scalar * w
                     lineHeight: scalar * h + 'px'
-                    left: l + (w - scalar * w) / 2
-                    top: t + (h - scalar * h) / 2
               $t.css visibility: 'visible'
           else
             $t.text text


Added bonnie_type field

Added bonnie_type field

--- app/assets/javascripts/cqlpatient.js.coffee
+++ app/assets/javascripts/cqlpatient.js.coffee
@@ -55,7 +55,9 @@
           classname = CQL_QDM.OIDMap.oidToClassName(dc)
           unless datatypes[classname]?
             datatypes[classname] = []
-          datatypes[classname].push new window['CQL_QDM'][classname](dc)
+          cql_dc = new window['CQL_QDM'][classname](dc)
+          cql_dc['bonnie_type'] = type
+          datatypes[classname].push cql_dc
     datatypes

   ###


Tests now use 'neft-unit'

Tests now use 'neft-unit'

--- test.spec.coffee
+++ test.spec.coffee
@@ -1,92 +1,85 @@
 'use strict'

 utils = require 'neft-utils'
+unit = require 'neft-unit'
+assert = require 'neft-assert'
 List = require './index'

+{describe, it, beforeEach, afterEach} = unit
+
 describe 'ctor', ->
-
 	it 'creates new List', ->
 		list = List()
-		expect(list).toEqual jasmine.any List
+		assert.instanceOf list, List

 describe 'length', ->
-
 	it 'returns amount of items', ->
 		list = List [1, 2]
-		expect(list.length).toBe 2
+		assert.is list.length, 2

 describe 'values are accessed by their keys', ->
 	list = List [1, 2]
-	expect(list[0]).toBe 1
-	expect(list[1]).toBe 2
-	expect(list[2]).toBe undefined
+	assert.is list[0], 1
+	assert.is list[1], 2
+	assert.is list[2], undefined

 describe 'set()', ->
-
 	it 'change existed item value', ->
 		list = List [1]
 		list.set 0, 2
-		expect(list[0]).toBe 2
+		assert.is list[0], 2

 describe 'append()', ->
-
 	it 'add an item on the end', ->
 		list = List [1]
 		list.append 2
-		expect(list.toArray()).toEqual [1, 2]
+		assert.isEqual list.toArray(), [1, 2]

 describe 'insert()', ->
-
 	it 'insert an item at given position', ->
 		list = List [1, 2]
 		list.insert 1, 0
 		list.insert 0, 2
-		expect(list.toArray()).toEqual [2, 1, 0, 2]
+		assert.isEqual list.toArray(), [2, 1, 0, 2]

 describe 'remove()', ->
-
 	it 'remove the first element equal given value', ->
 		list = List [0, 1, 1, 2]
 		list.remove 1
-		expect(list.toArray()).toEqual [0, 1, 2]
+		assert.isEqual list.toArray(), [0, 1, 2]

 describe 'pop()', ->
-
 	it 'remove the last item', ->
 		list = List [1, 2]
 		list.pop()
-		expect(list.toArray()).toEqual [1]
+		assert.isEqual list.toArray(), [1]

 	it 'remove the item at given position', ->
 		list = List [1, 2, 3]
 		list.pop 1
-		expect(list.toArray()).toEqual [1, 3]
+		assert.isEqual list.toArray(), [1, 3]
 		list.pop 0
-		expect(list.toArray()).toEqual [3]
+		assert.isEqual list.toArray(), [3]

 describe 'clear()', ->
-
 	it 'remove all items', ->
 		list = List [1, 2]
 		list.clear()
-		expect(list.toArray()).toEqual []
+		assert.isEqual list.toArray(), []

 describe 'index()', ->
-
 	it 'return the index of the first item equal given value', ->
 		list = List [1, 2, 2]
-		expect(list.index 2).toBe 1
-		expect(list.index 3).toBe -1
+		assert.is list.index(2), 1
+		assert.is list.index(3), -1

 describe 'has()', ->
-
 	it 'return true if given value exists in a list', ->
 		list = List [1, 2, 2]
-		expect(list.has 2).toBe true
-		expect(list.has 3).toBe false
+		assert.is list.has(2), true
+		assert.is list.has(3), false

 describe 'onChange signal', ->
-
 	list = listener = null
 	ok = false
 	args = copy = null
@@ -101,7 +94,7 @@
 			copy = utils.clone list

 	afterEach ->
-		expect(ok).toBeTruthy()
+		assert.ok ok
 		list.onChange.disconnect listener

 	it 'works with set()', ->
@@ -110,11 +103,10 @@

 		list.set 1, 'a'

-		expect(args).toEqual [[2, 1]]
-		expect(copy.toArray()).toEqual [1, 'a', 3]
+		assert.isEqual args, [[2, 1]]
+		assert.isEqual copy.toArray(), [1, 'a', 3]

 describe 'onInsert signal', ->
-
 	list = listener = null
 	ok = false
 	args = copy = null
@@ -129,7 +121,7 @@
 			copy = utils.clone list

 	afterEach ->
-		expect(ok).toBeTruthy()
+		assert.ok ok
 		list.onInsert.disconnect listener

 	it 'works with append()', ->
@@ -138,8 +130,8 @@

 		list.append 'a'

-		expect(args).toEqual [['a', 2]]
-		expect(copy.toArray()).toEqual [1, 2, 'a']
+		assert.isEqual args, [['a', 2]]
+		assert.isEqual copy.toArray(), [1, 2, 'a']

 	it 'works with insert()', ->
 		list = List [1, 2]
@@ -147,11 +139,10 @@

 		list.insert 1, 'a'

-		expect(args).toEqual [['a', 1]]
-		expect(copy.toArray()).toEqual [1, 'a', 2]
+		assert.isEqual args, [['a', 1]]
+		assert.isEqual copy.toArray(), [1, 'a', 2]

 describe 'onPop signal', ->
-
 	list = listener = null
 	ok = false
 	args = copy = null
@@ -166,7 +157,7 @@
 			copy = utils.clone list

 	afterEach ->
-		expect(ok).toBeTruthy()
+		assert.ok ok

 	it 'works with pop()', ->
 		list = List [1, 2]
@@ -174,8 +165,8 @@

 		list.pop()

-		expect(args).toEqual [[2, 1]]
-		expect(copy.toArray()).toEqual [1]
+		assert.isEqual args, [[2, 1]]
+		assert.isEqual copy.toArray(), [1]

 	it 'works with pop(i)', ->
 		list = List [1, 2]
@@ -183,8 +174,8 @@

 		list.pop 0

-		expect(args).toEqual [[1, 0]]
-		expect(copy.toArray()).toEqual [2]
+		assert.isEqual args, [[1, 0]]
+		assert.isEqual copy.toArray(), [2]

 	it 'works with remove()', ->
 		list = List [1, 2]
@@ -192,8 +183,8 @@

 		list.remove 2

-		expect(args).toEqual [[2, 1]]
-		expect(copy.toArray()).toEqual [1]
+		assert.isEqual args, [[2, 1]]
+		assert.isEqual copy.toArray(), [1]

 	it 'works with clear()', ->
 		list = List [1, 2]
@@ -201,5 +192,5 @@

 		list.clear()

-		expect(args).toEqual [[2, 1], [1, 0]]
-		expect(copy.toArray()).toEqual []
+		assert.isEqual args, [[2, 1], [1, 0]]
+		assert.isEqual copy.toArray(), []


[LFTUI-41] making socket configuration more ply-able

--- src/coffee/services/socket.coffee
+++ src/coffee/services/socket.coffee
@@ -19,8 +19,8 @@
   connect = (callback) ->
     is_connected = true

-    current_socket = io CONFIG.urls.api,
-      path: '/sock'
+    current_socket = io CONFIG.socket.host,
+      path: CONFIG.socket.path
       query:
         __sails_io_sdk_version: "0.11.0"
         __sails_io_platform: "browser"


Remove console log statements

Remove console log statements

--- src/smc-webapp/redux_account.coffee
+++ src/smc-webapp/redux_account.coffee
@@ -137,7 +137,6 @@
                     window.location.reload(false)

     push_state: (url) =>
-        console.log("setting", url)
         {set_url} = require('./history')
         if not url?
             url = @_last_history_state


Fetcher: fixing delete method by fixing name of the attribute for super agent

--- smsgw/static/js/api/fetcher.coffee
+++ smsgw/static/js/api/fetcher.coffee
@@ -68,6 +68,6 @@
     delete: (url, query = {}) ->
         token = @token
         new Promise (resolve, reject) ->
-            standartize request.delete(url), token, query
+            standartize request.del(url), token, query
                 .end handler resolve, reject
 }


Rename _doColumn() to _doAtom()

Rename _doColumn() to _doAtom()

--- src/dialects/sql-formatter.coffee
+++ src/dialects/sql-formatter.coffee
@@ -67,9 +67,9 @@
     column: (c) ->
         atom = _.firstOrSelf(c)
         alias = _.secondOrNull(c)
-        return @_doColumn(atom, alias, true)
-
-    _doColumn: (atom, alias, addAlias = false) ->
+        return @_doAtom(atom, alias, true)
+
+    _doAtom: (atom, alias, addAlias = false) ->
         token = @tokenizeAtom(atom)
         model = @findColumnModel(token)
         s = @_doToken(token, model)
@@ -109,7 +109,7 @@

     relop: (left, op, right) ->
-        l = @_doColumn(left)
+        l = @_doAtom(left)

         # MUST: replace with data driven approach
         op = op.toUpperCase()
@@ -203,10 +203,10 @@

     orderBy: (c) -> @doList(c.orderings, @ordering, ', ', ' ORDER BY ')

-    grouping: (atom) -> @_doColumn(atom)
+    grouping: (atom) -> @_doAtom(atom)

     ordering: (o) ->
-        s = @_doColumn(_.firstOrSelf(o))
+        s = @_doAtom(_.firstOrSelf(o))
         dir = if _.secondOrNull(o) == 'DESC' then 'DESC' else 'ASC'

         "#{s} #{dir}"


Update the tests

Update the tests

--- test/test.coffee
+++ test/test.coffee
@@ -1,8 +1,37 @@
 "use strict"

+StoreIt = require("..") # load StoreIt!
+StoreitError = StoreIt.StoreitError;
+
+describe "StoreIt.StoreitError", ->
+    describe "(before calling new)", ->
+        it "exists as a static property", ->
+            StoreitError.should.not.equal(undefined)
+        it "exposes the correct types", ->
+            StoreitError.should.have.property("loadUninitialized")
+            StoreitError.should.have.property("undefinedValue")
+            StoreitError.should.have.property("invalidNamespace")
+            StoreitError.should.have.property("nonexistentKey")
+            StoreitError.should.have.property("invalidKey")
+
+    describe "(after calling new)", ->
+        err = new StoreitError(StoreitError.invalidNamespace)
+
+        it "should be an instance of StoreitError", ->
+            (err instanceof StoreitError).should.equal(true)
+        it "should expose a `name` property", ->
+            err.should.have.property("name")
+            err.name.should.equal("StoreitError")
+        it "should expose a `type` property", ->
+            err.should.have.property("name")
+            err.type.should.equal("invalidNamespace")
+        it "should expose a `message` property", ->
+            err.should.have.property("message")
+            err.message.should.equal("namespace can not contain a hash character (#)")
+        it "should expose a `stack` property", ->
+            err.should.have.property("message")

 describe "StoreIt!", ->
-    StoreIt = require("..") # load StoreIt!
     service = null

     beforeEach ->
@@ -73,6 +102,12 @@
             StoreIt.EventName["modified"].should.equal("modified")
             StoreIt.EventName["removed"].should.equal("removed")
             StoreIt.EventName["cleared"].should.equal("cleared")
+
+        it "should throw a StoreitError when calling remove", ->
+            (=>
+                service.remove("key")
+            ).should.throw(StoreitError)
+#            ).should.throw(new StoreitError(StoreitError.nonexistentKey))

         describe "when calling has", ->

@@ -386,7 +421,7 @@
                 @keys.length.should.equal(0)

-    describe "with one items of pre-loaded data", ->
+    describe "with one item of pre-loaded data", ->
         beforeEach ->
             @publishAdded = sinon.stub()
             service = new StoreIt("loadns", @storageProvider)
@@ -412,6 +447,10 @@
                 service.get("testkey5").should.equal("test")
             it "should publish an 'added' event for each item", ->
                 @publishAdded.should.be.calledWith("test", "testkey5")
+            it "should NOT throw a StoreitError when calling remove", ->
+                (=>
+                    service.remove("testkey5")
+                ).should.not.throw(StoreitError)

         describe "when calling set with options.publish = false", ->
             beforeEach ->
@@ -420,3 +459,24 @@

             it "should be publish nothing", ->
                 @publishAdded.should.not.be.called
+
+    describe "without calling `load`", ->
+        beforeEach ->
+            service = new StoreIt("testns", @storageProvider)
+
+        it "should throw a StoreitError when calling has", ->
+            (=>
+                service.has("key")
+            ).should.throw(StoreitError)
+        it "should throw a StoreitError when calling get", ->
+            (=>
+                service.get("key")
+            ).should.throw(StoreitError)
+        it "should throw a StoreitError when calling set", ->
+            (=>
+                service.set("key", "value")
+            ).should.throw(StoreitError)
+        it "should throw a StoreitError when calling remove", ->
+            (=>
+                service.remove("key")
+            ).should.throw(StoreitError)


Better organization of the coffeescript code executed when a user clicks on a feed in the sidebar.

--- app/assets/javascripts/feeds/read_feed.js.coffee
+++ app/assets/javascripts/feeds/read_feed.js.coffee
@@ -1,6 +1,10 @@
 #= require ./alert_hiding

 $(document).ready ->
+
+  ########################################################
+  # EVENTS
+  ########################################################

   #-------------------------------------------------------
   # Load current feed entries when clicking on a feed in the sidebar
@@ -9,9 +13,7 @@

     # Function to insert new entries in the list
     insert_entries = (entries, status, xhr) ->
-      $(".icon-spin").removeClass("icon-spin").addClass "hidden"
-      $("#loading").addClass "hidden"
-      $("#feed-entries").removeClass "hidden"
+      entries_loaded()
       # Show and enable Refresh button
       $("#refresh-feed").removeClass("hidden").removeClass("disabled")
       # Unsubscribe and Folder Management buttons are shown and enabled only if reading a single feed
@@ -52,26 +54,40 @@
     # Creating a new folder adds this feed to it
     $("#new_folder_feed_id").val(feed_id)

+    show_feed_title this
+    loading_entries this
+
+    # Load the entries via Ajax
+    $("#feed-entries").load feed_path, null, insert_entries
+
+  ########################################################
+  # COMMON FUNCTIONS
+  ########################################################
+
+  #-------------------------------------------------------
+  # While loading entries hide the entries list,show the spinner and  show "Loading" message
+  #-------------------------------------------------------
+  loading_entries = (feed)->
+    $("#feed-entries").empty().addClass "hidden"
+    $("#start-info").addClass "hidden"
+    $("#loading").removeClass "hidden"
+    $(".icon-spinner", feed).addClass("icon-spin").removeClass "hidden"
+
+  #-------------------------------------------------------
+  # When entries have loaded hide the spinner and "Loading" message, show the entries list
+  #-------------------------------------------------------
+  entries_loaded = (feed)->
+    $(".icon-spin").removeClass("icon-spin").addClass "hidden"
+    $("#loading").addClass "hidden"
+    $("#feed-entries").removeClass "hidden"
+
+  #-------------------------------------------------------
+  # Show the feed title and link it to the feed URL
+  #-------------------------------------------------------
+  show_feed_title = (feed)->
     # Show the feed title
-    feed_title = $(this).attr "data-feed-title"
+    feed_title = $(feed).attr "data-feed-title"
     $("#feed-title a").text feed_title
     $("#feed-title").removeClass "hidden"
-
-    # The feed title links to the feed url
-    feed_url = $(this).attr "data-feed-url"
-    $("#feed-title a").attr("href", feed_url)
-
-    # Empty the entries list before loading
-    $("#feed-entries").empty().addClass "hidden"
-
-    # Hide the start page
-    $("#start-info").addClass "hidden"
-
-    # Show "loading" message
-    $("#loading").removeClass "hidden"
-
-    # Show a spinning icon while loading
-    $(".icon-spinner", this).addClass("icon-spin").removeClass "hidden"
-
-    # Load the entries via Ajax
-    $("#feed-entries").load feed_path, null, insert_entries+    feed_url = $(feed).attr "data-feed-url"
+    $("#feed-title a").attr("href", feed_url)


F dropzone js error

F dropzone js error

--- app/assets/javascripts/folio/console/base.coffee
+++ app/assets/javascripts/folio/console/base.coffee
@@ -3,6 +3,8 @@
 #= require popper
 #= require bootstrap-sprockets
 #= require dropzone
+
+
 #= require cocoon
 #= require redactor

@@ -10,13 +12,15 @@

 #= require ./redactor-init
 #= require ./redactor-imagemanager
-#= require ./nodes
-#= require ./files

 $ ->
   # disable auto discover
   Dropzone.autoDiscover = false

+#= require ./nodes
+#= require ./files
+
+$ ->
   $(document).on 'change', '#filter-form', ->
     $(this).submit()


Fix default playlistitem title

Fix default playlistitem title

--- app/assets/javascripts/me-add-to-playlist.coffee
+++ app/assets/javascripts/me-add-to-playlist.coffee
@@ -27,9 +27,9 @@
             structure_title = current_span.text().replace(re1,'').replace(re2,'').trim()
             parent = current_span.closest('ul').closest('li').prev()
             while parent.length > 0
-              structure_title = parent.text().trim()+' - '+structure_title
+              structure_title = [parent.text().trim(), structure_title].filter(String).join(' - ')
               parent = parent.closest('ul').closest('li').prev()
-            default_title = default_title+' - '+structure_title
+            default_title = [default_title, structure_title].filter(String).join(' - ')
           $('#playlist_item_title').val(default_title)
           $('#playlist_item_start').val(mejs.Utility.secondsToTimeCode(player.getCurrentTime(), true))
           if $('a.current-stream').length && typeof($('a.current-stream')[0].dataset.fragmentend) != typeof(undefined)


Add restriction for single characters in branch name

--- app/assets/javascripts/new_branch_form.js.coffee
+++ app/assets/javascripts/new_branch_form.js.coffee
@@ -34,13 +34,19 @@
       conjunction: "or"
     }

-    characters = {
+    invalid = {
       pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g
       prefix: "can't contains",
       conjunction: ", "
     }

-    @restrictions = [startsWith, characters, endsWith]
+    single = {
+      pattern: /^@+$/g
+      prefix: "can't be",
+      conjunction: "or"
+    }
+
+    @restrictions = [startsWith, invalid, endsWith, single]

   validate: =>
     @branchNameError.empty()


Use readonly transactions for read operations.

--- app/assets/javascripts/pods/pods_store.js.coffee
+++ app/assets/javascripts/pods/pods_store.js.coffee
@@ -5,7 +5,7 @@
     records = []
     promise = new Promise (resolve, reject) =>
       @database (db) ->
-        t = db.transaction 'pods', 'readwrite'
+        t = db.transaction 'pods', 'readonly'
         store = t.objectStore('pods')
         keyRange = IDBKeyRange.lowerBound(0)
         r = store.openCursor(keyRange)
@@ -27,7 +27,7 @@
         r = s.put(pod)
   countAll: (callback) ->
     @database (db) ->
-      t = db.transaction 'pods', 'readwrite'
+      t = db.transaction 'pods', 'readonly'
       s = t.objectStore('pods')
       r = s.count()
       r.onsuccess = ->


Symlink or copy packages from the user's real ATOM_HOME

--- spec/jasmine-test-runner.coffee
+++ spec/jasmine-test-runner.coffee
@@ -26,7 +26,19 @@
     get: -> documentTitle
     set: (title) -> documentTitle = title

+  userHome = process.env.ATOM_HOME or path.join(fs.getHomeDirectory(), '.atom')
   atomHome = temp.mkdirSync prefix: 'atom-test-home-'
+  if process.env.APM_TEST_PACKAGES
+    testPackages = process.env.APM_TEST_PACKAGES.split /\s+/
+    fs.makeTreeSync path.join(atomHome, 'packages')
+    for packName in testPackages
+      userPack = path.join(userHome, 'packages', packName)
+      loadablePack = path.join(atomHome, 'packages', packName)
+
+      try
+        fs.symlinkSync userPack, loadablePack, 'dir'
+      catch
+        fs.copySync userPack, loadablePack

   ApplicationDelegate = require '../src/application-delegate'
   applicationDelegate = new ApplicationDelegate()