Added collaborator url processing [#130836331]

--- src/code/providers/lara-provider.coffee
+++ src/code/providers/lara-provider.coffee
@@ -36,6 +36,7 @@

     @laraParams = if @urlParams.launchFromLara then @decodeParams(@urlParams.launchFromLara) else null
     @openSavedParams = null
+    @collaboratorUrls = []

     @docStoreUrl = new DocumentStoreUrl @urlParams.documentServer

@@ -163,32 +164,154 @@
       openSavedParams = @decodeParams openSavedParams

     @openSavedParams = openSavedParams
-
-    #
-    # if we have a document ID we can just load the document
-    #
-    if openSavedParams and openSavedParams.recordid
-      metadata.providerData = openSavedParams
+    @collaboratorUrls = if openSavedParams?.collaboratorUrls?.length > 0 then openSavedParams.collaboratorUrls else []
+
+    loadProviderFile = (providerData, callback) =>
+      metadata.providerData = providerData
       @load metadata, (err, content) =>
         @client.removeQueryParams @removableQueryParams
         callback err, content, metadata
-      return
+
+    #
+    # if we have a document ID we can just load the document
+    #
+    return loadProviderFile openSavedParams if openSavedParams?.recordid

     #
     # Process the initial run state response
     #
     processInitialRunState = (runStateUrl, sourceID, readOnlyKey, runState) =>
+      existingRunState = @extractRawDataFromRunState runState
+      docStore = existingRunState.docStore
+
+      haveCollaborators = @collaboratorUrls.length > 0
+
+      updateInteractiveRunStates = (urls, newDocStore, callback) ->
+
+        newRunState = _.cloneDeep existingRunState
+        newRunState.docStore = newDocStore
+
+        rawData = JSON.stringify(newRunState)
+        learnerUrl = if newRunState.learner_url? and typeof newRunState.learner_url is "string" then newRunState.learner_url else null
+        learnerParam = if learnerUrl then "&learner_url=#{encodeURIComponent(learnerUrl)}" else ""
+
+        updateRunState = (url, done) ->
+          $.ajax({
+            type: 'PUT'
+            url: "#{url}?raw_data=#{encodeURIComponent(rawData)}#{learnerParam}"
+            dataType: 'json'
+            xhrFields:
+              withCredentials: true
+          })
+          .done (data, status, jqXHR) ->
+            if data?.success is false
+              done("Could not open the specified document because an error occurred [updateState] (#{data.message})")
+            else
+              done(null)
+          .fail (jqXHR, status, error) ->
+            done("Could not open the specified document because an error occurred [updateState]")
+
+        urlQueue = urls.slice()
+        processQueue = ->
+          if urlQueue.length is 0
+            callback null
+          else
+            url = urlQueue.shift()
+            updateRunState url, (err) ->
+              if err
+                callback err
+              else
+                processQueue()
+        processQueue()
+
+      processCreateResponse = (createResponse) =>
+        docStore =
+          recordid: createResponse.id
+          accessKeys:
+            readOnly: createResponse.readAccessKey
+            readWrite: createResponse.readWriteAccessKey
+
+        codapUrl = if window.location.origin \
+                    then "#{window.location.origin}#{window.location.pathname}" \
+                    else "#{window.location.protocol}//#{window.location.host}#{window.location.pathname}"
+        reportUrlLaraParams =
+          recordid: createResponse.id
+          accessKeys:
+            readOnly: createResponse.readAccessKey
+        encodedLaraParams = @encodeParams reportUrlLaraParams
+        existingRunState.lara_options ?= {}
+        existingRunState.lara_options.reporting_url = "#{codapUrl}?launchFromLara=#{encodedLaraParams}"
+
       # Check if we have a document associated with this run state already (2a) or not (2b)
-      rawData = @extractRawDataFromRunState runState
-      docStoreParams = rawData.docStore
-      if docStoreParams? and docStoreParams.recordid? and
-          (docStoreParams.accessKeys?.readOnly? or docStoreParams.accessKeys?.readWrite?)
-        # (2a) load the document associated with this run state
-        metadata.providerData = _.cloneDeep docStoreParams
-        @load metadata, (err, content) =>
-          @client.removeQueryParams @removableQueryParams
-          callback err, content, metadata
-        return
+      if docStore?.recordid? and (docStore.accessKeys?.readOnly? or docStore.accessKeys?.readWrite?)
+
+        cloneDoc = (callback) =>
+          createParams =
+            source: docStore.recordid
+            accessKey: "RO::#{docStore.accessKeys.readOnly}"
+          {method, url} = @docStoreUrl.v2CreateDocument(createParams)
+          $.ajax({
+            type: method
+            url: url,
+            dataType: 'json'
+          })
+          .done (createResponse, status, jqXHR) ->
+            processCreateResponse createResponse
+            callback null
+          .fail (jqXHR, status, error) ->
+            callback "Could not open the specified document because an error occurred [createCopy]"
+
+        setFollowers = (err, callback) =>
+          if err
+            callback err
+          else
+            collaboratorParams = _.cloneDeep docStore
+            collaboratorParams.collaborator = 'follower'
+            updateInteractiveRunStates @collaboratorUrls, collaboratorParams, callback
+
+        becomeLeader = (err, callback) ->
+          if err
+            callback err
+          else
+            docStore.collaborator = 'leader'
+            updateInteractiveRunStates [runStateUrl], docStore, callback
+
+        removeCollaborator = (err, callback) ->
+          if err
+            callback err
+          else
+            delete docStore.collaborator
+            updateInteractiveRunStates [runStateUrl], docStore, callback
+
+        finished = (err) ->
+          if err
+            callback err
+          else
+            loadProviderFile _.cloneDeep(docStore), callback
+
+        # is this an existing collaborated document?
+        if docStore.collaborator
+          if docStore.collaborator is 'leader'
+            if haveCollaborators
+              # the current user is still the leader so update the collaborator states to follow the leader (in case there are new collaborators) and load the existing document
+              return setFollowers null, finished
+            else
+              # the current user has gone from leader to solo mode so update run state to remove collaborator and load the existing document
+              return removeCollaborator null, finished
+          else
+            if haveCollaborators
+              # the current user has switched from follower to leader so clone the existing leader document, become the new leader and update the followers and load the new document
+              return cloneDoc (err) -> becomeLeader(err, ((err) -> setFollowers(err, finished)))
+            else
+              # the current user has switched from follower to solo mode so clone the existing leader document, update the run state to remove the collaborator and load the new document
+              return cloneDoc (err) -> removeCollaborator(err, finished)
+        else
+          if haveCollaborators
+            # the current user has switched from solo mode to leader so update both the user's and the collaborators run states using the existing document
+            return becomeLeader null, (err) -> setFollowers(err, finished)
+          else
+            # the current user has opened an existing solo mode file so just open it
+            return finished()

       # we need a sourceID to be able to create a copy
       if not sourceID
@@ -206,65 +329,31 @@
         url: url,
         dataType: 'json'
       })
-      .done (data, status, jqXHR) ->
-        updateInteractiveRunStateAndLoad runStateUrl, runState, data
+      .done (createResponse, status, jqXHR) =>
+
+        processCreateResponse createResponse
+        if haveCollaborators
+          docStore.collaborator = 'leader'
+
+        providerData = _.merge({}, docStore, { url: runStateUrl })
+        updateFinished = -> loadProviderFile providerData, callback
+
+        # update the owners interactive run state
+        updateInteractiveRunStates [runStateUrl], docStore, (err) =>
+          if err
+            callback err
+          else if haveCollaborators
+            docStore.collaborator = 'follower'
+            updateInteractiveRunStates @collaboratorUrls, docStore, (err) ->
+              if err
+                callback err
+              else
+                updateFinished()
+          else
+            updateFinished()
+
       .fail (jqXHR, status, error) ->
         callback "Could not open the specified document because an error occurred [createCopy]"
-
-    #
-    # Utility function used when opening an unshared copy of a shared document.
-    # Updates the run state and then loads the newly created copy.
-    #
-    updateInteractiveRunStateAndLoad = (runStateUrl, runState, createResponse) =>
-      if runState? and createResponse.id? and
-          (createResponse.readAccessKey? or createResponse.readWriteAccessKey?)
-
-        # metadata for loading the file
-        docStoreMetadata =
-          recordid: createResponse.id
-          accessKeys:
-            readOnly: createResponse.readAccessKey
-            readWrite: createResponse.readWriteAccessKey
-        metadata.providerData = _.merge({}, docStoreMetadata, { url: runStateUrl })
-
-        # build the reporting_url
-        codapUrl = if window.location.origin \
-                    then "#{window.location.origin}#{window.location.pathname}" \
-                    else "#{window.location.protocol}//#{window.location.host}#{window.location.pathname}"
-        reportUrlLaraParams =
-          recordid: createResponse.id
-          accessKeys:
-            readOnly: createResponse.readAccessKey
-        encodedLaraParams = @encodeParams reportUrlLaraParams
-
-        # (3) update the interactive run state
-        rawData = @extractRawDataFromRunState runState
-        rawData.docStore = docStoreMetadata
-        rawData.lara_options ?= {}
-        rawData.lara_options.reporting_url = "#{codapUrl}?launchFromLara=#{encodedLaraParams}"
-        rawData = JSON.stringify(rawData)
-        learnerUrl = if runState.learner_url? and typeof runState.learner_url is "string" \
-                      then runState.learner_url \
-                      else null
-
-        putUrl = "#{openSavedParams.url}" +
-                    "?raw_data=#{encodeURIComponent(rawData)}" +
-                    if learnerUrl then "&learner_url=#{encodeURIComponent(learnerUrl)}" else ""
-        $.ajax({
-          type: 'PUT'
-          url: putUrl
-          dataType: 'json'
-          xhrFields:
-            withCredentials: true
-        })
-        # (4) success - load the document
-        .done (data, status, jqXHR) =>
-          @load metadata, (err, content) =>
-            @client.removeQueryParams @removableQueryParams
-            callback err, content, metadata
-        # failure - report back to user
-        .fail (jqXHR, status, error) ->
-          callback "Could not open the specified document because an error occurred [updateState]"

     #
     # We have a run state URL and a source document. We must copy the source
@@ -281,7 +370,6 @@
       })
       .done (data, status, jqXHR) ->
         processInitialRunState openSavedParams.url, openSavedParams.source, openSavedParams.readOnlyKey, data
-
       .fail (jqXHR, status, error) ->
         callback "Could not open the specified document because an error occurred [getState]"


make winners homepage fast

make winners homepage fast

--- controllers/index.coffee
+++ controllers/index.coffee
@@ -46,25 +46,30 @@

 # app.get '/', (req, res, next) ->
 #   res.render2 'index/index'
-app.get '/', [loadCanRegister, loadCurrentPersonWithTeam, loadRecentDeploys, loadInterestingTeams, loadFeaturedJudges], (req, res, next) ->
+if app.enabled('winners')
+  app.get '/', (req, res, next) ->
+    res.render2 'index/winners'

-  if app.enabled('coding')
-    template = 'index/coding'
-  else if app.enabled('post-coding')
-    template = 'index/postcoding'
-  else if app.enabled('voting') or app.enabled('post-voting')
-    template = 'index/voting'
-  else if app.enabled('winners')
-    template = 'index/winners'
-  else
-    template = 'index/index'
+else
+  app.get '/', [loadCanRegister, loadCurrentPersonWithTeam, loadRecentDeploys, loadInterestingTeams, loadFeaturedJudges], (req, res, next) ->

-  res.render2 template,
-    team: req.team
-    stats: app.stats
-    recentDeploys: req.recentDeploys
-    interestingTeams: req.interestingTeams
-    featuredJudges: req.featuredJudges
+    if app.enabled('coding')
+      template = 'index/coding'
+    else if app.enabled('post-coding')
+      template = 'index/postcoding'
+    else if app.enabled('voting') or app.enabled('post-voting')
+      template = 'index/voting'
+    else if app.enabled('winners')
+      template = 'index/winners'
+    else
+      template = 'index/index'
+
+    res.render2 template,
+      team: req.team
+      stats: app.stats
+      recentDeploys: req.recentDeploys
+      interestingTeams: req.interestingTeams
+      featuredJudges: req.featuredJudges

 app.get '/blog', (req, res) -> res.redirect("http://blog.nodeknockout.com/")


ah, found it

ah, found it

--- lib/phabricator.coffee
+++ lib/phabricator.coffee
@@ -127,8 +127,7 @@
       data = @data
       query = { 'names[0]': project }
       @phabGet msg, query, 'project.query', (json_body) ->
