Training subject selection

Training subject selection

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -195,7 +195,7 @@
     Subject.on 'fetch', @onSubjectFetch
     Subject.on 'select', @onSubjectSelect
     @Subject = Subject
-    @Subject.group = true
+    @Subject.group = '532b37203ae740fc7a000002'

     console.log @el.find(".frame-image")

@@ -603,6 +603,22 @@
     @starbleedCount.html("#{starbleedCount}" + "
" + "Star Bleed#{if starbleedCount is 1 then '' else 's'}")
     @hotpixelCount.html("#{hotpixelCount}"+ "
" + "Hotpixel#{if hotpixelCount is 1 then '' else 's'} / Cosmic Ray#{if hotpixelCount is 1 then '' else 's'}")

+  trainingRate: ->
+    count = zooniverse.models.User.current?.project?.classification_count or 0
+    count += zooniverse.models.Classification.sentThisSession
+
+    if count < 10
+      1 / 5
+    else if count < 20
+      1 / 10
+    else if count < 50
+      1 / 20
+    else
+      1 / 50
+
+  shouldShowTraining: ->
+    Math.random() < @trainingRate()
+
   onClickNextSubject: ->
     @removeElementsOfClass(".known-asteroid")
     element.hide() for element in [@summaryContainer, @nextSubjectButton, @rightPanelSummary]
@@ -611,7 +627,16 @@
     @stopPlayingFrames()
     element.show() for element in [@surfacesContainer, @finishButton, @rightPanel.find('.answers'), @cycleButton]
     @destroyFrames()
-    Subject.next()
+
+    if @shouldShowTraining()
+      app.api.get('projects/asteroid/groups/532b37203ae740fc7a000001/subjects').then (subjects) ->
+        subject = new zooniverse.models.Subject subjects[0]
+        queued = zooniverse.models.Subject.instances.pop()
+        zooniverse.models.Subject.instances.unshift queued
+        subject.select()
+    else
+      Subject.next()
+
     document.getElementById('frame-slider').value = 0
     @finishButton.prop 'disabled', true
     @onClickFlicker()


Fix feedback position, closes #289

Fix feedback position, closes #289

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -478,7 +478,7 @@
       #   x.event.dispatch       @ jquery.min.js:5
       #   v.handle               @ jquery.min.js:5

-      # This must be related to an earlier error, on the loading
+      # This must be related to an earlier error, on the loading
       # of the subject:
       # Uncaught TypeError: Cannot read property 'id' of undefined

@@ -596,7 +596,7 @@
     @level = Math.min(baseLevel, 3)
     denominator = (5 * Math.pow(Math.sqrt(2), @level - 1))
     @simRatio = 1 / denominator
-
+
     Subject.group = if @simRatio > Math.random() then @simulationGroup else @subjectGroup

     # Update sim freq text
@@ -695,8 +695,8 @@
         trainingType = trainingType.replace ' ', '_'

         # Get the location for the dialog
-        x = (training.x + 200) / @subjectDimension
-        y = 1 - 2.25 * (training.y / @subjectDimension)
+        x = (training.x + 25) / @subjectDimension
+        y = (@subjectDimension - training.y) / @subjectDimension

         # Check if any annotation over lens
         for index, annotation of @annotations


Remove unused method

Remove unused method

--- src/trix/core/object_view.coffee
+++ src/trix/core/object_view.coffee
@@ -54,9 +54,6 @@
   findViewForObject: (object) ->
     return view for view in @getAllChildViews() when view.object is object

-  findNodesForObject: (object) ->
-    @findViewForObject(object)?.nodes
-
   getViewCache: ->
     if @rootView is this
       if @isViewCachingEnabled()


Fix ajax image append after upload

Fix ajax image append after upload

--- app/views/images/create.js.coffee
+++ app/views/images/create.js.coffee
@@ -1 +1 @@
-$(".media-grid").append "<%= escape_javascript(render 'image', image: @image) %>"+$(".thumbnails").append "<%= escape_javascript(render 'image', image: @image) %>"


Use the receive helper

Use the receive helper

--- spec/network.coffee
+++ spec/network.coffee
@@ -28,7 +28,6 @@
     describe 'requesting runtime metadata', ->
       it 'should provide it back', (done) ->
         listener = (message) ->
-          #console.log 'got message', message
           window.removeEventListener 'message', listener, false
           msg = message.data
           msg = JSON.parse msg
@@ -220,21 +219,35 @@
     describe 'on starting the network', ->
       it 'should get started and stopped', (done) ->
         @timeout 15000
-        started = false
-        listener = (message) ->
-          msg = JSON.parse message.data
-
-          chai.expect(msg.protocol).to.equal 'network'
-          if msg.command is 'started'
-            chai.expect(msg.payload).to.be.an 'object'
-            chai.expect(msg.payload.graph).to.equal 'bar'
-            #chai.expect(msg.payload.time).to.be.a 'date'
-            started = true
-          if msg.command is 'stopped'
-            chai.expect(started).to.equal true
-            window.removeEventListener 'message', listener, false
-            done()
-        window.addEventListener 'message', listener, false
+        expected = [
+          protocol: 'network'
+          command: 'stopped'
+        ,
+          protocol: 'network'
+          command: 'started'
+        ,
+          protocol: 'network'
+          command: 'connect'
+        ,
+          protocol: 'network'
+          command: 'data'
+        ,
+          protocol: 'network'
+          command: 'connect'
+        ,
+          protocol: 'network'
+          command: 'data'
+        ,
+          protocol: 'network'
+          command: 'disconnect'
+        ,
+          protocol: 'network'
+          command: 'disconnect'
+        ,
+          protocol: 'network'
+          command: 'stopped'
+        ]
+        receive 'network', expected, done
         send 'network', 'start',
           graph: 'bar'


remove check for ownerId

remove check for ownerId

--- apps/fair_organizer/routes.coffee
+++ apps/fair_organizer/routes.coffee
@@ -39,7 +39,7 @@
       res.locals.tab = req.params.tab
     complete: ->
       profile = res.locals.profile
-      return next() unless profile?.isFairOrOrganizer() and profile?.ownerHasId()
+      return next() unless profile?.isFairOrOrganizer()
       fair = new Fair id: 'the-armory-show-2015'
       fair.fetch
         error: res.backboneError


removed context from extract function dialog

--- lib/extractFunction-view.coffee
+++ lib/extractFunction-view.coffee
@@ -9,10 +9,6 @@
     @div class: 'extract', =>
       @label 'Extract to function: '
       @input name:'fun', placeholder: 'functionName', outlet: 'fun', value: name
-      @label 'Context '
-      @select name:'context', outlet: 'context', value: 'Local' , =>
-        @option 'Local', value: 'local'
-        @option 'File', value: 'file'
       @button 'Ok', click: 'onConfirm', class: 'post-btn btn', outlet: 'okButton', type:'submit'
       @button 'Cancel', click: 'destroy', class:'post-btn btn', outlet: 'cancelButton'

@@ -21,12 +17,10 @@
     oldView = this
     @callback = fun
     @fun.val(name)
-    #@okButton.on 'click', => @onConfirm()
-    #@cancelButton.on 'click', => @destroy()
     @panel = atom.workspace.addBottomPanel(item: this)

   onConfirm:(text) ->
-    @callback(@fun.val(), @context.val())
+    @callback(@fun.val())
     @destroy()

   destroy: ->


Remove useless revision property on document. Compute it from the operations list.

--- src/Document.coffee
+++ src/Document.coffee
@@ -4,14 +4,13 @@
   constructor: ->
     @text = ""

-    @revision = 0
     @operations = []

   apply: (newOperation, revision) ->
     # Should't happened
-    throw new Error "The operation base revision is greater than the document revision" if revision > @revision
+    throw new Error "The operation base revision is greater than the document revision" if revision > @operations.length

-    if revision < @revision
+    if revision < @operations.length
       # Conflict !
       missedOperations = new OT.TextOperation
       missedOperations.targetLength = @operations[revision].baseLength
@@ -20,14 +19,11 @@
         missedOperations = missedOperations.compose @operations[index]

       [missedOperationsPrime, newOperationPrime] = missedOperations.transform newOperation
-      operationToPush = newOperationPrime
-    else
-      operationToPush = newOperation.clone()
+      newOperation = newOperationPrime

-    @text = operationToPush.apply @text
+    @text = newOperation.apply @text
+    @operations.push newOperation.clone()

-    @revision++
-    @operations.push operationToPush
     return


:fire: Remove grim require from PaneContainerElement

--- src/pane-container-element.coffee
+++ src/pane-container-element.coffee
@@ -1,5 +1,4 @@
 {CompositeDisposable} = require 'event-kit'
-Grim = require 'grim'
 _ = require 'underscore-plus'

 module.exports =


Prevent new books from being given a 'subcol' id

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -72,6 +72,10 @@
     toJSON: (options = {}) ->
       results = super(arguments...)

+      # Prevent new books with no id from being labelled a subcollection
+      if results.id is 'subcol'
+        delete results.id
+
       if options.includeTree and @isBook()
         results.tree =
           id: @getVersionedId()


Use simple backbone collection for authors

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -29,10 +29,6 @@

       return response

-  class Author extends Backbone.AssociatedModel
-    defaults:
-      name: 'Unknown'
-
   class Toc extends Backbone.AssociatedModel
     relations: [{
       type: Backbone.Many
@@ -56,7 +52,7 @@
     }, {
       type: Backbone.Many
       key: 'authors'
-      relatedModel: Author
+      collectionType: Backbone.Collection
     }]

     toJSON: () ->


fix opening fade in

fix opening fade in

--- src/scripts/slick-lightbox.coffee
+++ src/scripts/slick-lightbox.coffee
@@ -32,7 +32,7 @@
 				"""

"""
 		else
 			createItem = (el) =>
-				caption = @getElementCaption(el)
+				caption = @getElementCaption(el)
 				"""

#{ caption }

"""
 			# We need to start with the `index`-th item.
 			a = @element.find(@options.itemSelector)
@@ -48,7 +48,7 @@
 		links = @createModalItems index

 		html = """
-

+

#{ links.join('') }

 				<button type="button" class="slick-lightbox-close"></button>


restify replaced with request in Provider.coffee

--- src/steroids/data/Provider.coffee
+++ src/steroids/data/Provider.coffee
@@ -1,5 +1,3 @@
-restify = require "restify"
-
 request = require "request"

 paths = require "../paths"
@@ -20,9 +18,8 @@
   @legacyClient.headers["Authorization"] = Login.currentAccessToken()

   @apiClient = request.defaults
-    auth:
-      user: Login.currentAccessToken()
-      password: "X"
+    headers:
+      Authorization: Login.currentAccessToken()

   @forBackend: (backend) =>
     return new Promise (resolve, reject) =>
@@ -114,10 +111,9 @@
       url = "/app/#{dataHelpers.getAppId()}/raml.yaml?identification_hash=#{dataHelpers.getIdentificationHash()}"

       steroidsCli.debug "PROVIDER", "GETting from URL: #{url}"
-
-      @apiClient
+      @apiClient.get
         method: "get"
-        url: url
+        url: "#{configApiBaseUrl}/#{url}"
       , (err, res, body) =>

         if err?
@@ -126,7 +122,7 @@
           return

         if res.statusCode != 200
-          steroidsCli.debug "PROVIDER", "Getting current data configuration from cloud returned failure: #{res['body']}"
+          steroidsCli.debug "PROVIDER", "Getting current data configuration from cloud returned failure: #{body}"
           reject new CloudReadError body
           return

@@ -145,19 +141,20 @@
     return new Promise (resolve, reject) =>
       steroidsCli.debug "PROVIDER", "Getting providers from cloud"

-      @apiClient
+      @apiClient.get
         method: "get"
-        url: "/app/#{dataHelpers.getAppId()}/service_providers.json"
-      , (err, res, body) =>
+        url: "#{configApiBaseUrl}/app/#{dataHelpers.getAppId()}/service_providers.json"
+      , (err, res, body) =>
+        obj = JSON.parse body

         if err?
           steroidsCli.debug "PROVIDER", "Getting providers from cloud returned failure: #{JSON.stringify(obj)}"
           reject new CloudReadError err
           return

-        steroidsCli.debug "PROVIDER", "Getting providers from cloud returned success: #{JSON.stringify(obj)}"
+        steroidsCli.debug "PROVIDER", "Getting providers from cloud returned success: #{JSON.stringify(body)}"
         result = []
-        body.forEach (p)=>
+        obj.forEach (p)=>
           result.push @fromCloudObject(p)
         resolve result

@@ -169,7 +166,7 @@
     return provider

   constructor: (@options={}) ->
-    @legacyClient = Provider.apiClient
+    @apiClient = Provider.apiClient

     #TODO Provider.fromBackend(backend)
     @backend = @options.backend
@@ -186,8 +183,16 @@
         configurationKeys: @backend.configurationKeysForProxy()

       url = "/app/#{dataHelpers.getAppId()}/service_providers.json"
-      steroidsCli.debug "PROVIDER", "POSTing #{JSON.stringify(data)} to URL #{url}"
-      @legacyClient.post url, data, (err, req, res, obj) =>
+      steroidsCli.debug "PROVIDER", "POSTing #{JSON.stringify(data)} to URL #{configApiBaseUrl}/#{url}}"
+
+      @apiClient
+        method: "post"
+        json: data
+        url: "#{configApiBaseUrl}/#{url}"
+      , (err, res, body) =>
+
+        obj = body
+
         if obj.uid
           steroidsCli.debug "PROVIDER", "Creating a new provider #{@name} ID: #{@typeId} to cloud returned success: #{JSON.stringify(obj)}"

@@ -196,8 +201,6 @@
         else
           steroidsCli.debug "PROVIDER", "Creating a new provider #{@name} ID: #{@typeId} to cloud returned failure: #{JSON.stringify(obj)}"
           reject new CloudWriteError err
-
-        @legacyClient.close()

   fromCloudObject: (obj)=>
     steroidsCli.debug "PROVIDER", "Updating attributes for provider from object: #{JSON.stringify(obj)}"
@@ -213,7 +216,11 @@
       url = "/app/#{dataHelpers.getAppId()}/service_providers/#{@uid}/resources.json"

       steroidsCli.debug "PROVIDER", "GETting from URL: #{url}"
-      @legacyClient.get url, (err, req, res, obj) =>
+      @apiClient.get
+        method: "get"
+        url: "#{configApiBaseUrl}/#{url}"
+      , (err, res, body) =>
+        obj = JSON.parse body

         if err?
           steroidsCli.debug "PROVIDER", "Getting resources for provider #{@name} ID: #{@typeId} from cloud returned failure: #{err} #{JSON.stringify(obj)}"


update

update


add missing update event

add missing update event

--- src/ui/models/convsettings.coffee
+++ src/ui/models/convsettings.coffee
@@ -43,6 +43,7 @@
             display_name: p.display_name or p.fallback_name
       @id = c.conversation_id or c.id
       @name = c.name or ""
+      updated 'convsettings'

     reset: ->
       @searchedEntities = []


comments

comments

--- api/feed/routes.coffee
+++ api/feed/routes.coffee
@@ -20,6 +20,7 @@
 @index = (req, res, next) ->
   Tag.find (err, tags)->

+    # make a new backbone collection to combine instagram feeds and remove duplicates
     feed = new Backbone.Collection null
     feed.comparator = (model)-> - parseInt model.get('created_time')

@@ -27,6 +28,7 @@

     Q.allSettled(promises).then( (response)->

+      # add all retrieved feeds to the collection
       _.map response, (promise)-> feed.add promise.value

       res.send feed.toJSON()


Added new field to dmp.search's result: was the match exact?

--- app/lib/dmp/dmp.coffee
+++ app/lib/dmp/dmp.coffee
@@ -63,8 +63,10 @@
     dneIndex = @dmp.match_main txet, nrettap, expectedDneLoc
     endIndex = text.length - dneIndex
     matchLength = endIndex - startIndex
+    found = text.substr startIndex, matchLength

     result =
       start: startIndex
       end: endIndex
-      found: text.substr startIndex, matchLength
+      found: found
+      exact: found is pattern


Don't subscribe to pusher if there's no instance

--- app/routes/main.coffee
+++ app/routes/main.coffee
@@ -19,7 +19,7 @@

   activate: ->
     # subscribe to pusher only if we're at a main route
-    unless config.pro
+    if !config.pro && @pusher
       @pusher.subscribeAll(['common'])

 `export default Route`


auto switch to NON regexp search if possible

--- lib/provider/search-base.coffee
+++ lib/provider/search-base.coffee
@@ -53,10 +53,8 @@
       @readInput().then ({text, isRegExp}) =>
         @searchTerm = text

-        # Automatically switched to static search since it's faster for highlight
-        # And good for grammar syntax highlight
-        # @isRegExpSearch = isRegExp and _.escapeRegExp(text) isnt text
-        @isRegExpSearch = isRegExp
+        # Automatically switch to static search for faster range calcuration and good syntax highlight
+        @isRegExpSearch = isRegExp and _.escapeRegExp(text) isnt text

         history.save(@searchTerm)
         @searchIgnoreCase ?= @getIgnoreCaseValueForSearchTerm(@searchTerm)


return empty array if no input args are given

Fixes #1.

--- lib/python-debugger-view.coffee
+++ lib/python-debugger-view.coffee
@@ -185,7 +185,7 @@

   getInputArguments: ->
     args = @argsEntryView.getModel().getText()
-    args.split(" ") if !@stringIsBlank(args)
+    return if !@stringIsBlank(args) then args.split(" ") else []

   getCommand: ->
     command = @commandEntryView.getModel().getText()


:lipstick:

:lipstick:

--- lib/search-results-model.coffee
+++ lib/search-results-model.coffee
@@ -127,18 +127,18 @@
     @setBuffer(@editor.activeEditSession.buffer)

   onCursorMoved: =>
-    isWithinMarker = (pos, marker) ->
+    isWithinMarker = (bufferPosition, marker) ->
       # Using marker.getBufferRange() was slow on large sets. This is faster -- no object creation.
       start = marker.bufferMarker.tailPosition or marker.bufferMarker.headPosition
       end = marker.bufferMarker.headPosition
-      return false unless pos.column >= start.column and pos.column <= end.column
-      return false unless pos.row >= start.row and pos.row <= end.row
+      return false unless bufferPosition.column >= start.column and bufferPosition.column <= end.column
+      return false unless bufferPosition.row >= start.row and bufferPosition.row <= end.row
       true

-    pos = @editor.getCursorBufferPosition()
+    bufferPosition = @editor.getCursorBufferPosition()
     for i in [0...@markers.length]
       marker = @markers[i]
-      return @setCurrentResultIndex(i) if isWithinMarker(pos, marker)
+      return @setCurrentResultIndex(i) if isWithinMarker(bufferPosition, marker)

     @clearCurrentResult()


Logging in works!

Logging in works!

--- lib/spark-ide-login-view.coffee
+++ lib/spark-ide-login-view.coffee
@@ -37,6 +37,8 @@
       @cancel()

     @loginPromise = null
+
+    # TODO: Enter to submit

     # As Atom doesn't provide password input, we have to hack EditorView to act as one
     #
@@ -138,9 +140,18 @@
       self.spinner.addClass 'hidden'
       if !self.loginPromise
         return
+
+      settings.username = self.email
+      settings.override null, 'username', settings.username
+      settings.access_token = e
+      settings.override null, 'access_token', settings.access_token
+      atom.workspaceView.trigger 'spark-ide:updateLoginStatus'
+      self.cancel()
+
     , (e) ->
       self.spinner.addClass 'hidden'
       if !self.loginPromise
         return
       self.unlockUi()
       self.errorLabel.text(e).show()
+      self.loginPromise = null


Cleanup

Cleanup

--- src/SpaceJam.coffee
+++ src/SpaceJam.coffee
@@ -36,7 +36,7 @@
     if _.has(runCommands,command)
       runCommands[command](opts)
     else
-      log.error "'#{command}' is not a spacejam command\n"
+      log.error "\n'#{command}' is not a spacejam command\n" if command
       runCommands.help()

@@ -179,4 +179,4 @@
     "help"          : printHelp
   }

-module.exports = SpaceJam+module.exports = SpaceJam


Activity: deferred additions while putting activity items

--- client/Social/Activity/views/messagepane.coffee
+++ client/Social/Activity/views/messagepane.coffee
@@ -284,8 +284,8 @@

       console.time('populate')
       @listController.hideLazyLoader()
-      @listController.instantiateListItems items
       console.timeEnd('populate')
+      items.forEach (item) => KD.utils.defer => @appendMessage item

       KD.utils.defer @bound 'focus'


nCk() now more resistent to overflow. Thanks Mark Dominus.

--- scoring.coffee
+++ scoring.coffee
@@ -1,15 +1,14 @@

-nPk = (n, k) ->
+nCk = (n, k) ->
+  # http://blog.plover.com/math/choose.html
   return 0 if k > n
-  result = 1
-  result *= m for m in [n-k+1..n]
-  result
-
-nCk = (n, k) ->
   return 1 if k == 0
-  k_fact = 1
-  k_fact *= m for m in [1..k]
-  nPk(n, k) / k_fact
+  r = 1
+  for d in [1..k]
+    r *= n
+    r /= d
+    n -= 1
+  r

 lg = (n) -> Math.log(n) / Math.log(2)


fixing and enhancing the date picker

fixing and enhancing the date picker

--- client/app/views/konnector.coffee
+++ client/app/views/konnector.coffee
@@ -62,21 +62,29 @@
         fieldHtml += """

 </select>
-Date
+
+
+Select a starting date
+Date
 <input id="#{slug}-import-date" class="autoimport" maxlength="8" type="text"></input>
-
+

 """
         @$('.fields').append fieldHtml
-
+        if @$("##{slug}-autoimport-input").val() isnt 'none'
+            @$("##{slug}-first-import").show()
+        else
+            @$("##{slug}-first-import").hide()
+        @$("##{slug}-first-import-date").hide()
         @$("##{slug}-import-date").datepicker({minDate: 1, dateFormat: "dd-mm-yy" })
-
+        @$("##{slug}-first-import-link").click =>
+            @$("##{slug}-first-import-date").toggle()
         @$("##{slug}-autoimport-input").change =>
             if @$("##{slug}-autoimport-input").val() isnt 'none'
-                @$("##{slug}-firstimport").show()
+                @$("##{slug}-first-import").show()
             else
-                @$("##{slug}-firstimport").hide()
+                @$("##{slug}-first-import").hide()

     onImportClicked: =>
         fieldValues = {}


syntax-based code folding

syntax-based code folding

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -67,8 +67,6 @@
       delete h.hdr; stream.match /[^⍝\n\r]*/; s = stream.current()
       if /^\s*:/.test(s) || dfnHeader.test s
         stream.backUp s.length
-      else if /^\s*$/.test s
-        delete h.vars
       else
         h.vars = s.split notName
       'apl-trad'
@@ -98,8 +96,8 @@
             "apl-dfn apl-dfn#{dd}"
           else if c == '∇'
             i = a.length - 1; while i && a[i].t != '∇' then i--
-            if i then a.splice i else a.push t: '∇', oi: n, ii: n + swm
-            h.hdr = 1; 'apl-trad'
+            if i then a.splice i; delete h.vars else a.push t: '∇', oi: n, ii: n + swm; h.hdr = 1
+            'apl-trad'
           else if c == ':'
             ok = 0
             switch kw = stream.match(/\w*/)?[0]?.toLowerCase()
@@ -156,4 +154,24 @@
     else
       if /^\s*:(?:end|else|andif|orif|case|until)/i.test s then la.oi else la.ii

-  fold: 'indent'
+  fold: 'apl'
+
+
+# stackStr(h): a string representation of the block stack in CodeMirror's state object "h"
+stackStr = (h) -> r = ''; (for x in h.a then r += x.t + ' '); r
+
+isPrefix = (x, y) -> x == y[...x.length]
+
+CodeMirror.registerHelper 'fold', 'apl', (cm, start) ->
+  l0 = l = start.line; end = cm.lastLine()
+  x0 = stackStr cm.getStateAfter l0 - 1 # x0: the stackStr at the beginning of start.line
+  y0 = stackStr cm.getStateAfter l0     # y0: the stackStr at the end of start.line
+  if x0 != y0 && isPrefix x0, y0
+    while ++l <= end
+      x = stackStr cm.getStateAfter l - 1 # x: the stackStr at the beginning of the current line
+      y = stackStr cm.getStateAfter l     # y: the stackStr at the end of the current line
+      if !isPrefix(y0, x) || !isPrefix(y0, y) then break
+    if l <= end && l - l0 > 1 && x0 == y && isPrefix y, x
+      while l + 1 <= end && /^ *$/.test cm.getLine l + 1 then l++ # skip blank lines
+      from: CodeMirror.Pos l0, cm.getLine(l0).length
+      to:   CodeMirror.Pos l,  cm.getLine(l ).length


added test for all nodes if no package was selected

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

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -32,6 +32,11 @@

   # Some Mockup data:
   #------------------
+
+  allNodes1 = nodes = [
+    { 'id': 'my1.full.fqdn' },
+    { 'id': 'my2.full.fqdn' }
+  ]

   allPackages1 = [
     { 'name': 'xx', 'versions': [
@@ -123,17 +128,13 @@
     expect(scope.packages.length).toBe(0)

   it 'should be able to list /nodes', () ->
-    nodes = [
-      { 'id': 'my1.full.fqdn' },
-      { 'id': 'my2.full.fqdn' }
-    ]
-    paginateResponse @httpBackend, '/v1/nodes', nodes, () -> scope.fetchNodes()
+    paginateResponse @httpBackend, '/v1/nodes', allNodes1, () -> scope.fetchNodes()

     expect(scope.nodes.length).toBe(2)
     # test both nodes
     for idx in [0,1]
       expect(@typeOf(scope.nodes[idx])).toBe('object')
-      expect(scope.nodes[idx]['id']).toBe(nodes[idx]['id'])
+      expect(scope.nodes[idx]['id']).toBe(allNodes1[idx]['id'])

   it 'should be able to access /node/xyz info (empty node)', () ->
     id = 'test'
@@ -229,6 +230,11 @@
     expect(p.archive).toBe(r.archive)
     expect(p.nodes).toBe(r.nodes)

+  it 'should provide all nodes if no package is selected', () ->
+    scope.allNodes = allNodes1
+    scope.updateNodeSelection()
+    expect( scope.nodes ).toBe( scope.allNodes )
+
   it 'should provide all packages if no node is selected', () ->
     scope.allPackages = allPackages1
     scope.updatePackageSelection()


renamed context

renamed context

--- test/integration/tool_rpc.coffee
+++ test/integration/tool_rpc.coffee
@@ -17,7 +17,7 @@
         wd40.fill '#password', 'testing', ->
           wd40.click '#login', done

-  context "when create a dataset with the test app", ->
+  context "with a freshly created test app dataset", ->
     before (done) ->
       browser.waitForElementByCss '.dataset-list', 4000, done


Add missing tests

Add missing tests

--- test/optimist_parser_test.coffee
+++ test/optimist_parser_test.coffee
@@ -70,7 +70,13 @@

     context 'options given', ->

-      it 'returns the given options'
+      beforeEach ->
+        @result = new OptimistParser(create_argv(options: {reporter: 'spec', timeout: 20})).options()
+
+      it 'returns the given options', ->
+        expect(@result).to.have.property 'reporter', 'spec'
+        expect(@result).to.have.property 'timeout', 20
+

       it 'does not contain the Optimist-specific elements', ->
         expect(@result).to.not.have.property '_'


selection tests

selection tests

--- src/app/rounds/rounds.spec.coffee
+++ src/app/rounds/rounds.spec.coffee
@@ -51,6 +51,8 @@
 		$provide.value "TagsService",
 			random_tag: ->
 		   	'lotr'
+		   random_categories: ->
+		   	['series', 'anime', 'characters', 'dog breeds']
 		$provide.value "RandomDateService",
 			fromPastMonths: ->
 		   	'1391212800000'
@@ -82,6 +84,23 @@
 	afterEach ->
 		scope.httpBackend.verifyNoOutstandingExpectation()
 		scope.httpBackend.verifyNoOutstandingRequest()
+
+	describe "Select controller", ->
+
+		beforeEach angular.mock.inject(($controller) ->
+			$controller "SelectCtrl",
+			  $scope: scope
+			  $state: scope.$state
+			  gameStorage: storage
+		)
+
+		# tests start here
+		describe "Initial render", ->
+			it "reports no rounds won", ->
+				expect(scope.round).toEqual 1
+
+			it "displays four categories", ->
+				expect(scope.categories.length).toEqual 4

 	describe "Rounds controller", ->


small formatting change

small formatting change

--- library/exposure/publish.coffee
+++ library/exposure/publish.coffee
@@ -52,8 +52,7 @@
     # Typically an HTTP pathname pattern and a domain name pattern.
     # Try not to put constraints on the domain, unless necessary.
     # Also, the compounds for the composition system belong here.
-    @resource "/api/publish"
-    @documentation yes
+    @resource "/api/publish"; @documentation yes

     # Establish a heartbeat monitor. A hearbit monitor is a method
     # that tests whether some arbitrarty server functonality does


Rename up.popup.open to up.popup.attach

Rename up.popup.open to up.popup.attach

--- lib/assets/javascripts/up/popup.js.coffee
+++ lib/assets/javascripts/up/popup.js.coffee
@@ -114,9 +114,9 @@
     up.animate($popup, animation, animateOptions)

   ###*
-  Opens a popup overlay.
-
-  @method up.popup.open
+  Attaches a popup overlay to the given element or selector.
+
+  @method up.popup.attach
   @param {Element|jQuery|String} elementOrSelector
   @param {String} [options.url]
   @param {String} [options.position='bottom-right']
@@ -133,7 +133,7 @@
     open even if the page changes in the background.
   @param {Object} [options.history=false]
   ###
-  open = (linkOrSelector, options) ->
+  attach = (linkOrSelector, options) ->
     $link = $(linkOrSelector)

     options = u.options(options)
@@ -204,9 +204,9 @@
     $element.closest('.up-popup').length > 0

   ###*
-  Opens the target of this link in a popup overlay:
-
-      Switch deck
+  Opens this link's destination of in a popup overlay:
+
+      Switch deck

   If the `up-sticky` attribute is set, the dialog does not auto-close
   if a page fragment below the popup overlay updates:
@@ -224,7 +224,7 @@
     if $link.is('.up-current')
       close()
     else
-      open($link)
+      attach($link)
   )

   # Close the popup when someone clicks outside the popup
@@ -265,10 +265,13 @@
   # The framework is reset between tests
   up.on 'up:framework:reset', reset

-  open: open
+  attach: attach
   close: close
   source: source
   defaults: config.update
   contains: contains
-
+  open: (args...) ->
+    up.warn('up.popup.open has been renamed to up.popup.attach and will be removed in a future version')
+    attach(args...)
+
 )(jQuery)


Render after writing

Render after writing

--- bin/layer-write.coffee
+++ bin/layer-write.coffee
@@ -29,4 +29,5 @@
 data     = jsonFile.read dataFile
 accessor = new ObjectAccessor
 accessor.setValue data, argv.key, argv.value
-jsonFile.write dataFile, data+jsonFile.write dataFile, data
+require './layer-render'


Using variable for initalStateName

Using variable for initalStateName

Signed-off-by: Niels van Hoorn <9639b9eee3fe09ce53e8dfede54f918fb4bd39be@nvh.io>

--- test/tests/LayerStatesTest.coffee
+++ test/tests/LayerStatesTest.coffee
@@ -1,5 +1,7 @@
 assert = require "assert"
 {expect} = require "chai"
+
+initialStateName = "default"

 describe "LayerStates", ->

@@ -13,10 +15,10 @@
 		it "should emit willSwitch when switching", (done) ->

 			test = (previous, current, states) =>
-				previous.should.equal 'initial'
+				previous.should.equal initialStateName
 				current.should.equal 'a'
-				@layer.states.currentName.should.equal 'initial'
 				@layer.states.current.should.equal @layer.states.initial
+				@layer.states.currentName.should.equal initialStateName
 				done()

 			@layer.on Events.StateWillSwitch, test
@@ -25,7 +27,7 @@
 		it "should emit didSwitch when switching", (done) ->

 			test = (previous, current, states) =>
-				previous.should.equal 'initial'
+				previous.should.equal initialStateName
 				current.should.equal 'a'
 				@layer.states.currentName.should.equal 'a'
 				@layer.states.current.should.equal @layer.states.a
@@ -64,7 +66,7 @@
 				layer.states =
 					stateA: x:200
 					stateB: scale: 0.5
-				assert.deepEqual layer.stateNames, ['initial','stateA','stateB']
+				assert.deepEqual layer.stateNames, [initialStateName,'stateA','stateB']

 			it "should reset the previous and current states", ->
 				layer = new Layer
@@ -74,7 +76,7 @@
 					stateA: x:200
 					stateB: scale: 0.5
 				assert.equal layer.states.previousName, null
-				layer.states.currentName.should.equal 'initial'
+				layer.states.currentName.should.equal initialStateName

@@ -133,7 +135,7 @@
 			layer.style.fontFamily = "Helvetica"
 			layer.style.fontFamily.should.equal "Helvetica"

-			layer.animate "initial", instant: true
+			layer.animate initialStateName, instant: true
 			layer.x.should.equal 0
 			layer.style.fontFamily.should.equal "Helvetica"

@@ -175,7 +177,7 @@
 						layer.x.should.equal 200
 						layer.rotation.should.equal 180
 						layer.animateToNextState(time: 0.05)
-					when "initial"
+					when initialStateName
 						layer.x.should.equal 0
 						layer.rotation.should.equal 0
 						done()
@@ -204,7 +206,7 @@
 						layer.x.should.equal 302
 						layer.y.should.equal 445
 						layer.animateToNextState(time: 0.05)
-					when "initial"
+					when initialStateName
 						layer.x.should.equal 0
 						layer.rotation.should.equal 0
 						layer.animateToNextState(time: 0.05)
@@ -289,7 +291,7 @@
 			layer.animate "stateA", instant: true
 			layer.x.should.equal 200 - (layer.width // 2)

-			layer.animate "initial", instant: true
+			layer.animate initialStateName, instant: true
 			layer.x.should.equal 0

 		it "should set the parent", ->
@@ -311,7 +313,7 @@
 			layerB.animate "noParent", instant: true
 			assert.equal(layerB.parent, null)

-			layerB.animate "initial", instant: true
+			layerB.animate initialStateName, instant: true
 			# assert.equal(layerB.parent, layerA)

 		it "should set the current and previous states when switching", ->
@@ -334,9 +336,9 @@

 		it "should set the initial state when creating a Layer", ->
 			layer = new Layer
-			layer.states.currentName.should.equal 'initial'
+			layer.states.currentName.should.equal initialStateName
 			layer.states.initial.x.should.equal 0
-			assert.deepEqual layer.stateNames, ['initial']
+			assert.deepEqual layer.stateNames, [initialStateName]

 		it "should listen to options provided to animateToNextState", ->
 			layer = new Layer
@@ -394,7 +396,7 @@
 				layer.states.add
 					stateA: x: 200
 					stateB: scale: 0.5
-				assert.deepEqual layer.stateNames, ['initial','stateA','stateB']
+				assert.deepEqual layer.stateNames, [initialStateName,'stateA','stateB']
 				assert.deepEqual layer.states.stateA, x: 200
 				assert.deepEqual layer.states.stateB, scale: 0.5

@@ -403,9 +405,9 @@
 			layer.states =
 				stateA: x: 200
 				stateB: scale: 0.5
-			assert.deepEqual layer.stateNames, ['initial','stateA','stateB']
+			assert.deepEqual layer.stateNames, [initialStateName,'stateA','stateB']
 			layer.states.remove 'stateA'
-			assert.deepEqual layer.stateNames, ['initial','stateB']
+			assert.deepEqual layer.stateNames, [initialStateName,'stateB']

 		it "should still support layer.states.switch", (done) ->
 			layer = new Layer
@@ -430,14 +432,14 @@
 			layer.states =
 				stateA: x: 200
 				stateB: scale: 0.5
-			assert.deepEqual layer.states.all, ['initial','stateA','stateB']
+			assert.deepEqual layer.states.all, [initialStateName,'stateA','stateB']

 		it "should still support layer.states.states", ->
 			layer = new Layer
 			layer.states =
 				stateA: x: 200
 				stateB: scale: 0.5
-			assert.deepEqual layer.states.states, ['initial','stateA','stateB']
+			assert.deepEqual layer.states.states, [initialStateName,'stateA','stateB']

 		it "should still support layer.states.animatingKeys", ->
 			layer = new Layer
@@ -445,7 +447,7 @@
 				stateA: x: 200, y: 300
 				stateB: scale: 0.5
 			assert.deepEqual layer.states.animatingKeys(), ["width", "height", "visible", "opacity", "clip", "scrollHorizontal", "scrollVertical", "x", "y", "z", "scaleX", "scaleY", "scaleZ", "scale", "skewX", "skewY", "skew", "originX", "originY", "originZ", "perspective", "perspectiveOriginX", "perspectiveOriginY", "rotationX", "rotationY", "rotationZ", "rotation", "blur", "brightness", "saturate", "hueRotate", "contrast", "invert", "grayscale", "sepia", "shadowX", "shadowY", "shadowBlur", "shadowSpread", "shadowColor", "backgroundColor", "color", "borderColor", "borderWidth", "force2d", "flat", "backfaceVisible", "name", "borderRadius", "html", "image", "scrollX", "scrollY", "mouseWheelSpeedMultiplier", "velocityThreshold", "constrained"]
-			delete layer.states.initial
+			delete layer.states[initialStateName]
 			assert.deepEqual layer.states.animatingKeys(), ["x","y","scale"]

 		it "should still support layer.states.next", (done) ->
@@ -518,9 +520,9 @@
 			it "should emit willSwitch when switching", (done) ->

 				test = (previous, current, states) =>
-					previous.should.equal 'initial'
+					previous.should.equal initialStateName
 					current.should.equal 'a'
-					@layer.states.state.should.equal 'initial'
+					@layer.states.state.should.equal initialStateName
 					done()

 				@layer.states.on 'willSwitch', test
@@ -529,7 +531,7 @@
 			it "should emit didSwitch when switching", (done) ->

 				test = (previous, current, states) =>
-					previous.should.equal 'initial'
+					previous.should.equal initialStateName
 					current.should.equal 'a'
 					@layer.states.state.should.equal 'a'
 					done()


.

Former-commit-id: 964013f592e364bdd866c6444735a7b01be4ff41 [formerly 80bd14e65ea7915efd02a1afb86e96b4b0e01964] [formerly c2d549cad9a866ff343e75349b0adf905b94f20c [formerly cec76e1554ac6b86a67a1e7463ffc400337a0aa7]] [formerly 39313f9b5a6e7d2b7cca5c784f3cc77f22fe348e [formerly 06f8e70dbdd4f6526c6fa21236de51c5283799da] [formerly 17ba1cd4f03932a5ce78b4bfe1d9d6091e74760c [formerly 17ba1cd4f03932a5ce78b4bfe1d9d6091e74760c [formerly c87a6069662b878ee8a63709f72a544fcc4ecccd]]]]
Former-commit-id: 4c93357505698e410605953956385354b536f8ed [formerly 32d1bf00c0e86749775cfc02a9afae5ed2e59940] [formerly d4bcdb3ef76a886452b339e227d809d63220b57b [formerly 11709ad22598a90320601de7c176102007cbf255]]
Former-commit-id: 56be2ca6611fc4d3a1fe7e58e5a3f0fb84c763c5 [formerly 7bae1879e2a37c07ef8189b3bbb895fe055f505d]
Former-commit-id: 45634fe4dd8f364c8408de8dbb45589bebc18c1d
Former-commit-id: e289ec708067348099a78398b98b5731d31345d3

--- @client/admin.coffee
+++ @client/admin.coffee
@@ -1025,13 +1025,9 @@
               DIV style: {marginTop: 10}, error

         if @local.successful
-          parts = location.hostname.split('.')
-          subdomain = parts.shift()
-          domain = parts.join('.')
-
           DIV style: {borderRadius: 8, margin: 20, padding: 20, backgroundColor: '#E2FFE2', fontSize: 20},
             "Success! "
-            A style: {textDecoration: 'underline'}, href: "https://#{@local.successful}.#{domain}/dashboard/application", "Configure your shiny new subdomain"
+            A style: {textDecoration: 'underline'}, href: "#{location.protocol}//#{@local.successful}.#{location.hostname}/dashboard/application", "Configure your shiny new subdomain"


Add login route copy for /works-for-you

Add login route copy for /works-for-you

--- components/auth_modal/maps.coffee
+++ components/auth_modal/maps.coffee
@@ -34,3 +34,7 @@
       signup: null
       register: 'Sign up to follow galleries and museums'
       login: 'Login to follow galleries and museums'
+    '/works-for-you':
+      signup: null
+      register: null
+      login: 'Login to see works by artists you follow'


removed logging in test

removed logging in test

--- tests/collection-security.coffee
+++ tests/collection-security.coffee
@@ -80,7 +80,6 @@
       , (err, id) ->
         posts = Posts.find _id: id
         post = posts.fetch()[0]
-        console.log post

         test.equal post.secret, 'hello', 'secret should be "hello"'
         next()


Fix tests.

Fix tests.

When the implementation of windowIsFocused() changed, the tests started
failing.

(It's not clear how I didn't spot this sooner.  I've run the tests
countless times - and they passed - since that change was made.

--- tests/dom_tests/dom_tests.coffee
+++ tests/dom_tests/dom_tests.coffee
@@ -83,6 +83,7 @@
       stubSettings "filterLinkHints", isFilteredMode
       stubSettings "linkHintCharacters", "ab"
       stubSettings "linkHintNumbers", "12"
+      stub window, "windowIsFocused", -> true

     tearDown ->
       document.getElementById("test-div").innerHTML = ""
@@ -120,6 +121,7 @@
     document.getElementById("test-div").innerHTML = testContent
     stubSettings "filterLinkHints", true
     stubSettings "linkHintNumbers", "12"
+    stub window, "windowIsFocused", -> true

   tearDown ->
     document.getElementById("test-div").innerHTML = ""
@@ -209,6 +211,7 @@
     initializeModeState()
     stubSettings "filterLinkHints", false
     stubSettings "linkHintCharacters", "ab"
+    stub window, "windowIsFocused", -> true

     # Three hints will trigger double hint chars.
     createLinks 3
@@ -253,6 +256,7 @@
   setup ->
     stubSettings "filterLinkHints", true
     stubSettings "linkHintNumbers", "0123456789"
+    stub window, "windowIsFocused", -> true

   context "Text hints",


Remove redundância

Remove redundância

--- src/coffee/portal-minicart.coffee
+++ src/coffee/portal-minicart.coffee
@@ -29,10 +29,10 @@
 		$(window).trigger "minicartLoaded"

 	bindEvents: =>
-		@hoverContext.add('.show-minicart-on-hover').on 'mouseover', ->
+		@hoverContext.on 'mouseover', ->
 			$(window).trigger "minicartMouseOver"

-		@hoverContext.add('.show-minicart-on-hover').on 'mouseout', ->
+		@hoverContext.on 'mouseout', ->
 			$(window).trigger "minicartMouseOut"

 		$(window).on "minicartMouseOver", =>


Make tab-selected hint visible.

Make tab-selected hint visible.

When the user selects an active hint with Tab (for filtered hints),
adjust its z-index such at it is top of the stack.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -312,7 +312,7 @@
   updateKeyState: ({hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
     extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue}

-    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints @hintMarkers, tabCount
+    {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints @hintMarkers, tabCount, this.getNextZIndex.bind this
     if linksMatched.length == 0
       @deactivateMode()
     else if linksMatched.length == 1
@@ -513,7 +513,7 @@
     # strings.  This ensures that we always get hint strings in the same order.
     @filterLinkHints hintMarkers

-  getMatchingHints: (hintMarkers, tabCount = 0) ->
+  getMatchingHints: (hintMarkers, tabCount, getNextZIndex) ->
     # At this point, linkTextKeystrokeQueue and hintKeystrokeQueue have been updated to reflect the latest
     # input. Use them to filter the link hints accordingly.
     matchString = @hintKeystrokeQueue.join ""
@@ -526,6 +526,7 @@
     @activeHintMarker?.classList?.remove "vimiumActiveHintMarker"
     @activeHintMarker = linksMatched[tabCount]
     @activeHintMarker?.classList?.add "vimiumActiveHintMarker"
+    @activeHintMarker.style.zIndex = getNextZIndex()

     linksMatched: linksMatched
     userMightOverType: @hintKeystrokeQueue.length == 0 and 0 < @linkTextKeystrokeQueue.length


Use a count with link hints; exit on Backspace.

Normally, `Backspace` exits hints mode.  It should exit hint mode with a
count too.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -22,8 +22,11 @@
   activateMode: (count = 1, mode = OPEN_IN_CURRENT_TAB) ->
     if 0 < count
       new LinkHintsMode mode, (event = null) ->
-        unless event?.type == "keydown" and KeyboardUtils.isEscape event
-          LinkHints.activateMode count-1, mode
+        # Escape and Backspace are the two ways in which hints mode can exit following which we do no restart
+        # hints mode.
+        return if event?.type == "keydown" and KeyboardUtils.isEscape event
+        return if event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
+        LinkHints.activateMode count-1, mode

   activateModeToOpenInNewTab: (count) -> @activateMode count, OPEN_IN_NEW_BG_TAB
   activateModeToOpenInNewForegroundTab: (count) -> @activateMode count, OPEN_IN_NEW_FG_TAB
@@ -328,7 +331,9 @@
       if @markerMatcher.popKeyChar()
         @updateVisibleMarkers hintMarkers
       else
-        @deactivateMode()
+        # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and
+        # knows not to restart hints mode.
+        @hintMode.exit event

     else if event.keyCode == keyCodes.enter
       # Activate the active hint, if there is one.  Only FilterHints uses an active hint.


Couple fixes for tests that broke due to data.find refactoring into data.get_File_Data

--- src/controllers/View-Table.coffee
+++ src/controllers/View-Table.coffee
@@ -16,7 +16,7 @@
   table: (req, res)=>
     filename = req.params?.filename
     if filename
-      raw_data   = @.data_Files.find filename
+      raw_data   = @.data_Files.get_File_Data filename
       table_Data = @.transform_Data(raw_data)
       title      = raw_data?.metadata?.team

@@ -27,7 +27,7 @@
   table_Json: (req,res)=>     # sends the data in a transformation this is easy to show in a table
     filename = req.params?.filename
     if filename
-      data = @.data_Files.find filename
+      data = @.data_Files.get_File_Data filename
       res.setHeader('Content-Type', 'application/json');

       return res.send @.transform_Data(data).json_Pretty()


:lipstick: Remove extra newline

:lipstick: Remove extra newline

--- src/dedupe-package-modules.coffee
+++ src/dedupe-package-modules.coffee
@@ -18,7 +18,6 @@
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')

-
   run: ->
     # Move packages to ~/.atom/packages/node_modules
     # Build package.json with packages as dependencies to ~/.atom/packages/package.json


Button: Removed unused "confirm_on_click" parameter.

--- src/elements/Button/Button.coffee
+++ src/elements/Button/Button.coffee
@@ -420,54 +420,23 @@
 				@removeClass(CUI.defaults.class.Button.defaults.pressed_css_class)
 			return

-		do_click = =>
-			if ev.isImmediatePropagationStopped()
-				remove_click_class()
-				return
-
-			Events.trigger
-				type: "cui-button-click"
-				node: @
-				info:
-					event: ev
-
-			if ev.isImmediatePropagationStopped() or not @_onClick
-				remove_click_class()
-				return
-
-			CUI.decide(@_onClick.call(@, ev, @))
-			.always =>
-				remove_click_class()
+		if ev.isImmediatePropagationStopped()
+			remove_click_class()
 			return

-		if @_onClick and @_confirm_on_click and not ev.ctrlKey()
-			btns = []
-
-			if not isEmpty(CUI.defaults.class.Button.defaults.confirm_cancel)
-				btns.push
-					text: CUI.defaults.class.Button.defaults.confirm_cancel
-					onClick: =>
-						dialog.destroy()
-						remove_click_class()
-
-			btns.push
-				text: CUI.defaults.class.Button.defaults.confirm_ok
-				onClick: =>
-					dialog.destroy()
-					do_click()
-
-			dialog = new CUI.ConfirmationDialog
-				text: @_confirm_on_click
-				icon: CUI.defaults.class.Button.defaults.confirm_icon
-				title: CUI.defaults.class.Button.defaults.confirm_title
-				header_right:
-					icon: "close"
-					onClick: ->
-						dialog.destroy()
-				buttons: btns
-			dialog.show()
-		else
-			do_click()
+		Events.trigger
+			type: "cui-button-click"
+			node: @
+			info:
+				event: ev
+
+		if ev.isImmediatePropagationStopped() or not @_onClick
+			remove_click_class()
+			return
+
+		CUI.decide(@_onClick.call(@, ev, @))
+		.always =>
+			remove_click_class()
 		@

 	initOpts: ->
@@ -487,8 +456,6 @@
 				check: Boolean
 			onClick:
 				check: Function
-			confirm_on_click:
-				check: String
 			click_type:
 				default: "click" # "touchend"
 				mandatory: true


Completed AnimatedMesh

Completed AnimatedMesh

--- src/entities/mesh.animated.coffee
+++ src/entities/mesh.animated.coffee
@@ -1,14 +1,14 @@
 class AnimatedMesh extends Wage.Mesh
     constructor: (geometry, @materials, options={}) ->
         @animations = {}
-        @weightSchedule = []
-        @warpSchedule = []
-
+        @fadeStack = []
+        @warpStack = []
+        # get skinning material
         originalMaterial = materials[0]
         originalMaterial.skinning = true
-
+        # default visibility
         @meshVisible = true
-
+        # init
         super geometry, originalMaterial, options

     create: ->
@@ -40,14 +40,47 @@
         return

     update: (dt) ->
-        @animate(dt)
+        @updateFades(dt)
         @updateWarps(dt)
+        THREE.AnimationHandler.update(dt)
         return

-    animate: (dt) ->
+    updateFades: (dt) ->
+        for i in [@fadeStack.length-1..0]
+            data = @fadeStack[i]
+            data.timeElapsed += dt
+            #: if the transition is completed, remove it from the stack
+            if data.timeElapsed > data.duration
+                data.anim.weight = data.endWeight
+                @fadeStack.splice i, 1
+                #: if animation faded out, stop it
+                if data.anim.weight is 0
+                    data.anim.stop 0
+            #: otherwise interpolate the weight for the current time
+            else
+                data.anim.weight = data.startWeight + (data.endWeight - data.startWeight) * data.timeElapsed / data.duration
         return

     updateWarps: (dt) ->
+        for i in [@warpStack.length-1..0]
+            data = @warpStack[i]
+            data.timeElapsed += dt
+            if data.timeElapsed > data.duration
+                data.to.weight = 1
+                data.to.timeScale = 1
+                data.from.weight = 0
+                data.from.timeScale = 1
+                data.from.stop 0
+                @warpStack.splice i, 1
+            else
+                alpha = data.timeElapsed / data.duration
+                fromToRatio = data.from.data.length / data.to.data.length
+                toFromRatio = data.to.data.length / data.from.data.length
+                # scale each animation proportionally
+                data.from.timeScale = (1 - alpha) + fromToRatio * alpha
+                data.to.timeScale = alpha + toFromRatio * (1 - alpha)
+                data.from.weight = 1 - alpha
+                data.to.weight = alpha
         return

     play: (animation) ->
@@ -56,21 +89,59 @@
         return

     crossfade: (fromAnimation, toAnimation, duration) ->
+        anim1 = @animations[fromAnimation]
+        anim2 = @animations[toAnimation]
+        anim1.play 0, 1
+        anim2.play 0, 0
+        @fadeStack.push
+            anim: anim1
+            startWeight: 1
+            endWeight: 0
+            timeElapsed: 0
+            duration: duration
+
+        @fadeStack.push
+            anim: anim2
+            startWeight: 0
+            endWeight: 1
+            timeElapsed: 0
+            duration: duration
         return

     warp: (fromAnimation, toAnimation, duration) ->
+        anim1 = @animations[fromAnimation]
+        anim2 = @animations[toAnimation]
+        anim1.play 0, 1
+        anim2.play 0, 0
+        @warpStack.push
+            from: anim1
+            to: anim2
+            timeElapsed: 0
+            duration: duration
         return

     applyWeight: (animation, w) ->
         @animations[animation].weight = weight

     pauseAll: ->
+        for animName, animation of @animations
+            if animation.isPlaying
+                animation.stop()
         return

     unpauseAll: ->
+        for animName, animation of @animations
+            if animation.isPlaying and animation.isPaused
+                animation.pause()
         return

     stopAll: ->
+        for animName, animation of @animations
+            if animation.isPlaying
+                animation.stop 0
+            animation.weight = 0
+        @fadeStack.length = 0
+        @warpStack.length = 0
         return

     getForward: ->


fix extReg

fix extReg

--- src/js/core/engines/engine.coffee
+++ src/js/core/engines/engine.coffee
@@ -1,7 +1,7 @@
 do (root = this, factory = (cfg, utils, Events, EngineCore, AudioCore, FlashMP3Core, FlashMP4Core) ->
     {EVENTS, STATES} = cfg.engine
     timerResolution = cfg.timerResolution
-    extReg = /\.(\w+)(\?.*)$/
+    extReg = /\.(\w+)(\?.*)?$/

     class Engine
         # 隐藏容器, 用于容纳swf和audio等标签


Removed trailing whitespace in users collection

--- app/assets/javascripts/collections/users.js.coffee
+++ app/assets/javascripts/collections/users.js.coffee
@@ -2,46 +2,46 @@

   model: Cloudsdale.Models.User
   url: -> "/v1/users.json"
-
+
   # Public: Tries to find the model inside of the collection
   # if it can't be found it tries to initialize it from the server.
   #
-  # Examples:
-  #
+  # Examples:
+  #
   # collection.findOrInitialize('..someid...')
   # # => Cloud
   #
   # Returns an instance of Cloudsdale.Models.User if found, otherwise undefined.
   findOrInitialize: (args,options) ->
-
+
     args = if (getObjectClass(args) == "String") then [args] else args
-
+
     args = {} unless args
     options = {} unless options
-
+
     if getObjectClass(args) == "Array"
-
+
       users = []
       ids = []
-
+
       $.each args, (i,id) =>
         user = @get(id)
         unless user
           ids.push(id)
           user = new Cloudsdale.Models.User({id: id})
-
+
         users.push(user)
-
+
       if ids.length > 0
-
+
         if (options.specific_endpoint == true)
           url = if options.url then options.url else @url()
           data = null
         else
           data = { ids: ids }
-
+
         options.beforeRequest() if options.beforeRequest
-
+
         @fetch
           add: true
           data: data
@@ -52,43 +52,43 @@
             options.error(resp, xhr, _options) if options.error
       else
         options.success(users, 200, {}) if options.success
-
-
+
+
       return users
-
+
     else
-
+
       user = @get(args.id)
-
+
       if user
         if options.fetch
-
+
           options.beforeRequest() if options.beforeRequest
-
-          user.fetch
-            success: (resp, status, xhr) => options.success(resp, status, xhr) if options.success
-            error: (resp, xhr, _options) => options.error(resp, xhr, _options) if options.error
-
-        else
-          options.success(user) if options.success
-
-      else
-        user = new Cloudsdale.Models.User(args)
-
-        if user.get('is_transient')
-
-          options.beforeRequest() if options.beforeRequest
-
+
           user.fetch
             success: (resp, status, xhr) => options.success(resp, status, xhr) if options.success
             error: (resp, xhr, _options) => options.error(resp, xhr, _options) if options.error

         else
           options.success(user) if options.success
-
+
+      else
+        user = new Cloudsdale.Models.User(args)
+
+        if user.get('is_transient')
+
+          options.beforeRequest() if options.beforeRequest
+
+          user.fetch
+            success: (resp, status, xhr) => options.success(resp, status, xhr) if options.success
+            error: (resp, xhr, _options) => options.error(resp, xhr, _options) if options.error
+
+        else
+          options.success(user) if options.success
+
         @add(user)
-
-
+
+
       return user
-
-      +
+


Re-enable install

Re-enable install

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -263,5 +263,5 @@
   grunt.registerTask('ci', ciTasks)

   defaultTasks = ['download-electron', 'download-electron-chromedriver', 'build', 'set-version', 'generate-asar']
-  # defaultTasks.push 'install' unless process.platform is 'linux'
+  defaultTasks.push 'install' unless process.platform is 'linux'
   grunt.registerTask('default', defaultTasks)


Remove unused .coffee file

Remove unused .coffee file

--- app/assets/javascripts/form_applications.js.coffee
+++ app/assets/javascripts/form_applications.js.coffee
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://coffeescript.org/


Remove local test code

Remove local test code

--- app/assets/javascripts/lib/maps/map_manager.coffee
+++ app/assets/javascripts/lib/maps/map_manager.coffee
@@ -52,8 +52,8 @@

     @getNearbyPOIs: (callback) ->
       if lp.lodging.nearby_api_endpoint
-        $.getJSON '/top_rated_by_categories.json', callback
-        # $.getJSON lp.lodging.nearby_api_endpoint, callback
+        # $.getJSON '/top_rated_by_categories.json', callback
+        $.getJSON lp.lodging.nearby_api_endpoint, callback

     @parsePOIData: (data)->
       pois = {}


Groups: For each environment only group related domains are shown

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -1418,37 +1418,30 @@
     oldAddTags.call this, client, tags, options, callback

   fetchUserDomains: (client, callback) ->
+    {connection: {delegate}} = client
     {group} = client.context

     JDomain = require './domain'
-
-    JVM = require './vm'
-    JVM.fetchVmsByContext client, {}, (err, vms) =>
-      return callback err if err
-      Relationship.some
-        targetName: "JDomain"
-        sourceId  : @getId()
-        sourceName: "JAccount"
-      , {}, (err, rels)->
-        return callback err if err
-
-        selector = $or : [
-          {_id           : $in: (rel.targetId for rel in rels)},
-          {hostnameAlias : $in : vms}
-        ]
-        JDomain.some selector, {}, (err, domains)->
-          return callback err if err
-
-          domainList = []
-          # we don't allow users to work on domains such as
-          # shared-x/vm-x.groupSlug.kd.io or x.koding.kd.io
-          # so we are filtering them here.
-          domainList = domains.filter (domain)->
-            domainName = domain.domain
-            !(/^shared|vm[\-]?([0-9]+)?/.test domainName) and \
-            !(/(.*)\.(koding|guests)\.kd\.io$/.test domainName)
-
-          callback err, domainList
+    delegate.fetchDomains (err, domains) ->
+      return callback err  if err
+      domainList = []
+      domainList = filterDomains domains, delegate, group  if domains
+
+      callback null, domainList
+
+  # filters domains such as shared-x/vm-x.groupSlug.kd.io
+  # or x.koding.kd.io. Also shows only group related
+  # domains to users
+  filterDomains = (domains, account, group)->
+    domainList = []
+    domainList = domains.filter (domain)->
+      {domain} = domain
+      re = if group is "koding" then new RegExp(account.profile.nickname + '\.kd\.io$') \
+           else new RegExp('(.*)\.' + group + '\.kd\.io$')
+      isVmAlias         = (/^shared|vm[\-]?([0-9]+)?/.test domain)
+      isKodingSubdomain = (/(.*)\.(koding|guests)\.kd\.io$/.test domain)
+      isGroupAlias      = re.test domain
+      not isVmAlias and not isKodingSubdomain and isGroupAlias

   fetchDomains$: permit
     advanced: [


Payment: fetchGroupPlan: disable trial period check for now

--- workers/social/lib/social/models/payment.coffee
+++ workers/social/lib/social/models/payment.coffee
@@ -76,8 +76,9 @@
     get url, {}, (err, subscription) ->
       return callback err  if err

-      unless isSubscriptionOk group, subscription
-        return callback new KodingError 'Trial period exceeded'
+      # Comment out this one once plans enabled ~ GG
+      # unless isSubscriptionOk group, subscription
+      #   return callback new KodingError 'Trial period exceeded'

       subscription = sanitizeSubscription subscription


adding contentType: 'application/json' in remote admin handler

--- src/remote/services/admin.coffee
+++ src/remote/services/admin.coffee
@@ -19,6 +19,7 @@
         url: url
         type: req.method
         data: req_data
+        contentType: 'application/json'
         dataType: 'json'
         headers: headers


Only respond to the trigger 10% of the time

To make this way less annoying

--- src/scripts/git-you-sucka.coffee
+++ src/scripts/git-you-sucka.coffee
@@ -15,4 +15,4 @@

 module.exports = (robot) ->
   robot.hear /git/, (msg) ->
-    msg.reply 'ima git you sucka'
+    if Math.random() < 0.1 then msg.reply 'ima git you sucka'


Remove dropzone correctly on unmount

Remove dropzone correctly on unmount

--- src/smc-webapp/smc-dropzone.cjsx
+++ src/smc-webapp/smc-dropzone.cjsx
@@ -104,15 +104,17 @@

                 destroyInterval = window.setInterval =>
                     if @queueDestroy == false
-                        console.log "Queue Destroy is false"
+                        # If the component remounts somehow, don't destroy the dropzone.
                         return window.clearInterval(destroyInterval)

                     if @dropzone.getActiveFiles().length == 0
-                        console.log "No active files"
                         @_destroy(@dropzone)
                         delete @dropzone
                         return window.clearInterval(destroyInterval)
                 , 500
+            else
+                @_destroy(@dropzone)
+                delete @dropzone

     componentDidUpdate: ->
         @queueDestroy = false


remove inappropriate anchor in respond regex

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -42,7 +42,7 @@
     memeGenerator msg, 998, 203665, msg.match[1], msg.match[2], (url) ->
       msg.send url

-  robot.respond /^(GOOD NEWS EVERYONE[,.!]?) (.*)/i, (msg) ->
+  robot.respond /(GOOD NEWS EVERYONE[,.!]?) (.*)/i, (msg) ->
     memeGenerator msg, 1591, 112464, msg.match[1], msg.match[2], (url) ->
       msg.send url


checking for value equal to 0 instead of <= 0

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -262,7 +262,7 @@
     publishablePageValidation: () ->
       numUnpublishableContent = @unpublishableContentNum()

-      if numUnpublishableContent <= 0
+      if numUnpublishableContent is 0
         $('#publish-modal').modal()
       else
         $('#reject-publish-modal').modal()


fix (Make) Don't swallow errors from grunt

--- src/steroids/project/Base.coffee
+++ src/steroids/project/Base.coffee
@@ -97,12 +97,14 @@
         steroidsCli.debug "Running Grunt tasks..."

         grunt = new Grunt()
-        grunt.run {tasks: [steroidsCli.options.argv["gruntTask"] || "default"]}, =>
-          unless steroidsCli.options.argv.noSettingsJson == true
-            @createSettingsJson()
-          @createConfigXml()
-          @createConfigJson()
-          options.onSuccess.call() if options.onSuccess?
+        grunt
+          .run({tasks: [steroidsCli.options.argv["gruntTask"] || "default"]})
+          .then =>
+            unless steroidsCli.options.argv.noSettingsJson == true
+              @createSettingsJson()
+            @createConfigXml()
+            @createConfigJson()
+            options.onSuccess?()

       ).catch (errorMessage)->
         Help.error()


remove JSON extension from API

remove JSON extension from API

--- src/api.coffee
+++ src/api.coffee
@@ -139,7 +139,7 @@
       TaskActions.loadedUserTasks(results.items)

   apiHelper TeacherTaskPlanActions, TeacherTaskPlanActions.load, TeacherTaskPlanActions.loaded, 'GET', (course_id) ->
-    url: "/api/courses/#{course_id}/plans.json"
+    url: "/api/courses/#{course_id}/plans"

   CurrentUserActions.logout.addListener 'trigger', ->


used a macro to make file inclusion more concise

--- src/apl.coffee
+++ src/apl.coffee
@@ -1,49 +1,50 @@
-macro -> macro.fileToNode 'src/macros.coffee'
-macro -> macro.fileToNode 'src/helpers.coffee'
-macro -> macro.fileToNode 'src/errors.coffee'
-macro -> macro.fileToNode 'src/array.coffee'
-macro -> macro.fileToNode 'src/complex.coffee'
-macro -> macro.fileToNode 'src/vm.coffee'
-macro -> macro.fileToNode 'src/lexer.coffee'
-macro -> macro.fileToNode 'src/parser.coffee'
-macro -> macro.fileToNode 'src/vocabulary.coffee'
-macro -> macro.fileToNode 'src/vocabulary/vhelpers.coffee'
-macro -> macro.fileToNode 'src/vocabulary/arithmetic.coffee'
-macro -> macro.fileToNode 'src/vocabulary/backslash.coffee'
-macro -> macro.fileToNode 'src/vocabulary/circle.coffee'
-macro -> macro.fileToNode 'src/vocabulary/comma.coffee'
-macro -> macro.fileToNode 'src/vocabulary/comparisons.coffee'
-macro -> macro.fileToNode 'src/vocabulary/compose.coffee'
-macro -> macro.fileToNode 'src/vocabulary/cupcap.coffee'
-macro -> macro.fileToNode 'src/vocabulary/decode.coffee'
-macro -> macro.fileToNode 'src/vocabulary/dot.coffee'
-macro -> macro.fileToNode 'src/vocabulary/drop.coffee'
-macro -> macro.fileToNode 'src/vocabulary/each.coffee'
-macro -> macro.fileToNode 'src/vocabulary/encode.coffee'
-macro -> macro.fileToNode 'src/vocabulary/epsilon.coffee'
-macro -> macro.fileToNode 'src/vocabulary/exclamation.coffee'
-macro -> macro.fileToNode 'src/vocabulary/execute.coffee'
-macro -> macro.fileToNode 'src/vocabulary/find.coffee'
-macro -> macro.fileToNode 'src/vocabulary/floorceil.coffee'
-macro -> macro.fileToNode 'src/vocabulary/fork.coffee'
-macro -> macro.fileToNode 'src/vocabulary/format.coffee'
-macro -> macro.fileToNode 'src/vocabulary/grade.coffee'
-macro -> macro.fileToNode 'src/vocabulary/identity.coffee'
-macro -> macro.fileToNode 'src/vocabulary/iota.coffee'
-macro -> macro.fileToNode 'src/vocabulary/leftshoe.coffee'
-macro -> macro.fileToNode 'src/vocabulary/logic.coffee'
-macro -> macro.fileToNode 'src/vocabulary/poweroperator.coffee'
-macro -> macro.fileToNode 'src/vocabulary/quad.coffee'
-macro -> macro.fileToNode 'src/vocabulary/question.coffee'
-macro -> macro.fileToNode 'src/vocabulary/raise.coffee'
-macro -> macro.fileToNode 'src/vocabulary/rho.coffee'
-macro -> macro.fileToNode 'src/vocabulary/rotate.coffee'
-macro -> macro.fileToNode 'src/vocabulary/slash.coffee'
-macro -> macro.fileToNode 'src/vocabulary/squish.coffee'
-macro -> macro.fileToNode 'src/vocabulary/take.coffee'
-macro -> macro.fileToNode 'src/vocabulary/transpose.coffee'
-macro -> macro.fileToNode 'src/vocabulary/variant.coffee'
-macro -> macro.fileToNode 'src/compiler.coffee'
+macro include (f) -> macro.fileToNode "src/#{macro.nodeToVal f}.coffee"
+include 'macros'
+include 'helpers'
+include 'errors'
+include 'array'
+include 'complex'
+include 'vm'
+include 'lexer'
+include 'parser'
+include 'vocabulary'
+include 'vocabulary/vhelpers'
+include 'vocabulary/arithmetic'
+include 'vocabulary/backslash'
+include 'vocabulary/circle'
+include 'vocabulary/comma'
+include 'vocabulary/comparisons'
+include 'vocabulary/compose'
+include 'vocabulary/cupcap'
+include 'vocabulary/decode'
+include 'vocabulary/dot'
+include 'vocabulary/drop'
+include 'vocabulary/each'
+include 'vocabulary/encode'
+include 'vocabulary/epsilon'
+include 'vocabulary/exclamation'
+include 'vocabulary/execute'
+include 'vocabulary/find'
+include 'vocabulary/floorceil'
+include 'vocabulary/fork'
+include 'vocabulary/format'
+include 'vocabulary/grade'
+include 'vocabulary/identity'
+include 'vocabulary/iota'
+include 'vocabulary/leftshoe'
+include 'vocabulary/logic'
+include 'vocabulary/poweroperator'
+include 'vocabulary/quad'
+include 'vocabulary/question'
+include 'vocabulary/raise'
+include 'vocabulary/rho'
+include 'vocabulary/rotate'
+include 'vocabulary/slash'
+include 'vocabulary/squish'
+include 'vocabulary/take'
+include 'vocabulary/transpose'
+include 'vocabulary/variant'
+include 'compiler'

 @apl = apl = (aplCode) -> exec aplCode
 apl.approx = approx


Minor fixes, whitespace, etc.

Minor fixes, whitespace, etc.

--- tests/specs/SuperfishTest.coffee
+++ tests/specs/SuperfishTest.coffee
@@ -18,19 +18,20 @@
     expect($menu).toBe 'ul'

   it "should store options", ->
-    expect($menu.data('sf-options')).toBeDefined()
-
+    expect($menu.data 'sf-options' ).toBeDefined()
+
+
   describe "options", ->
-
+
     it "should have default options", ->
-      options = $menu.data('sf-options')
+      options = $menu.data 'sf-options'
       expect(options.speed).toMatch 'normal'

     it "should allow default options to be overridden", ->
       $menu.superfish 'destroy'
       $menu.superfish
         speed: 1000
-      options = $menu.data('sf-options')
+      options = $menu.data 'sf-options'
       expect(options.speed).toEqual 1000

@@ -40,10 +41,10 @@

     beforeEach ->
       $liHasUl = $menu.find('li:has(ul):first')
-
+
     it "'show' method should exist", ->
       expect($liHasUl.superfish('show')).toBeDefined()
-
+
     it "'hide' method should exist", ->
       expect($liHasUl.superfish('hide')).toBeDefined()

@@ -68,4 +69,3 @@
           $menu.superfish('destroy')
           $menu.superfish('destroy')
         ).not.toThrow new Error("Uncaught TypeError: Cannot read property 'sfTimer' of null")
-


Fix indentation

Fix indentation

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -79,7 +79,7 @@
           style: hiddenInputStyle

         LinesComponent {
-           ref: 'lines', @presenter, editor, hostElement, @useHardwareAcceleration, useShadowDOM, visible
+          ref: 'lines', @presenter, editor, hostElement, @useHardwareAcceleration, useShadowDOM, visible
         }

         ScrollbarComponent


Change public view to use a copy button

Change public view to use a copy button

Change share button to text: Private

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -153,14 +153,13 @@
         on_click : rtypes.func.isRequired

     render: ->
-        <Space/>
-            <Button
-                bsStyle = 'default'
-                bsSize  = 'xsmall'
+        <Space/><Space/>
+

-                <Icon name='share-square-o' /> Share...
-            </Button>
+                Private
+

 PublicButton = rclass
@@ -175,10 +174,24 @@
                 bsSize  = 'xsmall'
                 onClick = {@props.on_click}
             >
-                <Icon name='bullhorn' /> Public...
+                <Icon name='bullhorn' /> Public
             </Button>

+CopyButton = rclass
+    propTypes:
+        on_click : rtypes.func.isRequired
+
+    render: ->
+        <Space/>
+            <Button
+                bsStyle = 'info'
+                bsSize  = 'xsmall'
+                onClick = {@props.on_click}
+            >
+                <Icon name='files-o' /> Copy
+            </Button>
+
 generate_on_share_click = (full_path, actions) =>
     (e) =>
         e.preventDefault()
@@ -186,6 +199,13 @@
         actions.set_all_files_unchecked()
         actions.set_file_checked(full_path, true)
         actions.set_file_action('share')
+
+generate_on_copy_click = (full_path, actions) =>
+    (e) =>
+        e.preventDefault()
+        e.stopPropagation()
+        actions.set_file_checked(full_path, true)
+        actions.set_file_action('copy')

 FileRow = rclass
     displayName : 'ProjectFiles-FileRow'
@@ -204,6 +224,7 @@
         current_path : rtypes.string
         actions      : rtypes.object.isRequired
         no_select    : rtypes.bool
+        public_view  : rtypes.bool

     shouldComponentUpdate: (next) ->
         return @props.name  != next.name         or
@@ -215,7 +236,8 @@
         @props.public_data  != next.public_data  or
         @props.current_path != next.current_path or
         @props.bordered     != next.bordered     or
-        @props.no_select    != next.no_select
+        @props.no_select    != next.no_select    or
+        @props.public_view  != next.public_view

     render_icon: ->
         # get the file_associations[ext] just like it is defined in the editor
@@ -258,7 +280,11 @@

     render_public_file_info: ->
-        if @props.public_data? and @props.is_public
+        if @props.public_view
+            <CopyButton
+                on_click = {generate_on_copy_click(@full_path(), @props.actions)}
+            />
+        else if @props.is_public
             <PublicButton
                 id       = {@props.name}
                 on_click = {generate_on_share_click(@full_path(), @props.actions)}
@@ -363,6 +389,7 @@
         current_path : rtypes.string
         actions      : rtypes.object.isRequired
         no_select    : rtypes.bool
+        public_view  : rtypes.bool

     shouldComponentUpdate: (next) ->
         return @props.name  != next.name         or
@@ -375,7 +402,8 @@
         @props.public_data  != next.public_data  or
         @props.is_public    != next.is_public    or
         @props.current_path != next.current_path or
-        @props.no_select    != next.no_select
+        @props.no_select    != next.no_select    or
+        @props.public_view  != next.public_view

     handle_mouse_down: (e) ->
         @setState
@@ -387,7 +415,11 @@
             @props.actions.set_file_search('')

     render_public_directory_info: ->
-        if @props.public_data? and @props.is_public
+        if @props.public_view
+            <CopyButton
+                on_click = {generate_on_copy_click(@full_path(), @props.actions)}
+            />
+        else if @props.is_public
             <PublicButton
                 id       = {@props.name}
                 on_click = {generate_on_share_click(@full_path(), @props.actions)}
@@ -703,6 +735,7 @@
                 current_path = {@props.current_path}
                 actions      = {@props.actions}
                 no_select    = {@props.shift_is_down}
+                public_view  = {@props.public_view}
             />
         else
             return <FileRow
@@ -720,6 +753,7 @@
                 current_path = {@props.current_path}
                 actions      = {@props.actions}
                 no_select    = {@props.shift_is_down}
+                public_view  = {@props.public_view}
             />

     render_rows: ->


fix showing a good error for not public

fix showing a good error for not public

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -34,10 +34,9 @@
 immutable             = require('immutable')
 underscore            = require('underscore')
 {salvus_client}       = require('./salvus_client')
-{AccountPageRedux} = require('./account_page')
+{AccountPage} = require('./account_page')
 {UsersViewing}        = require('./profile')
 Combobox = require('react-widgets/lib/Combobox') #TODO: delete this when the combobox is in r_misc
-
 TERM_MODE_CHAR = '/'

 exports.file_action_buttons = file_action_buttons =
@@ -380,6 +379,7 @@

 NoFiles = rclass
     propTypes :
+        redux         : rtypes.object
         actions       : rtypes.object.isRequired
         create_folder : rtypes.func.isRequired
         create_file   : rtypes.func.isRequired
@@ -485,13 +485,22 @@
             </FileTypeSelector>

+    render_not_public_error : ->
+        if @props.redux.getStore('account').is_logged_in()
+            <ErrorDisplay title="Directory is not public" error={"You are trying to access a non public project that you are not a collaborator on. You need to ask a collaborator of the project to add you."} />
+        else
+

+                <ErrorDisplay title="Directory is not public" error={"You are not logged in. If you are collaborator on this project you need to log in first. This project is not public."} />
+                <AccountPage />
+

+
     render : ->
         <Row style={textAlign:'left', color:'#888', marginTop:'20px', wordWrap:'break-word'} >
             <Col sm=2>
             </Col>
             <Col sm=8>

-                    No Files Found
+                    {if @props.public_view then @render_not_public_error() else "No Files Found"}

                 {@render_create_button() if not @props.public_view}
@@ -510,6 +519,7 @@
     displayName : 'ProjectFiles-FileListing'

     propTypes :
+        redux               : rtypes.object
         listing             : rtypes.array.isRequired
         file_map            : rtypes.object.isRequired
         file_search         : rtypes.string
@@ -603,6 +613,7 @@
     render_no_files : ->
         if @props.listing.length is 0 and @props.file_search[0] isnt TERM_MODE_CHAR
             <NoFiles
+                redux         = {@props.redux}
                 current_path  = {@props.current_path}
                 actions       = {@props.actions}
                 public_view   = {@props.public_view}
@@ -1882,15 +1893,6 @@
                 style   = {error_style}
                 onClose = {=>@props.actions.setState(error:'')} />

-    render_not_public_error : ->
-        if @props.redux.getStore('account').is_logged_in()
-            <ErrorDisplay title="Directory is not public" error={"You are trying to access a non public project that you are not a collaborator on. You need to ask a collaborator of the project to add you."} />
-        else
-

-                <ErrorDisplay title="Directory is not public" error={"You are not logged in. If you are collaborator on this project you need to log in first. This project is not public."} />
-                <AccountPageRedux />
-

-
     render_file_listing: (listing, file_map, error, project_state, public_view) ->
         if project_state? and project_state not in ['running', 'saving']
             return @render_project_state(project_state)
@@ -1913,7 +1915,7 @@
                     else
                         e = <ErrorDisplay title="Directory listing error" error={error} />
             return

-                {if error == '"not_public"' then @render_not_public_error() else e}
+                {e}

                 <Button onClick={=>@props.actions.set_directory_files(@props.current_path, @props.sort_by_time, @props.show_hidden)}>
                     <Icon name='refresh'/> Try again to get directory listing
@@ -1921,6 +1923,7 @@

         else if listing?
             <FileListing
+                redux               = {@props.redux}
                 listing             = {listing}
                 page_size           = {@file_listing_page_size()}
                 page_number         = {@props.page_number}


Rather fix the coffee script than the js file...

--- src/cli.coffee
+++ src/cli.coffee
@@ -4,7 +4,7 @@
 path = require 'path'
 pjson = require path.join(__dirname, '..', 'package.json')
 program = require 'commander'
-
+util = require 'util'
 fontello = require path.join(__dirname, '..', 'lib', 'fontello')

@@ -37,11 +37,11 @@
     #
     if program.css and program.font
       unless dirIsValid program.css
-        print '--css path provided is not a directory.\n'.red
+        util.print '--css path provided is not a directory.\n'.red
         process.exit 1

       unless dirIsValid program.font
-        print '--font path provided is not a directory.\n'.red
+        util.print '--font path provided is not a directory.\n'.red
         process.exit 1

     fontello.install


Break as descendant should not add \n to getText

InnerText will return \n on breaks. Need ? to avoid "" being false

--- src/dom.coffee
+++ src/dom.coffee
@@ -147,7 +147,9 @@
     switch node.nodeType
       when DOM.ELEMENT_NODE
         return "" if node.tagName == DOM.DEFAULT_BREAK_TAG
-        return node.textContent or node.innerText or ""
+        return node.textContent if node.textContent?
+        return node.innerText.replace('\n', '') if node.innerText?
+        return ""
       when DOM.TEXT_NODE then return node.data or ""
       else return ""


Stringify object on debug message

Stringify object on debug message

--- src/esa.coffee
+++ src/esa.coffee
@@ -148,7 +148,7 @@
     robot.emit 'esa.debug', 'heared stats command'
     esa.getStats (stats) ->
       robot.emit 'esa.hear.stats', res, stats
-      robot.emit 'esa.debug', "emit esa.hear.stats with stats:\n#{stats}"
+      robot.emit 'esa.debug', "emit esa.hear.stats with stats:\n#{JSON.stringify(stats)}"

   robot.hear /https:\/\/(.+)\.esa\.io\/posts\/(\d+)(?!(\#comment-\d+))\b/, (res) ->
     robot.emit 'esa.debug', 'heared post url'
@@ -156,7 +156,7 @@
     unless team == options.team then return
     esa.getPost post_id, (post) ->
       robot.emit 'esa.hear.post', res, post
-      robot.emit 'esa.debug', "emit esa.hear.post with post:\n#{post}"
+      robot.emit 'esa.debug', "emit esa.hear.post with post:\n#{JSON.stringify(post)}"

   robot.hear /https:\/\/(.+)\.esa\.io\/posts\/(\d+)\#comment-(\d+)\b/, (res) ->
     robot.emit 'esa.debug', 'heared comment url'
@@ -165,7 +165,7 @@
     esa.getComment comment_id, (comment) ->
       esa.getPost post_id, (post) ->
         robot.emit 'esa.hear.comment', res, comment, post
-        robot.emit 'esa.debug', "emit esa.hear.comment with post:\n#{post}\n comment:\n#{comment}"
+        robot.emit 'esa.debug', "emit esa.hear.comment with post:\n#{JSON.stringify(post)}\n comment:\n#{JSON.stringify(comment)}"

   robot.router.post options.endpoint, (req, res) ->
     robot.emit 'esa.debug', "received a webhook by #{req.headers['user-agent']}"
@@ -179,7 +179,7 @@
       res.writeHead(409)
       res.end()
     .handle (kind, data) ->
-      robot.emit 'esa.debug', "emit esa.webhook with kind: #{kind}\n data:\n#{data}"
+      robot.emit 'esa.debug', "emit esa.webhook with kind: #{JSON.stringify(kind)}\n data:\n#{JSON.stringify(data)}"
       robot.emit 'esa.webhook', kind, data
       res.writeHead(204)
       res.end()


Fix Attachment#toJSON

Fix Attachment#toJSON

--- src/trix/models/attachment.coffee
+++ src/trix/models/attachment.coffee
@@ -69,7 +69,7 @@
       @uploadProgressDelegate?.attachmentDidChangeUploadProgress?(this)

   toJSON: ->
-    @attributes
+    @getAttributes()

   getCacheKey: ->
     [super, @attributes.getCacheKey()].join("/")


Clone source nodes before inserting them into the DOM

--- src/trix/views/object_view.coffee
+++ src/trix/views/object_view.coffee
@@ -5,6 +5,7 @@

   getNodes: ->
     @nodes ?= @createNodes()
+    node.cloneNode(true) for node in @nodes

   invalidate: ->
     delete @nodes


CustomPartials: Implemented preview mode.

--- client/Dashboard/views/customviews/customviewsmanager.coffee
+++ client/Dashboard/views/customviews/customviewsmanager.coffee
@@ -1,4 +1,6 @@
 class CustomViewsManager extends JView
+
+  cookieName = "custom-partials-preview-mode"

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

@@ -9,9 +11,11 @@
     @previewButton = new KDButtonView
       title        : "PREVIEW"
       cssClass     : "solid green preview"
-      callback     : ->
-        new KDNotificationView
-          title    : "Coming Soon!"
+      callback     : @bound "togglePreview"
+
+    if $.cookie cookieName
+      @previewButton.setTitle "CANCEL PREVIEW"
+      @previewButton.unsetClass "green"

     @homePages  = new CustomViewsDashboardView
       viewType  : "HOME"
@@ -23,6 +27,18 @@
       cssClass  : "widgets"
       itemClass : WidgetCustomViewItem

+  togglePreview: ->
+    isPreview = $.cookie cookieName
+
+    if isPreview
+      $.cookie cookieName, no
+      @previewButton.setTitle "PREVIEW"
+      @previewButton.setClass "green"
+    else
+      $.cookie cookieName, yes
+      @previewButton.setTitle   "CANCEL PREVIEW"
+      @previewButton.unsetClass "green"
+
   pistachio: ->
     """
       <div class="button-container">


groups: removed view revive methods from constructor

--- client/landing/app/MainApp/lazy/staticgroupcontroller.coffee
+++ client/landing/app/MainApp/lazy/staticgroupcontroller.coffee
@@ -29,6 +29,14 @@

     @navLinks = []
     @currentFacets = []
+
+    @reviveViews()
+
+    @checkGroupUserRelation()
+    @attachListeners()
+
+
+  reviveViews :->

     @landingView = new KDView
       lazyDomId : 'static-landing-page'
@@ -140,10 +148,6 @@
     @utils.defer =>
       groupLogoView.setClass 'animate'
       @landingView._windowDidResize()
-
-    @checkGroupUserRelation()
-    @attachListeners()
-
   checkGroupUserRelation:->

     KD.remote.cacheable @groupEntryPoint, (err, groups, name)=>


check for metakey and ctrl when clicking on links - fixes #13

--- client/code/app.coffee
+++ client/code/app.coffee
@@ -10,9 +10,10 @@
   if Backbone.history and Backbone.history._hasPushState
     $(document).delegate "a:not([href^=http])", "click", (evt) ->
       unless $(@).is '[data-nonpushstate]'
-        href = $(@).attr "href"
-        evt.preventDefault()
-        window.app.navigate href, trigger: true
+        unless evt.metaKey or evt.ctrlKey
+          href = $(@).attr "href"
+          evt.preventDefault()
+          window.app.navigate href, trigger: true

 class Cu.AppView
   constructor: (@selector) ->


Apps: Removed unused script includes.

Apps: Removed unused script includes.

--- client/includes.coffee
+++ client/includes.coffee
@@ -21,7 +21,7 @@
   # core
   "Framework/core/KDObject.coffee",
   "Framework/core/KDView.coffee",
-  "Framework/core/KDOverlayView.coffee",
+  "Framework/core/KDOverlayView.coffee",
   "Framework/core/JView.coffee",
   "Framework/core/KDCustomHTMLView.coffee",
   "Framework/core/KDScrollView.coffee",
@@ -614,8 +614,8 @@
   "app/Applications/Apps.kdapplication/Views/AppSubmission.coffee",
   "app/Applications/Apps.kdapplication/Views/appinfoview.coffee",
   "app/Applications/Apps.kdapplication/Views/appview.coffee",
-  "app/Applications/Apps.kdapplication/Views/appscreenshotlistitem.coffee",
-  "app/Applications/Apps.kdapplication/Views/appscreenshotsview.coffee",
+  # "app/Applications/Apps.kdapplication/Views/appscreenshotlistitem.coffee",
+  # "app/Applications/Apps.kdapplication/Views/appscreenshotsview.coffee",
   "app/Applications/Apps.kdapplication/Views/appdetailsview.coffee",

   "app/Applications/Apps.kdapplication/ContentDisplays/controller.coffee",


define hosts in pre-run

define hosts in pre-run

--- src/run.coffee
+++ src/run.coffee
@@ -6,7 +6,6 @@
 {EventEmitter} = require 'events'
 {merge} = require 'mecano/lib/misc'
 {flatten} = require './misc'
-load = require './load'
 context = require './context'
 {Tree} = require './tree'

@@ -35,13 +34,15 @@
     each(config.servers)
     .parallel(true)
     .on 'item', (server, next) =>
-      contexts[server.host] = context (merge {}, config, server), params.command
+      ctx = contexts[server.host] = context (merge {}, config, server), params.command
+      ctx.hosts = contexts
+      ctx.shared = shared
       @actions server.host, 'install', {}, (err, actions) =>
         return next err if err
-        contexts[server.host].actions = actions or []
+        ctx.actions = actions or []
         @modules server.host, 'install', {}, (err, modules) =>
           return next err if err
-          contexts[server.host].modules = modules or []
+          ctx.modules = modules or []
           next()
     .on 'error', (err) =>
       @emit 'error', err
@@ -57,8 +58,6 @@
         return next() if params.hosts? and params.hosts.indexOf(server.host) is -1
         ctx = contexts[server.host]
         ctx.run = @
-        ctx.shared = shared
-        ctx.hosts = contexts
         @emit 'context', ctx
         @actions server.host, params.command, params, (err, actions) =>
           # return next new Error "Invalid run list: #{@params.command}" unless actions?


Make it so we're not creating a redundant wrapper function.

--- guts/getControllers.coffee
+++ guts/getControllers.coffee
@@ -29,7 +29,7 @@
                         do
                                 _.chain controllers
                                 .map makeAsyncControllerParse
-                                .reduce (bigObject, piece) -> _.extend bigObject, piece
+                                .reduce _.extend
                                 .value

                 # Once everything is done from above, we return back the collective promise object and


Change matcher.

Change matcher.

--- test/conf/protractor-conf.coffee
+++ test/conf/protractor-conf.coffee
@@ -16,7 +16,7 @@

   # Run all e2e specs. The command line --specs option overrides
   # this setting.
-  specs: ['../e2e/**.spec.*']
+  specs: ['../e2e/*.spec.*']

   # Flag which directs Protractor to wait for the app to be
   # stable before each action.


Disabled query cache tests

Disabled query cache tests

--- test/generators/all_cache.coffee
+++ test/generators/all_cache.coffee
@@ -5,5 +5,5 @@
 module.exports = (options, callback) ->
   queue = new Queue(1)
   # queue.defer (callback) -> require('./cache/options')(options, callback)
-  queue.defer (callback) -> require('./cache/query')(options, callback)
+  # queue.defer (callback) -> require('./cache/query')(options, callback)
   queue.await callback


Fix new view tool wait

Fix new view tool wait

--- test/integration/new_view.coffee
+++ test/integration/new_view.coffee
@@ -31,10 +31,12 @@
       context 'when I click on the newview tool', ->
         before (done) ->
           wd40.click '.newview.tool', =>
-            browser.waitForElementByTagName 'iframe', 10000, =>
+            # XXX but of a rubbish long wait - what CSS element could we wait for instead?
+            setTimeout =>
               browser.url (err, url) =>
                 @currentUrl = url
                 done()
+            , 2000

         it 'takes me to the view page', ->
           @currentUrl.should.match new RegExp("#{home_url}/dataset/[^/]+/view/[^/]+")


the jsontool output is jsony by default, make it real json

--- test/mocha/ephemeris.spec.coffee
+++ test/mocha/ephemeris.spec.coffee
@@ -70,12 +70,14 @@
       beginning = "precious f test/io/for/nativity.json
  | node_modules/jsontool/lib/jsontool.js"
       mapExec [
-        "#{beginning}"
-        "#{beginning} 0.re | precious - | node_modules/jsontool/lib/jsontool.js"
+        "#{beginning} -o json"
+        "#{beginning} 0.re | precious -
+ | node_modules/jsontool/lib/jsontool.js -o json"
         ], (err, stdouts) ->
           for result in stdouts
-            console.log result
             results.push json.parse result
+            # Properties get reordered for some reason.
+            # delete results[results.length-1]["0"]["re"]
           done()

     it "yields the same results", ->


updates ember app router

updates ember app router

--- ember-checkout/app/router.coffee
+++ ember-checkout/app/router.coffee
@@ -5,6 +5,13 @@
   location: config.locationType

 Router.map ->
+  @route 'coupon', path: 'coupon/:coupon_code'
   @route 'cart'
+  @route 'checkout', ->
+    @route 'cart'
+    @route 'address'
+    @route 'delivery'
+    @route 'payment'
+    @route 'confirm'

 `export default Router`


Added basic test for parsing xml lists with AWS.XMLParser.

--- test/unit/xml_parser.spec.coffee
+++ test/unit/xml_parser.spec.coffee
@@ -24,11 +24,44 @@
       parse xml, rules, (data) ->
         expect(data).toEqual({foo:{bar:'yuck'}})

-    # to create an array, rules MUST define element as a list
-    it 'flattens lists into a scalars', ->
+    it 'returns everything as a string (even numbers)', ->
+      xml = '<xml><count>123</count></xml>'
+      parse xml, rules, (data) ->
+        expect(data).toEqual({count:'123'})
+
+    it 'flattens sibling elements of the same name', ->
       xml = '<xml><foo><bar>1</bar><bar>2</bar></foo></xml>'
       parse xml, rules, (data) ->
         expect(data).toEqual({foo:{bar:'1'}})
+
+  describe 'lists', ->
+
+    it 'Converts xml lists of strings into arrays of strings', ->
+      xml = """
+      <xml>
+        <items>
+          <item>abc</item>
+          <item>xyz</item>
+        </items>
+      </xml>
+      """
+      rules = {items:{t:'a',m:{n:'item'}}}
+      parse xml, rules, (data) ->
+        expect(data).toEqual({items:['abc','xyz']})
+
+  describe 'flattened lists', ->
+
+  describe 'maps', ->
+
+  describe 'booleans', ->
+
+  describe 'timestamp', ->
+
+  describe 'numbers', ->
+
+  describe 'integers', ->
+
+  describe 'renaming elements', ->

   describe 'parsing errors', ->


moar testSpecApp app tests

moar testSpecApp app tests

--- testSpecApp/app/specs/app.coffee
+++ testSpecApp/app/specs/app.coffee
@@ -1,4 +1,8 @@
 describe "App", ->
+
+  before (done)->
+    steroids.on "ready", ->
+      done()

   it "should be defined", ->
     steroids.app.should.be.defined
@@ -19,12 +23,31 @@
     it "should be a function", ->
       steroids.app.getNSUserDefaults.should.be.a "function"

+
   describe "app.absoluteUserFilesPath", ->
     it "should be a string", ->
-      steroids.app.userFilesPath.should.be.a "string"
+      steroids.app.absoluteUserFilesPath.should.be.a "string"
+
+    platformName = null
+    before (done) ->
+      steroids.device.platform.getName {}, onSuccess: (name) ->
+        platformName = name
+        done()
+
+    it "might be a file path", ->
+      if platformName is "ios"
+        steroids.app.absoluteUserFilesPath[0].should.equal "/"
+        # should test better for "/var/mobile/Applications/AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA/Scanner.app/Documents"
+
+      # needs test case for android
+

   describe "app.absolutePath", ->
     it "should be a string", ->
       steroids.app.absolutePath.should.be.a "string"

-  # needs still tests with each platform and each mode to make sure that there are the actually right values in the paths and stuff+
+
+  # needs still tests with each platform and each mode to make sure that there are the actually right values in the paths and stuff
+
+  # needs pixate load theme stuff


config.dev: increase default ulimit, do not remove logs when killall called

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -186,7 +186,7 @@
     socialworker        : command : "./watch-node #{projectRoot}/workers/social/index.js             -c #{configName} -p #{KONFIG.social.port} -r #{region} --disable-newrelic --kite-port=13020"
     userCreator         : command : "node #{projectRoot}/workers/usercreator/index.js               -c #{configName}"

-    clientWatcher       : command : "ulimit -n 1024 && coffee #{projectRoot}/build-client.coffee    --watch --sourceMapsUri /sourcemaps --verbose true"
+    clientWatcher       : command : "ulimit -n 4096 && coffee #{projectRoot}/build-client.coffee    --watch --sourceMapsUri /sourcemaps --verbose true"
     ngrokProxy          : command : "coffee #{projectRoot}/ngrokProxy --user #{publicHostname}"
     guestCleaner        : command : "#{GOBIN}/guestcleanerworker -c #{configName}"

@@ -467,7 +467,6 @@
       }

       function kill_all () {
-        rm -rf #{projectRoot}/.logs
         #{killlist()}
         nginx -s quit
         ps aux | grep koding | grep -E 'node|go/bin' | awk '{ print $2 }' | xargs kill -9
@@ -479,7 +478,6 @@
         echo "starting nginx"
         nginx -s quit
         nginx -c #{projectRoot}/.dev.nginx.conf
-

       }


config: add psql to dependencies

config: add psql to dependencies

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -645,6 +645,7 @@
         command -v gulp          >/dev/null 2>&1 || { echo >&2 "I require gulp but it's not installed. (npm i gulp -g)  Aborting."; exit 1; }
         # command -v stylus      >/dev/null 2>&1 || { echo >&2 "I require stylus  but it's not installed. (npm i stylus -g)  Aborting."; exit 1; }
         command -v coffee        >/dev/null 2>&1 || { echo >&2 "I require coffee-script but it's not installed. (npm i coffee-script -g)  Aborting."; exit 1; }
+        command -v psql          >/dev/null 2>&1 || { echo >&2 "I require psql but it's not installed. (brew install postgresql)  Aborting."; exit 1; }

         if [[ `uname` == 'Darwin' ]]; then
           brew info graphicsmagick >/dev/null 2>&1 || { echo >&2 "I require graphicsmagick but it's not installed.  Aborting."; exit 1; }


config/dev: use install-npm helper to install client deps :zap:

--- config/main.dev.coffee
+++ config/main.dev.coffee
@@ -497,9 +497,8 @@
         npm install --unsafe-perm

         echo '#---> BUILDING CLIENT <---#'
-        cd #{projectRoot}/client
-        npm install --unsafe-perm
-        make build
+        sh -c "scripts/install-npm.sh -d client -u -p -s"
+        make -C ${projectRoot}/client dist

         echo '#---> BUILDING GO WORKERS (@farslan) <---#'
         #{projectRoot}/go/build.sh
@@ -723,12 +722,12 @@

           echo
           echo '---------------------------------------------------------------'
-          echo '>>> CLIENT BUILD DISABLED! DO "cd client/ && make" MANUALLY <<<'
+          echo '>>> CLIENT BUILD DISABLED! DO "make -C client" MANUALLY <<<'
           echo '---------------------------------------------------------------'
           echo

         else
-          cd #{projectRoot}/client && make &
+          make -C #{projectRoot}/client
         fi

         # Show the all logs of workers
@@ -1048,9 +1047,8 @@

       elif [ "$1" == "buildclient" ]; then

-        cd #{projectRoot}/client
-        npm install --unsafe-perm
-        make landing development
+        sh -c "scripts/install-npm.sh -d client -u -p -s"
+        make -C ${projectRoot}/client watch

       elif [ "$1" == "services" ]; then
         check_service_dependencies


Increase number of workers, and remove logger

--- config/main.dyn.coffee
+++ config/main.dyn.coffee
@@ -73,7 +73,7 @@
     login       : 'prod-authworker'
     queueName   : socialQueueName+'auth'
     authResourceName: 'auth'
-    numberOfWorkers: 1
+    numberOfWorkers: 2
     watch       : no
   cacheWorker   :
     login       : 'prod-social'
@@ -142,11 +142,6 @@
     poolSize        : 1e4
     batchSize       : undefined
     cleanupCron     : '*/10 * * * * *'
-  logger            :
-    mq              :
-      host          : 'localhost'
-      login         : 'PROD-k5it50s4676pO9O'
-      password      : 'Dtxym6fRJXx4GJz'
   pidFile       : '/tmp/koding.server.pid'
   loggr:
     push: yes


Tests: Added createFile helper.

Tests: Added createFile helper.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -148,5 +148,25 @@
       .click                   webSelector
       .click                   webSelector + ' + .chevron'

+
+  createFile: (browser, user) ->
+
+    @openFolderContextMenu(browser, user, 'Web')
+
+    webPath   = '/home/' + user.username + '/Web'
+    paragraph = @getFakeText()
+    filename  = paragraph.split(' ')[0] + '.txt'
+
+    browser
+      .waitForElementVisible    'li.new-file', 5000
+      .click                    'li.new-file'
+      .waitForElementVisible    'li.selected .rename-container .hitenterview', 5000
+      .clearValue               'li.selected .rename-container .hitenterview'
+      .setValue                 'li.selected .rename-container .hitenterview', filename + '\n'
+      .waitForElementPresent    "span[title='" + webPath + '/' + filename + "']", 5000 # Assertion
+
+    return filename
+
+
   getUrl: ->
     return 'http://lvh.me:8090'


加入passwd_salt

--- core/model/user.coffee
+++ core/model/user.coffee
@@ -1,7 +1,9 @@
 BaseModel = require "./BaseModel"
+crypto = require "crypto"
 class User extends BaseModel
 	constructor : (@data = {name : null}) ->
 		super()
+		@data.passwd_salt = crypto.createHash('sha256').digest('hex')
 	@table : ->
 		'users'
 module.exports = User