GET, POST, and DELETE verbs now have some functionality on admin side

--- coffee/servers/admin.coffee
+++ coffee/servers/admin.coffee
@@ -1,11 +1,15 @@
 mongo = require 'mongodb'
 server = new mongo.Server 'localhost', 27017, {auto_reconnect : true}
+ObjectID = mongo.ObjectID

 Admin = module.exports.Admin = () ->
+   db = new mongo.Db 'stubserver', server
+   db.open () -> {}
+
    me = {
       qs: require 'querystring'

-      db: new mongo.Db 'stubserver', server
+      db: db

       RnR: require('../models/requestresponse').RequestResponse()

@@ -22,7 +26,6 @@
             if post
                signature.post = me.qs.parse post

-         newRnR = me.RnR signature
          response.writeHead 204, {}
          response.end()

@@ -35,33 +38,53 @@
             data = me.qs.parse data
             rNr = me.RnR.create data

-            me.db.open (err, db) ->
+            db.collection 'rnr', (err, collection) ->
                if not err
-                  db.collection 'rnr', (err, collection) ->
+                  collection.insert rNr, {safe:true}, (err, result) ->
                      if not err
-                        collection.insert rNr, {safe:true}, (err, result) ->
-                           if not err
-                              response.writeHead 201, {
-                                 'Content-Location' : "#{request.headers.host}/#{rNr._id}"
-                              }
-                              response.end()
-                           else
-                              me.sendSaveError()
+                        response.writeHead 201, {
+                           'Content-Location' : "#{request.headers.host}/#{rNr._id}"
+                        }
+                        response.end()
                      else
-                        me.sendServerError()
+                        me.sendSaveError response
                else
-                  me.sendServerError()
-
-
+                  me.sendServerError response

       goDELETE : (request, response) ->
-         response.writeHead 204, {}
-         response.end()
+         id = request.url.replace /^\/([a-f0-9]*)$/, '$1'
+
+         db.collection 'rnr', (err, collection) ->
+            if not err
+               collection.remove {_id : new ObjectID id}, {safe : true}, (err, result) ->
+                  if not err
+                     if not result
+                        me.sendNotFound response
+                     else
+                        response.writeHead 204, {}
+                        response.end()
+                  else
+                     me.sendServerError response
+            else
+               me.sendServerError response

       goGET : (request, response) ->
-         response.writeHead 200, {'Content-Type': 'application/json'}
-         response.write JSON.stringify(request.headers)
-         response.end()
+         id = request.url.replace /^\/([a-f0-9]*)$/, '$1'
+
+         db.collection 'rnr', (err, collection) ->
+            if not err
+               collection.findOne {_id : new ObjectID id}, (err, result) ->
+                  if not err
+                     if not result
+                        me.sendNotFound response
+                     else
+                        response.writeHead 200, {}
+                        response.write JSON.stringify result
+                        response.end()
+                  else
+                     me.sendServerError response
+            else
+               me.sendServerError response

       sendNotSupported : (response) ->
          response.writeHead 405, {
@@ -75,11 +98,11 @@
          response.writeHead 404, {'Content-Type': 'text/plain'}
          response.end()

-      sendServerError : () ->
+      sendServerError : (response) ->
          response.writeHead 500, {'Content-Type':'text/plain'}
          response.end()

-      sendSaveError : () ->
+      sendSaveError : (response) ->
          response.writeHead 507, {'Content-Type':'text/plain'}
          response.end()


Add arg props to app.react.Link

Add arg props to app.react.Link

--- app/client/js/react/link.coffee
+++ app/client/js/react/link.coffee
@@ -12,12 +12,16 @@
   ###*
     @param {este.Route} route
     @param {string} text
-    @param {Object=} params
+    @param {Object=} urlParams
+    @param {Object=} props
   ###
-  to: (route, text, params) ->
+  to: (route, text, urlParams, props) ->
     {a} = @touch.none 'a'

-    a
-      className: if @routes.active == route then 'active' else null
-      href: route.url params
-    , text
+    linkProps = href: route.url urlParams
+    goog.mixin linkProps, props if props
+    linkProps.className ?= ''
+    linkProps.className += ' ' + if @routes.active == route then 'active' else ''
+    delete linkProps.className if !linkProps.className.trim()
+
+    a linkProps, text


Update song model.

Update song model.

--- app/client/js/songs/song.coffee
+++ app/client/js/songs/song.coffee
@@ -3,18 +3,65 @@
 class app.songs.Song

   ###*
-    @param {string} name
-    @param {string} author
     @constructor
   ###
-  constructor: (@name, @author) ->
+  constructor: ->
+
+  ###*
+    ID is an empty string until first sync aka user is logged or created.
+    @type {string}
+    @expose
+  ###
+  id: ''
+
+  ###*
+    ISO 8601 string representation of date/time.
+    @type {string}
+    @expose
+  ###
+  created: ''
+
+  ###*
+    ISO 8601 string representation of date/time.
+    @type {string}
+    @expose
+  ###
+  updated: ''

   ###*
     @type {string}
+    @expose
   ###
   name: ''

   ###*
     @type {string}
+    @expose
   ###
-  author: ''+  urlName: ''
+
+  ###*
+    @type {string}
+    @expose
+  ###
+  author: ''
+
+  ###*
+    @type {string}
+    @expose
+  ###
+  interpret: ''
+
+  ###*
+    User id.
+    @type {string}
+    @expose
+  ###
+  creator: ''
+
+  ###*
+    http://linkesoft.com/songbook/chordproformat.html
+    @type {string}
+    @expose
+  ###
+  chordpro: ''


Increase changes limit to 1200

Increase changes limit to 1200

--- app/coffee/RangesManager.coffee
+++ app/coffee/RangesManager.coffee
@@ -3,7 +3,7 @@

 module.exports = RangesManager =
 	MAX_COMMENTS: 500
-	MAX_CHANGES: 1000
+	MAX_CHANGES: 1200

 	applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
 		{changes, comments} = entries


Increase changes count limit to 2000

Increase changes count limit to 2000

--- app/coffee/RangesManager.coffee
+++ app/coffee/RangesManager.coffee
@@ -3,7 +3,7 @@

 module.exports = RangesManager =
 	MAX_COMMENTS: 500
-	MAX_CHANGES: 1200
+	MAX_CHANGES: 2000

 	applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
 		{changes, comments} = entries


Manually debounce user search

Manually debounce user search

--- app/components/user-search.cjsx
+++ app/components/user-search.cjsx
@@ -1,23 +1,34 @@
 React = require 'react'
 Select = require 'react-select'
 apiClient = require '../api/client'
-debounce = require 'debounce'
+
+delayBy = (timeout, fn) ->
+  setTimeout fn, timeout

 module.exports = React.createClass
   displayName: 'UserSearch'

   getDefaultProps: ->
     multi: true
+    debounce: 200

-  searchUsers: (value, callback) ->
+  queryTimeout: NaN
+
+  searchUsers: (value) ->
+    clearTimeout @queryTimeout
+
     if value is ''
-      callback null, {}
+      Promise.resolve options: []
     else
-      apiClient.type('users').get search: "#{value}", page_size: 10
-        .then (users) =>
-          opts = for user in users
-            { value: user.id, label: "@#{ user.login }: #{ user.display_name }" }
-          { options: opts }
+      new Promise (resolve) =>
+        @queryTimeout = delayBy @props.debounce, =>
+          apiClient.type('users').get search: value, page_size: 10
+            .then (users) =>
+              for user in users
+                value: user.id
+                label: "@#{user.login}: #{user.display_name}"
+            .then (options) =>
+              resolve {options}

   render: ->
     <Select


Fix fan tool rendering

Fix fan tool rendering

--- app/controllers/fan_tool.coffee
+++ app/controllers/fan_tool.coffee
@@ -6,7 +6,7 @@
 optionsTemplate = require 'views/fan_tool_options'

 # Destructure math for convenience.
-{acos, atan2, cos, max, PI, pow, sin, sqrt} = Math
+{acos, asin, atan2, cos, max, PI, pow, sin, sqrt, tan} = Math
 sq = (n) -> pow n, 2

 class FanTool extends MarkingTool
@@ -97,42 +97,40 @@
     @handleDrag e, 'source'

   render: ->
-    # The triangle made from distance and spread angle:
+    halfSpread = (@mark.spread / 2) / (180 / PI)
+
+    # The right triangle made from distance and half the spread angle:
     adjacent = @mark.distance
-    hypotenuse = adjacent / cos @mark.spread / (180 / PI)
-    opposite = hypotenuse * sin @mark.spread / (180 / PI)
+    hypotenuse = adjacent / cos(halfSpread)
+    opposite = adjacent * tan(halfSpread)

-    inradius = (adjacent * opposite) / (adjacent + opposite + hypotenuse)
-    toSpreadHandle = sqrt(sq inradius) + inradius
-    spreadHandleX = @mark.distance - toSpreadHandle
-
-    # Don't overlap with the distance handle or the other spread handle.
-    spreadHandlePosition = max @targetMin * 2, toSpreadHandle
+    spreadRadius = (hypotenuse * sin halfSpread) / (1 + tan halfSpread) # THANKS BROOKE AND GREG!
+    spreadX = @mark.distance - spreadRadius

     @dots.distance.setPosition @mark.distance, 0
-    @dots.spreadA.setPosition spreadHandleX, -spreadHandlePosition
-    @dots.spreadB.setPosition spreadHandleX, +spreadHandlePosition
+    @dots.spreadA.setPosition spreadX, -spreadRadius
+    @dots.spreadB.setPosition spreadX, +spreadRadius

-    @lines.distance.setPoints [{x: 5, y: 0}, {x: @mark.distance, y: 0}]
+    @lines.distance.setPoints [{x: 0, y: 0}, {x: @mark.distance, y: 0}]

     @lines.spread.setPoints [
-      {x: spreadHandleX, y: -toSpreadHandle}
-      {x: spreadHandleX, y: +toSpreadHandle}
+      {x: spreadX, y: -spreadRadius}
+      {x: spreadX, y: +spreadRadius}
     ]

     @lines.bounding.setData """
-      M 5 -2
-      L #{spreadHandleX} #{-toSpreadHandle}
-      A 1 1 0 1 1 #{spreadHandleX} #{+toSpreadHandle}
-      L 5 2
+      M 0 0
+      L #{spreadX} #{-spreadRadius}
+      A 1 1 0 1 1 #{spreadX} #{spreadRadius}
+      L #{spreadX} #{+spreadRadius}
       Z
     """

     @lines.hiddenBound.setData """
-      M 0 -5
-      L #{spreadHandleX + 5} #{-toSpreadHandle - 5}
-      A 1 1 0 1 1 #{spreadHandleX - 5} #{+toSpreadHandle + 5}
-      L 0 5
+      M -5 0
+      L #{spreadX} #{-spreadRadius - 5}
+      A 1 1 0 1 1 #{spreadX} #{spreadRadius + 5}
+      L #{spreadX} #{+spreadRadius + 5}
       Z
     """


Removed CoffeeScript based specs

Removed CoffeeScript based specs

--- spec/taglibSpec.coffee
+++ spec/taglibSpec.coffee
@@ -1,36 +0,0 @@
-assert = require 'assert'
-vows = require 'vows'
-fs = require 'fs'
-Taglib = require __dirname + '/../lib/taglib'
-
-vows.describe('taglib bindings')
-.addBatch
-  'reading Tags from File':
-    topic: new Taglib.Tag(__dirname+'/sample.mp3')
-    'should be a `Tag`': (tag) ->
-      assert.equal Taglib.Tag, tag.constructor
-    'should be `A bit-bucket full of tags`': (tag) ->
-      assert.equal 'A bit-bucket full of tags', tag.title
-    'should be a `A bit-bucket full of tags`': (tag) ->
-      assert.equal 'A bit-bucket full of tags', tag.title
-    'should be by `gitzer\'s`': (tag) ->
-      assert.equal 'gitzer\'s', tag.artist
-    'should be on `Waffles for free!`': (tag) ->
-      assert.equal "Waffles for free!", tag.album
-    'should be the first': (tag) ->
-      assert.equal 1, tag.track
-    'should be from 2011': (tag) ->
-      assert.equal 2011, tag.year
-  'writing Tags to File':
-    topic: ->
-      filename = __dirname+'/sample-write.mp3'
-      fs.writeFileSync filename, fs.readFileSync(__dirname+'/sample.mp3')
-      t = new Taglib.Tag filename
-      t.title = 'Something completely different…'
-      t.save()
-      filename
-    'should have written `Something completely different…` to title': (filename) ->
-      tag = new Taglib.Tag filename
-      assert.equal "Something completely different…", tag.title
-
-.export(module)


The camera could start very slow.

The camera could start very slow.

--- spec/web/webrtc.coffee
+++ spec/web/webrtc.coffee
@@ -1,6 +1,7 @@
 describe 'webrtc', ->
   describe 'navigator.webkitGetUserMedia', ->
     it 'should call its callbacks', (done) ->
+      @timeout 5000
       navigator.webkitGetUserMedia audio: true, video: true,
         -> done()
         -> done()


account: hide loader on button when passwords don't match

--- client/Account/views/username.coffee
+++ client/Account/views/username.coffee
@@ -121,7 +121,7 @@

       @account.modify "profile.avatar": "#{url}?#{Date.now()}", callback

-  update:(formData)->
+  update: (formData) ->

     {daisy} = Bongo
     {JUser} = KD.remote.api
@@ -169,7 +169,10 @@
       =>
         # on third turn update password
         # check for password confirmation
-        return  notify "Passwords did not match" if password isnt confirmPassword
+        if password isnt confirmPassword
+          notify "Passwords did not match"
+          @emailForm.buttons.Save.hideLoader()
+          return
         # if password is empty than discard operation
         if password is ""
           {token} = KD.utils.parseQuery()


Client: Display auth symbols before username.

--- client/app/js/views/chat_view.coffee
+++ client/app/js/views/chat_view.coffee
@@ -129,6 +129,7 @@
           $input.val(@chatHistory[@chatHistoryIndex])

   userMessage: (username, message) =>
+    username = @collection.get(username)?.getDisplayName() || username
     highlight = (message.toLowerCase().indexOf(PokeBattle.username.toLowerCase())) != -1
     username = "<b style='color: #{@userColor(username)}'>#{username}:</b>"
     @updateChat("#{@timestamp()} #{username} #{@sanitize(message)}", {highlight})


whitespace changes

whitespace changes

--- client/plugins/linkmap/server.coffee
+++ client/plugins/linkmap/server.coffee
@@ -40,11 +40,6 @@
 	socket.on 'connection', (ws) ->
 		console.log 'connection established, ready to send'
 		ws.send JSON.stringify(linkmap, null, 2), (err) ->
-      console.log 'unable to send ws message:', err if err
-
-  # ws.on 'message', (message) ->
-  #   console.log 'linkmap client says:', message
-  #   ws.send message, (err) ->
-  #     console.log 'linkmap unable to send ws message:', err if err
+			console.log 'unable to send ws message:', err if err

 module.exports = {startServer}


stack-editor: add cache for created editor instances

--- client/stack-editor/lib/index.coffee
+++ client/stack-editor/lib/index.coffee
@@ -13,6 +13,13 @@
   @options     =
     name       : 'Stackeditor'
     behavior   : 'application'
+
+  constructor: (options = {}, data) ->
+
+    super options, data
+
+    # a cache to register created views.
+    @editors = {}

   openEditor: (stackTemplateId) ->


Modal cancellation

Modal cancellation

--- vendor/assets/javascripts/fustrate/components/modal.coffee
+++ vendor/assets/javascripts/fustrate/components/modal.coffee
@@ -125,6 +125,8 @@
                    '.modal-overlay',
                    @constructor.backgroundClicked

+    @buttons.cancel?.on 'click', @cancel
+
   focusFirstInput: =>
     # Focus requires a slight physical scroll on iOS 8.4
     return true if /iPad|iPhone|iPod/g.test navigator.userAgent
@@ -211,6 +213,12 @@
   # Just hide the modal immediately and don't bother with an overlay
   hide: =>
     @modal.removeClass('open locked').css @settings.css.close
+
+  cancel: =>
+    # Reject any deferrals
+    @deferred?.reject()
+
+    @close()

   openPreviousModal: =>
     @settings.previousModal.trigger 'open.modal'


remove comments

remove comments

--- apps/editorial_features/components/eoy/client.coffee
+++ apps/editorial_features/components/eoy/client.coffee
@@ -4,9 +4,6 @@
 Article = require '../../../../models/article.coffee'
 initCarousel = require '../../../../components/merry_go_round/horizontal_nav_mgr.coffee'
 sd = require('sharify').data
-
-
-# initCarousel = require '../../../../components/merry_go_round/horizontal_nav_mgr.coffee'

 module.exports.EoyView = class EoyView extends Backbone.View


Minor test change

Minor test change

--- test/_supertest/d3-radar.super.test.coffee
+++ test/_supertest/d3-radar.super.test.coffee
@@ -0,0 +1,21 @@
+Server  = require '../../src/server/Server'
+request = require 'supertest'
+cheerio = require 'cheerio'
+
+describe '_supertest | d3-radar', ->
+  server  = null
+  app     = null
+  $       = null
+
+  before ->
+    server = new Server().setup_Server().add_Controllers()
+    app    = server.app
+
+
+
+  xit 'page-components',->
+    request(app)
+      .get('/routes')
+      .expect 200
+      .expect (req)->
+        console.log req.body


mixpanel: add more events; remove unused code

--- client/finder/fs/fsfile.coffee
+++ client/finder/fs/fsfile.coffee
@@ -41,6 +41,8 @@

         if useEncoding
           content = KD.utils.utf8Decode content # Convert to String
+
+        KD.mixpanel "Fetch contents, success"

       callback.call @, err, content
       @emit "fs.job.finished", err, content
@@ -174,3 +176,4 @@
       if err then warn err
       @emit "fs.save.finished", err, res
       callback? err,res
+      KD.mixpanel "Save file, success"  unless err


Remove debug line

Remove debug line

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -341,7 +341,6 @@
         buildModal content, callback
       else
         getBalance type, (err, balance)->
-          console.log arguments
           if not err and balance > 0
             charge = (plan.feeMonthly - balance) / 100
             balance = balance / 100


groups: slug added to login/logout/register links

--- client/landing/app/MainApp/lazy/staticuserbuttonbar.coffee
+++ client/landing/app/MainApp/lazy/staticuserbuttonbar.coffee
@@ -6,6 +6,11 @@

     @ldc = @getSingleton 'lazyDomController'
     @mc  = @getSingleton 'mainController'
+
+    @prefix =
+      if @ldc.userEnteredFromGroup() and KD.config.groupEntryPoint isnt 'koding'
+          "/#{KD.config.groupEntryPoint}"
+      else ""

     @buttonGroup = new KDView

@@ -43,7 +48,7 @@
       callback    : =>
         @ldc.handleNavigationItemClick
           action  : 'login'
-          path    : '/Login'
+          path    : "#{@prefix}/Login"

   addLogoutButton:->
@@ -53,7 +58,7 @@
       callback    : =>
         @ldc.handleNavigationItemClick
           action  : 'logout'
-          path    : '/Logout'
+          path    : "/Logout"

   addRegisterButton:->
@@ -63,7 +68,7 @@
       callback    : =>
         @ldc.handleNavigationItemClick
           action  : 'register'
-          path    : '/Register'
+          path    : "#{@prefix}/Register"

   pistachio:->


method has been renamed

method has been renamed

--- client/lanes/components/shared/NetworkActivityOverlay.cjsx
+++ client/lanes/components/shared/NetworkActivityOverlay.cjsx
@@ -25,7 +25,7 @@

     listenNetworkEvents: true

-    setModelState: (state) ->
+    setNetworkActivity: (state) ->
         if state.hasError or state.isRequesting
             @installRemoval(state)
         else if not @state.hasError


Configur headers for medium editor

Configur headers for medium editor

--- client/views/admin/edit.coffee
+++ client/views/admin/edit.coffee
@@ -35,6 +35,8 @@
 makeEditor = (tpl) ->
   editor = new MediumEditor '.editable',
     placeholder: 'Start typing...'
+    firstHeader: 'h1'
+    secondHeader: 'h2'
     buttonLabels: 'fontawesome'
     buttons:
       ['bold', 'italic', 'underline', 'anchor', 'pre', 'header1', 'header2', 'orderedlist', 'unorderedlist', 'quote', 'image']


US1302 - fixed coffeescript

US1302 - fixed coffeescript

--- c3/app/assets/javascripts/s2_about_you/g4_nationalityAndResidency.coffee
+++ c3/app/assets/javascripts/s2_about_you/g4_nationalityAndResidency.coffee
@@ -19,16 +19,19 @@

 hideNationalityWrap = (nationalResidency) ->
-  $("#nationalityWrap").slideUp(0).attr 'aria-hidden', 'true', ->
+  emptyNationalResidency = ->
     $("#" + nationalResidency).val("")

+  $("#nationalityWrap").slideUp(0,emptyNationalResidency).attr 'aria-hidden', 'true'

 showNationalityWrap = ->
   $("#nationalityWrap").slideDown(0).attr 'aria-hidden', 'false'

 hideResidencyWrap = (resideInUKText) ->
-  $("#residencyWrap").slideUp(0).attr 'aria-hidden', 'true', ->
-  $("#" + resideInUKText).val("")
+  emptyResideInUKText = ->
+    $("#" + resideInUKText).val("")
+
+  $("#residencyWrap").slideUp(0,emptyResideInUKText).attr 'aria-hidden', 'true'

 showResidencyWrap = ->
   $("#residencyWrap").slideDown(0).attr 'aria-hidden', 'false'


Make requests significantly rarer (more realistic)

--- assets/game_state.coffee
+++ assets/game_state.coffee
@@ -3,7 +3,7 @@
   agents: []

   requestQueues: {}
-  chanceOfRequest: 0.02
+  chanceOfRequest: 0.005

   tickables: []
   tick: 0


apps: Showing default thumb icon for apps when hostname is localhost.

--- client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
+++ client/app/Applications/StartTab.kdapplication/views/appthumbview.coffee
@@ -35,6 +35,9 @@
       if icns and icns[String size]
         thumb = "#{resourceRoot}/#{icns[String size]}"
         break
+
+    if location.hostname is "localhost"
+      thumb = "/images/default.app.thumb.png"

     @img = new KDCustomHTMLView
       tagName     : "img"


update tests to use safe_exec

update tests to use safe_exec

--- test/unit/coffee/FileConverterTests.coffee
+++ test/unit/coffee/FileConverterTests.coffee
@@ -10,10 +10,9 @@

 	beforeEach ->

-		@child_process =
-			exec : sinon.stub()
+		@safe_exec = sinon.stub()
 		@converter = SandboxedModule.require modulePath, requires:
-			'child_process': @child_process
+			"./SafeExec": @safe_exec
 			"logger-sharelatex":
 				log:->
 				err:->
@@ -25,43 +24,43 @@
 	describe "convert", ->

 		it "should convert the source to the requested format", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.convert @sourcePath, @format, (err)=>
-				args = @child_process.exec.args[0][0]
+				args = @safe_exec.args[0][0]
 				args.indexOf(@sourcePath).should.not.equal -1
 				args.indexOf(@format).should.not.equal -1
 				done()

 		it "should return the dest path", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.convert @sourcePath, @format, (err, destPath)=>
 				destPath.should.equal "#{@sourcePath}.#{@format}"
 				done()

 		it "should return the error from convert", (done)->
-			@child_process.exec.callsArgWith(2, @error)
+			@safe_exec.callsArgWith(2, @error)
 			@converter.convert @sourcePath, @format, (err)=>
 				err.should.equal @error
 				done()

 		it "should not accapt an non aproved format", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.convert @sourcePath, "ahhhhh", (err)=>
 				expect(err).to.exist
 				done()

 	describe "thumbnail", ->
 		it "should call converter resize with args", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.thumbnail @sourcePath, (err)=>
-				args = @child_process.exec.args[0][0]
+				args = @safe_exec.args[0][0]
 				args.indexOf(@sourcePath).should.not.equal -1
 				done()

 	describe "preview", ->
 		it "should call converter resize with args", (done)->
-			@child_process.exec.callsArgWith(2)
+			@safe_exec.callsArgWith(2)
 			@converter.preview @sourcePath, (err)=>
-				args = @child_process.exec.args[0][0]
+				args = @safe_exec.args[0][0]
 				args.indexOf(@sourcePath).should.not.equal -1
 				done()


boyscout

boyscout

--- core/app/backbone/views/evidence/evidence_vote_view.coffee
+++ core/app/backbone/views/evidence/evidence_vote_view.coffee
@@ -27,7 +27,6 @@
     comment_votes_amount =
         R.span className:"comment-vote-amount spec-evidence-relevance",
           format_as_short_number(@model().relevance())
-

     coment_voting_arrows = [
         if Factlink.Global.signed_in


RemixerView: adjusted dropdown modules and render

--- coffee/src/views/remixer_view.coffee
+++ coffee/src/views/remixer_view.coffee
@@ -2,6 +2,15 @@

 goog.require 'acorn.player.DropdownView'
 goog.require 'acorn.shells.LinkShell'
+
+
+TextShell = acorn.shells.TextShell
+EmptyShell = acorn.shells.EmptyShell
+
+LinkShell = acorn.shells.LinkShell
+PDFLinkShell = acorn.shells.PDFLinkShell
+ImageLinkShell = acorn.shells.ImageLinkShell
+

 # View to select options.
 class acorn.player.RemixerView extends athena.lib.View
@@ -48,16 +57,23 @@
   initializeDropdownView: =>

     # get only the Link-based Modules
-    linkModules = _.filter acorn.shells.Registry.modules, (module) =>
-      !!module.id.match 'Link'
+    modules = [
+      {id:LinkShell.id, name:'Link (iframe)', icon: LinkShell.icon}
+      {id:ImageLinkShell.id, name:'Image Link', icon: ImageLinkShell.icon}
+      {id:PDFLinkShell.id, name:'PDF Link', icon: PDFLinkShell.icon}
+      # '---' divisors TODO
+      {id:TextShell.id, name:'Text', icon: TextShell.icon}
+      # '---' divisors TODO
+      {id:EmptyShell.id, name:'Empty', icon: EmptyShell.icon}
+    ]

-    # construct dropdown items
-    items = _.map linkModules, (module, shellid) =>
-      {id:module.id, name: module.title, icon: module.icon}
+    selected = @model.module.id
+    if selected == Shell.id
+      selected = LinkShell.id

     @dropdownView = new acorn.player.DropdownView
-      items: items
-      selected: LinkShell.id
+      items: modules
+      selected: selected
       eventhub: @eventhub

     @dropdownView.on 'Dropdown:Selected', (dropdown, value) =>
@@ -73,14 +89,13 @@

     @$el.html @template()

-    @$('input#link').val @model.link?()
-
     @dropdownView.setElement @$ '.dropdown-view'
     @dropdownView.render()

     @toolbarView.setElement @$ '.toolbar-view'
     @toolbarView.render()

+    @$('input#link').val @model.link?()
     @renderRemixSubview()

     @
@@ -100,7 +115,11 @@
   swapShell: (newShell) =>
     oldShell = @model
     @model = newShell
+
+    @$('input#link').val @model.link?()
+    @dropdownView.selected @model.shellid()
     @renderRemixSubview()
+
     @trigger 'Remixer:SwapShell', @, oldShell, newShell
     @


Fix exchange with known client secret

Fix exchange with known client secret

--- components/ExchangeGrantToken.coffee
+++ components/ExchangeGrantToken.coffee
@@ -9,7 +9,7 @@
         data = JSON.parse req.responseText
         callback new Error "Authentication token exchange failed with #{data.error}"
       catch e
-        callback e
+        callback new Error req.responseText
       return
     try
       data = JSON.parse req.responseText
@@ -22,14 +22,13 @@
   if params.clientsecret
     # We know the client secret. Get token directly from provider
     payload =
-      code: data
+      code: code
       client_id: params.clientid
       client_secret: params.clientsecret
       grant_type: 'authorization_code'
       redirect_uri: params.redirect or window.location.href
     req.open 'POST', "#{params.token_server}#{params.token_endpoint}", true
     req.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
-    req.setRequestHeader "Connection", "close"
     req.send qs.stringify payload
     return
   # Normal scenario: exchange token via Gatekeeper


Addition of Link in on the Lab Landing Page

--- app/pages/lab/landing-page.cjsx
+++ app/pages/lab/landing-page.cjsx
@@ -21,6 +21,7 @@
       policies: 'Project Builder Policies'
       bestPractices: 'Best Practices Guide'
       buildTalk: 'Project Builder Talk'
+      acknowledgements: 'Acknowledgements'

 module.exports = React.createClass
   displayName: 'ProjectBuilderLandingPage'
@@ -61,6 +62,7 @@
           <Link to="/lab-how-to"><Translate content="labLanding.links.buildHelp" /></Link>
           <Link to="/lab-policies"><Translate content="labLanding.links.policies"></Translate></Link>
           <Link to="/lab-best-practices/introduction"><Translate content="labLanding.links.bestPractices" /></Link>
+          <Link to="/about/acknowledgements"><Translate content="labLanding.links.acknowledgements" /></Link>
           <Link to="/talk/18"><Translate content="labLanding.links.buildTalk" /></Link>
         </div>
       </div>


fixes

fixes

--- common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
+++ common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee
@@ -91,7 +91,7 @@
       @combined.child_state = 'initial'
       @combined.rebind()
       expect(@combined.answer_area.attr("disabled")).toBeUndefined()
-      expect(@combined.submit_button.val()).toBe(gettext("Submit")
+      expect(@combined.submit_button.val()).toBe(gettext("Submit"))

     it 'when our child state is post_assessment', ->
       @combined.child_state = 'post_assessment'


Relax code bank section names character restrictions

--- app/schemas/models/level.coffee
+++ app/schemas/models/level.coffee
@@ -500,8 +500,8 @@
   methodsBankList: c.array {title: 'Methods Bank List'}, c.object {
     properties: {
       name: c.shortString(title: 'Name'),
-      section: c.shortString(title: 'Methods Bank Section', pattern: '^[a-z]+$'),
-      subSection: c.shortString(title: 'Methods Bank Sub-Section', pattern: '^[a-zA-Z]+$'),
+      section: c.shortString(title: 'Methods Bank Section', pattern: /^\w[\w ]*$/),
+      subSection: c.shortString(title: 'Methods Bank Sub-Section', pattern: /^\w[\w ]*$/),
       componentName: c.shortString(title: 'Level Component Name', description: 'Level Component to use for documentation in case there are multiple components with same property\'s documentation'),
     }
   }


correcting path to ticker

correcting path to ticker

--- app/src/api/authentication.coffee
+++ app/src/api/authentication.coffee
@@ -90,7 +90,7 @@
     .fail (error) =>
       throw error if _.isArray(error) and error.length is 3
       throw [null, "Unable to sign message", error]
-    .then (signature) => @_client.jqAjax(type: "POST", url: 'bitid/authenticate', data: {address: bitidAddress, coin: ledger.network.ticker, signature: signature}, contentType: 'application/json', dataType: 'json')
+    .then (signature) => @_client.jqAjax(type: "POST", url: 'bitid/authenticate', data: {address: bitidAddress, coin: ledger.config.network.ticker, signature: signature}, contentType: 'application/json', dataType: 'json')
     .then (data) =>
       @_authToken = data['token']
       ledger.app.emit 'wallet:authenticated'


social: added dispatchEvent functionality

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -420,6 +420,10 @@
 expireSubscription = (accountId, callback) ->
   url = "#{socialProxyUrl}/payments/customers/#{accountId}/expire"
   post url, {}, callback
+
+dispatchEvent = (eventName, data, callback) ->
+  url = "#{socialProxyUrl}/private/dispatcher/#{eventName}"
+  post url, data, callback

 fetchBotChannel = (data, callback) ->
   url = "#{webhookProxyUrl}/botchannel"
@@ -557,6 +561,7 @@
   updateNotificationSetting
   deleteNotificationSetting
   expireSubscription
+  dispatchEvent
   fetchBotChannel
   post
   get


Fix bugs

Fix bugs

--- views/services/plugin-manager.cjsx
+++ views/services/plugin-manager.cjsx
@@ -216,8 +216,8 @@
     outdatedPlugins = []
     outdatedList = []
     tasks = plugins.map async (plugin, index) =>
-      if plugin.needRollback
-        @plugins_[index]?.isOutdated = true
+      if semver.lt(POI_VERSION, plugin.earlistCompatibleMain)
+        @plugins_[index]?.isOutdated = @plugins_[index].needRollback
         @plugins_[index]?.lastestVersion = @plugins_[index]?.lastApiVer
       else try
         distTag = yield Promise.promisify(npm.commands.distTag)(['ls', plugin.packageName])
@@ -508,9 +508,9 @@
     plugin.priority ?= 10000
     plugin.enabled = config.get "plugin.#{plugin.id}.enable", true
     plugin.isInstalled = true
-    plugin.needRollback = semver.lt POI_VERSION, plugin.earlistCompatibleMain
+    plugin.needRollback = semver.lt(POI_VERSION, plugin.earlistCompatibleMain) && semver.gt(plugin.version, plugin.lastApiVer)
     plugin.isOutdated = plugin.needRollback
-    plugin.lastestVersion = plugin.lastApiVer if plugin.needRollback
+    plugin.lastestVersion = plugin.lastApiVer if semver.lt(POI_VERSION, plugin.earlistCompatibleMain)
     # i18n
     i18nFile = null
     if plugin.i18nDir?


don't ignore vendor, for now

don't ignore vendor, for now

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -3,7 +3,6 @@
     'fontFamily': 'Menlo'
     'fontSize': 15
     'showIndentGuide': true
-    'invisibles': {}
   'core':
     'disabledPackages': [
       'go-to-line'
@@ -19,7 +18,6 @@
       'log'
       'repositories'
       'tmp'
-      'vendor'
     ]
     'themes': [
       'atom-dark-ui'


Atom color highlight settings

Atom color highlight settings

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -111,3 +111,5 @@
   minimap:
     plugins:
       "git-diff": true
+  "atom-color-highlight":
+    markersAtEndOfLine: true


namespaced unbind

namespaced unbind

--- test/bindable.coffee
+++ test/bindable.coffee
@@ -48,6 +48,7 @@

   o.bind "test.TestNamespace", ->
     ok true
+  o.trigger ""

 module()


Keybindings title

Keybindings title

--- extensions/showkeybindings/showkeybindings.coffee
+++ extensions/showkeybindings/showkeybindings.coffee
@@ -16,6 +16,7 @@
     return if not url

     if url is 'atom://keybindings'
+      @title = 'Keybindings'
       @url = url
       @show()
       true


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -111,6 +111,6 @@
   5.times ->
     obj.update()

-
+  equals

 module()


[Prefetch] Fixed MR comments

[Prefetch] Fixed MR comments

--- workers/social/lib/social/prefetcher.coffee
+++ workers/social/lib/social/prefetcher.coffee
@@ -31,7 +31,7 @@
 # a custom route for prefetched content
 getRoute = (options)-> return "scriptblock"

-preFetchItems = (options, client, callback) ->
+prefetchedItems = (options, client, callback) ->

   {bongoModels, client, intro} = options

@@ -83,15 +83,13 @@
 activityFetcher = ->
   return console.log "interval options are not valid" unless intervalOptions
   {client} = intervalOptions
-  preFetchItems intervalOptions, client, (err, data)->
+  prefetchedItems intervalOptions, client, (err, data)->
     if err
       return console.log "An error occured while fetching in interval", err
     else
       console.log "updating in interval"

-intervalFetcher = setInterval(->
-  activityFetcher()
-, 25000)
+intervalFetcher = setInterval activityFetcher, 25000

 module.exports = (options = {}, callback)->

@@ -109,4 +107,4 @@
       prefetchedFeeds = cachedRoutes[route]
       return callback null, prefetchedFeeds

-  return preFetchItems options, client, callback
+  return prefetchedItems options, client, callback


Fix tests

Fix tests

--- test/dtc_test.coffee
+++ test/dtc_test.coffee
@@ -12,7 +12,5 @@
     require('../src/dtc')(@robot)

   it 'registers respond listeners', ->
-    expect(@robot.respond.args[0][0].toString()).to.equal("/dtc/i")
-    expect(@robot.respond.args[0][0].toString()).to.equal("/dans ton chat/i")
-    expect(@robot.respond.args[1][0].toString()).to.equal("/dernier dtc/i")
-    expect(@robot.respond.args[1][0].toString()).to.equal("/dernier dans ton chat/i")
+    expect(@robot.respond.args[0][0].toString()).to.equal("/dernier (dtc|dans ton chat)/i")
+    expect(@robot.respond.args[0][0].toString()).to.equal("/^ *(dtc|dans ton chat)/i")


Updated test

Updated test

--- test/mixedice.coffee
+++ test/mixedice.coffee
@@ -7,4 +7,5 @@
 describe 'mixedice', ->
   it 'empty parameters', ->
     result = mixedice()
-    expect(result).to.be('undefined')
+    expect(result).to.be.a('undefined')
+    expect(result).to.equal(undefined)


using sinon date mocking

using sinon date mocking

--- src/api/apps/articles/test/model/save.test.coffee
+++ src/api/apps/articles/test/model/save.test.coffee
@@ -11,14 +11,17 @@
 { ObjectId } = require 'mongojs'

 describe 'Save', ->
+  sandbox = sinon.sandbox.create();

   before (done) ->
     app.use '/__gravity', gravity
     @server = app.listen 5000, ->
       done()
+    sandbox.useFakeTimers(new Date(2019,0,1))

   after ->
     @server.close()
+    sandbox.restore()

   beforeEach (done) ->
     @removeStopWords = Save.__get__ 'removeStopWords'
@@ -102,7 +105,6 @@
       })

     it 'appends the current date to the slug if the slug exists in the database already but the date is invalid', (done) ->
-      todays_date = moment().format('MM-DD-YY')
       Save.sanitizeAndSave( =>
         Save.generateSlugs {
           thumbnail_title: 'Clockwork'
@@ -110,21 +112,20 @@
           author: name: 'Molly'
           published_at: '2017-07-dsdfdf26T17:37:03.065Zsdfdf'
         }, (err, article) =>
-          article.slugs[0].should.equal 'molly-clockwork' + '-' + todays_date
+          article.slugs[0].should.equal 'molly-clockwork-01-01-19'
           done()
       )(null, {
         slugs: ['molly-clockwork']
       })

     it 'appends the current date to the slug if the slug exists in the database already but there is no publish date', (done) ->
-      todays_date = moment().format('MM-DD-YY')
-      Save.sanitizeAndSave( =>
-        Save.generateSlugs {
-          thumbnail_title: 'Clockwork'
-          published: true
-          author: name: 'Molly'
-        }, (err, article) =>
-          article.slugs[0].should.equal 'molly-clockwork' + '-' + todays_date
+      Save.sanitizeAndSave( =>
+        Save.generateSlugs {
+          thumbnail_title: 'Clockwork'
+          published: true
+          author: name: 'Molly'
+        }, (err, article) =>
+          article.slugs[0].should.equal 'molly-clockwork-01-01-19'
           done()
       )(null, {
         slugs: ['molly-clockwork']
@@ -193,14 +194,13 @@
       })

     it 'appends unix timestamp for current date to the slug if the slug exists already and it is a draft and there is no publish date', (done) ->
-      todays_date = moment().format('X')
       Save.sanitizeAndSave( =>
         Save.generateSlugs {
           thumbnail_title: 'Clockwork'
           published: false
           author: name: 'Molly'
         }, (err, article) =>
-          article.slugs[0].should.equal 'molly-clockwork' + '-' + todays_date
+          article.slugs[0].should.equal 'molly-clockwork-1546318800'
           done()
       )(null, {
         slugs: ['molly-clockwork']


granular lock #norelease

granular lock #norelease

--- content/BetterBibTeX.coffee
+++ content/BetterBibTeX.coffee
@@ -191,13 +191,6 @@
     notify: (action, type, ids, extraData) ->
       debug('item.notify', {action, type, ids, extraData})

-      bench = (msg) ->
-        now = new Date()
-        debug("notify: #{msg} took #{(now - bench.start) / 1000.0}s")
-        bench.start = now
-        return
-      bench.start = new Date()
-
       # safe to use Zotero.Items.get(...) rather than Zotero.Items.getAsync here
       # https://groups.google.com/forum/#!topic/zotero-dev/99wkhAk-jm0
       # items = Zotero.Items.get(ids)
@@ -207,7 +200,6 @@
       # CACHE.remove(parents)

       CACHE.remove(ids)
-      bench('cache remove')

       # safe to use Zotero.Items.get(...) rather than Zotero.Items.getAsync here
       # https://groups.google.com/forum/#!topic/zotero-dev/99wkhAk-jm0
@@ -221,7 +213,6 @@
           debug("event.#{type}.#{action}", {ids, extraData})
           KeyManager.remove(ids)
           events.emit('items-removed', ids)
-          bench('remove')

         when 'add', 'modify'
           for item in items
@@ -355,12 +346,6 @@
     INIT
   ###

-  bench = (msg) ->
-    now = new Date()
-    debug("startup: #{msg} took #{(now - bench.start) / 1000.0}s")
-    bench.start = now
-    return
-
   module.exports.ErrorReport = Zotero.Promise.coroutine((includeReferences) ->
     debug('ErrorReport::start', includeReferences)
     items = null
@@ -391,46 +376,36 @@
   class Lock
     postfix: '-better-bibtex-locked'

+    constructor: ->
+      @mark = { ts: new Date() }
+
     lock: Zotero.Promise.coroutine((msg)->
-      #@decks = {}
-      #for id in [ 'zotero-items-pane-content', 'zotero-item-pane-content' ]
-      #  @decks[id] = {
-      #    element: document.getElementById(id)
-      #    id: id
-      #  }
-      #  @decks[id].selected = @decks[id].element.selectedIndex
-      #  do (deck = @decks[id]) =>
-      #    @hide(deck.element)
-      #    deck.observer = new MutationObserver((mutations) => @hide(deck.element))
-      #    deck.observer.observe(deck.element, {attributes: true, childList: true})
-      #    return
-
       yield Zotero.uiReadyPromise

       yield Zotero.unlockPromise if Zotero.locked

-      Zotero.showZoteroPaneProgressMeter(msg || 'Initializing Better BibTeX...')
+      @update(msg || 'Initializing')

       @toggle(true)

       return
     )

+    bench: (msg) ->
+      ts = new Date()
+      debug('Lock:', @mark.msg, 'took', (ts - @mark.ts) / 1000.0, 's') if @mark.msg
+      @mark = { ts, msg }
+      return
+
     update: (msg) ->
-      Zotero.showZoteroPaneProgressMeter(msg)
+      @bench(msg)
+      Zotero.showZoteroPaneProgressMeter("Better BibTeX: #{msg}...")
       return

     unlock: ->
-      #return unless @decks
-
-      #for id, deck of @decks
-      #  deck.observer.disconnect()
-      #  deck.element.selectedIndex = deck.selected
-      #  deck.element.removeChild(document.getElementById(id + @postfix))
-      #@decks = null
+      @bench()

       Zotero.hideZoteroPaneOverlays()
-
       @toggle(false)

       return
@@ -464,58 +439,44 @@

     ready = Zotero.Promise.defer()
     module.exports.ready = ready.promise
-    bench.start = new Date()
-
-    # progressWin = new Zotero.ProgressWindow({ closeOnClick: false })
-
-    # progressWin.changeHeadline('BetterBibTeX: Waiting for Zotero database')
-    # progressWin.show()

     # oh FFS -- datadir is async now

     lock = new Lock()
-    yield lock.lock('BetterBibTeX: Waiting for Zotero database...')
+    yield lock.lock('Waiting for Zotero database')

     CACHE.init()
-    bench('Zotero.uiReadyPromise')

     # Zotero startup is a hot mess; https://groups.google.com/d/msg/zotero-dev/QYNGxqTSpaQ/uvGObVNlCgAJ
     yield Zotero.Schema.schemaUpdatePromise
-    bench('Zotero.Schema.schemaUpdatePromise')
-
-    # progressWin.changeHeadline('BetterBibTeX: Initializing')
-    lock.update('BetterBibTeX: Initializing...')
-
+
+    lock.update('Loading database')
     yield DB.init()
-    bench('DB.init()')
-
+
+    lock.update('Starting Auto Export')
     AutoExport.init()
-    bench('AutoExport.init()')
-
+
+    lock.update('Starting Key Manager')
     yield KeyManager.init() # inits the key cache by scanning the DB
-    bench('KeyManager.init()')
-
+
+    lock.update('Starting Serialization Cache')
     yield Serializer.init() # creates simplify et al
-    bench('Serializer.init()')

     if Prefs.get('testing')
+      lock.update('Loading Test Support')
       module.exports.TestSupport = require('./test/support.coffee')
-      bench('Zotero.BetterBibTeX.TestSupport')
     else
       debug('starting, skipping test support')

+    lock.update('Loading Journal Abbreviator')
     JournalAbbrev.init()

+    lock.update('Loading Translators')
     yield Translators.init()
-    bench('Translators.init()')
-
-    # progressWin.changeHeadline('BetterBibTeX: Ready for business')
-    # progressWin.startCloseTimer(500)

     # should be safe to start tests at this point. I hate async.

     ready.resolve(true)
-    bench('ready')

     lock.unlock()


input: observable... not from

input: observable... not from

--- input/index.coffee
+++ input/index.coffee
@@ -41,8 +41,6 @@
     isFloating ?= false
     isDisabled ?= false
     autocapitalize ?= 'none'
-
-    label = if label.subscribe? then label else Rx.Observable.from label

     @state = z.state {
       @value


Delete get-repo.coffee

Delete get-repo.coffee

--- assets-src/coffee/get-repo.coffee
+++ assets-src/coffee/get-repo.coffee
@@ -1,14 +0,0 @@
-$( ->
-  $.getJSON("https://api.github.com/users/Hiroto-K/repos", {per_page: "100"}, (data, status, xhr) ->
-    if status is "success"
-      $(".repo-count").html(data.length)
-      data.forEach((obj) ->
-        $(".repo-list").append("#{obj.full_name}")
-        $(".pages-list").append("#{obj.full_name}") if obj.has_pages
-      )
-    else
-      console.log(status)
-      $(".github-main").hide()
-      $(".github-error").show()
-  )
-)


Close over robot.brain to actually persist things.

--- src/chalkcircle.coffee
+++ src/chalkcircle.coffee
@@ -11,8 +11,8 @@
     @match = null

     # Initialize storage structures.
-    @storage = robot.brain.data.hammersport ?= {}
-    @storage.challengers ?= {}
+    @storage = -> robot.brain.data.hammersport ?= {}
+    @storage().challengers ?= {}

   botName: -> @name

@@ -44,7 +44,7 @@
   # Public: Access or create a Challenger corresponding to a Hubot User.
   #
   getChallenger: (user) ->
-    cdata = @storage.challengers[user.id] ?= {}
+    cdata = @storage().challengers[user.id] ?= {}
     new Challenger(user, cdata)

 module.exports = ChalkCircle


ObjectDumper: Fixed in a better way the previous commit '0f4a1beef2ef7348a10fc4a70cb083b2ee1bf538'

--- src/elements/ObjectDumper/ObjectDumperNode.coffee
+++ src/elements/ObjectDumper/ObjectDumperNode.coffee
@@ -36,8 +36,8 @@
 		if @_parse_json and CUI.util.isString(data)
 			try
 				@__data = JSON.parse(data)
-				if not CUI.isPlainObject(@__data)
-					@__data(data)
+				if CUI.util.isString(@__data)
+					@__data = data
 			catch e
 				@__data = data
 		else


activity: set `CommentView` instance as delegate of the `ActivityActionsView` instance

--- client/Social/Activity/views/activitylistitem.coffee
+++ client/Social/Activity/views/activitylistitem.coffee
@@ -24,7 +24,7 @@
     @author     = new ProfileLinkView { origin }
     @commentBox = new CommentView options.commentSettings, data

-    @actionLinks = new ActivityActionsView delegate: @commentBox.commentList, data
+    @actionLinks = new ActivityActionsView delegate: @commentBox, data
     @likeSummary = new ActivityLikeSummaryView {}, data

     @settingsButton = new ActivitySettingsView


Moderation: Set Parent click event name is changed

--- client/Social/Topics/Views/TopicsListItemView.coffee
+++ client/Social/Topics/Views/TopicsListItemView.coffee
@@ -46,8 +46,8 @@
       callback  : => KD.getSingleton('mainController').emit 'TopicItemEditClicked', @
     menu["Delete"] =
       callback : => KD.getSingleton('mainController').emit 'TopicItemDeleteClicked', @
-    menu["Set Synonym"] =
-      callback : => KD.getSingleton('mainController').emit 'TopicItemSynonymClicked', @
+    menu["Set Parent"] =
+      callback : => KD.getSingleton('mainController').emit 'TopicItemSetParentClicked', @

     menu


teams: minor date grammer fix

teams: minor date grammer fix

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -40,10 +40,10 @@

   getChannelCreationDate: (givenDate) ->
     dateString = moment(givenDate).calendar null,
-      sameDay  : '[Today]'
-      lastDay  : '[Yesterday]'
-      lastWeek : '[Last] dddd'
-      sameElse : 'MMM D'
+      sameDay  : '[today]'
+      lastDay  : '[yesterday]'
+      lastWeek : '[last] dddd'
+      sameElse : '[on] MMM D'

   getChannelCreatorProfile: (accountId) ->


Removed white space

Removed white space

--- client/activity/lib/views/activityactionsview.coffee
+++ client/activity/lib/views/activityactionsview.coffee
@@ -80,7 +80,7 @@

   viewAppended: ->
-
+
     @loader.hide()

     super


Remove unneeded method calling.

Remove unneeded method calling.

--- client/ide/lib/collaboration/helpers/realtime.coffee
+++ client/ide/lib/collaboration/helpers/realtime.coffee
@@ -164,13 +164,7 @@
   return refs

-getParticipantWatchMap = (manager, nickname) ->
-
-  watchMap = getFromManager manager, "#{nickname}WatchMap", 'map', {}
-
-  manager.bindRealtimeListeners watchMap, 'map'
-
-  return watchMap
+getParticipantWatchMap = (manager, nickname) ->  getFromManager manager, "#{nickname}WatchMap", 'map', {}

 ###*


implemented last active app logic to appManager

--- client/landing/app/MainApp/ApplicationManager.coffee
+++ client/landing/app/MainApp/ApplicationManager.coffee
@@ -148,6 +148,7 @@

     if KD.isLoggedIn()
       @emit 'AppManagerWantsToShowAnApp', appInstance, appView, appOptions
+      @setLastActiveIndex appInstance
       callback? appInstance
     else
       KD.getSingleton('router').handleRoute '/', replaceState: yes
@@ -161,6 +162,7 @@

     if KD.isLoggedIn()
       @emit 'AppManagerWantsToShowAnApp', appInstance, appView, appOptions
+      @setLastActiveIndex appInstance
       callback? appInstance
     else
       KD.getSingleton('router').handleRoute '/', replaceState: yes
@@ -170,37 +172,50 @@
     @unregister appInstance
     callback()

-  get:(name)-> @appControllers[name]?.first or null
+  get:(name)->
+
+    if apps = @appControllers[name]
+      apps.instances[apps.lastActiveIndex] or apps.instances.first
+    else
+      null

   getByView: (view)->

     appInstance = null
     for name, apps of @appControllers
-      apps.forEach (appController)=>
+      for appController in apps.instances
         if view.getId() is appController.getView?()?.getId()
           appInstance = appController
+          break
+      break if appInstance

     return appInstance

   getFrontApp:-> @frontApp

-  setFrontApp:(@frontApp)->
+  setFrontApp:(appInstance)->
+
+    @setLastActiveIndex appInstance
+    @frontApp = appInstance

   register:(appInstance)->

     name = appInstance.getOption "name"
-    @appControllers[name] ?= []
-    @appControllers[name].push appInstance
+    @appControllers[name] ?=
+      instances       : []
+      lastActiveIndex : null
+
+    @appControllers[name].instances.push appInstance
     @setListeners appInstance

   unregister:(appInstance)->

     name  = appInstance.getOption "name"
-    index = @appControllers[name].indexOf appInstance
+    index = @appControllers[name].instances.indexOf appInstance

     if index >= 0
-      @appControllers[name].splice index, 1
-      if @appControllers[name].length is 0
+      @appControllers[name].instances.splice index, 1
+      if @appControllers[name].instances.length is 0
         delete @appControllers[name]
       appInstance.destroy()

@@ -214,6 +229,14 @@
     appView?.once "KDObjectWillBeDestroyed", =>
       @unregister appInstance

+  setLastActiveIndex:(appInstance)->
+
+    return unless appInstance
+
+    if optionSet = @appControllers[appInstance.getOption "name"]
+      index = optionSet.instances.indexOf appInstance
+      if index is -1 then optionSet.lastActiveIndex = null
+      else optionSet.lastActiveIndex = index


Disable autosave on save failure; prevents repeated save failure error dialogs.

--- src/code/client.coffee
+++ src/code/client.coffee
@@ -206,9 +206,15 @@
         saving: metadata
       currentContent = @_createOrUpdateCurrentContent stringContent, metadata
       metadata.provider.save currentContent, metadata, (err) =>
-        return @_error(err) if err
+        if err
+          # disable autosave on save failure; clear "saving..." message
+          metadata.autoSaveDisabled = true
+          @_setState { metadata: metadata, saving: null }
+          return @_error(err)
         if @state.metadata isnt metadata
           @_closeCurrentFile()
+        # reenable autosave on save success
+        metadata.autoSaveDisabled = false
         @_fileChanged 'savedFile', currentContent, metadata, {saved: true}, @_getHashParams metadata
         callback? currentContent, metadata
     else
@@ -359,7 +365,7 @@
     if interval > 1000
       interval = Math.round(interval / 1000)
     if interval > 0
-      @_autoSaveInterval = setInterval (=> @save() if @state.dirty and @state.metadata?.provider?.can 'save'), (interval * 1000)
+      @_autoSaveInterval = setInterval (=> @save() if @state.dirty and not @state.metadata?.autoSaveDisabled and @state.metadata?.provider?.can 'save'), (interval * 1000)

   isAutoSaving: ->
     @_autoSaveInterval?


Wrap evaled code in a function.

Wrap evaled code in a function.

--- src/coffee/main.coffee
+++ src/coffee/main.coffee
@@ -84,12 +84,12 @@

     # inject amCoffee object into inspected window
     inject : ->
-        chrome.devtools.inspectedWindow.eval """
+        chrome.devtools.inspectedWindow.eval """(function () {
             if (window.__amCoffee__) return;
             window.__amCoffee__ = {
                 process : #{C.process.toString()}
             };
-        """, C.console.init
+        })();""", C.console.init

     focusPrompt : (e) ->
         C.$prompt.focus()


service objects can now pass message to one another

--- lib/assets/javascripts/transponder/service.coffee
+++ lib/assets/javascripts/transponder/service.coffee
@@ -7,12 +7,12 @@
     throw new Transponder.InvalidService('no service name specified.') unless @serviceName?
     _this = this
     serviceEvent = "#{_this.module}:services:#{_this.serviceName}"
-    $(document).on serviceEvent, (e) ->
+    $(document).on serviceEvent, (e, message = {}) ->
       service = _this.serviceName
-      $(".#{service}:not(#{service}_active)").each (i, e) ->
+      $(".#{service}:not(#{service}_active)").each (i, f) ->
         _this.element = $(this)
         _this.element.addClass("#{service}_active")
-        _this.serve()
+        _this.serve(message)

   serve: ->


Fix profile feed

Fix profile feed

--- lib/assets/javascripts/views/posts_feed.js.coffee
+++ lib/assets/javascripts/views/posts_feed.js.coffee
@@ -15,8 +15,8 @@
     @feed_headers = options.headers || {}

     default_queries = [
-      { profiles: 'entity,refs', max_refs: 1, entities: false },
-      { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], mentions: 'subscribing', profiles: 'entity,refs', entities: false },
+      { profiles: 'entity,refs', max_refs: 1, entities: options.entity || false },
+      { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], mentions: 'subscribing', profiles: 'entity,refs', entities: options.entity || false },
       { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], entities: @entity, profiles: 'entity' }
     ]
     @feed_queries = _.map options.feed_queries || default_queries, (q) => q.types ?= @types; q


broken

broken

--- core/KDWindowController.coffee
+++ core/KDWindowController.coffee
@@ -104,10 +104,8 @@

     @utils.repeat 1000, do (cookie = $.cookie 'clientId') => =>
       if cookie? and cookie isnt $.cookie 'clientId'
-        if KD.isLoggingIn then KD.isLoggingIn = no
-        else
-          window.removeEventListener 'beforeunload', @bound 'beforeUnload'
-          window.location.replace '/'
+        window.removeEventListener 'beforeunload', @bound 'beforeUnload'
+        window.location.replace '/'
       cookie = $.cookie 'clientId'

   addUnloadListener:(listener)-> @unloadListeners.push listener


Documenting Router class

Documenting Router class

--- www/src/theoricus/core/router.coffee
+++ www/src/theoricus/core/router.coffee
@@ -1,5 +1,10 @@
 ## Router & Route logic inspired by RouterJS:
 ## https://github.com/haithembelhaj/RouterJs
+
+###*
+  Core module
+  @module core
+###

 StringUril = require 'theoricus/utils/string_util'
 Route = require 'theoricus/core/route'
@@ -8,19 +13,31 @@

 Factory = null

-###
-Proxies browser's History API, routing request to and from the aplication
+###*
+  Proxies browser's History API, routing request to and from the aplication
+  @class Router
 ###
 module.exports = class Router

+  ###*
+    Array storing all the routes defined in the application's route file.
+
+    @property {Array} routes
+  ###
   routes: []
-  listeners: []

+  ###*
+    If false, doesn't handle the url route.
+
+    @property {Boolean} trigger
+  ###
   trigger: true

-  ###
-  @param [theoricus.Theoricus] @the   Shortcut for app's instance
-  @param [Function] @on_change  state/url change handler
+  ###*
+  @class Router
+  @constructor
+  @param @the {Theoricus} Shortcut for app's instance
+  @param @on_change {Function} state/url change handler
   ###
   constructor:( @the, @Routes, @on_change )->
     Factory = @the.factory
@@ -37,13 +54,13 @@
       @run url
     , 1

-  ###
+  ###*
   Creates and store a route
-
-  @param [String] route
-  @param [String] to
-  @param [String] at
-  @param [String] el
+  @method map
+  @param route {String}
+  @param to {String} Controller and action to which the route will be sent.
+  @param at {String} Route to be called as a dependency.
+  @param el {String} CSS selector to define where the template will be rendered.
   ###
   map:( route, to, at, el )->
     @routes.push route = new Route route, to, at, el, @
@@ -61,7 +78,7 @@

       #remove base path from incoming url
       ( url = url.replace @the.base_path, '' ) if @the.base_path?
-
+
       # removes the prepended '.' from HistoryJS
       url = url.slice 1 if (url.slice 0, 1) is '.'

@@ -119,4 +136,4 @@
     History.back()

   forward:()->
-    History.forward()+    History.forward()


fix jquery selector in helper/textarea autosize call

--- www/src/views/helper/textarea.coffee
+++ www/src/views/helper/textarea.coffee
@@ -41,7 +41,7 @@
             @$("textarea").val(val)
             cachedText[@cid] = val
             @updatePreview()
-            $("textarea").trigger "autosize"
+            @$("textarea").trigger "autosize"
             return

         hide: ->


using contextify for cheerio context

using contextify for cheerio context

--- modules/gui/platforms/node/lib/HttpContext.coffee
+++ modules/gui/platforms/node/lib/HttpContext.coffee
@@ -289,7 +289,8 @@

                     ##
-                    require('vm').runInContext "(#{__boot_cheerio__})(#{model})", ctx
+                    #require('vm').runInContext "(#{__boot_cheerio__})(#{model})", ctx
+                    ctx.run "(#{__boot_cheerio__})(#{model})"

@@ -300,14 +301,18 @@
             vm = require('vm')

             ##
-            ctx = vm.createContext
+            #ctx = vm.createContext
+            ctx =
                 window: {}
                 process: process
                 console: console
                 setTimeout: setTimeout

-            ##
-            vm.runInContext @__SCRIPT+"var floyd = require('floyd');", ctx
+            require('contextify') ctx
+
+            ##
+            #vm.runInContext @__SCRIPT+"var floyd = require('floyd');", ctx
+            ctx.run @__SCRIPT+"var floyd = require('floyd');", ctx

             ctx.floyd.__parent =
                 lookup: (name, identity, fn)=>
@@ -339,7 +344,7 @@
         ##
         ##
         _releaseContext: (ctx)->
-            persistentKeys = ['floyd', 'require', '_modules', 'process', 'console']
+            persistentKeys = ['run', 'getGlobal', 'dispose', 'setTimeout', 'floyd', 'require', '_modules', 'process', 'console']

             if @__POOL__.length < @data.poolsize
                 for key, value of ctx
@@ -349,6 +354,9 @@
                 ## check again... just for the case
                 if @__POOL__.length < @data.poolsize
                     @__POOL__.push ctx
+
+                else
+                    ctx.dispose()

         ##


coordinator: Output discovery errors to stderr, include input data

--- src/coordinator.coffee
+++ src/coordinator.coffee
@@ -134,7 +134,7 @@
           try
             @handleFbpMessage msg.data
           catch e
-            console.log 'Participant discovery failed:', e.message, '\n', e.stack
+            console.error 'Participant discovery failed:', e.message, '\n', e.stack, '\n', JSON.stringify(msg.data, 2, null)
           @broker.ackMessage msg
         @started = true
         debug 'started', err, @started


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -411,17 +411,16 @@
             .style('stroke', 'black')
             .attr('stroke-width', 1)
             .attr('marker-end', 'url(#circ)')
-            .style('opacity', 0)
+            #.style('opacity', 0)
         pointers.exit().remove()
         pointers.transition()
-            .delay(500)
             .duration(1000)
             .attr('d', (d) ->
                 if d.cx > d.l
                     'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
                 else
                     'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            ).style('opacity', 1)
+            )#.style('opacity', 1)

         # Display total count
         totalTickets = 0
@@ -441,10 +440,6 @@
             .attr('font-size',(if typeof totalTickets isnt 'string' then radiusi + 'px' else '2em'))
             .style('opacity', 1)

-        #console.log('window.oldPieData BOTTOM',window.oldPieData)
-        #window.oldPieData[@instanceDataId] = piedata
-        #window.oldPieData = window.oldPieData or {}
-        #window.oldPieData[@instanceDataId] = window.oldPieData[@instanceDataId] or []
         window.oldPieData[@instanceDataId] = piedata
-        #console.log('piedata',piedata)
+
         return


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -647,13 +647,13 @@
     pointers = pointerGroup.selectAll('path.pointer').data(piedata)
     pointers.enter().append('path').attr('class', 'pointer').style('fill', 'none').style('stroke', 'black').attr 'marker-end', 'url(#circ)'
     pointers.exit().remove()
-    #pointers.transition().attr 'd', (d) ->
-    #  console.log 'ts: d', d
-    #  if d.cx > d.l
-    #    console.log 'if d.cx > d.l','M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #    'M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #  else
-    #    console.log 'else', 'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
-    #    'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
+    pointers.transition().attr 'd', (d) ->
+      console.log 'ts: d', d
+      if d.cx > d.l
+        console.log 'if d.cx > d.l','M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
+        'M' + d.l + 2 + ',' + d.b + 'L' + d.r - 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
+      else
+        console.log 'else', 'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
+        'M' + d.r - 2 + ',' + d.b + 'L' + d.l + 2 + ',' + d.b + ' ' + d.cx + ',' + d.cy
     oldPieData = piedata
     return


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -67,6 +67,7 @@
         dataSet = @get("data")
     if !dataSet
         return
+    $(@node).children("svg").remove();
     width = 750
     height = 450
     radius = Math.min(width, height) / 2


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -59,7 +59,7 @@
         .attr("id","dropshadow")
     filter.append("feGaussianBlur")
         .attr("in","SourceAlpha")
-        .attr("stdDeviation","4")
+        .attr("stdDeviation","1")
         .attr("result","blur")
     filter.append("feOffset")
         .attr("in","blur")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -54,7 +54,7 @@
     sum=0
     for val in data
       sum += val.value
-    textRadiusInc = Math.Round(sum / object.keys(data).length)
+
     if !sum
       arcs.append("svg:text")
         .attr('fill', "#fff")


adding error message for when homeworks don't have page_ids

--- src/components/task-plan/homework/exercises.cjsx
+++ src/components/task-plan/homework/exercises.cjsx
@@ -145,7 +145,12 @@
     load = @renderLoading()
     if (load)
       return load
+
     {courseId, pageIds, planId} = @props
+
+    if not TaskPlanStore.getTopics(planId).length
+      return
Failed loading exercises

+
     exercise_ids = TaskPlanStore.getExercises(planId)
     exercises = _.map(exercise_ids, ExerciseStore.getExerciseById)
     renderedExercises = _.map(exercises, @renderExercise)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/core_object.coffee
+++ src/core_object.coffee
@@ -158,6 +158,7 @@
             self.extend Module(I, self)
         else

+          self.extend Module(I, self)

       return self


Send the original host in the push notification

--- packages/rocketchat-lib/server/sendMessage.coffee
+++ packages/rocketchat-lib/server/sendMessage.coffee
@@ -70,6 +70,7 @@
 						badge: 1
 						sound: 'chime'
 						payload:
+							host: Meteor.absoluteUrl()
 							rid: message.rid
 							sender: message.u
 							type: room.t
@@ -143,6 +144,7 @@
 							badge: 1
 							sound: 'chime'
 							payload:
+								host: Meteor.absoluteUrl()
 								rid: message.rid
 								sender: message.u
 								type: room.t


Set gitlabs scope to 'api', the only support scope.

Fixes #3987

--- packages/rocketchat-gitlab/common.coffee
+++ packages/rocketchat-gitlab/common.coffee
@@ -1,6 +1,7 @@
 config =
 	serverURL: 'https://gitlab.com'
 	identityPath: '/api/v3/user'
+	scope: 'api'
 	addAutopublishFields:
 		forLoggedInUser: ['services.gitlab']
 		forOtherUsers: ['services.gitlab.username']


Changed: french translation to use 'étiquette' for tag

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -1,12 +1,12 @@
 module.exports =
     "todo": "A faire"
     "archived": "Archivées"
-    "untagged": "non taguées"
+    "untagged": "sans étiquette"
     "all tasks": "Toutes les tâches"
     "all archived tasks": "Toutes les tâches archivées"
-    "untagged tasks": "Tâches non taguées"
-    "tasks of": "Tâches correspondant au tag %{tagsList} |||| Tâches correspondant aux tags %{tagsList}"
-    "archived tasks of": "Tâches archivées correspondant au tag %{tagsList} |||| Tâches archivées correspondant aux tags %{tagsList}"
+    "untagged tasks": "Tâches sans étiquettes"
+    "tasks of": "Tâches correspondant à l'étiquette %{tagsList} |||| Tâches correspondant aux étiquettes %{tagsList}"
+    "archived tasks of": "Tâches archivées correspondant à l'étiquette %{tagsList} |||| Tâches archivées correspondant aux étiquettes %{tagsList}"
     "and": "et"
     "archived date format": "{dd}/{MM}/{yyyy} à {HH}h{mm}"
     "actions headline": "Actions"


Removed unwanted change

Removed unwanted change

--- client/app/locales/fr.coffee
+++ client/app/locales/fr.coffee
@@ -9,7 +9,7 @@
     'imported data:': 'Données importées :'
     'importing...': 'importation en cours...'
     'no import performed': "Pas d'importation effectuée"
-    'import already running': "L'import est déjà en cours."link
+    'import already running': "L'import est déjà en cours."
     'firstname': 'Prénom'
     'lastname': 'Nom'
     'login': 'Identifiant'


fix a reference error

fix a reference error

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -543,7 +543,7 @@

         cursor.toArray (err, arr) ->
           return callback err  if err
-          callback null, arr.map ({ hostnameAlias, region, hostKite }) ->
+          callback null, arr.map ({ hostnameAlias, region, hostKite, stack }) ->
             hostKite = null  if hostKite in ['(banned)', '(maintenance)']
             { hostnameAlias, region, hostKite, stack }


VM: KONFIG reference error fixed

VM: KONFIG reference error fixed

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -1,5 +1,7 @@
 {Model} = require 'bongo'
 {Relationship, Module} = require 'jraphical'
+{argv} = require 'optimist'
+KONFIG = require('koding-config-manager').load("main.#{argv.c}")

 module.exports = class JVM extends Module


Scale properly

Scale properly

--- src/draw_turtle.coffee
+++ src/draw_turtle.coffee
@@ -27,11 +27,10 @@
 exports.draw_turtle = {
     stroke: "green"
     transform: (world, args) ->
-      console.log('turtle.transform')
-      console.log(world)
       scale = world.get('scale')
-      x = world.get('i') + 0.5 * scale
-      y = world.get('j') + 0.5 * scale
+      x = scale*(world.get('i') + 0.5)
+      y = scale*(world.get('j') + 0.5)
+      # console.log("turtle.transform #{world}" )
       # TODO: perform real triginometry
       angle = 90*(1-world.get('v_i')) #0, 90, 180, 90, 0
       angle = -angle if world.get('v_j') == -1
@@ -44,8 +43,8 @@
       ]
     children: {
       ME: {
-        i: 4
-        j: 4
+        i: 4.0
+        j: 4.0
         v_i: 0
         v_j: -1
         fill: "#88ff88"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/emitterable.coffee
+++ src/emitterable.coffee
@@ -38,6 +38,8 @@
   self.bind 'beforeUpdate', (dt) ->
     I.particles.invoke "trigger", "beforeUpdate", dt

+  self.bind 'afterUpdate', (dt) ->
+    I.particles.invoke "trigger", "beforeUpdate", dt

   self.bind 'update', (dt) ->
     I.batchSize.times ->


Remove a check

Remove a check

The [0]th item must be valid or the entire RegEx would have failed to
find a match.

--- src/parser.coffee
+++ src/parser.coffee
@@ -269,7 +269,7 @@

   if nameMatches = new RegExp(ArgumentListItemRegex).exec(argumentString)
     name = nameMatches[1]
-    description = description.replace(nameMatches[0], '') if nameMatches[0]?
+    description = description.replace(nameMatches[0], '')
     type = getLinkMatch(description)
     isOptional = !!nameMatches[3]


Add ERRORMESSAGE & system on error (#191)

--- src/parser.coffee
+++ src/parser.coffee
@@ -50,8 +50,8 @@
         @parseXmlDocument(null, [], options, xml, cb)

     @vent = new EventEmitter()
-    @track: (templates, errorCode, emittedData) ->
-        @vent.emit 'VAST-error', VASTUtil.merge(DEFAULT_EVENT_DATA, errorCode, emittedData)
+    @track: (templates, errorCode, data...) ->
+        @vent.emit 'VAST-error', VASTUtil.merge(DEFAULT_EVENT_DATA, errorCode, data...)
         VASTUtil.track(templates, errorCode)

     @on: (eventName, cb) ->
@@ -121,7 +121,9 @@
                             @track(
                                 ad.errorURLTemplates.concat(response.errorURLTemplates),
                                 { ERRORCODE: ad.errorCode || 303 },
-                                { extensions : ad.extensions }
+                                { ERRORMESSAGE: ad.errorMessage || '' },
+                                { extensions : ad.extensions },
+                                { system: ad.system }
                             )
                             response.ads.splice(index, 1)

@@ -149,6 +151,7 @@
                         # Timeout of VAST URI provided in Wrapper element, or of VAST URI provided in a subsequent Wrapper element.
                         # (URI was either unavailable or reached a timeout as defined by the video player.)
                         ad.errorCode = 301
+                        ad.errorMessage = err.message
                         complete()
                         return


Refactor

Refactor

--- src/parser.coffee
+++ src/parser.coffee
@@ -43,6 +43,14 @@
   });
 }`

+reOpenSync = /<sync/i
+reLineEnding = /\r\n?|\n/g
+reBrokenTag = /<[a-z]*[^>]*<[a-z]*/g
+reStartTime = /<sync[^>]+?start[^=]*=[^0-9]*([0-9]*)["^0-9"]*/i
+reBr = /<br[^>]*>/ig
+reStyle = /<style[^>]*>([\s\S]*?)<\/style[^>]*>/i
+reComment = /(<!--|-->)/g
+
 class Parser
   @defaultLanguage: {className: 'KRCC', lang: 'ko', reClassName: /class[^=]*?=["']?(KRCC)["']?/i}
   @defaultLanguageCode: 'ko'
@@ -58,16 +66,16 @@
     ret = []

     while true
-      startTagIdx = str.search(/<sync/i)
+      startTagIdx = str.search(reOpenSync)
       break if nextStartTagIdx <= 0 || startTagIdx < 0
-      nextStartTagIdx = str.slice(startTagIdx+1).search(/<sync/i)+1
+      nextStartTagIdx = str.slice(startTagIdx+1).search(reOpenSync)+1
       if nextStartTagIdx > 0
         element = str.slice(startTagIdx, startTagIdx+nextStartTagIdx)
       else
         element = str.slice(startTagIdx)

-      lineNum += str.slice(0, startTagIdx).match(/\r\n?|\n/g)?.length or 0
-      if isBroken = /<[a-z]*[^>]*<[a-z]*/g.test(element)
+      lineNum += str.slice(0, startTagIdx).match(reLineEnding)?.length or 0
+      if isBroken = reBrokenTag.test(element)
         e = new Error('ERROR_BROKEN_TAGS')
         e.line = lineNum
         e.context = element
@@ -75,7 +83,7 @@

       str = str.slice(startTagIdx+nextStartTagIdx)

-      startTime = +element.match(/<sync[^>]+?start[^=]*=[^0-9]*([0-9]*)["^0-9"]*/i)?[1] or -1
+      startTime = +element.match(reStartTime)?[1] or -1

       if startTime < 0
         e = new Error('ERROR_INVALID_TIME')
@@ -83,15 +91,15 @@
         e.context = element
         @errors.push(e)

-      lineNum += element.match(/\r\n?|\n/g)?.length or 0
+      lineNum += element.match(reLineEnding)?.length or 0

       for lang in @availableLanguages when lang.reClassName.test element
         lang = lang.lang
         break;

       lang or= @defaultLanguageCode
-      element = element.replace(/[\r\n]/g, '')
-      element = element.replace(/<br[^>]*>/ig, "\n")
+      element = element.replace(reLineEnding, '')
+      element = element.replace(reBr, "\n")
       innerText = strip_tags(element).trim()
       lang = @getLanguage(element)
       item = {startTime, languages: {}, contents: innerText}
@@ -120,8 +128,8 @@

   getAvailableLanguages: (str) ->
     try
-      matched = str.match(/<style[^>]*>([\s\S]*?)<\/style[^>]*>/i)?[1] or ''
-      matched = matched.replace(/(<!--|-->)/g, '')
+      matched = str.match(reStyle)?[1] or ''
+      matched = matched.replace(reComment, '')
       parsed = cssParse matched

       for rule in parsed.stylesheet.rules


Cleanup/doc

Cleanup/doc

--- src/pkgman.coffee
+++ src/pkgman.coffee
@@ -1,82 +1,162 @@
+
+path = require 'path'
+
+config = require 'config'

 class PackageManager

-  @normalizePath: (path) ->
-
+  # ## PackageManager#constructor
   constructor: ->

     @_hookIndex = {}
-    @_pathIndex = {}
+    @_pkgIndex = {}

     @_packageList = []

+  # ## PackageManager#invoke
+  #
+  # * (String) `hook` - The hook to invoke.
+  #
+  # * (Array) `args` - The arguments passed to the hook implementations.
+  #
+  # *Invoke a hook, heying the results by package implementations.*
   invoke: (hook, args...) ->
     results = {}
-    for path in @packagesImplementing hook
-      results[path] = @invokePackage path, hook, args...
+    for pkg in @packagesImplementing hook
+      results[pkg] = @invokePackage pkg, hook, args...
     return results

+  # ## PackageManager#invokeFlat
+  #
+  # * (String) `hook` - The hook to invoke.
+  #
+  # * (Array) `args` - The arguments passed to the hook implementations.
+  #
+  # *Invoke a hook, returning the results as a flattened array.*
   invokeFlat: (hook, args...) ->
-    for path in @packagesImplementing hook
-      @invokePackage path, hook, args...
+    for pkg in @packagesImplementing hook
+      @invokePackage pkg, hook, args...

-  invokePackage: (path, hook, args...) -> @_pathIndex?[path]?[hook]? args...
+  # ## PackageManager#invokePackage
+  #
+  # * (String) `pkg` - The package.
+  #
+  # * (String) `hook` - The hook to invoke.
+  #
+  # * (Array) `args` - The arguments passed to the hook implementation.
+  #
+  # *Invoke a hook, returning the result.*
+  invokePackage: (pkg, hook, args...) -> @_pkgIndex?[pkg]?[hook]? args...

-  isPackageRegistered: (path) -> -1 isnt @_packageList.indexOf path
+  # ## PackageManager#isPackageRegistered
+  #
+  # * (String) `pkg` - The package.
+  #
+  # *Check whether a package is registered.*
+  isPackageRegistered: (pkg) -> -1 isnt @_packageList.indexOf pkg

+  # ## PackageManager#packageImplements
+  #
+  # * (String) `pkg` - The package.
+  #
+  # * (String) `hook` - The hook to check.
+  #
+  # *Check whether a package implements a hook.*
+  packageImplements: (pkg, hook) -> @_pkgIndex?[pkg]?[hook]?
+
+  # ## PackageManager#packageList
+  #
+  # *Get the list of registered packages.*
+  packageList: -> @_packageList
+
+  # ## PackageManager#packagePath
+  #
+  # * (String) `pkg` - The package.
+  #
+  # *Get the filepath of a package.*
+  packagePath: (pkg) ->
+
+    path_ = config.get 'path'
+    path.relative path_, path.dirname require.resolve pkg
+
+  # ## PackageManager#packagesImplementing
+  #
+  # * (String) `hook` - The hook to check.
+  #
+  # *Get the list of registered packages implementing a hook.*
   packagesImplementing: (hook) -> @_hookIndex?[hook] ? []

-  registerPackage: (path) ->
-    return if @isPackageRegistered path
+  # ## PackageManager#registerPackage
+  #
+  # * (String) `pkg` - The package.
+  #
+  # *Register a package.*
+  registerPackage: (pkg) ->
+    return if @isPackageRegistered pkg

     try
-      module_ = require path
+      module_ = require pkg

     # Suppress missing package errors.
     catch error
-      if error.toString() is "Error: Cannot find module '#{path}'"
+      if error.toString() is "Error: Cannot find module '#{pkg}'"
         return

       throw error

-    @_packageList.push path
+    @_packageList.push pkg

     module_.pkgmanRegister? new PackageManager.Registrar(
-      @_hookIndex, @_pathIndex, path
+      @_hookIndex, @_pkgIndex, pkg
     )

-  registerPackages: (paths) -> @registerPackage path for path in paths
+  # ## PackageManager#registerPackages
+  #
+  # * (Array of String) `pkgs` - The packages.
+  #
+  # *Register packages.*
+  registerPackages: (pkgs) -> @registerPackage pkg for pkg in pkgs

-  unregisterPackage: (path) ->
-    return unless @isPackageRegistered path
+  # ## PackageManager#unregisterPackage
+  #
+  # * (String) `pkg` - The package.
+  #
+  # *Unregister a package.*
+  unregisterPackage: (pkg) ->
+    return unless @isPackageRegistered pkg

-    for hook of @_pathIndex[path]
+    for hook of @_pkgIndex[pkg]

-      if -1 isnt index = @_hookIndex[hook].indexOf path
+      if -1 isnt index = @_hookIndex[hook].indexOf pkg
         @_hookIndex[hook].splice index, 1
         delete @_hookIndex[hook] if @_hookIndex[hook].length is 0

-    delete @_pathIndex[path]
+    delete @_pkgIndex[pkg]

-    index = @_packageList.indexOf path
+    index = @_packageList.indexOf pkg
     @_packageList.splice index, 1

     return

-  unregisterPackages: (paths) -> @unregisterPackage path for path in paths
+  # ## PackageManager#unregisterPackages
+  #
+  # * (Array of String) `pkgs` - The packages.
+  #
+  # *Unregister a list of packages.*
+  unregisterPackages: (pkgs) -> @unregisterPackage pkg for pkg in pkgs

 class PackageManager.Registrar

-  constructor: (@_hookIndex, @_pathIndex, @_path) ->
+  constructor: (@_hookIndex, @_pkgIndex, @_path) ->

   path: -> @_path

   recur: (paths) ->
-    for path in paths
-      subpath = "#{@_path}/#{path}"
+    for path_ in paths
+      subpath = "#{@_path}/#{path_}"
       submodule = require subpath
       submodule.pkgmanRegister? new PackageManager.Registrar(
-        @_hookIndex, @_pathIndex, subpath
+        @_hookIndex, @_pkgIndex, subpath
       )

   registerHook: (submodule, hook, impl) ->
@@ -85,17 +165,17 @@
     # against.
     if impl?

-      path = "#{@_path}/#{submodule}"
+      path_ = "#{@_path}/#{submodule}"

     # Otherwise, fix up the args.
     else

-      path = @_path
+      path_ = @_path
       impl = hook
       hook = submodule

-    (@_hookIndex[hook] ?= []).push path
-    (@_pathIndex[path] ?= {})[hook] = impl
+    (@_hookIndex[hook] ?= []).push path_
+    (@_pkgIndex[path_] ?= {})[hook] = impl

 module.exports = new PackageManager()
 module.exports.PackageManager = PackageManager


fix visibility field calculation

fix visibility field calculation

--- coffee/field.coffee
+++ coffee/field.coffee
@@ -53,22 +53,18 @@
 #  list of chains to append
 exports.farNeighborhood = farNeighborhood = (center, r, appendRewrite, n, m) ->
   cells = new NodeHashMap
+  cells.put center, true
   getNeighbors = mooreNeighborhood n, m, appendRewrite
+  getCellList = (cells) ->
+    cellList = []
+    cells.forItems (cell, state) ->
+      cellList.push cell
+    return cellList

+  for i in [0...r] by 1
+    for cell in getCellList cells
+      for nei in getNeighbors cell
+        cells.put nei, true

-  walk = ( cell, level ) ->
-    return if cells.get cell
-    cells.put cell, true
-    if level < r
-      for nei in getNeighbors cell
-        walk(nei, level+1)
-    return
+  getCellList cells

-  walk null, 0
-
-  cellList = []
-  cells.forItems (cell, state) ->
-    cellList.push cell
-
-  return cellList
-


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -23,6 +23,8 @@
         I.alpha = originalAlpha
       else
         I.alpha = I.flickerAlpha
+    else
+      I.alpha = origin

   ###*
   A convenient way to set the flicker instance variables on a sprite. You can modify the


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -48,7 +48,7 @@
   @param {Number} [frequency=3] The number of frames in between opacity changes
   @param {Number} [alpha=0.5] The alpha value to flicker to
   ###
-  flicker: ({duration: 30, frequency: 3, alpha: 0.5}) ->
+  flicker: ({duration, frequency, alpha}) ->
     Object.reverseMerge I,
       flickerAlpha: 0.5
       flickerDuration: 30


mocking up with reporters

mocking up with reporters

--- src/report.coffee
+++ src/report.coffee
@@ -7,47 +7,16 @@
   constructor: (@options) ->

   generate: (@map) ->
-    result = ""
-    xmlDoc = "<?xml version='1.0' encoding='UTF-8' ?><pmd-cpd>"
-    verbose = @options.verbose
-
-    for clone in @map.clones
-      do (clone) ->
-        result = result + "\n\t-
-#{clone.firstFile}:#{clone.firstFileStart}-#{clone.firstFileStart + clone.linesCount}\n\t
-#{clone.secondFile}:#{clone.secondFileStart}-#{clone.secondFileStart + clone.linesCount}\n\t"

-        result = "#{result}\n#{clone.getLines()}" if verbose
-        xmlDoc = "#{xmlDoc}
-<duplication lines='#{clone.linesCount}' tokens='#{clone.tokensCount}'>
-<file path='#{clone.firstFile}' line='#{clone.firstFileStart}'/>
-<file path='#{clone.secondFile}' line='#{clone.secondFileStart}'/>
-<codefragment>#{htmlspecialchars(clone.getLines())}</codefragment>
-</duplication>"
-
-    xmlDoc = xmlDoc + "</pmd-cpd>"
-    fs.writeFileSync(@options.output, xmlDoc) if @options.output
-
-    result = "Found #{@map.clones.length} exact clones with
- #{@map.numberOfDuplication} duplicated lines in
- #{@map.numberOfFiles} files\n #{result}"
+    result = "Found #{map.clones.length} exact clones with
+    #{map.numberOfDuplication} duplicated lines in
+    #{map.numberOfFiles} files\n #{result}"

     logger.info "#{result}\n\n
-#{@map.getPercentage()}% (#{@map.numberOfDuplication} lines)
- duplicated lines out of
- #{@map.numberOfLines} total lines of code.\n"
+    #{map.getPercentage()}% (#{map.numberOfDuplication} lines)
+    duplicated lines out of
+    #{map.numberOfLines} total lines of code.\n"

     return xmlDoc

-
-htmlspecialchars = (str) ->
-  if (typeof(str) == "string")
-    str = str.replace(/&/g, "&")
-    str = str.replace(/"/g, """)
-    str = str.replace(/'/g, "'")
-    str = str.replace(/</g, "<")
-    str = str.replace(/>/g, ">")
-  str
-
-
 exports.Report = Report


Fix ocd_id in request urls.

Fix ocd_id in request urls.

--- src/router.coffee
+++ src/router.coffee
@@ -37,7 +37,7 @@

         _fetchDivisions: (division_ids) ->
             @app_state.divisions
-                .setFilter 'ocd_id', division_ids
+                .setFilter 'ocd_id', division_ids.join(',')
                 .setFilter 'geometry', true
                 .fetch()


fixed missing the last resolve object at $router.reload()

--- src/router.coffee
+++ src/router.coffee
@@ -67,7 +67,7 @@
             if typeof(reload) is 'string'
                 for view, index in @views when view.rule.namespace is reload
                     @views.splice index + 1
-                    @resolves.splice index - 1
+                    @resolves.splice index
                     diffRuleIndex = index
                     break
                 @nextRule = nextRule ? @findRuleByUri $location.path()


remove step from game object default events

--- src/game_object.coffee
+++ src/game_object.coffee
@@ -129,7 +129,7 @@
     @methodOf GameObject#
     ###
     update: (elapsedTime) ->
-      #TODO Extract this I.active check out in
+      #TODO Extract this I.active check out into an engine gameO
       if I.active
         self.trigger 'update', elapsedTime


Run server in cluster mode

Run server in cluster mode

--- src/server.coffee
+++ src/server.coffee
@@ -13,6 +13,8 @@
 Cors          = require 'cors'
 StringifySafe = require 'json-stringify-safe'
 Morgan        = require 'morgan'
+Cluster       = require 'cluster'
+OS            = require 'os'

 ExpressJSONLD  = require 'express-jsonld'
 Schemo = require 'mongoose-jsonld'
@@ -164,7 +166,13 @@
 					log.info "Starting server on #{CONFIG.port}"
 					@app.listen CONFIG.port

-server = new InfolisWebservice()
-server.startServer()
+if Cluster.isMaster
+	nr_cpus = OS.cpus().length
+	log.info "Forking #{nr_cpus} workers"
+	for [0 ... nr_cpus]
+		Cluster.fork()
+else
+	server = new InfolisWebservice()
+	server.startServer()

 # ALT: test/test.coffee


another commit

another commit

--- src/hello-world.coffee
+++ src/hello-world.coffee
@@ -12,7 +12,7 @@
 #   hubot orly - "yarly"
 #
 # Author:
-#   tombell
+#   markalolo

 module.exports = (robot) ->
   robot.respond /bible me/i, (msg) ->


Note disabled in page popup.

Note disabled in page popup.

On pages where the browser does not run extensions like vimium,
replace the popup with a note explaining what and why.

Fixes #2850.

Currently, this is unsatisfactory because, on the Chrome new tab page,
Vimium does not become active until the user tabs or clicks into the
page (which is weird).

--- pages/options.coffee
+++ pages/options.coffee
@@ -277,9 +277,27 @@
     exclusions = null
     document.getElementById("optionsLink").setAttribute "href", chrome.runtime.getURL("pages/options.html")

-    # As the active URL, we choose the most recently registered URL from a frame in the tab, or the tab's own
-    # URL.
-    url = chrome.extension.getBackgroundPage().urlForTab[tab.id] || tab.url
+    # As the active URL, we choose the most recently registered URL from a frame in the tab.
+    url = chrome.extension.getBackgroundPage().urlForTab[tab.id]
+
+    unless url
+      # The browser has disabled Vimium on this page. Place a message explaining this into the popup.
+      document.body.innerHTML = """
+

+

+            Vimium is not running on this page.
+

+

+            Your browser does not run web extensions like Vimium on certain pages,
+            usually for security reasons.
+

+

+            Unless your browser's developers change their policy, then unfortunately it is not possible to make Vimium (or any other
+            web extension, for that matter) work on this page.
+

+

+      """
+      return

     updateState = ->
       rule = bgExclusions.getRule url, exclusions.readValueFromElement()


minor bug fix with filter passing

minor bug fix with filter passing

--- @client/customizations_helpers.coffee
+++ @client/customizations_helpers.coffee
@@ -5,8 +5,8 @@

   passes = true
   for tag in tags
-    passes &&= user.tags[tag] && \
-     !("#{user.tags[tag]}".toLowerCase() in ['no', 'false'])
+    passes &&= user?.tags?[tag] && \
+     !("#{user?.tags?[tag]}".toLowerCase() in ['no', 'false'])
   passes

 window.passes_tag_filter = (user, tag, regex) ->


ComputeProvider: updateGroupInstanceUsage: helper similar to stack updater

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -360,6 +360,26 @@
       callback if change is 'increment' then err else null

+  @updateGroupInstanceUsage = (group, change, amount, callback) ->
+
+    plan = group.getAt 'config.plan'
+    return callback null  unless plan
+    return callback null  if amount is 0
+
+    plan = teamutils.getPlanData plan
+
+    JCounter = require '../counter'
+    JCounter[change]
+      namespace : group.getAt 'slug'
+      amount    : amount
+      type      : 'member-instances'
+      max       : plan.maxInstance
+      min       : 0
+    , (err) ->
+      # no worries about `decrement` errors
+      # since 0 is already defined as min ~ GG
+      callback if change is 'increment' then err else null
+

   @createGroupStack = (client, options, callback) ->


ComputeProvider: create: creates a machine for given provider

This method first asks to provider to create machine with given information (to
make sure provider is ready to create that machine ~ like checking credits etc.)
when it gets the necessary information to create a JMachine document as "meta",
it uses ComputeProvider::createMachine method to create it as a mongo document.
Then when its done with the document it also calls postCreate method for given
provider which helps provider API developers to do some other operations after
machine createad as a document such as creating domains for Koding's provider.

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -116,6 +116,31 @@

     success: revive (client, options, callback)->

+      { provider, stack, label } = options
+
+      provider.create client, options, (err, machineData)=>
+
+        return callback err  if err
+
+        { connection: { delegate:account }, context: { group } } = client
+        { meta, postCreateOptions } = machineData
+
+        @createMachine {
+          vendor : provider.slug
+          label, stack, meta, group, account
+        }, (err, machine)->
+
+          # TODO if any error occurs here which means user paid for
+          # not created vm ~ GG
+          return callback err  if err
+
+          provider.postCreate {
+            postCreateOptions, machine, meta, stack
+          }, (err)=>
+            # ----
+            account.sendNotification "MachineCreated"  unless err
+            callback err
+

   @update = secure revive (client, options, callback)->

@@ -146,8 +171,6 @@
     { vendor, label, stack, meta, group, account } = options

     JGroup = require '../group'
-    JStack = require '../stack'
-
     JGroup.one { slug: group }, (err, groupObj)=>

       return callback err  if err


add tests 'file to directory' and 'directory to directory'

--- src/smc-hub/test/api/copy_between_projects.coffee
+++ src/smc-hub/test/api/copy_between_projects.coffee
@@ -13,11 +13,10 @@

     project_id  = undefined
     project_id2 = undefined
-
+
     path1 = 'doc1.txt'
     path2 = 'B2/doc2.txt'
     path3 = 'FOO/abc.txt'
-    path2dir = path2.split('/')[0]

     content  = 'TEST CONTENT'
     src_dir  = 'TEST_DIR'
@@ -71,7 +70,7 @@
                 expect(resp?.event).toBe('success')
                 done(err)

-    it "reads file from target project", (done) ->
+    it "reads target file 1", (done) ->
         @timeout(15000)
         api.call
             event : 'read_text_file_from_project'
@@ -97,7 +96,7 @@
                 expect(resp?.event).toBe('file_written_to_project')
                 done(err)

-    it "copies second file", (done) ->
+    it "copies src with directory and file", (done) ->
         @timeout(15000)
         api.call
             event : 'copy_path_between_projects'
@@ -110,7 +109,7 @@
                 expect(resp?.event).toBe('success')
                 done(err)

-    it "reads second file", (done) ->
+    it "reads target file 2", (done) ->
         @timeout(15000)
         api.call
             event : 'read_text_file_from_project'
@@ -118,6 +117,62 @@
                 project_id: project_id2
                 path      : path2
             cb    : (err, resp) ->
+                expect(resp?.event).toBe('text_file_read_from_project')
+                expect(resp?.content).toBe(content)
+                done(err)
+
+    it "copies file to directory", (done) ->
+        # src:'B2/doc2.txt' tgt:'DIR3/ result:'DIR3/doc2.txt'
+        @timeout(15000)
+        api.call
+            event : 'copy_path_between_projects'
+            body  :
+                src_project_id   : project_id
+                src_path         : path2
+                target_project_id: project_id2
+                target_path      : 'DIR3/'
+            cb    : (err, resp) ->
+                expect(err).toEqual(null)
+                expect(resp?.event).toBe('success')
+                done(err)
+
+    it "reads target file 3", (done) ->
+        @timeout(15000)
+        api.call
+            event : 'read_text_file_from_project'
+            body  :
+                project_id: project_id2
+                path      : 'DIR3/doc2.txt'
+            cb    : (err, resp) ->
+                expect(err).toEqual(null)
+                expect(resp?.event).toBe('text_file_read_from_project')
+                expect(resp?.content).toBe(content)
+                done(err)
+
+    it "copies directory to directory", (done) ->
+        # src:'B2/' tgt:'DIR4/ result:'DIR4/doc2.txt'
+        @timeout(15000)
+        api.call
+            event : 'copy_path_between_projects'
+            body  :
+                src_project_id   : project_id
+                src_path         : 'B2/'
+                target_project_id: project_id2
+                target_path      : 'DIR4/'
+            cb    : (err, resp) ->
+                expect(err).toEqual(null)
+                expect(resp?.event).toBe('success')
+                done(err)
+
+    it "reads target file 4", (done) ->
+        @timeout(15000)
+        api.call
+            event : 'read_text_file_from_project'
+            body  :
+                project_id: project_id2
+                path      : 'DIR4/doc2.txt'
+            cb    : (err, resp) ->
+                expect(err).toEqual(null)
                 expect(resp?.event).toBe('text_file_read_from_project')
                 expect(resp?.content).toBe(content)
                 done(err)
@@ -132,6 +187,7 @@
                 done(err)

     it "copies a public file to different target dir", (done) ->
+        # src:'B2/doc2.txt'(public) tgt:'FOO/abc.txt'
         api.call
             event : 'copy_public_path_between_projects'
             body  :
@@ -154,4 +210,3 @@
                 expect(resp?.event).toBe('text_file_read_from_project')
                 expect(resp?.content).toBe(content)
                 done(err)
-


Working analytics tracking and bootstrapping.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -8,6 +8,8 @@
 ##
 ## BOOTSTRAPPING FUNCTIONS
 ##
+window._gaq = window._gaq || []
+
 loadScript = (url, callback) ->
   script = document.createElement("script")
   script.async = true
@@ -17,30 +19,39 @@
     script.onreadystatechange = ->
       if script.readyState == "loaded" || script.readyState == "complete"
         script.onreadystatechange = null
-        callback()
+        callback?()
   else # Others
     script.onload = ->
-      callback();
+      callback?();

   script.src = url
   document.getElementsByTagName("head")[0].appendChild(script)

-withGoogleAnalytics = (callback) ->
-  if ! _gaq
-    analyticsSrc = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
-    loadScript(analyticsSrc, callback)
-  else
-    callback()
+loadGAIfNeeded = ->
+  # Determine if GAQ was already loaded on the page by checking to see
+  # If reduce is a valid method on it. If the real _gaq has been loaded,
+  # it won't be an array, and this will return false.
+  if _gaq.reduce?
+    secure = 'https:' == document.location.protocol
+    if secure
+      domainRoot = 'https://ssl'
+    else
+      domainRoot = 'http://www'
+
+    analyticsSrc = domainRoot + '.google-analytics.com/ga.js'
+
+    # After the script is loaded, we need to make sure to reassign our local
+    # _gaq to the _gaq loaded by Google Analytics.
+    loadScript analyticsSrc

 withJQueryLoaded = (callback) ->
-  if ! jQuery
+  if ! jQuery?
     loadScript("http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js", callback)
   else if ! /^1.[678]/.test(jQuery.fn.jquery)
     console?.error("AnchorTab is disabled because you are using an unsupported version of jQuery.")

     # Track this on GA so we can see if we need to adjust this value over time.
-    withGoogleAnalytics ->
-      _gaq.push ["anchortab._trackEvent", "Bootstrap", "Incompatible jQuery Failure", jQuery.fn.jquery]
+    _gaq.push ["at._trackEvent", "Bootstrap", "Incompatible jQuery Failure", jQuery.fn.jquery]

   else
     callback()
@@ -77,19 +88,20 @@
   # then we'll have to refactor some of this code, I suppose.
   $ = jQuery # In case we're in compability mode.

-  $.ajax
-    url: 'https://anchortab.com/api/v1/embed/abc123'
+  #$.ajax
+    #url: 'https://anchortab.com/api/v1/embed/abc123'

 # Do the neccicary mojo to make sure we load up our goods after the
 # window is loaded. This includes making sure all our dependancies
 # are in place.
 oldOnload = window.onload
 window.onload = ->
-  withGoogleAnalytics ->
-    _gaq.push ["anchortab._setAccount", "UA-35269224-2"]
-    _gaq.push ["anchortab._setDomainName", "embed.anchortab.com"]
-    _gaq.push ["anchortab._trackPageview"]
+  _gaq.push ['at._setAccount', 'UA-35269224-2']
+  _gaq.push ['at._setDomainName', 'anchortab.com']
+  _gaq.push ['at._setAllowLinker', true]
+  _gaq.push ["at._trackPageview"]

+  loadGAIfNeeded()
   withJQueryLoaded(loadAnchorTab)

   oldOnload?()


setTimeout default is 0 ¯\_(ツ)_/¯

setTimeout default is 0 ¯\_(ツ)_/¯

--- resources/assets/coffee/_classes/store-supporter-tag.coffee
+++ resources/assets/coffee/_classes/store-supporter-tag.coffee
@@ -161,7 +161,7 @@
       setTimeout(() =>
         @updateSearchResult(true)
         @setUserInteraction(false)
-      , 0)
+      )

     @debouncedGetUser(event.currentTarget.value)