-        if (typeof json_body.result.data is 'array') or
-            Object.keys(json_body.result.data).length > 0
+        if json_body.result.data.length > 0 or Object.keys(json_body.result.data).length > 0
           phid = Object.keys(json_body.result.data)[0]
           data.projects[project] = { phid: phid }
           projectData = {


Eliminate erroneous call to livereload

Eliminate erroneous call to livereload

--- src/web/gulpfile.coffee
+++ src/web/gulpfile.coffee
@@ -5,7 +5,6 @@
 clean = require 'gulp-clean'
 coffee = require 'gulp-coffee'
 concat = require 'gulp-concat'
-livereload = require 'gulp-livereload'
 cssmin = require 'gulp-minify-css'
 html2js = require 'gulp-ng-html2js'
 htmlmin = require 'gulp-htmlmin'


Apply class native-key-bindings

Apply class native-key-bindings

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -13,7 +13,7 @@
     @div class: 'outer-scriptView', =>
       @subview 'headerView', new HeaderView()
       # Display layout and outlets
-      @div class: 'tool-panel panel panel-bottom padding scriptView', outlet: 'script', tabindex: -1, =>
+      @div class: 'tool-panel panel panel-bottom padding scriptView native-key-bindings', outlet: 'script', tabindex: -1, =>
         @div class: 'panel-body padded output', outlet: 'output'

   initialize: (serializeState) ->


Use debug for reporter instead of console.log.

--- lib/understream.coffee
+++ lib/understream.coffee
@@ -1,7 +1,7 @@
 {Readable, Writable, PassThrough, Transform} = require 'stream'
 fs     = require('fs')
 _      = require 'underscore'
-debug  = require('debug') 'us'
+debug  = require('debug') 'us:'
 domain = require 'domain'
 {EventEmitter} = require 'events'
 {is_readable} = require './helpers'
@@ -43,7 +43,7 @@
 ]).compact().join(' ')

 add_reporter = (streams) ->
-  report = -> console.log _(streams).map(to_report_string).join(' | ')
+  report = -> debug _(streams).map(to_report_string).join(' | ')
   interval = setInterval report, 5000
   _(streams).each (stream) -> add_listener_unsafe stream, 'error', -> clearInterval interval
   _(streams).last().on 'finish', -> clearInterval interval


Remove sillyRefresh route

Remove sillyRefresh route

--- storage/lib/rest.coffee
+++ storage/lib/rest.coffee
@@ -3,8 +3,6 @@
 express = require 'express'
 JSONStream = require 'JSONStream'
 Promise = require 'bluebird'
-
-async = require './rest-async'

 module.exports = (services) ->
     {access, geocache, geolog} = services
@@ -36,11 +34,6 @@
         res.status 200
         res.send 'Okay'

-    app.post '/sillyRefresh', async (req, res, next) ->
-        yield geolog.refresh()
-        res.status 200
-        res.send 'Refreshed'
-
     require('./geocache/rest') app, geocache
     require('./poi/rest') app, geocache
     require('./feed/rest') app, geocache


removed duplicate function.

removed duplicate function.

--- web/coffee/login.coffee
+++ web/coffee/login.coffee
@@ -1,9 +1,3 @@
-window.checkLoggedIn = ->
- apiCall "GET", "/api/user/isloggedin"
-  .done (data) ->
-    if data['status'] == 1
-      document.location.href = "/problems"
-
 login = (e) ->
   e.preventDefault()
   apiCall "POST", "/api/user/login", $("#login-form").serialize()


Minor change.

Minor change.

--- tasks/shortStats.coffee
+++ tasks/shortStats.coffee
@@ -34,7 +34,7 @@
           if b.elo is a.elo
             console.log 'same elo', b.playerName, b.elo, a.playerName, a.elo
             console.log b.winRate, a.winRate, b.winRate - a.winRate
-            return b.winRate - a.winRate
+            return a.winRate - b.winRate
           return b.elo - a.elo
         )
         _.each(eloArray, (player, ind)->


exit for citest mode

exit for citest mode

--- tasks/test/karma.coffee
+++ tasks/test/karma.coffee
@@ -5,7 +5,10 @@

 module.exports = (gulp, plugins) -> ->
 	karma.server.start options, (code) ->
-		return if citest
+		if citest
+			process.exit code if code
+			return
+
 		if code > 0
 			notify 'Karma tests failed', false
 		else


bits: Add hexdump()

bits: Add hexdump()

--- bits.coffee
+++ bits.coffee
@@ -41,6 +41,12 @@
     # => 10100011 11111111
 ###

+zeropad = (width, num) ->
+  num += ''
+  while num.length < width
+    num = '0' + num
+  num
+
 class Bits
   constructor: (buffer) ->
     @buf = null
@@ -400,4 +406,36 @@
     if col isnt 0
       console.log()

+  @hexdump: (buffer) ->
+    col = 0
+    strline = ''
+    dump = ''
+
+    endline = ->
+      pad = '  '
+      while col < 16
+        pad += '  '
+        if col % 2 is 0
+          pad += ' '
+        col++
+      dump += pad + strline + '\n'
+      strline = ''
+
+    for byte in buffer
+      if 0x20 <= byte <= 0x7e  # printable char
+        strline += String.fromCharCode byte
+      else
+        strline += ' '
+      dump += zeropad(2, byte.toString(16))
+      col++
+      if col is 16
+        endline()
+        col = 0
+      else if col % 2 is 0
+        dump += ' '
+    if col isnt 0
+      endline()
+
+    process.stdout.write dump
+
 module.exports = Bits


trying to fix error that phil is running into

--- test-integration/helpers/ui/task.coffee
+++ test-integration/helpers/ui/task.coffee
@@ -48,10 +48,11 @@
   canContinue: => @el.enabledContinueButton().isPresent()

   getCurrentStep: =>
-    if @isReading()
-      @el.readingNextArrow().get().getAttribute('data-step')
-    else
-      @el.currentBreadcrumbStep().get().getAttribute('data-reactid')
+    @isReading().then (isPresent) =>
+      if (isPresent)
+        @el.readingNextArrow().get().getAttribute('data-step')
+      else
+        @el.currentBreadcrumbStep().get().getAttribute('data-reactid')

   continue: =>
@@ -69,10 +70,11 @@
         selenium.until.elementIsEnabled(@el.continueButton().get())

   continueTask: =>
-    if @isReading()
-      @el.readingNextArrow().waitClick()
-    else
-      @el.enabledContinueButton().waitClick()
+    @isReading().then (isPresent) =>
+      if isPresent
+        @el.readingNextArrow().waitClick()
+      else
+        @el.enabledContinueButton().waitClick()

   goToHelpLink: =>
     @el.helpLink().waitClick()


Add "centre" as a recognised tbl keyword

GNU tbl supports this as an alternative of the "center" keyword.

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -2361,7 +2361,7 @@
 				captures:
 					1: patterns: [
 						{match: ",", name: "punctuation.separator.comma.tbl.roff"}
-						{match: "\\b(center|expand|box|allbox|doublebox)\\b", name: "constant.language.$1.tbl.roff"}
+						{match: "\\b(center|centre|expand|box|allbox|doublebox)\\b", name: "constant.language.$1.tbl.roff"}
 						{match: "\\b((tab|linesize|delim)(\\()([^\\)\\s]*)(\\)))"
 						captures:
 							1: name: "constant.language.$2.tbl.roff"


adding test modules

adding test modules

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -1,4 +1,6 @@
-test "Core#extend", ->
+module "Core"
+
+test "#extend", ->
   o = Core()

   o.extend
@@ -26,7 +28,7 @@
   equal calledBefore, true
   equal calledAfter, true

-test "Core#attrAccessor", ->
+test "#attrAccessor", ->
   o = Core
     test: "my_val"

@@ -36,7 +38,7 @@
   equal o.test("new_val"), o
   equal o.test(), "new_val"

-test "Core#attrReader", ->
+test "#attrReader", ->
   o = Core
     test: "my_val"

@@ -46,7 +48,7 @@
   equal o.test("new_val"), "my_val"
   equal o.test(), "my_val"

-test "Core#include", ->
+test "#include", ->
   o = Core
     test: "my_val"

@@ -60,3 +62,5 @@
   equal o.test(), "my_val"
   equal o.test2, "cool"

+module undefined
+


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -24,12 +24,12 @@
     frequency: 5
     alpha: 0.2

-  5.times (n) ->
+  5.times ->
     player.update()
     equals player.I.alpha, 0.2

-  player.update()
-  equals player.I.alpha, 0.8
+  5.times ->
+    player.up

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -27,7 +27,7 @@

   enemy.include Flickerable

-  en
+  enemy.flicker()

 test 'it should change the alpha value when the flicker duration has elapsed', ->
   player = GameObject


when plans for course have not yet been loaded, do not need to add to plan publish for quick publish status

--- tutor/src/flux/teacher-task-plan.coffee
+++ tutor/src/flux/teacher-task-plan.coffee
@@ -45,6 +45,7 @@
       @emitChange()

   addPublishingPlan: (plan, courseId) ->
+    return unless @_local[courseId]?
     addPlan(plan, @_local[courseId])
     @emitChange()


add initial history object location matching test

--- test/router.test.coffee
+++ test/router.test.coffee
@@ -336,6 +336,19 @@
       expect(history.replace.callCount).to.be(0)
       expect(history.replaceState.callCount).to.be(0)

+    it 'should match the initial location of the provided history object', (callback) ->
+      history = createMemoryHistory()
+      history.push('/projects')
+
+      router = new Router({ history })
+      router.addRoutes(routes)
+      router.addTarget(targetA, 'project-manager')
+      router.listen (component) ->
+        expect(router.getCurrentComponent()).to.be.an('object')
+        expect(component).to.be.an('object')
+        expect(_.isEqual(router.getCurrentComponent(), component)).to.be(true)
+        callback()
+
     it 'should not allow a default route when using a custom history object', ->
       history = createMemoryHistory()
       expect(->


add renderToStaticMarkup test case

add renderToStaticMarkup test case

--- test/router.test.coffee
+++ test/router.test.coffee
@@ -200,9 +200,24 @@

     router.listen ->
       component = router.getCurrentComponent()
+      shallowRenderer = ReactTestUtils.createRenderer()
       shallowRenderer.render(component)
       output = shallowRenderer.getRenderOutput()
       expect(output.props.children).to.be(text)

     text = 'componentA...'
     router.getRouterProps().transitionTo('project-manager/list')
+
+  it 'should render component to string', ->
+    router = new Router(history: 'memory')
+    router.addRoutes(routes)
+    router.addTarget(targetA, 'project-manager')
+    router.addTarget(targetB)
+
+    result = '
componentB...
'
+
+    router.listen (component) ->
+      expect(React.renderToStaticMarkup(component)).to.be(result)
+
+    result = '
componentA...
'
+    router.getRouterProps().transitionTo('project-manager/list')


Updated tests

Updated tests

--- test/twitch_test.coffee
+++ test/twitch_test.coffee
@@ -8,12 +8,14 @@
     beforeEach ->
         @robot =
             respond: sinon.spy()
-            hear: sinon.spy()

         require("../src/twitch")(@robot)

     it "registers a respond listener", ->
-        expect(@robot.respond).to.have.been.calledWith(/hello/)
-
-    it "registers a hear listener", ->
-        expect(@robot.hear).to.have.been.calledWith(/orly/)+        expect(@robot.respond).to.have.been.calledWith(/ttv follows/i)
+        expect(@robot.respond).to.have.been.calledWith(/ttv link (.+)/i)
+        expect(@robot.respond).to.have.been.calledWith(/ttv featured/i)
+        expect(@robot.respond).to.have.been.calledWith(/ttv game (.+)/i)
+        expect(@robot.respond).to.have.been.calledWith(/ttv search (.+)/i)
+        expect(@robot.respond).to.have.been.calledWith(/ttv stream (.+)/i)
+        expect(@robot.respond).to.have.been.calledWith(/ttv top/i)


add prefix attribute for OGP

add prefix attribute for OGP

--- prerender.coffee
+++ prerender.coffee
@@ -1,12 +1,18 @@
-moment = require 'moment'
-React = require 'react'
-fse = require 'fs-extra'
 ServerReactRootIndex = require 'react/lib/ServerReactRootIndex'
-{AppView} = require './src/scripts/views/app-view'

 # hack for generating same react id
 ServerReactRootIndex.createReactRootIndex = ->
   0
+
+HTMLDOMPropertyConfig = require 'react/lib/HTMLDOMPropertyConfig'
+
+# hack for html.prefix attribute
+HTMLDOMPropertyConfig.Properties.prefix = null
+
+moment = require 'moment'
+React = require 'react'
+fse = require 'fs-extra'
+{AppView} = require './src/scripts/views/app-view'

 buildBody = (props) ->
   React.DOM.body(
@@ -64,7 +70,7 @@

 buildHtml = (props) ->
   React.DOM.html(
-    null
+    { prefix: 'og: http://ogp.me/ns#' }
     buildHead(props)
     buildBody(props)
   )


Forgot to use xsrf service on decision endpoint

--- src/server/routes/post/decision.coffee
+++ src/server/routes/post/decision.coffee
@@ -1,20 +1,14 @@
 ensure = require "connect-ensure-login"

 auth = require "../../common/AuthService.js"
-messages = require "../../common/messages.js"
+xsrf = require "../../common/XsrfService.js"

 routes =
 	verb: "post"
 	path: "/decision"
 	fn: [
 		ensure.ensureLoggedIn "/api/login"
-		(req, res, next) ->
-			cookie = req.cookies.xsrf
-			if cookie then res.clearCookie "xsrf"
-			post = req.body.xsrf
-			if !cookie or !post or (cookie isnt post)
-				return next { error: messages.invalid.xsrf }
-			else next()
+		xsrf.check
 		auth.decision()
 	]


guard for group before adminsInclude

guard for group before adminsInclude

--- vue/src/shared/models/poll_model.coffee
+++ vue/src/shared/models/poll_model.coffee
@@ -57,10 +57,10 @@

   adminsInclude: (user) ->
     stance = @stanceFor(user)
-    @authorIs(user) || (stance && stance.admin) || @group().adminsInclude(user)
+    @authorIs(user) || (stance && stance.admin) || (@group && @group().adminsInclude(user))

   membersInclude: (user) ->
-    @stanceFor(user) || @group().membersInclude(user)
+    @stanceFor(user) || (@group && @group().membersInclude(user))

   stanceFor: (user) ->
     head orderBy(@recordStore.stances.find(latest: true, pollId: @id, participantId: user.id), 'createdAt', 'desc')


revert to use getDebug() until issue #8 is decided

--- plugin/browserify.coffee
+++ plugin/browserify.coffee
@@ -30,10 +30,17 @@
     # Browserify will look here for npm modules
     basedir: getBasedir(step)

-    # Browserify automatically adds a source map comment
-    # since in production mode Meteor automatically minifies away all comments,
-    # it's safe to do this even in production builds
-    debug: true
+    # TODO: look into this issue more. Created issue #8 for it
+    # comment from @stubailo:
+    #   Browserify automatically adds a source map comment
+    #   since in production mode Meteor automatically minifies away all comments,
+    #   it's safe to do this even in production builds
+    # comment by @elidoran:
+    #   something causes the file to be larger when debug is true for production.
+    #   Until I determine which is the proper behavior I prefer to maintain
+    #   the debug value based on whether it's a dev or prod build.
+    #   it can be overridden by the new per file options.
+    debug: getDebug()

   # merge user options with defaults
   browserifyOptions = _.defaults userOptions, defaultOptions


Don't populate authors in publications with unnecessary fields.

--- server/jobs/arxiv.coffee
+++ server/jobs/arxiv.coffee
@@ -127,7 +127,7 @@
         else
           authorCreatedAt = moment.utc().toDate()
           author._id = Random.id()
-          Person.documents.insert Person.applyDefaultAccess null, _.extend author,
+          Person.documents.insert Person.applyDefaultAccess null, _.extend {}, author,
             slug: author._id # We set it manually to prevent two documents having temporary null value which is invalid and throws a duplicate key error
             user: null
             publications: []


Suppress a purposeful lack-of-encoders development warning

--- tests/batman/model/encoders_test.coffee
+++ tests/batman/model/encoders_test.coffee
@@ -53,7 +53,8 @@
   oldDecoders = Batman.Model::_batman.decoders
   Batman.Model::_batman.decoders = new Batman.SimpleHash
   p = new TestProduct
-  p.fromJSON {name: "Cool Snowboard", cost: 12.99, rails_is_silly: "yup"}
+  Batman.developer.suppress ->
+    p.fromJSON {name: "Cool Snowboard", cost: 12.99, rails_is_silly: "yup"}

   equal p.get('name'), "Cool Snowboard"
   equal p.get('cost'), 12.99


Use contextual archive url if a page is in a book

--- src/scripts/models/contents/node.coffee
+++ src/scripts/models/contents/node.coffee
@@ -28,6 +28,9 @@
         url = "#{AUTHORING}/users/contents"
       else if @isDraft()
         url = "#{AUTHORING}/contents/#{id}.json" # FIX: Remove .json from URL
+      else if @isInBook()
+        book = @get('book')
+        url = "#{ARCHIVE}/contents/#{book.getVersionedId()}:#{id}.json"
       else
         url = "#{ARCHIVE}/contents/#{id}.json"


fix interrupt rule reseting after updaterules

--- static/coffee/flows/controllers.coffee
+++ static/coffee/flows/controllers.coffee
@@ -1309,12 +1309,22 @@
       category = {}
       category[flow.base_language] = "Interrupted"

+      ruleId = uuid()
+      destination = null
+
+      ruleset.rules
+        .filter (rule) -> rule.test.type == INTERRUPTED_TYPE
+        .map (rule) ->
+          destination = rule.destination
+          category = rule.category
+          ruleId = rule.uuid
+
       rules.push
         test:
           test: "interrupted"
           type: INTERRUPTED_TYPE
         destination: destination
-        uuid: uuid()
+        uuid: ruleId
         category: category

     $scope.ruleset.rules = rules


limit the maximum file size for uplaoded file and the format supported

--- static/coffee/flows/controllers.coffee
+++ static/coffee/flows/controllers.coffee
@@ -187,6 +187,14 @@
       showDialog('Wrong File Type', 'Audio files need to in the WAV format. Please choose a WAV file and try again.')
       return

+    if action.type in ['reply', 'send'] and file.type not in ['audio/wav', 'audio/x-wav', 'audio/mp3','video/mp4', 'image/jpeg']
+      showDialog('Wrong File Type', 'Only supported formats are JPG for images, WAV and MP3 for audios and MP4 for videos. Please choose another file and try again.')
+      return
+
+    if action.type in ['reply', 'send'] and (file.size > 20000000 or (file.name.endsWith('.jpg') and file.size > 5000000))
+      showDialog('Wrong File Size', "The file size should be less than 20MB and less than 5MB for images. Please choose another file and try again.")
+      return
+
     # if we have a recording already, confirm they want to replace it
     if action.type == 'say' and action._translation_recording
       modal = showDialog('Overwrite Recording', 'This step already has a recording, would you like to replace this recording with ' + file.name + '?', 'Overwrite Recording', false)
@@ -198,7 +206,7 @@

     # if we have an attachement already, confirm they want to replace it
     if action.type in ['reply', 'send'] and action._translation_media
-      modal = showDialog('Overwrite Attachment', 'This step already has a attacment, would you like to replace this attachment with ' + file.name + '?', 'Overwrite Attachemnt', false)
+      modal = showDialog('Overwrite Attachment', 'This step already has a attachment, would you like to replace this attachment with ' + file.name + '?', 'Overwrite Attachemnt', false)
       modal.result.then (value) ->
         if value == 'ok'
           action._translation_media = null


Deggis config

Deggis config

--- config.cson
+++ config.cson
@@ -14,4 +14,34 @@
         host: "enttec1"
         type: "rgb"
         address: 2
+      1:
+        name: "testi lamppu"
+        host: "enttec1"
+        type: "rgb"
+        address: 8
+      2:
+        name: "testi lamppu"
+        host: "enttec1"
+        type: "rgb"
+        address: 16
+      3:
+        name: "testi lamppu"
+        host: "enttec1"
+        type: "rgb"
+        address: 24
+      4:
+        name: "testi lamppu"
+        host: "enttec1"
+        type: "rgb"
+        address: 32
+      5:
+        name: "testi lamppu"
+        host: "enttec1"
+        type: "rgb"
+        address: 40
+      6:
+        name: "testi lamppu"
+        host: "enttec1"
+        type: "rgb"
+        address: 48
 }


inputview: minor: code cleanup

inputview: minor: code cleanup

--- lib/components/inputs/inputview.coffee
+++ lib/components/inputs/inputview.coffee
@@ -91,7 +91,7 @@
     @validationNotifications = {}
     @valid = yes
     @inputCallback = null
-    @previousHeight= null
+    @previousHeight = null
     @setName options.name
     @setLabel()
     @setCallback()
@@ -449,7 +449,7 @@

     # input content is copied into clone
     # element to get calculated height
-    @_clone = $ '<div/>', class : 'invisible'
+    @_clone = $ '<div/>', { class: 'invisible' }

     @on 'focus', =>
       @_clone.appendTo 'body'


s/tempVars/tempVars.general/ This should allow us to store more than one category of temporary variables.

--- scope.coffee
+++ scope.coffee
@@ -20,11 +20,12 @@
   constructor: (parent, expressions, method) ->
     [@parent, @expressions, @method] = [parent, expressions, method]
     @variables = {}
+    @tempVars  =
+      general: '_a'
     if @parent
-      @tempVar = @parent.tempVar
+      (@tempVars[k] = val) for k, val of @parent.tempVars
     else
       Scope.root = this
-      @tempVar = '_a'

   # Look up a variable name in lexical scope, and declare it if it does not
   # already exist.
@@ -54,11 +55,11 @@
   # If we need to store an intermediate result, find an available name for a
   # compiler-generated variable. `_a`, `_b`, and so on...
   freeVariable: ->
-    while @check @tempVar
-      ordinal = 1 + parseInt @tempVar.substr(1), 36
-      @tempVar = '_' + ordinal.toString(36).replace(/\d/g, 'a')
-    @variables[@tempVar] = 'var'
-    @tempVar
+    while @check @tempVars.general
+      ordinal = 1 + parseInt @tempVars.general.substr(1), 36
+      @tempVars.general = '_' + ordinal.toString(36).replace(/\d/g, 'a')
+    @variables[@tempVars.general] = 'var'
+    @tempVars.general

   # Ensure that an assignment is made at the top of this scope
   # (or at the top-level scope, if requested).


--- SingularityUI/app/initialize.coffee
+++ SingularityUI/app/initialize.coffee
@@ -17,4 +17,17 @@

     _oldAjax.call jQuery, opts

+# Configure moment().calender() to be used as an alternative to moment().from()
+relative = -> "[#{ @from() }]"
+relativePlus = -> "[#{ @from() }] ([#{ @format('l h:mma') }])"
+
+moment.lang 'en',
+    calendar:
+        nextWeek: relativePlus
+        nextDay: relativePlus
+        sameDay: relative
+        lastDay: relativePlus
+        lastWeek: relativePlus
+        sameElse: relativePlus
+
 $ -> app.initialize()


Log options as object

Log options as object

--- lib/ghc-mod/interactive-process.coffee
+++ lib/ghc-mod/interactive-process.coffee
@@ -12,7 +12,7 @@
     @cwd = options.cwd

     debug "Spawning new ghc-modi instance for #{options.cwd} with
-          #{"options.#{k} = #{v}" for k, v of options}"
+          options = ", options
     @proc = CP.spawn(path, cmd, options)
     @proc.stdout.setEncoding 'utf-8'
     @proc.stderr.setEncoding 'utf-8'


fix cache path

fix cache path

--- bin/crawler.coffee
+++ bin/crawler.coffee
@@ -14,13 +14,13 @@
       _.each dayObj.stories, (storyObj)->
         Daily.fetchStory storyObj.id, (err, storyObj)->
           unless err
-            Daily.saveStory storyObj, dayObj.date, (err)->
+            Daily.saveStory storyObj, dayObj.date, (err, storyObj)->
               unless err
                 if crawler.fetch is "today"
                   Daily.getStory storyObj.id, (err, storyObj)->
                     unless err
                       if storyObj.pre
-                        cache.del "//story/#{storyObj.pre.id}", ->
+                        cache.del "/story/#{storyObj.pre.id}", ->
                   cache.del "//mobile", ->
                   cache.del "//desktop", ->
                   cache.del "/day/#{dayObj.date}/mobile", ->


syncstrings: fix some subtle issues, mainly related to combining patches

--- salvus/syncstring.coffee
+++ salvus/syncstring.coffee
@@ -248,7 +248,8 @@
         cb?()

-
+exports.get_syncstring_db = () ->
+    return syncstring_db

 # oldest first -- unlike in page/activity.coffee
@@ -285,9 +286,7 @@
 # having to apply hundreds of patches when opening a string, and saves
 # space.   (Of course, having the complete history could also be
 # interesting...)
-#DB_PATCH_SQUASH_THRESH = 100
-# basically disable for now until we more carefully debug this.
-DB_PATCH_SQUASH_THRESH = 1000000000
+DB_PATCH_SQUASH_THRESH = 50

 # emits a 'change' event whenever live is changed as a result of syncing with the database
 class StringsDBString extends EventEmitter
@@ -319,7 +318,8 @@

     # DO NOT CHANGE THESE TWO FUNCTIONS, ever!  It'll break everything in the db
     patch_to_string: (patch) => JSON.stringify(diffsync.compress_patch(patch))
-    string_to_patch: (patch) => diffsync.decompress_patch(JSON.parse(patch))
+
+    string_to_patch: (patch) => diffsync.decompress_patch_compat(JSON.parse(patch))

     dbg: (f, m) =>
         winston.debug("StringsDB.#{f}: #{m}")
@@ -449,6 +449,7 @@
         # turns out to be sensible.
         #  SIMPLIFIED: ignore is_first and never trim.
         @dbg("_process_updates", "process #{updates.length} updates")
+        #@dbg("_process_updates", "#{misc.to_json(updates)}")
         new_patches = {}
         for update in updates
             update.timestamp = update.timestamp - 0  # better to key map based on string of timestamp as number
@@ -489,6 +490,14 @@
             i = patches.length - 1
             while i > 0
                 if patches[i].is_first
+                    if i > 0
+                        winston.debug("some older patches are no longer needed -- deleting from DB")
+                    for p in patches.slice(0,i)
+                        @db.delete
+                            table : 'syncstrings'
+                            where :
+                                string_id : string_id
+                                timestamp : p.timestamp
                     patches = patches.slice(i)
                     break
                 i -= 1
@@ -520,7 +529,7 @@
                 try
                     string.last_sync = diffsync.dmp.patch_apply(p.patch, string.last_sync)[0]
                 catch e
-                    winston.debug("syncstring database error applying a patch -- failed due to corruption (?) -- #{misc.to_json(p.patch)}")
+                    winston.debug("syncstring database error applying a patch -- failed due to corruption (?) -- #{misc.to_json(p.patch)} -- err=#{e}")
                 string.applied_patches[p.timestamp] = p
                 if squash and p.timestamp <= squash_time and (i == patches.length-1 or patches[i+1].timestamp > squash_time)
                     @_squash_patches
@@ -568,7 +577,7 @@
                 @db.update
                     table : 'syncstrings'
                     set   :
-                        patch    : misc.to_json(patch)
+                        patch    : @patch_to_string(patch)
                         is_first : true
                     where :
                         string_id : opts.string_id


removing recent changes

removing recent changes

--- scripts/TM-Github.coffee
+++ scripts/TM-Github.coffee
@@ -41,7 +41,7 @@
     request.post options, (error, response, data)->
       res.reply 'Request sent, take a look at https://ci.appveyor.com/project/DinisCruz/tm-4-0-build where a new build should have just started'
       res.reply 'In a bit, the compiled node_modules will be at the https://github.com/tm-build/TM_4_0_Windows repo'
-
+  ###
   robot.respond /deploy to azure/i,(res) ->
     res.reply "About to trigger a deploy to azure..."
     options =
@@ -58,7 +58,7 @@
   robot.hear 'TM_4_0_Design.Dev(.*)commits', (res)->
     res.reply 'New commit to TM_4_0_Design/Dev detected ...'
     res.reply '...time to deploy to azure'
-
+  ###
   robot.hear /show(.*)tm graph/i,(res) ->
     res.reply 'http://4.bp.blogspot.com/-7g82Ltxt7wI/VLqbd1CLOyI/AAAAAAAAKcc/kisGogPKcmo/s1600/Screen%2BShot%2B2014-11-02%2Bat%2B16.49.16.png'
     res.reply 'http://2.bp.blogspot.com/-PUi2erHHCKA/VLqbmSw69pI/AAAAAAAAKeY/fWA8nqSam8k/s1600/Screen%2BShot%2B2014-11-18%2Bat%2B10.57.13.png'


fix comment

fix comment

--- lib/operators/general-operators.coffee
+++ lib/operators/general-operators.coffee
@@ -30,12 +30,12 @@

   # Public: Marks this as ready to execute and saves the motion.
   #
-  # motion - The motion used to select what to operate on.
+  # target - TextObject or Motion to operate on.
   #
   # Returns nothing.
   compose: (target) ->
     unless _.isFunction(target.select)
-     throw new OperatorError('Must compose with a motion')
+     throw new OperatorError('Must respond to select')

     @target = target
     @complete = true


fix issue #322

fix issue #322

the branch name extraction logic is in confirmed

--- lib/views/pull-branch-list-view.coffee
+++ lib/views/pull-branch-list-view.coffee
@@ -17,7 +17,7 @@
       branches = items.filter((item) -> item isnt '').map (item) ->
         name: item.replace(/\s/g, '')
       if branches.length is 1
-        @pull branches[0]
+        @confirmed branches[0]
       else
         @setItems [currentBranch].concat branches
       @focusFilterEditor()


Tweak and improve config schema

Tweak and improve config schema

--- lib/config-schema.coffee
+++ lib/config-schema.coffee
@@ -31,16 +31,19 @@
     enum: ['pdflatex', 'lualatex', 'xelatex']

   moveResultToSourceDirectory:
+    title: 'Move Result to Source Directory'
     description: 'Ensures that the output file produced by a successful build
       is stored together with the TeX document that produced it.'
     type: 'boolean'
     default: true

   openResultAfterBuild:
+    title: 'Open Result after Successful Build'
     type: 'boolean'
     default: true

   openResultInBackground:
+    title: 'Open Result in Background'
     type: 'boolean'
     default: true

@@ -52,8 +55,15 @@
     default: ''

   skimPath:
+    description: 'Full application path to Skim (OS X).'
     type: 'string'
     default: '/Applications/Skim.app'
+
+  sumatraPath:
+    title: 'SumatraPDF Path'
+    description: 'Full application path to SumatraPDF (Windows).'
+    type: 'string'
+    default: 'C:\\Program Files\\SumatraPDF\\SumatraPDF.exe'

   texPath:
     title: 'TeX Path'


New Document::toJSON() model support added

--- lib/tasks/build/parse/documents.coffee
+++ lib/tasks/build/parse/documents.coffee
@@ -41,7 +41,9 @@
 				unless node.getAttr('neft:style')
 					node.setAttr 'neft:style', elem.style

-		Document.fromHTML name, htmlNode
+		file = Document.fromHTML name, htmlNode
+		Document.parse file
+		file

 	saveView = (name, view, callback) ->
 		json = JSON.stringify view


Remove unused dependency

Remove unused dependency

--- lib/update-package-dependencies.coffee
+++ lib/update-package-dependencies.coffee
@@ -1,4 +1,3 @@
-childProcess = require 'child_process'
 {$$, BufferedNodeProcess} = require 'atom'

 module.exports =


Use `git pull` instead of fetch + merge.

--- lib/views/pull-branch-list-view.coffee
+++ lib/views/pull-branch-list-view.coffee
@@ -15,26 +15,9 @@

     pull: (remoteBranch='') ->
       view = new OutputView()
-      remote = @remote
       git.cmd
-        args: ['fetch', @remote]
+        args: ['pull', @remote, remoteBranch]
         cwd: @repo.getWorkingDirectory()
-        stdout: (data) =>
-          if data.toString().length is 0
-            git.cmd
-              args: ['merge', remote + "/" + remoteBranch]
-              cwd: @repo.getWorkingDirectory()
-              stdout: (data) -> view.addLine(data.toString())
-              stderr: (data) -> view.addLine(data.toString())
-              exit: (code) =>
-                view.finish()
+        stdout: (data) -> view.addLine(data.toString())
         stderr: (data) -> view.addLine(data.toString())
-
-    # Once, 'pull' is fixed in Yosemite, revert back to this
-    # pull: (remoteBranch='') ->
-    #   view = new OutputView()
-    #   git.cmd
-    #     args: ['pull', @remote, remoteBranch]
-    #     stdout: (data) -> view.addLine(data.toString())
-    #     stderr: (data) -> view.addLine(data.toString())
-    #     exit: (code) => view.finish()
+        exit: (code) => view.finish()


:art: Add dismissible true back to error notification

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -57,7 +57,7 @@
       new Promise((resolve) =>
         resolve(linter.lint(@editor))
       ).then(EditorLinter._validateResults).catch((error) ->
-        atom.notifications.addError error.message, {detail: error.stack}
+        atom.notifications.addError error.message, {detail: error.stack, dismissable: true}
         []
       ).then (results) =>
         if linter.scope is 'project' then @linter.messagesProject.set linter, results


check for duplicates in suggested tags.

check for duplicates in suggested tags.

--- server/Translator.coffee
+++ server/Translator.coffee
@@ -43,7 +43,7 @@

     for post in Posts.find( 'parent_id': server.parent_id ).fetch()
       for key of post.tags
-        unless key of @my_tags or key of @tags
+        unless key of @my_tags or key of @suggested_tags
           @suggested_tags.push( key )

     author = Meteor.users.findOne( '_id': server.author_id )


oops layout thing

oops layout thing

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -61,7 +61,7 @@

 # Render login page
 app.get '/login/?', (req, resp) ->
-  resp.render 'login' { layout: 'layout' }
+  resp.render 'login'

 app.post "/login", passport.authenticate("local",
   successRedirect: "/"


Update messaging for banner config flag

Update messaging for banner config flag

Co-authored-by: Adam Daniels <0e18f44c1fec03ec4083422cb58ba6a09ac4fb2a@mediadrive.ca>

--- lib/assets/javascripts/unpoly/log.coffee
+++ lib/assets/javascripts/unpoly/log.coffee
@@ -29,7 +29,7 @@
     and which [events](/up.event) are being emitted.
     Note that errors will always be printed, regardless of this setting.
   @param {boolean} [options.banner=true]
-    Whether to show the unpoly banner in the console.
+    Print the Unpoly banner to the developer console.
   @stable
   ###
   config = new up.Config ->


Reverse playlists

Reverse playlists

--- server/routes/api.coffee
+++ server/routes/api.coffee
@@ -68,7 +68,7 @@
   Playlist.find()
     #.populate('_songs')
     .exec (err, playlists) ->
-      res.json playlists
+      res.json playlists.reverse()

 router.get '/playlist/:id', (req, res) ->
   Playlist.findById(req.params.id)


Fix failing test in IE

Fix failing test in IE

The moveCursor helper doesn't always move across blocks correctly in IE

--- test/src/system/basic_input_test.coffee
+++ test/src/system/basic_input_test.coffee
@@ -142,7 +142,8 @@
 editorTest "paste nested list into empty list item", (done) ->
   clickToolbarButton attribute: "bullet", ->
     typeCharacters "y\nzz", ->
-      moveCursor direction: "left", times: 3, ->
+      getSelectionManager().setLocationRange([0,1])
+      defer ->
         pressKey "backspace", ->
           pasteContent "text/html", "

a

b

", ->
           document = getDocument()
@@ -164,7 +165,8 @@
 editorTest "paste nested list over list item contents", (done) ->
   clickToolbarButton attribute: "bullet", ->
     typeCharacters "y\nzz", ->
-      moveCursor direction: "left", times: 3, ->
+      getSelectionManager().setLocationRange([0,1])
+      defer ->
         expandSelection "left", ->
           pasteContent "text/html", "

a

b

", ->
           document = getDocument()


updates now dont spawn another push from squishle, so we can update squishle on card update

--- lib/pegg-squishle.coffee
+++ lib/pegg-squishle.coffee
@@ -57,10 +57,7 @@
         @updateCard card
           .then (result) =>
             log "card updated: ", pretty result
-          # FIX: updating the post causes an infinite loop.
-          # If we need this someday, then find some way to check the request on wp_insert_post action...
-          #   to see if update came from this app or the user hitting the publish button.
-          #   @updatePost postId, JSON.stringify result
+            @updatePost postId, JSON.stringify result
             @backupImages card.choices
       else
         @createCard card


"Reference error corrected preventing filename changes working properly"

--- lib/plunker/index.coffee
+++ lib/plunker/index.coffee
@@ -98,7 +98,8 @@
     next null, json

   handleFileChanges: (plunk, json, next) ->
-    old_files = _.clone(plunk.files)
+    old_files = {}
+    old_files[filename] = _.clone(file) for filename, file of plunk.files

     errors = []


Remove a TODO

Remove a TODO

--- drivers/javascript/ast.coffee
+++ drivers/javascript/ast.coffee
@@ -120,8 +120,6 @@
     toString: -> err.printQuery(@)

 class RDBVal extends TermBase
-    #TODO aropt?
-
     eq: (args...) -> new Eq {}, @, args...
     ne: (args...) -> new Ne {}, @, args...
     lt: (args...) -> new Lt {}, @, args...


Fix fatal mistake on method completion logic...

--- lib/robe-provider.coffee
+++ lib/robe-provider.coffee
@@ -33,7 +33,7 @@
       # TODO: revise regexp by robe.el's robe-call-context logic
       callTarget = /([a-zA-Z0-9_?!.:]+)\..*$/.exec(wholePrefix)?[1]
       callTarget = if callTarget is 'self' then '' else callTarget
-      methodPrefix = if prefix or '.' then '' else prefix
+      methodPrefix = if prefix is '.' then '' else prefix
       isInstance = not callTarget and isInstanceMethod
       @_prepareClient().then (client) =>
         # TODO: show arguments


:racehorse: Improve table update speed by marking dirty cells

--- lib/table-element.coffee
+++ lib/table-element.coffee
@@ -216,15 +216,32 @@
     subs.add @tableEditor.onDidRemoveColumn (e) => @requestUpdate()
     subs.add @tableEditor.onDidRemoveColumn => @requestUpdate()
     subs.add @tableEditor.onDidChangeColumnOption => @requestUpdate()
-    subs.add @tableEditor.onDidChange => @requestUpdate()
+    subs.add @tableEditor.onDidChange =>
+      @wholeTableIsDirty = true
+      @requestUpdate()
     subs.add @tableEditor.onDidChangeRowHeight => @requestUpdate()
-    subs.add @tableEditor.onDidChangeCellValue => @requestUpdate()
     subs.add @tableEditor.onDidAddCursor => @requestUpdate()
     subs.add @tableEditor.onDidRemoveCursor => @requestUpdate()
     subs.add @tableEditor.onDidChangeCursorPosition => @requestUpdate()
-    subs.add @tableEditor.onDidAddSelection => @requestUpdate()
-    subs.add @tableEditor.onDidRemoveSelection => @requestUpdate()
-    subs.add @tableEditor.onDidChangeSelectionRange => @requestUpdate()
+    subs.add @tableEditor.onDidAddSelection ({selection}) =>
+      @markDirtyRange(selection.getRange())
+      @requestUpdate()
+    subs.add @tableEditor.onDidRemoveSelection ({selection}) =>
+      @markDirtyRange(selection.getRange())
+      @requestUpdate()
+    subs.add @tableEditor.onDidChangeSelectionRange ({oldRange, newRange}) =>
+      @markDirtyRange(oldRange)
+      @markDirtyRange(newRange)
+      @requestUpdate()
+    subs.add @tableEditor.onDidChangeCellValue (e) =>
+      if e.screenPosition?
+        @markDirtyCell(e.screenPosition)
+      else if e.screenPositions?
+        @markDirtyCells(e.screenPositions)
+      else if e.screenRange?
+        @markDirtyRange(e.screenRange)
+
+      @requestUpdate()
     subs.add @tableEditor.onDidDestroy =>
       @unsetModel()
       @subscriptions.dispose()
@@ -931,6 +948,17 @@
     requestAnimationFrame =>
       @update()
       @updateRequested = false
+
+  markDirtyCell: (position) ->
+    @dirtyPositions ?= []
+    @dirtyPositions[position.row] ?= []
+    @dirtyPositions[position.row][position.column] = true
+
+  markDirtyCells: (positions) ->
+    @markDirtyCell(position) for position in positions
+
+  markDirtyRange: (range) ->
+    range.each (row, column) => @markDirtyCell({row, column})

   update: =>
     return unless @tableEditor?
@@ -1048,18 +1076,25 @@
           @appendHeaderCell(columns[column], column)
           @appendCell(row, column) for row in visibleRows

-    for row in [intactFirstRow...intactLastRow]
-      @gutterCells[row]?.setModel({row})
+    if @dirtyPositions? or @wholeTableIsDirty
+      for row in [intactFirstRow...intactLastRow]
+        if @wholeTableIsDirty or @dirtyPositions[row]?
+          @gutterCells[row]?.setModel({row})
+
+        for column in [intactFirstColumn...intactLastColumn]
+          if @wholeTableIsDirty or @dirtyPositions[row]?[column]
+            @cells[row + '-' + column]?.setModel(@getCellObjectAtPosition([row, column]))
+
       for column in [intactFirstColumn...intactLastColumn]
-        @cells[row + '-' + column]?.setModel(@getCellObjectAtPosition([row, column]))
-    for column in [intactFirstColumn...intactLastColumn]
-      @headerCells[column]?.setModel({column: columns[column], index: column})
+        @headerCells[column]?.setModel({column: columns[column], index: column})

     @firstRenderedRow = firstRow
     @lastRenderedRow = lastRow
     @firstRenderedColumn = firstColumn
     @lastRenderedColumn = lastColumn
     @hasChanged = false
+    @dirtyPositions = null
+    @wholeTableIsDirty = false

   updateWidthAndHeight: ->
     @tableCells.style.cssText = """


Remove incorrect test of reject when false returned

--- specsrc/iteration.coffee
+++ specsrc/iteration.coffee
@@ -193,25 +193,6 @@
                 expect(a[1]).toBe false
             waitsFor (-> p.isRejected()), 'the task to reject', WAIT_TIME

-        it 'should rejectWith the correct context and pass nothing if the body fn returns false', ->
-            a = [false, false]
-            e = 'foo'
-            context =
-                i: 0
-            bodyFn = ->
-                if @i > 0
-                    return false
-                a[@i] = true
-                @i++
-
-            p = method a, bodyFn, context
-            p.fail (err) ->
-                expect(this).toBe context
-                expect(err).toBe undefined
-                expect(a[0]).toBe true
-                expect(a[1]).toBe false
-            waitsFor (-> p.isRejected()), 'the task to reject', WAIT_TIME
-
 root.backburner.describeIterationMethod = describeIterationMethod
 if exports?
     exports.describeIterationMethod = describeIterationMethod


Write collection module fixes #91

Write collection module fixes #91

--- ui/scripts/framework/collection.coffee
+++ ui/scripts/framework/collection.coffee
@@ -15,12 +15,32 @@
     url: ''
     makeUrl: Model::makeUrl

+    # For each model provided, find a model with the same ID.
+    # Update existing model if found, otherwise create a new instance
+    # with the given properties.
+    set: (models) ->
+        for model in models
+            m = @models.find((m) -> return m.id is model.id)
+            if m
+                m.set(model)
+            else
+                @models.push(new @Model(model))
+
     # Get data from the server.
     # Provide options, which will in turn be sent to the URL function.
     fetch: (options) ->
+        return @ajax({
+            method: 'GET'
+            url: @makeUrl(options)
+            done: (json) =>
+                @set(@parse(json))
+                @trigger('sync')
+            fail: (json) =>
+                @trigger('error', json)
+        })

     # The parse function determines what to do when the response comes back
-    # from the server. Overwrite per cocllection.
+    # from the server. Overwrite per collection.
     parse: (json) ->
         return json


Fix react-router compatibility in IsActiveMixing

--- src/IsActiveMixin.coffee
+++ src/IsActiveMixin.coffee
@@ -1,9 +1,9 @@
 React = require 'react'
-ActiveState = require 'react-router/modules/mixins/ActiveState'
+State = require 'react-router/modules/mixins/State'
 RouteToMixin = require './RouteToMixin'

 IsActiveMixin =
-  mixins: [ ActiveState, RouteToMixin ]
+  mixins: [ State, RouteToMixin ]

   getInitialState: ->
     isActive: false


Finished implementing TaskScheduler.

Finished implementing TaskScheduler.

--- src/TaskScheduler.coffee
+++ src/TaskScheduler.coffee
@@ -1,34 +1,53 @@
+bindMeteor = Meteor.bindEnvironment.bind(Meteor)
+
 class TaskScheduler

   constructor: (options) ->
-    options = Setter.merge({
-      runDuration: 10000
-      waitDuration: 1000
+    @options = Setter.merge({
+      runDuration: 2000
+      waitDuration: 100
     }, options)
     @queue = new DeferredQueue()
-    @pause()
+    @bufferQueue = []
+    @status == 'idle'
+    @reset()

   add: (callback) ->
-    @queue.add(callback)
+    unless callback
+      throw new Error('Callback not defined')
+    @bufferQueue.push(callback)

   run: ->
-    if @status == 'running'
-      throw new Error('Already running')
+    if @runDf
+      return @runDf.promise
+    @runDf = Q.defer()
     if @pauseDf
       @pauseDf.resolve()
       @pauseDf = null
     runDuration = @options.runDuration
     Logger.debug('Task scheduler running for ' + runDuration + 'ms...')
-    @handle = setTimeout(@_wait.bind(@), @options.runDuration)
-    @queue.waitForAll()
+    @waitHandle = setTimeout (bindMeteor => @_wait()), @options.runDuration
+
+    @status = 'running'
+    runNext = bindMeteor =>
+      if @bufferQueue.length == 0
+        @runDf.resolve()
+        @reset()
+        return
+      callback = @bufferQueue.shift()
+      @queue.add(callback).fin(runNext)
+    runNext()
+
+    @runDf.promise

   _wait: ->
     waitDuration = @options.waitDuration
     Logger.debug('Task scheduler waiting for ' + waitDuration + 'ms...')
-    waitDf = Q.defer()
-    wait = -> @waitDf.promise
-    setTimeout(wait, waitDuration)
-    @queue.unshift(wait)
+    wait = =>
+      @waitDf = Q.defer()
+      setTimeout (=> @waitDf.resolve()), waitDuration
+      @waitDf.promise
+    @bufferQueue.unshift(wait)

   pause: ->
     if @pauseDf
@@ -36,6 +55,19 @@
     Logger.debug('Task scheduler pausing...')
     @pauseDf = Q.defer()
     @status == 'pausing'
-    pause = -> @pauseDf.promise
-    @queue.unshift(pause)
-    clearTimeout(@handle)
+    pause = =>
+      # If the pause was cancelled, ignore this callback.
+      return unless @pauseDf
+      @status = 'paused'
+      @pauseDf.promise
+    @bufferQueue.unshift(pause)
+    clearTimeout(@waitHandle)
+
+  reset: ->
+    @status == 'idle'
+    _.each ['runDf', 'waitDf'], (name) =>
+      df = @[name]
+      if df && Q.isPending(df.promise)
+        df.reject('Task scheduler reset')
+      @[name] = null
+    clearTimeout(@waitHandle)


states.add -> states =

states.add -> states =

--- framer/Components/ShareComponent.coffee
+++ framer/Components/ShareComponent.coffee
@@ -59,7 +59,7 @@

 		@props = _.merge(defaultProps, options)

-		@states.add
+		@states =
 			hover: opacity: .8
 			full: opacity: 1
 		@states.animationOptions =


Make view deserialization work with arbitrary view classes

--- src/app/root-view.coffee
+++ src/app/root-view.coffee
@@ -42,6 +42,12 @@
     window.rootView = this
     @packageStates ?= {}
     @packageModules = {}
+    @viewClasses = {
+      "Pane": Pane,
+      "PaneRow": PaneRow,
+      "PaneColumn": PaneColumn,
+      "Editor": Editor
+    }
     @handleEvents()

     if not projectOrPathToOpen or _.isString(projectOrPathToOpen)
@@ -121,12 +127,11 @@
         console?.error("Exception serializing '#{name}' package's module\n", e.stack)
     packageStates

+  registerViewClass: (viewClass) ->
+    @viewClasses[viewClass.name] = viewClass
+
   deserializeView: (viewState) ->
-    switch viewState.viewClass
-      when 'Pane' then Pane.deserialize(viewState, this)
-      when 'PaneRow' then PaneRow.deserialize(viewState, this)
-      when 'PaneColumn' then PaneColumn.deserialize(viewState, this)
-      when 'Editor' then Editor.deserialize(viewState, this)
+    @viewClasses[viewState.viewClass]?.deserialize(viewState, this)

   activatePackage: (name, packageModule) ->
     config.setDefaults(name, packageModule.configDefaults) if packageModule.configDefaults?


Selection.proto.insertText clears the selection *before* inserting the given text

--- src/app/selection.coffee
+++ src/app/selection.coffee
@@ -99,10 +99,12 @@

   insertText: (text) ->
     { text, shouldOutdent } = @autoIndentText(text)
-    newBufferRange = @editor.buffer.change(@getBufferRange(), text)
-    @cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if @isReversed()
+    oldBufferRange = @getBufferRange()
+    isReversed = @isReversed()
+    @clearSelection()
+    newBufferRange = @editor.buffer.change(oldBufferRange, text)
+    @cursor.setBufferPosition(newBufferRange.end, skipAtomicTokens: true) if isReversed
     @autoOutdentText() if shouldOutdent
-    @clearSelection()

   indentSelectedRows: ->
     range = @getBufferRange()


search history with text, not objects

search history with text, not objects

--- lib/view-models/search-view-model.coffee
+++ lib/view-models/search-view-model.coffee
@@ -10,7 +10,7 @@
     atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistorySearch)

   restoreHistory: (index) ->
-    @view.editorElement.getModel().setText(@history(index).value)
+    @view.editorElement.getModel().setText(@history(index))

   history: (index) ->
     @vimState.getSearchHistoryItem(index)
@@ -30,5 +30,5 @@
       @restoreHistory(@historyIndex)

   confirm: (view) =>
-    @vimState.pushSearchHistory(@)
     super(view)
+    @vimState.pushSearchHistory(@value)


Equation builder, equals sign replaces operators Fixes #4713610

--- math/expression_components_spec.coffee
+++ math/expression_components_spec.coffee
@@ -20,6 +20,7 @@
       @comp.isVariable()
       @comp.isFraction()
       @comp.isExponentiation()
+      @comp.isRoot()

 describe "Expression Components", ->
   beforeEach ->


Fix basket.empty()

Fix basket.empty()

--- app/assets/javascripts/services/basket_service.coffee
+++ app/assets/javascripts/services/basket_service.coffee
@@ -19,8 +19,12 @@
       products.splice(products.indexOf(entry), 1)

     empty = ->
+      searchObj = $location.search()
+      delete searchObj.buyer
+      delete searchObj[entry.product.name] for entry in products
+
       products.splice(0, products.length)
-      $location.search('buyer', null).replace()
+      $location.search(searchObj).replace()

     entryAmountReducer = (sum, entry)-> sum+entry.amount
     productCount = -> products.reduce entryAmountReducer, 0


Fix bad Coffeescript code

Fix bad Coffeescript code

--- app/assets/javascripts/utils/course_date_utils.coffee
+++ app/assets/javascripts/utils/course_date_utils.coffee
@@ -8,8 +8,8 @@

   formattedDateTime: (datetime, showTime = false) ->
     timeZoneAbbr = ' ' + moment(datetime).toDate().toString().split('(')[1].slice(0, -1)
-    format = "YYYY-MM-DD#{if showTime then ' HH:mm'}"
-    return moment(datetime).format(format) + "#{if showTime then timeZoneAbbr}";
+    format = "YYYY-MM-DD#{if showTime then ' HH:mm' else ''}"
+    return moment(datetime).format(format) + "#{if showTime then timeZoneAbbr else ''}";

   # Returns an object of minDate and maxDate props for each date field of a course
   dateProps: (course, type) ->


:memo: Make HEAD all caps in title

:memo: Make HEAD all caps in title

--- src/config-schema.coffee
+++ src/config-schema.coffee
@@ -97,6 +97,7 @@
       confirmCheckoutHeadRevision:
         type: 'boolean'
         default: true
+        title: 'Confirm Checkout HEAD Revision'
       invisibles:
         type: 'object'
         properties:


Remove string "undefind" from feeds where there is no intro

--- site/server/lists.coffee
+++ site/server/lists.coffee
@@ -322,7 +322,7 @@
     )
     doc.published_at = moment.utc(doc.published_at).toDate().toGMTString()
     doc.full_url = "#{site.link}/#{doc.type}/#{doc.slug}"
-    doc.full_html = "#{doc.intro_html}

#{doc.body_html}"
+    doc.full_html = if doc.intro_html? then "#{doc.intro_html}

#{doc.body_html}" else doc.body_html
     return doc

   return templates.render 'feed.xml', req,


add close_unused_projects to c.coffee

add close_unused_projects to c.coffee

--- src/c.coffee
+++ src/c.coffee
@@ -96,3 +96,22 @@
                     else
                         done("SUCCESS!")
 console.log("delete_account 'email@foo.bar'  -- marks an account deleted")
+
+
+global.close_unused_projects = (host) ->
+    require('smc-hub/compute-client').compute_server
+        db_hosts : db_hosts
+        cb       : (err, s)->
+            if err
+                done("FAIL -- #{err}")
+                return
+            s.close_open_unused_projects
+                dry_run      : false
+                min_age_days : 60
+                max_age_days : 300
+                threads      : 5
+                host         : host
+                cb           : done()
+
+console.log("close_unused_projects('hostname') -- close all projects on that host not used in the last 60 days")
+


arrays are valid as content args

arrays are valid as content args

--- action.coffee
+++ action.coffee
@@ -97,6 +97,8 @@
           [options.body] = args
         when "object"
           [options.content] = args
+        when "array"
+          [options.content] = args
         else
           throw new Error "Invalid arguments for action"
     else


wip on authworker

wip on authworker

--- workers/auth/lib/auth/authworker.coffee
+++ workers/auth/lib/auth/authworker.coffee
@@ -18,7 +18,7 @@

   requireSession:(clientId, routingKey, callback)->
     {JSession} = @bongo.models
-    JSession.fetchSession clientId, (err, session)->
+    JSession.fetchSession clientId, (err, session)=>
       if err? or not session? then @rejectClient routingKey
       else
         tokenHasChanged = session.clientId isnt clientId
@@ -34,6 +34,7 @@
   getNextServiceName:(serviceType)->
     count = @counts[serviceType] ?= 0
     servicesOfType = @services[serviceType]
+    return  unless servicesOfType
     serviceName = servicesOfType[count % servicesOfType.length - 1]
     @counts[serviceType] += 1
     return serviceName
@@ -65,8 +66,8 @@

   joinKiteClient:(messageData, routingKey, socketId)->
     {channel} = messageData
-    @authenticate messageData, routingKey, (session)->
-      @bongo.mq.connection.exchange @getNextServiceName(channel), (exchange)->
+    @authenticate messageData, routingKey, (session)=>
+      @bongo.mq.connection.exchange @getNextServiceName(channel), (exchange)=>
         exchange.publish 'auth.join', {
           username    : session.username
           routingKey  : routingKey
@@ -81,7 +82,7 @@

   joinClient:(data, socketId)->
     {channel, routingKey, serviceType} = data
-    @addRoutingKeyBySocketId socketId, routingKey
+    # @addRoutingKeyBySocketId socketId, routingKey
     switch serviceType
       when 'bongo' then @joinBongoClient data, routingKey, socketId
       when 'kite'  then @joinKiteClient data, routingKey, socketId
@@ -94,7 +95,7 @@
       }

   cleanUpAfterDisconnect:(socketId)->
-    @getClients(socketId).forEach @bound 'cleanUpClient'
+    @getClients(socketId)?.forEach @bound 'cleanUpClient'

     # TODO: implement cleanup


Make cover photos and videos show up in RSS feeds

--- site/server/lists.coffee
+++ site/server/lists.coffee
@@ -313,16 +313,19 @@
       site = doc if doc.type is 'site'

   docs = _.map docs, (doc) ->
-    if doc.intro?
-      doc.intro_html = md.makeHtml(
-        doc.intro.replace(/\{\{?baseURL\}?\}/g, dutils.getBaseURL(req))
-      )
+    doc.intro_html = ''
+    if doc.intro?
+      doc.intro_html = md.makeHtml(
+        doc.intro.replace(/\{\{?baseURL\}?\}/g, dutils.getBaseURL(req))
+      )
+    doc.intro_html = "

" + doc.intro_html if doc.photo
+    doc.intro_html = doc.intro_html + doc.video if doc.video
     doc.body_html = md.makeHtml(
       doc.body.replace(/\{\{?baseURL\}?\}/g, dutils.getBaseURL(req))
     )
     doc.published_at = moment.utc(doc.published_at).toDate().toGMTString()
     doc.full_url = "#{site.link}/#{doc.type}/#{doc.slug}"
-    doc.full_html = if doc.intro_html? then "#{doc.intro_html}

#{doc.body_html}" else doc.body_html
+    doc.full_html = "#{doc.intro_html} #{doc.body_html}"
     return doc

   return templates.render 'feed.xml', req,


Slackだと <end> を比較できなかったため end に変更

Slackだと <end> を比較できなかったため end に変更

--- scripts/randompop.coffee
+++ scripts/randompop.coffee
@@ -35,7 +35,7 @@
     listener = new TextListener robot, /.*/, (msg) ->
       {envelope} = msg
       if envelope.room is room and envelope.user.id is userId
-        if msg.envelope.message.text.toLowerCase() is '<end>'
+        if msg.envelope.message.text.toLowerCase() is 'end'
           removeListener listener
         else
           callback.apply null, arguments
@@ -47,12 +47,12 @@
       msg.send " #{idx + 1}:#{name} さん"

   robot.respond /presenter\s+add/i, (msg) ->
-    msg.reply '発表者をリストに追加します。終了する場合は <end> と入力してください。'
+    msg.reply '発表者をリストに追加します。終了する場合は end と入力してください。'
     waitForResponse msg, (msg) ->
       presenter = msg.envelope.message.text
       presenterList.push(presenter)
       msg.reply "'#{presenter}' さんをリストに追加しました。"
-      msg.reply '続けて発表者をリストに追加します。終了する場合は <end> と入力してください。'
+      msg.reply '続けて発表者をリストに追加します。終了する場合は end と入力してください。'

   robot.respond /presenter\s+del\s+([0-9]+)/i, (msg) ->
     idx = parseInt(msg.match[1]) - 1


returning response

returning response

--- scripts/transport.coffee
+++ scripts/transport.coffee
@@ -1,15 +1,17 @@
 #https://github.com/github/hubot/blob/master/docs/scripting.md

 module.exports = (robot) ->
-  robot.hear /(.*)/i, (res) -> #capture everything and forward it to API.AI
-    url = "https://api.api.ai/api/query?v=20150910&lang=en&sessionId="+createUID()+"&query="+encodeURI(res.match[1])
+  robot.hear /@test (.*)/i, (msg) -> #capture everything and forward it to API.AI
+    url = "https://api.api.ai/api/query?v=20150910&lang=en&sessionId="+createUID()+"&query="+encodeURI(msg.match[1])
     robot.logger.debug('url: '+url)
     robot.http(url)
         .headers(Accept: 'application/json', Authorization: 'Bearer '+process.env.APIAI_AUTH)
         .get() (err, res, body) ->
             robot.logger.debug('response: '+body)
-         #res.match[1]
-
+            data   = JSON.parse body
+            robot.logger.debug('response: '+JSON.stringify data.result.fulfillment)
+            msg.send data.result.fulfillment.speech
+            return

 uid = undefined
 HEX_CHARS =


add commit and version in market url

add commit and version in market url

--- server/lib/market.coffee
+++ server/lib/market.coffee
@@ -37,16 +37,18 @@
         path = "https://registry.cozycloud.cc/cozy-registry.json"

     version = 0
+    commit = 0
     if fs.existsSync './market.json'
         data = fs.readFileSync './market.json', 'utf8'
         oldMarket = JSON.parse(data)
         version = oldMarket.version
+        commit = oldMarket.commit
     path = path + "?version=#{version}"
     path = url.parse path
     client = new Client "#{path.protocol}//#{path.host}"
     client.headers['user-agent'] = 'cozy'
-    client.get path.pathname, (err, res, body) ->
-        if not err and body.apps_list?
+    client.get "#{path.pathname}?version=#{version}&commit=#{commit}", (err, res, body) ->
+        if not err and body.apps_list? and Object.keys(body.apps_list).length > 0
             apps = body.apps_list
             fs.writeFileSync './market.json', JSON.stringify(body)
         else if oldMarket?


MailChimp API doesn't let us segment by custom properties like chinaVersion.

--- server/users/User.coffee
+++ server/users/User.coffee
@@ -136,7 +136,6 @@
     groupings: [{id: mail.MAILCHIMP_GROUP_ID, groups: newGroups}]
     'new-email': doc.get('email')
   }
-  #params.merge_vars.chinaVersion = true if doc.get('chinaVersion')  # ???
   params.update_existing = true

   onSuccess = (data) ->


30th Commit

30th Commit

--- protractor.conf.coffee
+++ protractor.conf.coffee
@@ -1,6 +1,6 @@
 class Config
   @directConnect: true
-  @specs: ['dist/dev/**/*.e2e.js']
+  @specs: ['./src/**/*.e2e.js']
   @exclude: []
   @capabilities:
     browserName: 'chrome'


More spaces

More spaces

--- resources/assets/coffee/react/_components/bbcode-editor.coffee
+++ resources/assets/coffee/react/_components/bbcode-editor.coffee
@@ -112,6 +112,8 @@
       className: 'js-bbcode-preview--hide btn-osu-big btn-osu-big--forum-secondary'
       disabled: @props.disabled
       osu.trans('forum.topic.create.preview_hide')
+
+
   renderPreviewShowButton: ->
     button
       type: 'button'


fixes #273

fixes #273

--- chrome/content/zotero-better-bibtex/keymanager.coffee
+++ chrome/content/zotero-better-bibtex/keymanager.coffee
@@ -188,7 +188,7 @@
     extra += " \nbibtex: #{citekey}" if citekey
     extra = extra.trim()
     item.setField('extra', extra)
-    item.save()
+    item.save({skipDateModifiedUpdate: true})

   set: (item, citekey, pin) ->
     throw new Error('Cannot set empty cite key') if !citekey || citekey.trim() == ''


Rename calculateContentSize to calculateContentFrame

--- framer/Components/ScrollComponent.coffee
+++ framer/Components/ScrollComponent.coffee
@@ -80,7 +80,7 @@
 		@_applyOptionsAndDefaults(options)
 		@_enableMouseWheelHandling()

-	calculateContentSize: ->
+	calculateContentFrame: ->

 		# Calculates the size of the content. By default this returns the total
 		# size of all the content layers based on width and height. You can override
@@ -89,6 +89,8 @@
 		contentFrame = @content.contentFrame()

 		return size =
+			x: 0
+			y: 0
 			width:  Math.max(@width,  contentFrame.x + contentFrame.width)
 			height: Math.max(@height, contentFrame.y + contentFrame.height)

@@ -129,12 +131,12 @@

 		return unless @content

-		contentFrame = @calculateContentSize()
-		contentFrame.x = 0 + @_contentInset.left
-		contentFrame.y = 0 + @_contentInset.top
+		contentFrame = @calculateContentFrame()
+		contentFrame.x = contentFrame.x + @_contentInset.left
+		contentFrame.y = contentFrame.x + @_contentInset.top
 		@content.frame = contentFrame

-		constraintsFrame = @calculateContentSize()
+		constraintsFrame = @calculateContentFrame()
 		constraintsFrame =
 			x: -constraintsFrame.width  + @width - @_contentInset.right
 			y: -constraintsFrame.height + @height - @_contentInset.bottom
@@ -262,18 +264,6 @@
 	closestContentLayerForScrollPoint: (scrollPoint, originX=0, originY=0) ->
 		return _.first(@_contentLayersSortedByDistanceForScrollPoint(scrollPoint, originX, originY))

-	contentLayersAbove: (point) -> _.filter @content.subLayers, (l) =>
-			Utils.framePointForOrigin(l.frame, @originX, @originY).y > point.y
-
-	contentLayersBelow: (point) -> _.filter @content.subLayers, (l) =>
-			Utils.framePointForOrigin(l.frame, @originX, @originY).y < point.y
-
-	contentLayersLeft: (point) -> _.filter @content.subLayers, (l) =>
-			Utils.framePointForOrigin(l.frame, @originX, @originY).x < point.x
-
-	contentLayersRight: (point) -> _.filter @content.subLayers, (l) =>
-			Utils.framePointForOrigin(l.frame, @originX, @originY).x > point.x
-
 	_scrollPointForLayer: (layer, originX=0, originY=0, clamp=true) ->
 		return Utils.framePointForOrigin(layer, originX, originY)


make sure all slider min/max/value stay useable numbers

--- framer/Components/SliderComponent.coffee
+++ framer/Components/SliderComponent.coffee
@@ -184,15 +184,16 @@

 	@define "min",
 		get: -> @_min or 0
-		set: (value) -> @_min = value
+		set: (value) -> @_min = value if _.isFinite(value)

 	@define "max",
 		get: -> @_max or 1
-		set: (value) -> @_max = value
+		set: (value) -> @_max = value if _.isFinite(value)

 	@define "value",
 		get: -> return @_value
 		set: (value) ->
+			return unless _.isFinite(value)

 			@_value = Utils.clamp(value, @min, @max)

@@ -244,6 +245,7 @@
 				return Utils.modulate(value, [0, @height], [@min, @max], true)

 	animateToValue: (value, animationOptions={curve: "spring(300, 25, 0)"}) ->
+		return unless _.isFinite(value)
 		if @width > @height
 			animationOptions.properties = {x: @pointForValue(value) - (@knob.width/2)}
 		else


wrapping up swipe

wrapping up swipe

--- client/main.coffee
+++ client/main.coffee
@@ -10,15 +10,25 @@
     swipecontrol = $("body").hammer(
         drag_min_distance:1
         swipe_velocity:0.1
-        prevent_default: true
     )
-    swipecontrol.on('swipeleft', ->
+    swipecontrol.on('swipeleft', (evt) ->
+        evt.preventDefault()
         console.log('swipeleft')
-        history.back()
+        if document.location.pathname is "/"
+            return
+        else if document.location.pathname is "/ontap"
+            return
+        else
+            history.forward()
     )
-    swipecontrol.on('swiperight', ->
+    swipecontrol.on('swiperight', (evt) ->
+        evt.preventDefault()
         console.log('swiperight')
-        history.back()
+        if document.location.pathname is "/"
+            history.back()
+        else if document.location.pathname is "/ontap"
+            history.back()
+
     )

 if Meteor.isCordova


Converted js to coffeescript

Converted js to coffeescript

--- server/methods/getSecurityBanner.coffee
+++ server/methods/getSecurityBanner.coffee
@@ -3,53 +3,61 @@
 		if not Meteor.userId()
 			throw new Meteor.Error('invalid-user', "[methods] getSecurityBanner -> Invalid user")

-		banner = {};
+		banner = {}

-		perms = new Jedis.AccessPermission(permissionIds).toArray();
+		perms = new Jedis.AccessPermission permissionIds
+			.toArray();

-		# TODO: get code from settings when implemented
-		systemCountryCode = { _id: '300', trigraph: 'USA', label: 'United States', type: 'Release Caveat' };
+		#systemCountryCode = Jedis.accessManager.getPermissions(Meteor.settings.public.system.countryCode);
+		#if( systemCountryCode.length === 0 ) {
+		#	console.log('System country not found.  Defaulting to USA');
+			systemCountryCode = _id: '300', trigraph: 'USA', label: 'United States', type: 'Release Caveat'
+		#} else {
+		#	systemCountryCode = systemCountryCode[0];
+		#}

 		# Obtain classification, add to banner. If none, default: 'UNCLASSIFIED'
-		classification = _.chain(perms)
-			.filter( function(perm) {return perm.type === 'classification'})
+		classification = _.chain perms
+			.filter (perm) -> return perm.type is 'classification'
 			# there should only be a single classification label
 			.first()
 			# if no classification then default to unclassified
-			.value() || {_id : 'U', label : 'UNCLASSIFIED'};
-			# TODO: should this also specify trigraph, type for completeness?
+			.value() || _id : 'U', label : 'UNCLASSIFIED'

 		# get all sci and sap labels, sort separately by trigraph
 		# join trigraphs separated by ' / ''
-		sciLabels = _.chain(perms)
-			.filter( function(perm) { return perm.type === 'SCI'})
-			.pluck( 'trigraph')
+		sciLabels = _.chain perms
+			.filter (perm) -> return perm.type is 'SCI'
+			.pluck 'trigraph'
 			.sort()
-			.value();
-		sapLabels = _.chain(perms)
-			.filter( function(perm) { return perm.type === 'SAP'})
-			.pluck( 'trigraph')
+			.value()
+		sapLabels = _.chain perms
+			.filter (perm) -> return perm.type is 'SAP'
+			.pluck 'trigraph'
 			.sort()
-			.value();
-		sciSapLabels = _.flatten([sciLabels, sapLabels]).join(' / ');
+			.value()
+		sciSapLabels = _.flatten [sciLabels, sapLabels]
+			.join ' / '

 		# get all rel-to countries, add to banner with ', ' separator
 		# if none specified (or only 'USA'), default to 'NOFORN'
-		reltoLabels = _.chain(perms)
-			.filter( function(perm) { return perm.type === 'Release Caveat'})
-			// exclude system country code because we prepend later as first country
-			.reject( function(perm) { return perm._id === systemCountryCode._id} )
-			.pluck( 'trigraph' )
+		reltoLabels = _.chain perms
+			.filter (perm) -> return perm.type is 'Release Caveat'
+			# exclude system country code because we prepend later as first country
+			.reject (perm) -> return perm._id is systemCountryCode._id
+			.pluck 'trigraph'
 			.sort()
-			.value();
+			.value()
 		# if still contains entries, hard-code system country code at front else 'NOFORN'
-		reltoLabels.splice(0, 0, (reltoLabels.length > 0 ? 'REL TO ' + systemCountryCode.trigraph : 'NOFORN'));
-		reltoLabels = reltoLabels.join(', ');
+		reltoLabels.splice(0, 0, ( if reltoLabels.length > 0 then 'REL TO ' + systemCountryCode.trigraph else 'NOFORN'))
+		reltoLabels = reltoLabels.join ', '
+

-		banner.classificationId = classification._id;
-		banner.text = _.compact([classification.label, sciSapLabels, reltoLabels]).join(' // ');
+		banner.classificationId = classification._id
+		banner.text = _.compact [classification.label.toUpperCase(), sciSapLabels, reltoLabels]
+			.join ' // '

-		return banner;+		return banner


Sidebar: removed chat section

Sidebar: removed chat section

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -326,7 +326,6 @@
     @addFollowedTopics()
     @addConversations()
     @addMessages()
-    # @addChat()

   addGroupDescription: ->
@@ -423,18 +422,4 @@
       dataSource : (callback) ->
         KD.singletons.socialapi.message.fetchPrivateMessages
           limit  : 5
-        , callback
-
-
-  addChat: ->
-
-    @addSubView @sections.chat = new ActivitySideView
-      title      : 'Chat'
-      cssClass   : 'chat users'
-      itemClass  : SidebarChatMemberItem
-      delegate   : this
-      headerLink : new CustomLinkView
-        title    : 'NEW'
-        href     : KD.utils.groupifyLink '/Activity/Chat/New'
-      dataSource : (callback) ->
-        KD.getGroup().fetchNewestMembers {}, {limit : 10}, callback
+        , callback


activity: update client-side follow state when a user's post receives a comment

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -102,6 +102,8 @@

       return KD.showError err  if err

+      # when someone replies to a user's post, we mark that post as "followed" by that user.
+      data.isFollowed = yes
       # and add to the sidebar
       # (if the item is already on sidebar, it's handled on @addItem)
       item = @addItem data, yes


chat: improve getList method code-style

chat: improve getList method code-style

--- client/activity/lib/models/channelparticipants.coffee
+++ client/activity/lib/models/channelparticipants.coffee
@@ -168,12 +168,11 @@
   ###
   getLists: ->

-    all = @computeAllParticipants @participants
-
-    lists =
-      preview : @computePreviewParticipants all
-      hidden  : @computeHiddenParticipants all
-      all     : all
+    all     = @computeAllParticipants @participants
+    preview = @computePreviewParticipants all
+    hidden  = @computeHiddenParticipants all
+
+    return { all, preview, hidden }

   ###*


geoip: indentation is fixed

geoip: indentation is fixed

--- client/activity/lib/views/activitylistitemview.coffee
+++ client/activity/lib/views/activitylistitemview.coffee
@@ -287,7 +287,7 @@
     @hasShowMoreMark = yes
     location = ""
     if @getData().payload?.location?
-      location =  ", from #{@getData().payload.location}"
+      location =  " from #{@getData().payload.location}"

     """

@@ -296,7 +296,7 @@

         {{> @author}}

-            {{> @timeAgoView}}#{location}
+          {{> @timeAgoView}}#{location}

       {{> @editWidgetWrapper}}


GroupsController: listen realtime updates from NodeNotificationClient

This will keep the existing channel subscription for socialApi over
PubNub as is but it will use the NNC if PN is not enabled in current env

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -70,35 +70,50 @@
         rest = remote.revive rest
         @emit event, rest...

+
+  knownEvents = [
+    'StackTemplateChanged'
+    'GroupStackTemplateRemoved'
+    'InstanceChanged'
+    'InstanceDeleted'
+    'GroupDestroyed'
+    'GroupJoined'
+    'MembershipRoleChanged'
+    'InvitationChanged'
+    'GroupLeft'
+    'StackAdminMessageCreated'
+    'SharedStackTemplateAccessLevel'
+  ]
+
   openSocialGroupChannel: (group, callback = -> ) ->

     { realtime, socialapi } = kd.singletons

-    socialapi.channel.byId { id: group.socialApiChannelId }, (err, channel) =>
-      return callback err  if err
-
-      socialapi.registerAndOpenChannel group, channel, (err, registeredChan) =>
+    if realtime.isPubNubEnabled()
+
+      socialapi.channel.byId { id: group.socialApiChannelId }, (err, channel) =>
         return callback err  if err

-        realtimeChan = registeredChan?.delegate
-
-        return callback 'realtime chan is not set'  unless realtimeChan
-
-        @filterXssAndForwardEvents realtimeChan, [
-          'StackTemplateChanged'
-          'GroupStackTemplateRemoved'
-          'InstanceChanged'
-          'InstanceDeleted'
-          'GroupDestroyed'
-          'GroupJoined'
-          'MembershipRoleChanged'
-          'InvitationChanged'
-          'GroupLeft'
-          'StackAdminMessageCreated'
-          'SharedStackTemplateAccessLevel'
-        ]
-
-        callback null
+        socialapi.registerAndOpenChannel group, channel, (err, registeredChan) =>
+          return callback err  if err
+
+          realtimeChan = registeredChan?.delegate
+          return callback 'realtime chan is not set'  unless realtimeChan
+
+          @filterXssAndForwardEvents realtimeChan, knownEvents
+          callback null
+
+    else
+
+      realtime.nodeNotificationClient.on 'message', (notification) =>
+
+        debug 'got notification from nodeNotificationClient', notification
+
+        { event } = notification
+        if event in knownEvents
+          notification = remote.revive notification
+          @emit event, notification
+

   changeGroup: (groupName = 'koding', callback = kd.noop) ->


test: test onPlaybackTick with jasmine mock clock

@jbenet this is a more robust way to test timeouts

--- coffee/test/views/timed_media_player_view.spec.coffee
+++ coffee/test/views/timed_media_player_view.spec.coffee
@@ -63,16 +63,31 @@
     it 'should call onPlaybackTick every 100ms once started', ->
       view = new TimedMediaPlayerView options()
       view.render()
+      jasmine.Clock.useMock()

       spyOn view, 'onPlaybackTick'
-      check = (count) -> (-> view.onPlaybackTick.callCount is count)
       view.timer.startTick()

-      waitsFor check(1), 'called onPlaybackTick', 101
-      waitsFor check(2), 'called onPlaybackTick', 101
-      waitsFor check(3), 'called onPlaybackTick', 101
-      waitsFor check(4), 'called onPlaybackTick', 101
-      runs -> expect(view.onPlaybackTick.callCount).toBe 4
+      jasmine.Clock.tick(99)
+      expect(view.onPlaybackTick.callCount).toBe 0
+      jasmine.Clock.tick(2)
+      expect(view.onPlaybackTick.callCount).toBe 1
+      jasmine.Clock.tick(98)
+      expect(view.onPlaybackTick.callCount).toBe 1
+      jasmine.Clock.tick(2)
+      expect(view.onPlaybackTick.callCount).toBe 2
+      jasmine.Clock.tick(98)
+      expect(view.onPlaybackTick.callCount).toBe 2
+      jasmine.Clock.tick(2)
+      expect(view.onPlaybackTick.callCount).toBe 3
+      jasmine.Clock.tick(98)
+      expect(view.onPlaybackTick.callCount).toBe 3
+      jasmine.Clock.tick(2)
+      expect(view.onPlaybackTick.callCount).toBe 4
+      jasmine.Clock.tick(98)
+      expect(view.onPlaybackTick.callCount).toBe 4
+      jasmine.Clock.tick(2)
+      expect(view.onPlaybackTick.callCount).toBe 5

   describe 'TimedMediaPlayerView::loops', ->


Update apm -v spec

Update apm -v spec

--- spec/apm-cli-spec.coffee
+++ spec/apm-cli-spec.coffee
@@ -25,7 +25,10 @@
       apm.run(['-v'])
       expect(console.error).not.toHaveBeenCalled()
       expect(console.log).toHaveBeenCalled()
-      expect(console.log.argsForCall[0][0]).toBe JSON.parse(fs.readFileSync('package.json')).version
+      lines = console.log.argsForCall[0][0].split('\n')
+      expect(lines[0]).toBe "apm  #{require('../package.json').version}"
+      expect(lines[1]).toBe "npm  #{require('npm/package.json').version}"
+      expect(lines[2]).toBe "node #{process.versions.node}"

   describe 'when an unrecognized command is specified', ->
     it 'prints an error message and exits', ->


raw overrides for #329

raw overrides for #329

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -563,25 +563,26 @@
       @remove(name)
       continue

+    raw = (value.format in ['navie', 'json'])
     switch name
       when 'mr'
-        fields.push({ name: 'mrnumber', value: value.value })
+        fields.push({ name: 'mrnumber', value: value.value, raw: raw })
       when 'zbl'
-        fields.push({ name: 'zmnumber', value: value.value })
+        fields.push({ name: 'zmnumber', value: value.value, raw: raw })
       when 'lccn', 'pmcid'
-        fields.push({ name: name, value: value.value })
+        fields.push({ name: name, value: value.value, raw: raw })
       when 'pmid', 'arxiv', 'jstor', 'hdl'
         if Translator.BetterBibLaTeX
           fields.push({ name: 'eprinttype', value: name.toLowerCase() })
-          fields.push({ name: 'eprint', value: value.value })
+          fields.push({ name: 'eprint', value: value.value, raw: raw })
         else
-          fields.push({ name, value: value.value })
+          fields.push({ name, value: value.value, raw: raw })
       when 'googlebooksid'
         if Translator.BetterBibLaTeX
           fields.push({ name: 'eprinttype', value: 'googlebooks' })
-          fields.push({ name: 'eprint', value: value.value })
+          fields.push({ name: 'eprint', value: value.value, raw: raw })
         else
-          fields.push({ name: 'googlebooks', value: value.value })
+          fields.push({ name: 'googlebooks', value: value.value, raw: raw })
       when 'xref'
         fields.push({ name, value: value.value, enc: 'raw' })

@@ -590,7 +591,7 @@
         @referencetype = value.value

       else
-        fields.push({ name, value: value.value })
+        fields.push({ name, value: value.value, raw: raw })

   for field in fields
     field = @field(Translator.BibLaTeXDataFieldMap[field.name], field.value) if Translator.BibLaTeXDataFieldMap[field.name]


Remove useless comments

Remove useless comments

--- lib/atom-perl6-editor-tools-view.coffee
+++ lib/atom-perl6-editor-tools-view.coffee
@@ -105,12 +105,10 @@
   renderHTMLCode: (text) ->
     if not atom.config.get("atom-perl6-editor-tools.triggerOnSave") and @editor.getPath()? then @save () =>
       iframe = document.createElement("iframe")
-      # Fix from @kwaak (https://github.com/webBoxio/atom-perl6-editor-tools/issues/1/#issuecomment-49639162)
       # Allows for the use of relative resources (scripts, styles)
       iframe.setAttribute("sandbox", "allow-scripts allow-same-origin")
       iframe.src = @tmpPath
       @html $ iframe
-      # @trigger('atom-perl6-editor-tools:html-changed')
       atom.commands.dispatch 'atom-perl6-editor-tools', 'html-changed'

   getTitle: ->


Add before sharing callback for facebook

--- app/assets/javascripts/sharing_tags/share/base.js.coffee
+++ app/assets/javascripts/sharing_tags/share/base.js.coffee
@@ -27,7 +27,7 @@
     share_window?.closed || iteration >= 5

   _before_callback: (provider)=>
-    jQuery?(window).trigger("sharing_tags.share", url: @url, provider: provider)
+    jQuery?(window).trigger("sharing_tags.share", [url: @url, provider: provider])

   _after_callback: =>
     jQuery?("body").trigger("sharing_tags.shared")


Fixes #30. Update community name on profile save.

--- app/assets/javascripts/views/profile_edit_view.js.coffee
+++ app/assets/javascripts/views/profile_edit_view.js.coffee
@@ -40,6 +40,9 @@
     save: (event) ->
       event.preventDefault()
       request = @model.save()
+      request.done(=>
+        @model.set('community_name', @communitySelect.collection.get(@model.get('community_id')).get('name'))
+      )
       request.fail(=>
         @displayFormError()
         # TODO: Handle server side failure.


fix: finally fix memleak

fix: finally fix memleak

--- scripts-src/standalone_searcher.coffee
+++ scripts-src/standalone_searcher.coffee
@@ -112,8 +112,7 @@
       #pick spaceships
       #catcher.scan field
       scanForPatterns field, vacuumPeriod, 4, maxLen, (pattern)->
-        fs.write outputStream, JSON.stringify({g: generation, p:pattern})+"\n"
-        #console.log pattern.length
+        fs.writeSync outputStream, JSON.stringify({g: generation, p:pattern})+"\n"
         patternsCount += 1
     console.log "#################################"
     performance = patternsCount / (Date.now() - timeStart) * 1000


:lipstick:

:lipstick:

--- spec/welcome-spec.coffee
+++ spec/welcome-spec.coffee
@@ -34,7 +34,6 @@

       atom.commands.dispatch(workspaceElement, 'welcome:show-welcome-buffer')

-      editor = null
       waitsFor ->
         editor = atom.workspace.getActiveTextEditor()


bugfix: prohibit @error call if no error

--- lib/Context.coffee
+++ lib/Context.coffee
@@ -381,7 +381,8 @@
         ## * emits booting and booted
         ##
         boot: (done)->
-            @_errorHandler = done
+            @_errorHandler = (err)=>
+                done(err) if err

             @_changeStatus 'booting'


Fixed History navigation

Fixed History navigation

--- muleview/app/controller/History.coffee
+++ muleview/app/controller/History.coffee
@@ -6,7 +6,7 @@

   gotoToken: (token) ->
     [key, retention] = (token ? "").split(";")
-    Muleview.event "graphRequest", key, retention if key
+    Muleview.event "viewChange", key, retention if key

   init: ->
   onLaunch: ->


[i18n] tg-2693 #ready-for-test

[i18n] tg-2693 #ready-for-test

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -293,7 +293,7 @@
     $rootscope.contribPlugins = @.taigaContribPlugins
     $rootscope.adminPlugins = _.where(@.taigaContribPlugins, {"type": "admin"})

-    $rootscope.$on "$translateLoadingEnd", (e, ctx) ->
+    $rootscope.$on "$translateChangeEnd", (e, ctx) ->
         lang = ctx.language
         i18nInit(lang, $translate)


serialize period before sending to flux store

--- tutor/src/components/task-plan/builder/tasking-date-times.cjsx
+++ tutor/src/components/task-plan/builder/tasking-date-times.cjsx
@@ -31,7 +31,7 @@

     {id, period} = @props

-    return false if TaskingStore.isTaskingValid(id, period)
+    return false if TaskingStore.isTaskingValid(id, period?.serialize())

     _.first(TaskingStore.getTaskingErrors(id, period))

@@ -48,16 +48,16 @@
   setDate: (type, value) ->
     {id, period} = @props
     value = value.format(TimeHelper.ISO_DATE_FORMAT) if moment.isMoment(value)
-    TaskingActions.updateDate(id, period, type, value)
+    TaskingActions.updateDate(id, period?.serialize(), type, value)

   setTime: (type, value) ->
     {id, period} = @props
     value = value.format(TimeHelper.ISO_DATE_FORMAT) if moment.isMoment(value)
-    TaskingActions.updateTime(id, period, type, value)
+    TaskingActions.updateTime(id, period?.serialize(), type, value)

   render: ->
     {isVisibleToStudents, isEditable, period, id, termStart, termEnd} = @props
-
+    period = period.serialize() if period
     commonDateTimesProps = _.pick @props, 'required', 'currentLocale', 'taskingIdentifier'

     defaults = TaskingStore.getDefaultsForTasking(id, period)


Fix menu appeareance

Fix menu appeareance

--- source/assets/javascripts/app.js.coffee
+++ source/assets/javascripts/app.js.coffee
@@ -266,7 +266,7 @@

 $('header.main').on 'mouseout', ->
   hideMenu = ->
-    $('heade.main nav').removeClass('visible')
+    $('header.main nav').removeClass('visible')
   setTimeout hideMenu, 2000

 $('.fonts input.tester').on 'change', ->


Case 9000808: Updated jquery javascript

Case 9000808: Updated jquery javascript

--- _assets/javascripts/main.js.coffee
+++ _assets/javascripts/main.js.coffee
@@ -1,2 +1,2 @@
-#= require vendor/jquery-2.2.4
+#= require vendor/jquery-3.2.1.min
 #= require_tree ./vendor/


Rewrite underscore code

Rewrite underscore code

--- spec/project-spec.coffee
+++ spec/project-spec.coffee
@@ -315,11 +315,14 @@
           console.log(paths)
           try
             expect(paths.length).toBe inputs.length
-            matches.forEach (match) ->
-              expect(match).toEqual 'evil'

             inputs.forEach (file) ->
-              expect(_.some(paths, (p) -> p.endsWith(file)))
+              itWorked = false
+              paths.forEach (p) ->
+                itWorked = itWorked || p.endsWith(file)
+
+              expect(itWorked)
+
           finally
             inputs.forEach (input) ->
               fs.unlinkSync (path.join(target, input))


server: attach group when transferring ownership

--- workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
+++ workers/social/lib/social/models/computeproviders/handlers/memberremoved.coffee
@@ -41,11 +41,11 @@
         log 'Failed to delete stack:', err  if err

-setOwnerOfMachine = (machine, { account, user, oldOwner }) ->
+setOwnerOfMachine = (machine, { account, user, oldOwner, group }) ->

   # Update machine ownership to the admin who kicked the member
   machine.addUsers {
-    targets: [ user ], asOwner: yes, sudo: yes
+    targets: [ user ], asOwner: yes, sudo: yes, group: group.slug
   }, (err) ->
     log 'Failed to change ownership of machine:', err  if err

@@ -152,6 +152,7 @@
         requester :
           user    : requesterJUser
           account : requester
+          group   : group
         reason
       }
       queue.next()


webserver: define var

webserver: define var

--- servers/lib/server/handlers/app.coffee
+++ servers/lib/server/handlers/app.coffee
@@ -5,7 +5,7 @@

 module.exports = (req, res, next, options)->

-  { JName, JGroup }           = koding.models
+  { JName, JGroup }           = bongoModels = koding.models
   { params }                  = req
   { name, section, slug }     = params
   { path, loggedIn, account } = options