use fetchMachinesByUsername in machine.test.coffee

--- workers/social/lib/social/models/computeproviders/machine.test.coffee
+++ workers/social/lib/social/models/computeproviders/machine.test.coffee
@@ -2,7 +2,8 @@
 JMachine                      = require './machine'

 { createUserAndMachine
-  generateMachineParams }     = require '../../../../testhelper/models/machinehelper'
+  generateMachineParams
+  fetchMachinesByUsername }   = require '../../../../testhelper/models/machinehelper'

 { daisy
   expect
@@ -99,6 +100,12 @@
           expect(machine.provider).to.be.equal 'koding'
           expect(machine.status.state).to.be.equal 'NotInitialized'
           expect(machine.users[0].id + '').to.be.equal user.getId() + ''
+          expect(machine.label).to.exist
+          expect(machine.users.length).to.be.above 0
+          expect(machine.users[0].sudo).to.be.ok
+          expect(machine.users[0].owner).to.be.ok
+          expect(machine.domain).to.exist
+          expect(machine.slug).to.exist

           done()

@@ -107,7 +114,7 @@

     describe 'when user is the owner', ->

-      it 'maching should be destroyed', (done) ->
+      it 'machine should be destroyed', (done) ->

         user          = {}
         machine       = {}
@@ -501,7 +508,6 @@

       user             = {}
       client           = {}
-      machine          = {}
       account          = {}
       userFormData     = generateDummyUserFormData()

@@ -509,7 +515,7 @@

         ->
           withDummyClient { group : 'koding' }, (client_) ->
-            client = client_
+            client = client_

             # registering a new user
             JUser.convert client, userFormData, (err, data) ->
@@ -521,18 +527,15 @@

         ->
           # fetching machine instance of newly registered user
-          JMachine.fetchByUsername userFormData.username, (err, machines) ->
-            expect(err).to.not.exist
+          fetchMachinesByUsername userFormData.username, (machines) ->
             machine = machines[0]
-            queue.next()
-
-        ->
-          # expecting machine to be fetched by machine id and client
-          JMachine.one$ client, machine._id + '', (err, machine_) ->
-            expect(err).to.not.exist
-            expect(machine).to.exist
-            expect(machine._id + '').to.be.equal machine_._id + ''
-            queue.next()
+
+            # expecting machine to be fetched by machine id and client
+            JMachine.one$ client, machine._id + '', (err, machine_) ->
+              expect(err).to.not.exist
+              expect(machine).to.exist
+              expect(machine._id + '').to.be.equal machine_._id + ''
+              queue.next()

         -> done()

@@ -548,7 +551,6 @@
       it 'should return error if user is not owner', (done) ->

         client           = {}
-        machine          = {}
         account          = {}
         userFormData     = generateDummyUserFormData()
         anotherClient    = {}
@@ -569,7 +571,7 @@
           ->
             withDummyClient { group : 'koding' }, (client) ->
               anotherClient = client
-              #
+
               # registering another user
               JUser.convert anotherClient, generateDummyUserFormData(), (err, data) ->
                 expect(err).to.not.exist
@@ -577,16 +579,13 @@

           ->
             # fetching owner user's machine
-            JMachine.fetchByUsername userFormData.username, (err, machines) ->
-              expect(err).to.not.exist
+            fetchMachinesByUsername userFormData.username, (machines) ->
               machine = machines[0]
-              queue.next()
-
-          ->
-            # expecting error when another user attempts to revive users
-            machine.reviveUsers anotherClient, {}, (err) ->
-              expect(err?.message).to.be.equal 'Access denied'
-              queue.next()
+
+              # expecting error when another user attempts to revive users
+              machine.reviveUsers anotherClient, {}, (err) ->
+                expect(err?.message).to.be.equal 'Access denied'
+                queue.next()

           -> done()

@@ -600,7 +599,6 @@
       it 'should revive users', (done) ->

         client           = {}
-        machine          = {}
         account          = {}
         userFormData     = generateDummyUserFormData()

@@ -620,16 +618,13 @@

           ->
             # fetching machine of owner user
-            JMachine.fetchByUsername userFormData.username, (err, machines) ->
-              expect(err).to.not.exist
+            fetchMachinesByUsername userFormData.username, (machines) ->
               machine = machines[0]
-              queue.next()
-
-          ->
-            # expecting to revive users without error
-            machine.reviveUsers client, {}, (err) ->
-              expect(err?.message).to.not.exist
-              queue.next()
+
+              # expecting to revive users without error
+              machine.reviveUsers client, {}, (err) ->
+                expect(err?.message).to.not.exist
+                queue.next()

           -> done()

@@ -704,7 +699,6 @@
       it 'should be able set label on machine', (done) ->

         client           = {}
-        machine          = {}
         account          = {}
         userFormData     = generateDummyUserFormData()

@@ -723,19 +717,15 @@
                 queue.next()

           ->
-            # fetching machine of user
-            JMachine.fetchByUsername userFormData.username, (err, machines) ->
-              expect(err).to.not.exist
+            fetchMachinesByUsername userFormData.username, (machines) ->
               machine = machines[0]
-              queue.next()
-
-          ->
-            # generating random label and expecting machine label to be set to it
-            newLabel = generateRandomString()
-            machine.setLabel client, newLabel, (err) ->
-              expect(err).to.not.exist
-              expect(machine.label).to.be.equal newLabel
-              queue.next()
+
+              # generating random label and expecting machine label to be set to it
+              newLabel = generateRandomString()
+              machine.setLabel client, newLabel, (err) ->
+                expect(err).to.not.exist
+                expect(machine.label).to.be.equal newLabel
+                queue.next()

           -> done()

@@ -792,7 +782,6 @@

         user             = {}
         client           = {}
-        machine          = {}
         account          = {}
         userFormData     = generateDummyUserFormData()
         provisionerCount = 0
@@ -812,19 +801,15 @@
                 queue.next()

           ->
-            # fetching machine of user
-            JMachine.fetchByUsername userFormData.username, (err, machines) ->
-              expect(err).to.not.exist
+            fetchMachinesByUsername userFormData.username, (machines) ->
               machine = machines[0]
-              queue.next()
-
-          ->
-            # expecting provisioner to be set on machine
-            machine.setProvisioner client, 'devrim/koding-base', (err) ->
-              expect(err?.message).to.not.exist
-              expect(machine.provisioners).to.exist
-              expect(machine.provisioners.length).to.be.equal provisionerCount + 1
-              queue.next()
+
+              # expecting provisioner to be set on machine
+              machine.setProvisioner client, 'devrim/koding-base', (err) ->
+                expect(err?.message).to.not.exist
+                expect(machine.provisioners).to.exist
+                expect(machine.provisioners.length).to.be.equal provisionerCount + 1
+                queue.next()

           -> done()

@@ -838,7 +823,6 @@
     it 'machine should be shared with specified users', (done) ->

       client              = {}
-      machine             = {}
       account             = {}
       userCount           = 0
       userFormData        = generateDummyUserFormData()
@@ -872,19 +856,15 @@
               queue.next()

         ->
-          # fetching machine of user
-          JMachine.fetchByUsername userFormData.username, (err, machines) ->
-            expect(err).to.not.exist
+          fetchMachinesByUsername userFormData.username, (machines) ->
             machine = machines[0]
             userCount = machine.users.length
-            queue.next()
-
-        ->
-          # expecting machine to be shared with anotherUser
-          machine.share client, [anotherUserFormData.username], (err) ->
-            expect(err?.message).to.not.exist
-            expect(machine.users.length).to.be.equal userCount + 1
-            queue.next()
+
+            # expecting machine to be shared with anotherUser
+            machine.share client, [anotherUserFormData.username], (err) ->
+              expect(err?.message).to.not.exist
+              expect(machine.users.length).to.be.equal userCount + 1
+              queue.next()

         -> done()


Fixed a bug in the list where the selected model was not passed in the select event if the rendering was handled by a controller

--- src/_spine/list.coffee
+++ src/_spine/list.coffee
@@ -52,6 +52,7 @@
 			@addChild child
 			child.prepareWithModel item
 			@append child
+			$(child.el).data('item', item)
 			console.log "child was created: #{child.id}" if @debug

 		else


Add post hook endpoint and console logging

--- src/ac-webhooks.coffee
+++ src/ac-webhooks.coffee
@@ -17,5 +17,9 @@
 #   digitalsadhu
 module.exports = (robot) ->

-  robot.router.get "/hubot/ac-webhooks", (req, res) ->
-    robot.send "ac webhooks url hit!"+  robot.router.post "/hubot/ac-webhooks", (req, res) ->
+    console.log req.body
+    user = {}
+    user.room = process.env['HUBOT_CAMPFIRE_ROOMS'].split(',')[0]
+    robot.send user, 'ac webhooks url hit!'
+    res.end '{"success":true}'


Don't mangle variable names

Don't mangle variable names

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -19,7 +19,10 @@
 		"output": {filename: "framer.js", pathinfo:true}
 		"plugins": [
 			new webpack.optimize.DedupePlugin(),
-			new webpack.optimize.UglifyJsPlugin()
+			new webpack.optimize.UglifyJsPlugin({
+				"mangle": false,
+				"compress": {warnings: false}
+			})
 		]

 	return gulp.src(config.entry)


cleaning up new post function.

cleaning up new post function.

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -48,16 +48,30 @@
   y = date.getFullYear()
   m = date.getMonth()+1 # yes...js months are 0 based
   folderDate =  y + "-" + ("0" + m).slice(-2)
-  opts = minimist process.argv.slice(2)
-  postTitle = folderDate + "_" + opts.title
+  knownOpts =
+    string: 'title'
+    defaults: folderDate
+  opts = if process.argv.length > 1 then minimist process.argv.slice(2), knownOpts else {'title':null}
+  fileTitle = if opts.title then "_"+opts.title.replace(/\s+/g, '-').toLowerCase() else ""
+  humanTitle = opts.title
+  postTitle = folderDate + fileTitle

   postDir = "contents/articles/" + "#{ postTitle }"
-  fs.mkdir postDir
+  try
+    fs.mkdirSync postDir
+  catch e
+    if e.code == 'EEXIST'
+      gutil.log gutil.colors.white.bgRed("Warning: "), "Directory already exists at path:", gutil.colors.white.bgRed("#{ e.path }"),  "\nTry an alternate title with gulp newPost --title 'my post title'"
+    return
+
   postHeader = "---\n
-                title: #{ opts.title }\n
+                title: #{ humanTitle }\n
                 date: #{ date }\n
+                author: Pupil Dev Team\n
                 ---"
-  fs.writeFile postDir+"/index.md", postHeader
+  fs.writeFile postDir+"/index.md", postHeader
+  gutil.log gutil.colors.white.bgBlue("Success! "), "New post created at", gutil.colors.white.bgBlue("#{ postDir }")
+

 gulp.task "preview", ->
     wintersmith.preview()
@@ -73,16 +87,13 @@

   gulp.watch "assets/coffeescript/**", ->
     js()
-    console.log "Coffeescript file changed. Compiling and reloading..."
+    gutil.log gutil.colors.white.bgBlue("Coffeescript file changed."), "Compiling and reloading..."

   gulp.watch "assets/stylus/**", ->
     css()
-    console.log "Stylus file changed. Compiling and reloading..."
+    gutil.log gutil.colors.white.bgBlue("Stylus file changed."), "Compiling and reloading..."

-  gulp.watch "contents/store/products/products.json", ->
-    copyProductJSON()
-    console.log "Product list changed. Copying file to coffeescript asset."
   # gulp.watch "templates/**", ->
   #   jade()
-  #   console.log "Template file changed. Compiling and reloading..."
+  #   gutil.log "Template file changed. Compiling and reloading..."


Replaced yargs

Replaced yargs

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -4,7 +4,6 @@
 gulp        = require 'gulp'
 path        = require 'path'
 nconf       = require 'nconf'
-yargs       = require 'yargs'
 gulpif      = require 'gulp-if'
 sass        = require 'gulp-sass'
 debug       = require 'gulp-debug'
@@ -30,12 +29,9 @@
      .env()
      .file({ file: 'local_settings.json' });

-# read commandline params into object
-args = yargs.argv
-
 # Set some options for debugging
 debug_opts = {}
-debug_opts.verbose = args.v? || args.verbose?
+debug_opts.verbose = nconf.get('v')? || nconf.get('verbose')?

 # Also require the webserver and live-reload related tasks
 require './gulp-serve.coffee'


support sourcemap ?

support sourcemap ?

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -49,6 +49,7 @@
     .src paths.appDir + '/index.html'
     .pipe usemin()
     .pipe gulp.dest paths.distDir
+    .pipe browserSync.reload(stream: true)

 gulp.task 'karma', (done) ->
   karma = require 'gulp-karma'
@@ -89,10 +90,11 @@
   merge = require 'merge-stream'
   compiled = gulp
     .src paths.appFiles
+    .pipe sourcemaps.init()
     .pipe typescript typescriptProject
   merge(
     compiled.dts.pipe gulp.dest paths.compiledAppDir
-    compiled.js.pipe gulp.dest paths.compiledAppDir
+    compiled.js.pipe(sourcemaps.write()).pipe gulp.dest paths.compiledAppDir
   )

 gulp.task 'typescript-app', ->
@@ -143,6 +145,7 @@
       filename: 'main.js'
     resolve:
       extensions: ['', '.js']
+    devtool: 'eval'
   gulp
     .src paths.compiledApp
     .pipe webpack options


Fix issue with gulpfile

Fix issue with gulpfile

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -67,7 +67,7 @@
   gulp.watch "./src/**/*", ["compile", "restart"]

 gulp.task "watch", ->
-  gulp.watch "./src/**/*", "compile"
+  gulp.watch "./src/**/*", ["compile"]

 gulp.task "clean:out", (cb) ->
   del ["out/"], cb


Promisified add-admin script.

Promisified add-admin script.

--- add-admin.coffee
+++ add-admin.coffee
@@ -17,25 +17,33 @@
 'use strict'

-lib = require './lib.coffee'
-sync = require 'sync'
+{async, await} = require 'asyncawait'
+Promise = require 'bluebird'

 readline = require 'readline'
 rl = readline.createInterface
 	input: process.stdin
 	output: process.stdout

+lib = require './lib.coffee'
+
 u = null
 p = null

-ask = (what, checker, rules, callback) ->
-	rl.question "#{what} (#{rules}): ", (answer) ->
-		if checker(answer) is true
-			callback null, answer
-		else
-			ask(what, checker, rules, callback)
+ask = (what, checker, rules) ->

-sync ->
+	_ask = (what, checker, rules, callback) ->
+		rl.question "#{what} (#{rules}): ", (answer) ->
+			if checker(answer) is true
+				callback answer
+			else
+				_ask(what, checker, rules, callback)
+
+	return new Promise (resolve, reject) ->
+		_ask what, checker, rules, resolve
+
+
+(async ->
 	if process.argv[2] is '-h' or process.argv[2] is '--help'
 		console.log 'Usage: no params, or <username>, or <username> <password>'
 		console.log 'You will be prompted to enter missing params from tty.'
@@ -46,28 +54,26 @@
 		p = process.argv[3]
 	else if process.argv.length is 3
 		u = process.argv[2]
-		p = ask.sync null, 'Password', lib.validation.passwordIsValid, '4-32 symbols, [!@#$%^&*()_+A-Za-z0-9]'
+		p = await ask 'Password', lib.validation.passwordIsValid, '4-32 symbols, [!@#$%^&*()_+A-Za-z0-9]'
 	else if process.argv.length is 2
-		u = ask.sync null, 'Username', lib.validation.usernameIsValid, '2-32 symbols, [a-zA-Z0-9а-яА-ЯёЁйЙру _-]'
-		p = ask.sync null, 'Password', lib.validation.passwordIsValid, '4-32 symbols, [!@#$%^&*()_+A-Za-z0-9]'
+		u = await ask 'Username', lib.validation.usernameIsValid, '2-32 symbols, [a-zA-Z0-9а-яА-ЯёЁйЙру _-]'
+		p = await ask 'Password', lib.validation.passwordIsValid, '4-32 symbols, [!@#$%^&*()_+A-Za-z0-9]'
 	else
 		console.log 'Usage: no params, or <username>, or <username> <password>'
 		console.log 'You will be prompted to enter missing params from tty.'
 		process.exit 2

-	config = require './config'
+	config = require './config.coffee'
 	anyDB = require 'any-db'
-	conn = anyDB.createConnection config.DATABASE_URL
+	conn = Promise.promisifyAll(anyDB.createConnection(config.DATABASE_URL))

-	try
-		exists = lib.user.userExists.sync null, conn, u
-		if exists is true
-			console.log "User `#{u}` already exists."
-			process.exit 1
+	exists = await lib.user.userExists conn, u
+	if exists is true
+		console.log "User `#{u}` already exists."
+		process.exit 1

-		lib.user.registerUser.sync null, conn, u, p, 'admin'
-		console.log "New admin `#{u}` registered successfully."
-		process.exit 0
-	catch ex
-		console.error ex.stack
-		process.exit 1
+	await lib.user.registerUser conn, u, p, 'admin'
+
+	console.log "New admin `#{u}` registered successfully."
+	process.exit 0
+)()


remove pointless assignment

remove pointless assignment

--- client/Main/kodingappscontroller.coffee
+++ client/Main/kodingappscontroller.coffee
@@ -185,8 +185,6 @@
         KD.utils.defer -> obj.appendToSelector 'head'

     else
-
-      url  = "#{url}"
       bind = ''
       load = noop


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/color.coffee
+++ src/color.coffee
@@ -45,7 +45,12 @@
       parsedColor = [parseInt(arguments[0]), parseInt(arguments[1]), parseInt(arguments[2]), parseFloat(alpha)]
     else if color.length > 2 && Object.prototype.toString.call(color) == '[object Array]'
       alpha = if color[3]? then color[3] else 1
-      parsedColor = [parseInt(color[0]), parseInt(color[1]), parseInt(color[2]), parseFloat(alpha)]
+      parsedColor = [
+        parseInt(color[0]),
+        parseInt(color[1]),
+        parseInt(color[2]),
+        parseFloat(alpha)
+      ]
     else
       parsedColor = lookup[normalizeKey(color)] || parseHex(color) || parseRGB(color)


Add meta-w keybinding to close window

Add meta-w keybinding to close window

--- src/atom/window.coffee
+++ src/atom/window.coffee
@@ -33,6 +33,7 @@

   bindKeys: ->
     @bindKey 'meta+s', => @editor.save()
+    @bindKey 'meta+w', => @close()

   bindMenuItems: ->
     @bindMenuItem "File > Save", => @editor.save()


CUI.Common

CUI.Common

- Fixed $tr_one_row: don't error out if an empty argument is passed.
  Bug introduced in b39bf6d63c4ec2c699b05c69f48e5e007fa7f58b.

--- src/base/Common.coffee
+++ src/base/Common.coffee
@@ -436,7 +436,7 @@
 			if $.isArray(___a)
 				for a in ___a
 					add_content(a)
-			else if ___a.DOM
+			else if ___a?.DOM
 				td.append(___a.DOM)
 			else if not isNull(___a)
 				td.append(___a)


pricing: change the description of "free plan"

--- client/Pricing/views/developerplan.coffee
+++ client/Pricing/views/developerplan.coffee
@@ -58,7 +58,7 @@
   updateContent: (index = @planIndex)->
     if index is 0
       title = 'Free Account'
-      desc  = '"free" as in "free speech"'
+      desc  = '"free" as in "free beer"'
       @buyNow.setTitle 'SIGN UP'
     else
       title = "#{index}x Resource Pack"


:memo: notes on scroll logic, fix scroll logic

--- src/components/pinned-header-footer-card/index.cjsx
+++ src/components/pinned-header-footer-card/index.cjsx
@@ -14,7 +14,7 @@

   getDefaultProps: ->
     buffer: 60
-    scrollSpeedBuffer: 20
+    scrollSpeedBuffer: 10

   getInitialState: ->
     pinned: false
@@ -35,18 +35,22 @@
     Math.abs(prevScrollTop - currentScrollTop) <= @props.scrollSpeedBuffer

   isScrollingUp: (prevScrollTop, currentScrollTop) ->
-    (prevScrollTop - currentScrollTop) > @props.scrollSpeedBuffer
+    currentScrollTop < prevScrollTop
+
+  isScrollingDown: (prevScrollTop, currentScrollTop) ->
+    currentScrollTop > prevScrollTop

   shouldPinHeader: (prevScrollTop, currentScrollTop) ->
-    if @isScrollingSlowed(prevScrollTop, currentScrollTop)
-      # keep as previous pinned state
+    if @isScrollingDown(prevScrollTop, currentScrollTop)
+      # on down scroll, check if the scroll position is past buffer
+      shouldPinHeader = (currentScrollTop > @props.buffer)
+    # if upscrolling is slow
+    else if @isScrollingSlowed(prevScrollTop, currentScrollTop)
+      # keep as current pinned state
       shouldPinHeader = @state.pinned
     else if @isScrollingUp(prevScrollTop, currentScrollTop)
       # unpinned on upscroll
       shouldPinHeader = false
-    else
-      # otherwise, on down scroll, check if the scroll position is past buffer
-      shouldPinHeader = (currentScrollTop > @props.buffer)

     shouldPinHeader

@@ -55,12 +59,14 @@
     stateNoScroll = _.omit(@state, 'isScrolling', 'scrollTop')
     nextStateNoScroll = _.omit(nextState, 'isScrolling', 'scrollTop')

-    # manually check if should pin has changed
+    # manually check if pinned will change
     didShouldPinChange = not @state.pinned is @shouldPinHeader(@state.scrollTop, nextState.scrollTop)
     # check props and non-scroll states
     didPropsUpdate = not _.isEqual(@props, nextProps)
     didStateUpdate = not _.isEqual(stateNoScroll, nextStateNoScroll)

+    # component should only update if pin state will change and non-scroll props or state will change
+    # this is to bypass the component trying to update at every frame the user is scrolling
     didShouldPinChange or didPropsUpdate or didStateUpdate

   componentDidUpdate: (prevProps, prevState) ->
@@ -68,7 +74,7 @@
       'remove' # remove class if shouldPinHeader is false
       'add' # add class if shouldPinHeader is true
     ]
-
+    # set the pinned state
     @setState(pinned : @shouldPinHeader(prevState.scrollTop, @state.scrollTop))
     shouldPinHeader = @state.pinned * 1
     classAction = addOrRemove[shouldPinHeader]


swiching

swiching

--- rtmp.coffee
+++ rtmp.coffee
@@ -542,8 +542,16 @@
       continue

     if session.isPlaying && !session.streamPublisher
+      if session.stream_switched
+        session.server.sendVideoPacket session.stream, [session.stream.spsPacket], session.stream.last_pts, session.stream.last_dts
+        session.server.sendVideoPacket session.stream, [session.stream.ppsPacket], session.stream.last_pts, session.stream.last_dts
+        #session.constructMetadata 10, (meta) =>
+        #  session.sendData meta
+        session.stream_switched = false
+        console.log "metadata re-sent"
+
       for rtmpMessage in msgs
-        rtmpMessage.timestamp = session.getScaledTimestamp(rtmpMessage.originalTimestamp) % TIMESTAMP_ROUNDOFF
+        session.lastTimestamp = rtmpMessage.timestamp = session.offsetTimestamp + session.getScaledTimestamp(rtmpMessage.originalTimestamp) % TIMESTAMP_ROUNDOFF

       if msgs.length > 1
         buf = createRTMPAggregateMessage msgs, session.chunkSize
@@ -771,8 +779,8 @@
   , chunkSize

 class RTMPSession
-  constructor: (socket, stream) ->
-    console.log "[rtmp] created a new session in #{stream}"
+  constructor: (socket, server) ->
+    console.log "[rtmp] created a new session in #{server}"
     @listeners = {}
     @state = SESSION_STATE_NEW
     @socket = socket
@@ -786,6 +794,10 @@
     @windowAckSize = null
     @lastSentAckBytes = 0
     @receivedBytes = 0
+    @stream_switched = false
+    @server = server
+    @lastTimestamp = 0
+    @offsetTimestamp = 0

   clearTimeout: ->
     if @timeoutTimer?
@@ -1543,6 +1555,18 @@
       ]
     , @chunkSize

+    @constructMetadata 0, (metadata) =>
+      callback null, @concatenate [
+        setChunkSize, streamBegin1, playReset,
+        playStart,
+        rtmpSampleAccess, metadata
+      ]
+
+    # ready for playing
+    @isWaitingForKeyFrame = true
+
+
+  constructMetadata: (timestamp, callback) ->
     metadata =
       canSeekToEnd: false
       cuePoints   : []
@@ -1571,7 +1595,7 @@

     onMetaData = createAMF0DataMessage
       chunkStreamID: 4
-      timestamp: 0
+      timestamp: timestamp
       messageStreamID: 1
       objects: [
         createAMF0Data('onMetaData'),
@@ -1581,17 +1605,9 @@

     codecConfigs = @getCodecConfigs()

-    callback null, @concatenate [
-      setChunkSize, streamBegin1, playReset,
-      playStart,
-      rtmpSampleAccess, onMetaData,
-      codecConfigs
-    ]
-
-    # ready for playing
-    @isWaitingForKeyFrame = true
-
-  getCodecConfigs: ->
+    callback @concatenate [ onMetaData, codecConfigs ]
+
+  getCodecConfigs: (timestamp) ->
     configMessages = []

     spsPacket = @stream.spsPacket
@@ -1631,7 +1647,7 @@
       ]
       videoConfigMessage = createVideoMessage
         body: buf
-        timestamp: 0
+        timestamp: timestamp
         chunkSize: @chunkSize
       configMessages.push videoConfigMessage

@@ -1650,7 +1666,7 @@

       audioConfigMessage = createAudioMessage
         body: buf
-        timestamp: 0
+        timestamp: timestamp
         chunkSize: @chunkSize
       configMessages.push audioConfigMessage

@@ -1947,13 +1963,21 @@

 class RTMPServer
   setBroadcastStream: (name) ->
-    console.log "setBroadcastStream"
+    console.log "setBroadcastStream", name
     broadcasted_stream = name
     stream = getStreamByName(name)
+    stream_ts = 0
     for clientID, session of sessions
-      if session.streamName == "broadcast"
+      if session.stream == stream
+        stream_ts = session.lastTimestamp
+
+    console.log "setBroadcastStream", stream_ts
+    for clientID, session of sessions
+      if session.streamName == "broadcast" && session.stream != stream
+        session.offsetTimestamp = session.lastTimestamp - stream_ts
         session.stream = stream
-        console.log "switched stream"
+        session.stream_switched = true
+        console.log "switched stream", session.offsetTimestamp
     return

   on: (event, listener) ->
@@ -1976,7 +2000,7 @@
     @server = net.createServer (c) =>
       console.log "[rtmp] new client"
       c.clientId = ++clientMaxId
-      sess = new RTMPSession c
+      sess = new RTMPSession c, this
       sessions[c.clientId] = sess
       sessionsCount++
       c.rtmpSession = sess
@@ -2043,10 +2067,13 @@

   # Packets must be come in DTS ascending order
   sendVideoPacket: (stream, nalUnits, pts, dts) ->
+    stream.last_pts = pts
+    stream.last_dts = dts
+
     if dts > pts
       throw new Error "pts must be >= dts (pts=#{pts} dts=#{dts})"
     timestamp = convertPTSToMilliseconds dts
-    lastTimestamp = timestamp
+    @lastTimestamp = lastTimestamp = timestamp

     if sessionsCount + rtmptSessionsCount is 0
       return


upgrade wakatime cli to v4.1.7

upgrade wakatime cli to v4.1.7

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -14,7 +14,7 @@
 request = require 'request'
 rimraf = require 'rimraf'

-latestCLIVersion = '4.1.6'
+latestCLIVersion = '4.1.7'

 packageVersion = null
 unloadHandler = null


Don't use jquery unnecessarily

Don't use jquery unnecessarily

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -496,12 +496,12 @@
             when 'summary'
               if Editing && info.d.global
                 cell.classList.add('null')
-                $(cell).append('Whole volume')
+                cell.appendChild(document.createTextNode('Whole volume'))
                 del = cell.appendChild(document.createElement('a'))
                 del.className = 'button mini global-record white icon-text'
                 delicon = del.appendChild(document.createElement('span'))
                 delicon.className = 'icon trash'
-                $(del).append('Remove')
+                del.appendChild(document.createTextNode('Remove'))
                 $(del).on 'click', $scope.$lift(clickGlobal)
             else
               if info.metric.type == 'void' && info.d
@@ -560,7 +560,7 @@
                   add.className = 'button mini white global-record icon-text'
                   addicon = add.appendChild(document.createElement('span'))
                   addicon.className = 'add icon'
-                  $(add).append('Set as whole-volume descriptor')
+                  add.appendChild(document.createTextNode('Set as whole-volume descriptor'))
                   $(add).on 'click', $scope.$lift(clickGlobal)
                 td.id = ID + '-no_' + info.i + '_' + info.c
           else


trigger "input" on typeahead.

trigger "input" on typeahead.

It suddenly stopped workging with just typing.

--- test/javascripts/integration/admin_journal_user_roles_test.js.coffee
+++ test/javascripts/integration/admin_journal_user_roles_test.js.coffee
@@ -153,7 +153,7 @@
   fillIn '.admin-user-search-input', TahiTest.query
   click '.admin-user-search-button'
   click '.assign-role-button'
-  .then -> $('.add-role-input').typeahead 'val', 'Edit'
+  .then -> $('.add-role-input').val('Edit').trigger('input')
   .then -> click '.tt-suggestion'
   andThen -> ok Em.$.trim(find('.assigned-role').text()).indexOf('Editor') isnt -1


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -44,7 +44,7 @@
   equals o.test(), "my_val"
   equals o.test2, "cool"

-test ""
+test "#include same module t"

 test "#include multiple", ->
   o = Core


neo4j: add checker if activity feed is done loading before loading more; this temp. fixes infinite loading loop

--- client/landing/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Activity.kdapplication/AppController.coffee
@@ -308,10 +308,11 @@

   continueLoadingTeasers:->

+    return  if @isLoading
+
     # HACK: this gets called multiple times if there's no wait
-    KD.utils.wait 1000, =>
-      lastTimeStamp = (new Date @lastFrom or Date.now()).getTime()
-      @populateActivity {slug : "before/#{lastTimeStamp}", to: lastTimeStamp}
+    lastTimeStamp = (new Date @lastFrom or Date.now()).getTime()
+    @populateActivity {slug : "before/#{lastTimeStamp}", to: lastTimeStamp}

   teasersLoaded:->
     # the page structure has changed


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -28,6 +28,8 @@

   equals player.I.alpha, 0.2

+  player
+
   4.times ->
     player.update()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -9,6 +9,6 @@
   equals player.I.alpha, 0.9

   player.flicker
-    durat
+    duration:

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -12,31 +12,31 @@
     equals player.I.flickerFrequency, 3
     equals player.I.flickerDuration, 30

-test 'it should change the alpha value when the flicker duration has elapsed', ->
-  player = GameObject
-    alpha: 0.9
-
-  player.include Flickerable
+  test 'it should change the alpha value when the flicker duration has elapsed', ->
+    player = GameObject
+      alpha: 0.9

-  equals player.I.alpha, 0.9
-
-  player.flicker
-    duration: 20
-    frequency: 5
-    alpha: 0.2
+    player.include Flickerable

-  player.update()
-
-  equals player.I.alpha, 0.2
-
-  5.times ->
+    equals player.I.alpha, 0.9
+
+    player.flicker
+      duration: 20
+      frequency: 5
+      alpha: 0.2
+
     player.update()

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

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

 module()


[test,tool] progress, failure for fake jobs

--- test/job-creator.coffee
+++ test/job-creator.coffee
@@ -20,12 +20,28 @@
     stats.listen()

   cookTaco: (job, done) ->
-    {data, queue} = job
+    {data, queue, jobId} = job
     {protein, salsa, cooktime, orderNumber} = data
     log "#{queue.name} ##{orderNumber}: #{protein}, #{salsa} cooking for #{(cooktime/1000).toFixed 2}s"
+
+    progress = 10
+    update = ->
+      job.progress progress
+      progress += 25
+
+    maybeThrow = ->
+      dice = Math.random()
+      done new Error "taco #{jobId} burned" if dice < 0.2
+
     wake = ->
       log "#{queue.name} ##{orderNumber}: #{protein}, #{salsa} served"
       done()
+
+    setTimeout update, cooktime/5
+    setTimeout update, cooktime/5 * 2
+    setTimeout update, cooktime/5 * 3
+    setTimeout update, cooktime/5 * 4
+    setTimeout maybeThrow, cooktime * Math.random()
     setTimeout wake, cooktime

   makeJob: (orderNumber) ->


Set url for updating vocab

Set url for updating vocab

--- exercises/src/api/index.coffee
+++ exercises/src/api/index.coffee
@@ -28,11 +28,8 @@

   connectModify(ExerciseActions, {trigger: 'publish', onSuccess: 'published'},
     (id) ->
-      obj = ExerciseStore.get(id)
-      uid = ExerciseStore.getId(id)
-
-      url: "exercises/#{uid}/publish"
-      data: obj
+      url: "exercises/#{ExerciseStore.getId(id)}/publish"
+      data: ExerciseStore.get(id)
   )

   connectDelete(ExerciseActions, {trigger: 'deleteAttachment', onSuccess: 'attachmentDeleted'},
@@ -48,9 +45,10 @@
     data: VocabularyStore.get(id)
   )

-  connectModify(VocabularyActions,
-    trigger: 'publish', onSuccess: 'published'
-    route: VocabularyStore.get, data: VocabularyStore.get
+  connectModify(VocabularyActions, {trigger: 'publish', onSuccess: 'published'},
+    (id) ->
+      url: "vocab_terms/#{id}/publish"
+      data: VocabularyStore.get(id)
   )

 uploadExerciseImage = (exerciseUid, image, cb) ->


Created a better filter for the flags

Created a better filter for the flags

--- src/dploy.coffee
+++ src/dploy.coffee
@@ -41,8 +41,8 @@
 			@ignoreInclude = @servers.indexOf("-i") >= 0 or @servers.indexOf("--ignore-include") >= 0
 			# Check if we should catchup with the server and only upload the revision file
 			@catchup = @servers.indexOf("-c") >= 0 or @servers.indexOf("--catchup") >= 0
-			# Remove the ignore flag from the server list
-			@servers = @servers.filter (value) -> value isnt "-i" and value isnt "--ignore-include"
+			# Filter the flags from the server names
+			@servers = @_filterFlags @servers, ["-i", "--ignore-include", "-c", "--catchup"]
 			# If you don't set any servers, add an empty one to upload the first environment only
 			@servers.push null if @servers.length is 0

@@ -67,4 +67,12 @@
 			else
 				process.exit(code=0)

-		return @+		return @
+
+
+	_filterFlags: (servers, flags) ->
+		servers = servers.filter (value) ->
+			valid = true
+			flags.forEach (flag) -> valid = false if flag is value
+			return valid
+		servers


Remove old coffee cache

Remove old coffee cache

--- src/coffee-cache.coffee
+++ src/coffee-cache.coffee
@@ -1,76 +0,0 @@
-crypto = require 'crypto'
-path = require 'path'
-
-CoffeeScript = require 'coffee-script'
-CSON = require 'season'
-fs = require 'fs-plus'
-
-cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache')
-
-stats =
-  hits: 0
-  misses: 0
-
-# Use separate compile cache when sudo'ing as root to avoid permission issues
-if process.env.USER is 'root' and process.env.SUDO_USER and process.env.SUDO_USER isnt process.env.USER
-  cacheDir = path.join(cacheDir, 'root')
-
-coffeeCacheDir = path.join(cacheDir, 'coffee')
-CSON.setCacheDir(path.join(cacheDir, 'cson'))
-
-getCachePath = (coffee) ->
-  digest = crypto.createHash('sha1').update(coffee, 'utf8').digest('hex')
-  path.join(coffeeCacheDir, "#{digest}.js")
-
-getCachedJavaScript = (cachePath) ->
-  if fs.isFileSync(cachePath)
-    try
-      cachedJavaScript = fs.readFileSync(cachePath, 'utf8')
-      stats.hits++
-      return cachedJavaScript
-  return
-
-convertFilePath = (filePath) ->
-  if process.platform is 'win32'
-    filePath = "/#{path.resolve(filePath).replace(/\\/g, '/')}"
-  encodeURI(filePath)
-
-compileCoffeeScript = (coffee, filePath, cachePath) ->
-  {js, v3SourceMap} = CoffeeScript.compile(coffee, filename: filePath, sourceMap: true)
-  stats.misses++
-  # Include source map in the web page environment.
-  if btoa? and JSON? and unescape? and encodeURIComponent?
-    js = "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=#{convertFilePath(filePath)}"
-  try
-    fs.writeFileSync(cachePath, js)
-  js
-
-requireCoffeeScript = (module, filePath) ->
-  coffee = fs.readFileSync(filePath, 'utf8')
-  cachePath = getCachePath(coffee)
-  js = getCachedJavaScript(cachePath) ? compileCoffeeScript(coffee, filePath, cachePath)
-  module._compile(js, filePath)
-
-module.exports =
-  cacheDir: cacheDir
-
-  register: ->
-    Object.defineProperty(require.extensions, '.coffee', {
-      writable: false
-      value: requireCoffeeScript
-    })
-
-  addPathToCache: (filePath) ->
-    switch path.extname(filePath)
-      when '.coffee'
-        content = fs.readFileSync(filePath, 'utf8')
-        cachePath = getCachePath(coffee)
-        compileCoffeeScript(coffee, filePath, cachePath)
-      when '.cson'
-        CSON.readFileSync(filePath)
-      when '.js'
-        require('./6to5').addPathToCache(filePath)
-
-  getCacheMisses: -> stats.misses
-
-  getCacheHits: -> stats.hits


Add a testcase involving a long line.

Add a testcase involving a long line.

--- test/parser_line.coffee
+++ test/parser_line.coffee
@@ -17,6 +17,12 @@
         it 'parses simple text line', ->
             res = line.run 'Hello World.'
             expect(res).to.eql ['Hello World.']
+
+        it 'parses long text line', ->
+            long = "A bc def ghij klmno pqrstu vwxyzäö ü1234567 890 "
+            long = long + long + long + long
+            res = line.run long
+            expect(res).to.eql [long]

         it 'parses simple element', ->
             res = line.run '[math]'


change private member variable name

change private member variable name

--- src/event.coffee
+++ src/event.coffee
@@ -67,19 +67,19 @@
             this

         delegate_on: (delegator) ->
-            this.delegators || (this.delegators = new collection.List());
-            this.delegators.append delegator
+            this._delegators || (this._delegators = new collection.List());
+            this._delegators.append delegator

             this

         delegate_off: (delegator) ->
-            return this if not this.delegators
-            this.delegators.remove delegator
+            return this if not this._delegators
+            this._delegators.remove delegator

             this

         delegate: ->
-            delegateEvents(this.delegators, arguments) if this.delegators and this.delegators.size() > 0
+            delegateEvents(this._delegators, arguments) if this._delegators and this._delegators.size() > 0

             return this if (!this._listeners)

@@ -106,7 +106,7 @@
                 name: name
             });

-            delegateEvents(this.delegators, args) if this.delegators and this.delegators.size() > 0
+            delegateEvents(this._delegators, args) if this._delegators and this._delegators.size() > 0

             return this if not this._listeners

@@ -163,8 +163,8 @@

         true

-    triggerEvents = (events, args) ->
-        ev.callback.apply(ev.ctx, args) for ev in events
+    triggerEvents = (listeners, args) ->
+        ev.callback.apply(ev.ctx, args) for ev in listeners

     delegateEvents = (delegators, args) ->
         delegators.forEach (delegator) ->


Reload, reloadgit add .

Reload, reloadgit add .

--- modules/player/index.coffee
+++ modules/player/index.coffee
@@ -3,7 +3,7 @@
 reload = require('require-reload')(require)
 AudioModuleCommands = reload './commands'
 audioFilters = reload '../../filters'
-AudioHud = require './hud'
+AudioHud = reload './hud'

 class PlayerModule extends BotModule
   init: =>


don't wait for dom to load

don't wait for dom to load

--- vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
+++ vendor/assets/javascripts/twitter/bootstrap/rails/confirm.coffee
@@ -1,75 +1,74 @@
-$ ->
-  $.fn.twitter_bootstrap_confirmbox =
-    defaults:
-      title: null
-      cancel: "Cancel"
-      proceed: "OK"
-      proceed_class: "btn proceed btn-primary"
+$.fn.twitter_bootstrap_confirmbox =
+  defaults:
+    title: null
+    cancel: "Cancel"
+    proceed: "OK"
+    proceed_class: "btn proceed btn-primary"

-  TwitterBootstrapConfirmBox = (message, element, callback) ->
-    $(document.body).append(
-      $(
-        '

-
<button type="button" class="close" data-dismiss="modal">×</button>
...

-

-
......

-
'
-      )
+TwitterBootstrapConfirmBox = (message, element, callback) ->
+  $(document.body).append(
+    $(
+      '

+
<button type="button" class="close" data-dismiss="modal">×</button>
...

+

+
......

+
'
     )
+  )

-    $("#confirmation_dialog .modal-body").html(message)
-    $("#confirmation_dialog .modal-header h3").html(element.data("confirm-title") || $.fn.twitter_bootstrap_confirmbox.defaults.title || window.top.location.origin)
-    $("#confirmation_dialog .modal-footer .cancel").html(element.data("confirm-cancel") || $.fn.twitter_bootstrap_confirmbox.defaults.cancel)
-    $("#confirmation_dialog .modal-footer .proceed").html(element.data("confirm-proceed") || $.fn.twitter_bootstrap_confirmbox.defaults.proceed).attr("class", $.fn.twitter_bootstrap_confirmbox.defaults.proceed_class).addClass(element.data("confirm-proceed-class"))
-    $("#confirmation_dialog").modal "show"
+  $("#confirmation_dialog .modal-body").html(message)
+  $("#confirmation_dialog .modal-header h3").html(element.data("confirm-title") || $.fn.twitter_bootstrap_confirmbox.defaults.title || window.top.location.origin)
+  $("#confirmation_dialog .modal-footer .cancel").html(element.data("confirm-cancel") || $.fn.twitter_bootstrap_confirmbox.defaults.cancel)
+  $("#confirmation_dialog .modal-footer .proceed").html(element.data("confirm-proceed") || $.fn.twitter_bootstrap_confirmbox.defaults.proceed).attr("class", $.fn.twitter_bootstrap_confirmbox.defaults.proceed_class).addClass(element.data("confirm-proceed-class"))
+  $("#confirmation_dialog").modal "show"

-    $("#confirmation_dialog .proceed").click ->
-      $("#confirmation_dialog").modal("hide").remove()
-      callback()
-      true
+  $("#confirmation_dialog .proceed").click ->
+    $("#confirmation_dialog").modal("hide").remove()
+    callback()
+    true

-    $("#confirmation_dialog .cancel").click ->
-      $("#confirmation_dialog").modal("hide").remove()
-      false
+  $("#confirmation_dialog .cancel").click ->
+    $("#confirmation_dialog").modal("hide").remove()
+    false

-  $.rails.allowAction = (element) ->
-    message = element.data("confirm")
-    answer = false
-    return true unless message
+$.rails.allowAction = (element) ->
+  message = element.data("confirm")
+  answer = false
+  return true unless message

-    if $.rails.fire(element, "confirm")
-      TwitterBootstrapConfirmBox message, element, ->
-        if $.rails.fire(element, "confirm:complete", [answer])
-          allowAction = $.rails.allowAction
+  if $.rails.fire(element, "confirm")
+    TwitterBootstrapConfirmBox message, element, ->
+      if $.rails.fire(element, "confirm:complete", [answer])
+        allowAction = $.rails.allowAction

-          $.rails.allowAction = ->
-            true
-
-          if element.get(0).click
-            element.get(0).click()
-          else if $.isFunction(document.createEvent)
-            evt = document.createEvent "MouseEvents"
-            evt.initMouseEvent(
-              "click",
-              true,   # e.bubbles,
-              true,   #e.cancelable,
-              window, #e.view,
-              0,      #e.detail,
-              0,      #e.screenX,
-              0,      #e.screenY,
-              0,      #e.clientX,
-              0,      #e.clientY,
-              false,  #e.ctrlKey,
-              false,  #e.altKey,
-              false,  #e.shiftKey,
-              false,  #e.metaKey,
-              0,      #e.button,
-              document.body.parentNode #e.relatedTarget
-            )
-            element.get(0).dispatchEvent(evt)
-          else
-            element.trigger "click"
+        $.rails.allowAction = ->
+          true
+
+        if element.get(0).click
+          element.get(0).click()
+        else if $.isFunction(document.createEvent)
+          evt = document.createEvent "MouseEvents"
+          evt.initMouseEvent(
+            "click",
+            true,   # e.bubbles,
+            true,   #e.cancelable,
+            window, #e.view,
+            0,      #e.detail,
+            0,      #e.screenX,
+            0,      #e.screenY,
+            0,      #e.clientX,
+            0,      #e.clientY,
+            false,  #e.ctrlKey,
+            false,  #e.altKey,
+            false,  #e.shiftKey,
+            false,  #e.metaKey,
+            0,      #e.button,
+            document.body.parentNode #e.relatedTarget
+          )
+          element.get(0).dispatchEvent(evt)
+        else
+          element.trigger "click"

-          $.rails.allowAction = allowAction
-
-    false+        $.rails.allowAction = allowAction
+
+  false


fix #709 add validation for nicEdit-main in card

--- app/assets/javascripts/card.js.coffee
+++ app/assets/javascripts/card.js.coffee
@@ -3,7 +3,7 @@
 validateForm = (event) ->
   validated = false
   $(".card-form:first-child .validate").each (index, element) ->
-    if $(element).val() != ""
+    if $(element).val() != "" || $(element).text() != ""
       validated = true
   unless validated
     alert "You cannot make empty card."
@@ -16,6 +16,7 @@
     buttonList: ["material", "tool", "blueprint", "attachment", "ol", "ul", "bold", "italic", "underline"]
   }
   editor.panelInstance "markup-area"
+  $(".nicEdit-main").addClass "validate"

 $ ->
   $.rails.ajax = (option) ->


Rotate through carousel with keyboard.

Rotate through carousel with keyboard.

--- app/assets/javascripts/case.js.coffee
+++ app/assets/javascripts/case.js.coffee
@@ -32,6 +32,8 @@
   carouselLeft()  if $('.catalog-cases-featured').length > 0

 carouselLeft = () ->
+  return if window.mutex
+  window.mutex = true
   carousel = $(".catalog-cases-featured")
   items = carousel.children()
   if carouselShouldScroll carousel
@@ -40,8 +42,11 @@
     carousel.animate {scrollLeft: items[1].offsetLeft}, 500, "easeOutExpo", ->
       items.first().detach()
       carousel.scrollLeft 0
+      window.mutex = false

 carouselRight = () ->
+  return if window.mutex
+  window.mutex = true
   carousel = $(".catalog-cases-featured")
   items = carousel.children()
   if carouselShouldScroll carousel
@@ -50,6 +55,7 @@
     carousel.scrollLeft items[1].offsetLeft
     carousel.animate {scrollLeft: 0}, 500, "easeOutExpo", ->
       items.last().detach()
+      window.mutex = false

 $(document).on 'ready turbolinks:load', ->
   window.carouselScrollInterval = setInterval scrollCarousel, 5000
@@ -66,7 +72,10 @@
     $('.carousel-button-right').animate { right: '-3em';  }, 400, "easeInQuad", ->
       $(this).hide()

+  $(document).keydown (e) -> carouselRight() if e.which == 37  # Left arrow
+  $(document).keydown (e) -> carouselLeft() if e.which == 39  # Right arrow
   $('.carousel-button-left').on 'click', () -> carouselRight()
   $('.carousel-button-right').on 'click', () -> carouselLeft()
+
   $('.carousel').on 'swiperight', () -> carouselRight()
   $('.carousel').on 'swiperight', () -> carouselLeft()


updates for click tries

updates for click tries

--- app/assets/javascripts/game.js.coffee
+++ app/assets/javascripts/game.js.coffee
@@ -21,6 +21,8 @@

   p1_face_index = undefined
   p2_face_index = undefined
+
+  max_click_try = 3

   p1_click_try = 0
   p2_click_try = 0
@@ -90,7 +92,7 @@
     else
       """

Your turn

-
Please ask a question or choose face on apponent board (you have #{3 - p2_click_try} attempts)

+
Please ask a question or choose face on apponent board (you have #{max_click_try - p2_click_try} attempts)

           A person
           <select class="relation"><option value="+">have</option><option value="-">dont have</option></select>
@@ -131,6 +133,11 @@

     if (face_index == p2_face_index)
       finished('player1')
+    else
+      if p1_click_try == max_click_try
+        finished('player2')
+      else
+        choose_turn()

   choose_face_p2 = ->
@@ -140,6 +147,12 @@

     if (face_index == p1_face_index)
       finished('player2')
+    else
+      if p2_click_try == max_click_try
+        finished('player1')
+      else
+        choose_turn()
+

   choose_question = ->


Refactor: extract method set_fields_to_view_mode

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -149,12 +149,15 @@
     return 'Data you have entered may not be saved.' if is_dirty()
     return undefined                                      # 'undefined' suppresses 'leave page?' prompt

+set_fields_to_view_mode = ->
+  set_edit_mode_class(user_name(), edit_mode_checkbox().checked)  # TODO: smells; why do we need to know about edit_mode_checkbox?
+  set_edit_mode_class(user_title(), edit_mode_checkbox().checked)
+  set_edit_mode_class(user_email(), false)              # always 'false' because email is not editable
+
 ready = ->
   if is_resume_page()
     install_handlers()
-    set_edit_mode_class(user_name(), edit_mode_checkbox().checked)  # TODO: smells; why do we need to know about edit_mode_checkbox?
-    set_edit_mode_class(user_title(), edit_mode_checkbox().checked)
-    set_edit_mode_class(user_email(), false)              # always 'false' because email is not editable
+    set_fields_to_view_mode()
     set_dirty(false)
     display_data()


Refactoring

Refactoring

--- app/assets/javascripts/home.js.coffee
+++ app/assets/javascripts/home.js.coffee
@@ -27,19 +27,19 @@

   # infinite scroll
   baseAjaxURL = '/?max_tag_id='
-  isLoading = false
+  isAjaxLoading = false
   ajaxCount = 0
   ajaxInterval = 1000
   setInterval ->
     # judgment
-    power = 5
-    docH = $(document).height()
-    winH = $(window).height()
-    scrT = $(window).scrollTop()
-    restH  = docH - scrT - winH
-    return unless restH < winH * power
+    _power = 5
+    _docH  = $(document).height()
+    _winH  = $(window).height()
+    _scrT  = $(window).scrollTop()
+    _restH = _docH - _scrT - _winH
+    return unless _restH < _winH * _power
     return if ajaxCount > 30
-    return if isLoading
+    return if isAjaxLoading

     # reset ajaxCount
     $(window).scroll ->
@@ -51,7 +51,7 @@

   ajaxLoading = ->
     ajaxCount += 1
-    isLoading = true
+    isAjaxLoading = true
     $.ajax({
       type: 'GET',
       url: nextAjaxURL(),
@@ -60,9 +60,9 @@
       success: (json)->
         addNewImages(json.data)
         nextAjaxURL(json.pagination.next_max_tag_id)
-        isLoading = false
+        isAjaxLoading = false
       error: (a,b,c)->
-        isLoading = false
+        isAjaxLoading = false
     })

   nextAjaxURL = (->
@@ -80,11 +80,11 @@
       if spamFilter(d)
         spamCount += 1
         return true
-      obj= {}
-      obj.link = d.link
-      obj.url = d.images.low_resolution.url
-      obj.text = if d.caption then $.trim(d.caption.text) else ''
-      $html = $($.trim(JST['templates/home/pic_list_item'](obj)))
+      _obj= {}
+      _obj.link = d.link
+      _obj.url = d.images.low_resolution.url
+      _obj.text = if d.caption then $.trim(d.caption.text) else ''
+      $html = $($.trim(JST['templates/home/pic_list_item'](_obj)))
       $html.children('a').css(visibility: 'hidden')
       addImageLoadedListener($html)
       $('#js-pic-list').append($html)
@@ -94,15 +94,15 @@
   ignoreTagNames = [ 'support', 'skin', 'random', 'shadow', 'fashion', 'perfect' ]
   tagCountThreshold = 30
   spamFilter = (d)->
-    isSpam = false
+    _isSpam = false
     if d.tags.length >= tagCountThreshold
       console.log("Threshold: " + d.link) if isDev
       return true
     _(ignoreTagNames).each (tag)->
       if _(d.tags).include(tag)
         console.log("Tag: " + d.link) if isDev
-        isSpam = true
-    if isSpam then true else false
+        _isSpam = true
+    if _isSpam then true else false

   addImageLoadedListener = ($html)->
     $html.imagesLoaded ->


begin refactor js code

begin refactor js code

--- app/assets/javascripts/logs.js.coffee
+++ app/assets/javascripts/logs.js.coffee
@@ -1,11 +1,11 @@
 root = global ? window

 root.MongodbLoggerMain =
-  tail_logs_url: null
-  tail_log_started: false
-  log_info_offset: null
-  log_info_padding: 15
-  is_charts_ready: false
+  tailLogsUrl: null
+  tailLogStarted: false
+  logInfoOffset: null
+  logInfoPadding: 15
+  isChartsReady: false

   init: ->
     # spinner
@@ -15,12 +15,12 @@
       $('#ajax_loader').hide()
     # tail logs buttons
     $(document).on 'click', '#tail_logs_link', (event) =>
-      MongodbLoggerMain.tail_logs_url = $(event.target).attr('data-url')
+      MongodbLoggerMain.tailLogsUrl = $(event.currentTarget).attr('data-url')
       $('#tail_logs_block').addClass('started')
       MongodbLoggerMain.tail_logs(null)
       return false
     $(document).on 'click', '#tail_logs_stop_link', (event) =>
-      MongodbLoggerMain.tail_log_started = false
+      MongodbLoggerMain.tailLogStarted = false
       $('#tail_logs_block').removeClass('started')
       return false
     # log info click
@@ -125,7 +125,7 @@
     $('body').bind 'pjax:end', () =>
       $('#ajax_loader').hide()
       # stop tailing
-      MongodbLoggerMain.tail_log_started = false
+      MongodbLoggerMain.tailLogStarted = false
       # scroll on top
       if ($(window).scrollTop() > 100)
         $('html, body').stop().animate({ scrollTop: 0 }, 'slow')
@@ -144,23 +144,23 @@
       yearRange: 'c-50:c+10'
     # log info window
     if $("#log_info").length > 0
-      MongodbLoggerMain.log_info_offset = $("#log_info").offset()
+      MongodbLoggerMain.logInfoOffset = $("#log_info").offset()
       $(window).scroll =>
-        if $(window).scrollTop() > MongodbLoggerMain.log_info_offset.top
+        if $(window).scrollTop() > MongodbLoggerMain.logInfoOffset.top
           $("#log_info").stop().animate
-            marginTop: $(window).scrollTop() - MongodbLoggerMain.log_info_offset.top + MongodbLoggerMain.log_info_padding
+            marginTop: $(window).scrollTop() - MongodbLoggerMain.logInfoOffset.top + MongodbLoggerMain.logInfoPadding
         else
           $("#log_info").stop().animate
             marginTop: 0
   # tail logs function
   tail_logs: (log_last_id) ->
-    url = MongodbLoggerMain.tail_logs_url
+    url = MongodbLoggerMain.tailLogsUrl
     if log_last_id? && log_last_id.length > 0
-      url = MongodbLoggerMain.tail_logs_url + "/" + log_last_id
+      url = MongodbLoggerMain.tailLogsUrl + "/" + log_last_id
     else
-      MongodbLoggerMain.tail_log_started = true
+      MongodbLoggerMain.tailLogStarted = true
       log_last_id = ""
-    if MongodbLoggerMain.tail_log_started
+    if MongodbLoggerMain.tailLogStarted
       $.ajax
         url: url
         dataType: "json"
@@ -175,7 +175,7 @@
               $('#logs_list tr:first').after(elements).effect("highlight", {}, 1000)
             if data.collection_stats && $("#collection_stats").length > 0
               $("#collection_stats").html(data.collection_stats)
-          if MongodbLoggerMain.tail_log_started
+          if MongodbLoggerMain.tailLogStarted
             fcallback = -> MongodbLoggerMain.tail_logs(log_last_id)
             setTimeout fcallback, 2000
   # move using keys by logs
@@ -222,10 +222,10 @@
     return ((docViewTop < elemTop) && (docViewBottom > elemBottom))
   # charts ready for usage
   init_analytic_charts: ->
-    MongodbLoggerMain.is_charts_ready = true
+    MongodbLoggerMain.isChartsReady = true
   # build charts
   build_analytic_charts: (data) ->
-    if MongodbLoggerMain.is_charts_ready is true
+    if MongodbLoggerMain.isChartsReady is true
       if data.data?
         data_table = new google.visualization.DataTable()
         data_table.addColumn('date', 'Date')


type bug for filtering

type bug for filtering

--- app/assets/javascripts/main.js.coffee
+++ app/assets/javascripts/main.js.coffee
@@ -80,9 +80,10 @@
         for id, options of mappings
           if options.length > 0
             css_id = '#q_' + id
-            console.log css_id, options
+            #console.log css_id, options
             $('option', css_id).each () ->
-              if @.value and options.indexOf(@.value) == -1
+              #console.log @.value, options
+              if @.value && options.indexOf(parseInt(@.value)) == -1
                 #console.log 'disabling ', @
                 $(@).attr('disabled', true)
               else if options.indexOf(@.value) > -1 and $(@).is(':selected')


Fix pjax opening external hash url

Fix pjax opening external hash url

--- app/assets/javascripts/pjax.js.coffee
+++ app/assets/javascripts/pjax.js.coffee
@@ -59,7 +59,7 @@
   xhr.historyMethod = history_method if history_method
   settings.dataType = 'html'
   if settings.crossDomain
-    window.open(xhr.url)
+    window.open($self.attr('href') || xhr.url)
     return false
   window.pjax = xhr


Added transparency to candle colors so you can see the chart background.

--- app/assets/javascripts/tickers.coffee
+++ app/assets/javascripts/tickers.coffee
@@ -4,8 +4,8 @@
         title: text: 'Test'
         plotOptions: {
           candlestick: {
-            color: 'red'
-            upColor: 'green'
+            color: 'rgba(255, 102, 102, 0.3)'
+            upColor: 'rgba(112, 219, 112, 0.3)'
           }
         }
         series: [ {


don't let parallelism cost or concurrency cost go below 0

--- js/inner.coffee
+++ js/inner.coffee
@@ -1198,7 +1198,7 @@
       Math.round(log2(p) * 99)

   parallelism_cost: (plan, p) =>
-    @raw_parallelism_cost(p) - @raw_parallelism_cost(plan.min_parallelism)
+    Math.max(0, @raw_parallelism_cost(p) - @raw_parallelism_cost(plan.min_parallelism))

   # p2 > p1
   parallelism_cost_difference: (plan, p1, p2) =>
@@ -1208,7 +1208,7 @@
     if plan.concurrency == "Unlimited"
       0
     else
-      (c - plan.concurrency) * 49
+      Math.max(0, (c - plan.concurrency) * 49)

   calculateCost: (plan, concurrency, parallelism) =>
     unless plan


fix(permissions): disable view permissions in public project

--- app/coffee/modules/admin/roles.coffee
+++ app/coffee/modules/admin/roles.coffee
@@ -355,73 +355,91 @@
             categories = []

             epicPermissions = [
-                { key: "view_epics", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.VIEW_EPICS"}
                 { key: "add_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.ADD_EPICS"}
                 { key: "modify_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.MODIFY_EPICS"}
                 { key: "comment_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.COMMENT_EPICS"}
                 { key: "delete_epic", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.DELETE_EPICS"}
             ]
+
+            if $scope.project.is_private
+                epicPermissions.unshift({ key: "view_epics", name: "COMMON.PERMISIONS_CATEGORIES.EPICS.VIEW_EPICS"})
+
             categories.push({
                 name: "COMMON.PERMISIONS_CATEGORIES.EPICS.NAME" ,
                 permissions: setActivePermissions(epicPermissions)
             })

             milestonePermissions = [
-                { key: "view_milestones", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.VIEW_SPRINTS"}
                 { key: "add_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.ADD_SPRINTS"}
                 { key: "modify_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.MODIFY_SPRINTS"}
                 { key: "delete_milestone", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.DELETE_SPRINTS"}
             ]
+
+            if $scope.project.is_private
+                milestonePermissions.unshift({ key: "view_milestones", name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.VIEW_SPRINTS"})
+
             categories.push({
                 name: "COMMON.PERMISIONS_CATEGORIES.SPRINTS.NAME",
                 permissions: setActivePermissions(milestonePermissions)
             })

             userStoryPermissions = [
-                { key: "view_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.VIEW_USER_STORIES"}
                 { key: "add_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.ADD_USER_STORIES"}
                 { key: "modify_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.MODIFY_USER_STORIES"}
                 { key: "comment_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.COMMENT_USER_STORIES"}
                 { key: "delete_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.DELETE_USER_STORIES"}
             ]
+
+            if $scope.project.is_private
+                userStoryPermissions.unshift({ key: "view_us", name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.VIEW_USER_STORIES"})
+
             categories.push({
                 name: "COMMON.PERMISIONS_CATEGORIES.USER_STORIES.NAME",
                 permissions: setActivePermissions(userStoryPermissions)
             })

             taskPermissions = [
-                { key: "view_tasks", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.VIEW_TASKS"}
                 { key: "add_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.ADD_TASKS"}
                 { key: "modify_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.MODIFY_TASKS"}
                 { key: "comment_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.COMMENT_TASKS"}
                 { key: "delete_task", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.DELETE_TASKS"}
             ]
+
+            if $scope.project.is_private
+                taskPermissions.unshift({ key: "view_tasks", name: "COMMON.PERMISIONS_CATEGORIES.TASKS.VIEW_TASKS"})
+
             categories.push({
                 name: "COMMON.PERMISIONS_CATEGORIES.TASKS.NAME" ,
                 permissions: setActivePermissions(taskPermissions)
             })

             issuePermissions = [
-                { key: "view_issues", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.VIEW_ISSUES"}
                 { key: "add_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.ADD_ISSUES"}
                 { key: "modify_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.MODIFY_ISSUES"}
                 { key: "comment_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.COMMENT_ISSUES"}
                 { key: "delete_issue", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.DELETE_ISSUES"}
             ]
+
+            if $scope.project.is_private
+                issuePermissions.unshift({ key: "view_issues", name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.VIEW_ISSUES"})
+
             categories.push({
                 name: "COMMON.PERMISIONS_CATEGORIES.ISSUES.NAME",
                 permissions: setActivePermissions(issuePermissions)
             })

             wikiPermissions = [
-                { key: "view_wiki_pages", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.VIEW_WIKI_PAGES"}
                 { key: "add_wiki_page", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.ADD_WIKI_PAGES"}
                 { key: "modify_wiki_page", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.MODIFY_WIKI_PAGES"}
                 { key: "delete_wiki_page", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.DELETE_WIKI_PAGES"}
-                { key: "view_wiki_links", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.VIEW_WIKI_LINKS"}
                 { key: "add_wiki_link", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.ADD_WIKI_LINKS"}
                 { key: "delete_wiki_link", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.DELETE_WIKI_LINKS"}
             ]
+
+            if $scope.project.is_private
+                wikiPermissions.unshift({ key: "view_wiki_pages", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.VIEW_WIKI_PAGES"})
+                wikiPermissions.push({ key: "view_wiki_links", name: "COMMON.PERMISIONS_CATEGORIES.WIKI.VIEW_WIKI_LINKS"})
+
             categories.push({
                 name: "COMMON.PERMISIONS_CATEGORIES.WIKI.NAME",
                 permissions: setActivePermissions(wikiPermissions)


Simplify loading indicator for now

Simplify loading indicator for now

--- app/components/loading-indicator.cjsx
+++ app/components/loading-indicator.cjsx
@@ -4,8 +4,5 @@
   displayName: 'LoadingIndicator'

   render: ->
-
-      •
-      •
-      •
-
+    # TODO
+


add + to the pager override time regex

add + to the pager override time regex

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -290,7 +290,7 @@
         else
           msg.send "None found!"

-  robot.respond /(pager|major)( me)? (override) ([\w\-]+) ([\w\-:]+) - ([\w\-:]+)( (.*))?$/i, (msg) ->
+  robot.respond /(pager|major)( me)? (override) ([\w\-]+) ([\w\-:\+]+) - ([\w\-:\+]+)( (.*))?$/i, (msg) ->
     if msg.match[8]
       overrideUser = robot.brain.userForName(msg.match[8])


a fix for #13

a fix for #13

--- src/scripts/ui-scroll.coffee
+++ src/scripts/ui-scroll.coffee
@@ -31,8 +31,8 @@
 				priority: 1000
 				terminal: true

-				compile: (element, attr, linker) ->
-					($scope, $element, $attr, controllers) ->
+				compile: (elementTemplate, attr, linker) ->
+					($scope, element, $attr, controllers) ->

 						match = $attr.ngScroll.match /^\s*(\w+)\s+in\s+(\w+)\s*$/
 						if !match
@@ -415,4 +415,4 @@

 ###
 //# sourceURL=src/scripts/ui-scroll.js
-###+###


log4js doesn't take functions, more's the pity

--- src/server/classifier.coffee
+++ src/server/classifier.coffee
@@ -131,7 +131,7 @@
         message: match.error.message
         status: match.error.status
       }
-      @log.debug => "Request failure: #{r} => #{error}"
+      @log.debug "Request failure: #{r} => #{error}"
     else
       classification = JSON.stringify {
         resource: match.resource_type, action: match.action_name,


Change location of traditional Chinese language file

--- app/index.coffee
+++ app/index.coffee
@@ -49,7 +49,7 @@
 languageManager = new LanguageManager
   translations:
     en: label: 'English', strings: enUs
-    zh: label: "繁體中文 Chinese (Traditional)", strings: './translations/zh.json'
+    "zh-hant": label: "繁體中文 Chinese (Traditional)", strings: './translations/zh-hant.json'

 languageManager.on 'change-language', (e, code, strings) ->
   translate.load strings


(re fix completes:5d03d) reactor: Handle locals-null signifier

This wraps up the last of the obviously-broken fallout from the parser refactor. :D

--- Paws/Source/reactor.coffee
+++ Paws/Source/reactor.coffee
@@ -155,9 +155,6 @@
       combo = stagee.advance result
       @current = stagee

-      if process.env['TRACE_REACTOR'] and combo.subject?
-         Paws.warning "   ╰┄ combo: #{combo.subject} × #{combo.message}"
-
       # If the staging has passed #next, then it's safe to grant it the ownership it's requesting
       @table.give stagee, requestedMask if requestedMask

@@ -166,8 +163,14 @@
          combo.apply stagee, [result, this]

       else
-         @stage combo.subject.receiver.clone(),
-            Unit.receiver_parameters stagee, combo.subject, combo.message
+         subject = combo.subject ? stagee.locals
+         message = combo.message ? stagee.locals
+
+         if process.env['TRACE_REACTOR']
+            Paws.warning "   ╰┄ combo: #{combo.subject} × #{combo.message}"
+
+         @stage subject.receiver.clone(),
+            Unit.receiver_parameters stagee, subject, message

       @table.remove accessor: stagee if stagee.complete()


improved /update script

improved /update script

--- src/server/src/Server.coffee
+++ src/server/src/Server.coffee
@@ -22,18 +22,32 @@
       res.end 'pong'

     @.app.use '/update', (req, res) =>
+
+      run_Command = (cmd,args, callback)->
+        childProcess = cmd.start_Process args
+        childProcess.stdout.on 'data', (data)-> console.log(data.str().trim())
+        childProcess.stderr.on 'data', (data)-> console.log(data.str().trim())
+        childProcess.on 'exit', ()->
+          callback()
+
       console.log '[request] /update'
-      'git'.start_Process_Redirect_Console 'pull'
-           .on 'exit', ()->
-              'npm'.start_Process_Redirect_Console 'run  build-data'
-                .on 'exit', ()->
-                  childProcess = './node_modules/.bin/gulp'.start_Process 'styles','pug','build'
-                  childProcess.stdout.on 'data', (data)-> console.log(data.str().trim())
-                  childProcess.stderr.on 'data', (data)-> console.log(data.str().trim())
-                  console.log '----------------'
-                  console.log req.url
-                  childProcess.on 'exit', ()->
-                    res.json { thanks: 'server-updated'}
+      console.time(".... Updating site");
+      run_Command 'git', 'pull', ->
+        run_Command 'npm',['run','build-data'], ->
+          run_Command './node_modules/.bin/gulp',['styles','pug','build'],->
+            console.timeEnd(".... Updating site");
+            res.json { thanks: 'server-updated'}
+#      'git'.start_Process_Redirect_Console 'pull'
+#           .on 'exit', ()->
+#              'npm'.start_Process_Redirect_Console 'run build-data'
+#                .on 'exit', ()->
+#                  childProcess = './node_modules/.bin/gulp'.start_Process 'styles','pug','build'
+#                  childProcess.stdout.on 'data', (data)-> console.log(data.str().trim())
+#                  childProcess.stderr.on 'data', (data)-> console.log(data.str().trim())
+#                  console.log '----------------'
+#                  console.log req.url
+#                  childProcess.on 'exit', ()->
+#                    res.json { thanks: 'server-updated'}

     @


Update AnimalDetails#getBabies to filter checked

- For cases when there is not a 'babies' checkbox in the view

--- app/controllers/animal_details.coffee
+++ app/controllers/animal_details.coffee
@@ -67,7 +67,7 @@
       $(checkbox).val()

   getBabies: ->
-    !!@babiesCheckbox.attr 'checked'
+    !!@babiesCheckbox.filter(':checked')

   onClickCancel: ->
     @hide()


show import notifications first.

show import notifications first.

--- src/smc-webapp/file_use.cjsx
+++ src/smc-webapp/file_use.cjsx
@@ -271,11 +271,25 @@
             y.search = @_search(y)
             @_process_users(y)
             v.push(y)
-        v.sort (a,b)->misc.cmp(b.last_edited, a.last_edited)
+        w0 = []
+        w1 = []
+        w2 = []
+        for a in v
+            if a.notify and a.is_unread
+                w0.push(a)
+            else if a.show_chat and a.is_unread
+                w1.push(a)
+            else
+                w2.push(a)
+        c = (a,b) -> misc.cmp(b.last_edited, a.last_edited)
+        w0.sort(c)
+        w1.sort(c)
+        w2.sort(c)
+        v = w0.concat(w1.concat(w2))
         @_cache =
-            sorted_file_use_list : v
+            sorted_file_use_list           : v
             sorted_file_use_immutable_list : immutable.fromJS(v)
-            notify_count         : (x for x in v when x.notify).length
+            notify_count                   : (x for x in v when x.notify).length
         return v

     # See above for the definition of unread and unseen.


webapp/sagews: sometimes, when printing threejs the scene data is not defined -- in print_to_pdf_data: TypeError: Cannot read property "set_static_renderer" of undefined

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -2286,7 +2286,9 @@
         for elt in @output_elements()
             for e in elt.find(".salvus-3d-container")
                 f = $(e)
-                scene = $(e).data('salvus-threejs')
+                scene = f.data('salvus-threejs')
+                if not scene?
+                    continue
                 scene.set_static_renderer()
                 data_url = scene.static_image
                 if data_url?


registration

registration

--- app/js/controllers/editaccount.coffee
+++ app/js/controllers/editaccount.coffee
@@ -2,7 +2,6 @@
 	$scope.oldName=$stateParams.name
 	$scope.newName=$stateParams.name
 	$scope.paywith=$stateParams.name
-	$scope.registration = false
 	$scope.delegate = false
 	$scope.pairs = [{key: 'github', val: 'nikakhov'}, {key: 'favorite food', val: 'steak'}]
 	$scope.addKeyVal = ->


remove authenticate user service

remove authenticate user service

--- app/services/authenticate-user.coffee
+++ app/services/authenticate-user.coffee
@@ -1,18 +0,0 @@
-null
-
-### @ngInject ###
-global.cobudgetApp.factory 'AuthenticateUser', (Toast, $location, $q, $auth) ->
-  # maybe remove later?
-  () ->
-    deferred = $q.defer()
-    $auth.validateUser()
-      .then (user) ->
-        global.cobudgetApp.currentUserId = user.id
-        deferred.resolve()
-      .catch ->
-        global.cobudgetApp.currentUserId = null
-        $location.path('/')
-        Toast.show('Please log in to continue')
-        deferred.reject()
-
-    return deferred.promise


Tell startNewSupervisor which image to start, we need to start the working image on a restart, and the new (possibly broken) image on update.

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -21,16 +21,16 @@
 	containerIdPromise.then (containerId) ->
 		docker.getContainer(containerId)

-startNewSupervisor = (currentSupervisor, waitForSuccess = true) ->
-	console.log('Creating supervisor container:', localImage)
+startNewSupervisor = (currentSupervisor, image, waitForSuccess = true) ->
+	console.log('Creating supervisor container:', image)
 	docker.createContainerAsync(
-		Image: localImage
+		Image: image
 		Cmd: [ '/start' ]
 		Volumes: config.supervisorContainer.Volumes
 		Env: currentSupervisor.Config.Env
 	)
 	.tap (container) ->
-		console.log('Starting supervisor container:', localImage)
+		console.log('Starting supervisor container:', image)
 		container.startAsync(
 			Privileged: true
 			Binds: config.supervisorContainer.Binds
@@ -85,7 +85,7 @@
 		console.log('Supervisor restart (for binds/mounts)')
 		restart = ->
 			# When restarting for just binds/mounts we just wait for the supervisor updates to start.
-			startNewSupervisor(currentSupervisor, false)
+			startNewSupervisor(currentSupervisor, localImage, false)
 			.catch (err) ->
 				console.error('Error restarting', err)
 				# If there's an error just keep attempting to restart to get to a useful state.
@@ -113,7 +113,7 @@
 				utils.mixpanelTrack('Supervisor up to date')
 				return
 			utils.mixpanelTrack('Supervisor update start', image: localImageId)
-			startNewSupervisor(currentSupervisor)
+			startNewSupervisor(currentSupervisor, remoteImage)
 		.catch (err) ->
 			utils.mixpanelTrack('Supervisor update failed', error: err)
 			# The error here is intentionally not propagated further up the chain,


Don't record every url, it's not useful

Don't record every url, it's not useful

--- js/inner.coffee
+++ js/inner.coffee
@@ -880,7 +880,6 @@

     # Google analytics
     @bind 'event-context-after', ->
-      _kmq.push(['record', 'Viewed ' + @path]);
       if window._gaq? # we dont use ga in test mode
         window._gaq.push @path


hide loading indicator on setupAjax

hide loading indicator on setupAjax

--- app/index.coffee
+++ app/index.coffee
@@ -78,7 +78,7 @@
       Friend.fetch()

   setupAJAX: ->
-    el = $('

').prependTo($('body'))
+    el = $('

').prependTo($('body')).hide()
     el.ajaxSend( (e, xhr, options) ->
       return unless options.url.match(Spine.Model.host)
       el.show()


Sorting by images by position

Sorting by images by position

--- app/index.coffee
+++ app/index.coffee
@@ -124,6 +124,12 @@
     @_addImages(parsedHilights)

   _shiftImages: (hilights, axis, size) ->
+    hilights.sort (a, b) ->
+      aPos = a.data('imagePosition')
+      bPos = b.data('imagePosition')
+      return -1 if aPos[axis] < bPos[axis]
+      return 1 if aPos[axis] > bPos[axis]
+      0
     last = _.first(hilights)
     for hilight, i in hilights
       continue if i == 0


SocialWorker: JCombinedAppStorage: check owner validty for upsert method

--- workers/social/lib/social/models/combinedappstorage.coffee
+++ workers/social/lib/social/models/combinedappstorage.coffee
@@ -31,17 +31,22 @@
     unless appId
       return callback new KodingError 'appId is not set!'

-    options.accountId = @getAt 'accountId'
+    options.accountId = accountId = @getAt 'accountId'

     { connection: { delegate }, context: { group } } = client
-    { nickname } = delegate.profile
+
+    unless accountId.equals delegate.getId()
+      return callback new KodingError 'Access denied'

     JCombinedAppStorage.upsert appId, options, (err, storage) ->
+      return callback err  if err
+
       if options.notify and not err
-        options = { group, appId }
+        options      = { group, appId }
+        { nickname } = delegate.profile
         notifyByUsernames [ nickname ], 'StorageUpdated', options

-      return callback err, storage
+      return callback null, storage

   @upsert: (appId, options, callback) ->


fix rev for fonts

fix rev for fonts

--- app/templates/gulp/plugins/rev.coffee
+++ app/templates/gulp/plugins/rev.coffee
@@ -68,12 +68,17 @@
     urls = urldata decl.value
     rev_urls = _.map urls, (_url)->
       return if /^http/.test _url
-      url = switch _url[0]
-        when "." then _url
-        when "/" then "..#{_url}"
-        else "../#{_url}"
+      return if _url.indexOf("base64,iVBORw0KG") > -1
+      if _url.indexOf("./") is 0
+        url = _url
+      else if _url.indexOf("/") is 0
+        url = "..#{_url}"
+      else
+        url = "./#{_url}"
+      url = url.split("#")[0].split("?")[0]
       #abs path
       absurl = libpath.resolve filedir, url
+
       ext = libpath.extname(absurl).toLowerCase()
       #relative path
       relurl = libpath.relative root, absurl


When showing case table, only show Sage table rather than all case tables [#142390951] - Use instance properties rather than constants for data context and collection names

--- src/code/models/codap-connect.coffee
+++ src/code/models/codap-connect.coffee
@@ -22,6 +22,10 @@
     @graphStore = GraphStore.store
     @lastTimeSent = @_timeStamp()
     @sendThrottleMs = 300
+
+    @dataContextName = "Sage Simulation"
+    @simulationCollectionName = "Simulation"
+    @samplesCollectionName = "Samples"

     SimulationStore.actions.recordingFramesCreated.listen  @addData.bind(@)

@@ -74,7 +78,7 @@
     # if we don't create one with our collections. Then kick off init
     @codapPhone.call
       action: 'get',
-      resource: 'dataContext[Sage Simulation]'
+      resource: "dataContext[#{@dataContextName}]"
     , (ret) =>
       # ret==null is indication of timeout, not an indication that the data set
       # doesn't exist.
@@ -98,11 +102,11 @@
         action: 'create'
         resource: 'dataContext'
         values:
-          name: 'Sage Simulation'
-          title: 'Sage Simulation'
+          name: @dataContextName
+          title: @dataContextName
           collections:[
             {
-              name: 'Simulation'
+              name: @simulationCollectionName
               title: 'Sage Simulation'
               labels:
                 singleCase: 'run'
@@ -113,9 +117,9 @@
               ]
             },
             {
-              parent: "Simulation"
-              name: 'Samples'
-              title: 'Samples'
+              parent: @simulationCollectionName
+              name: @samplesCollectionName
+              title: @samplesCollectionName
               labels:
                 singleCase: 'sample'
                 pluralCase: 'samples'
@@ -165,7 +169,7 @@
           newAttributes = _.select currentAttributes, (a) -> (! _.includes(values,a.name))
           message =
             action: 'create'
-            resource: 'dataContext[Sage Simulation].collection[Samples].attribute'
+            resource: "dataContext[#{@dataContextName}].collection[#{@samplesCollectionName}].attribute"
             values: newAttributes
           @codapPhone.call message, (response) =>
             if response.success
@@ -180,7 +184,7 @@

       getListing =
         action: 'get'
-        resource: 'dataContext[Sage Simulation].collection[Samples].attributeList'
+        resource: "dataContext[#{@dataContextName}].collection[#{@samplesCollectionName}].attributeList"
       @codapPhone.call(getListing, doResolve)
       log.info "requested list of attributes"

@@ -253,7 +257,7 @@

     createItemsMessage =
       action: 'create',
-      resource: "dataContext[Sage Simulation].item",
+      resource: "dataContext[#{@dataContextName}].item",
       values: sampleData

     if sampleData.length > 0
@@ -339,6 +343,7 @@
         resource: 'component',
         values:
           type: 'caseTable'
+          dataContext: @dataContextName
       @tableCreated = true

   codapRequestHandler: (cmd, callback) =>


Update steps in CODAP parent case during run

As will will now be sending data in arbitrary sets, codap-connect
doesn't know from the start how many steps there are, but we still
want to show this data in the parent column in CODAP.

This updates the current parent case every time we send new data

[#102294388]

--- src/code/models/codap-connect.coffee
+++ src/code/models/codap-connect.coffee
@@ -8,6 +8,8 @@
   codapPhone: null

   currentCaseID: null
+
+  stepsInCurrentCase: 0

   queue: []

@@ -78,6 +80,7 @@
     }, (result) =>
       if result?.success
         @currentCaseID = result.caseID
+        @stepsInCurrentCase = 0
         @_flushQueue()
       else
         @currentCaseID = null
@@ -102,6 +105,16 @@
         collection: 'Samples',
         parent: @currentCaseID,
         values: sampleData
+      }
+
+    # Update the parent case with the current number of steps
+    @stepsInCurrentCase += sampleData.length
+    @codapPhone.call
+      action: 'updateCase'
+      args: {
+        collection: 'Simulation',
+        caseID: @currentCaseID
+        values: [@stepsInCurrentCase]
       }

   _flushQueue: ->


feat(productActions): add support for setCategoryOrderHint action

--- src/coffee/sync/utils/product.coffee
+++ src/coffee/sync/utils/product.coffee
@@ -92,6 +92,9 @@
     actions = []
     _.each actionsBaseList(), (item) =>
       action = @_buildBaseAttributesAction(item, diff, old_obj)
+      if action && action.action == "setCategoryOrderHint"
+        action.categoryId = Object.keys(action.categoryOrderHints)[0]
+        action.orderHint = action.categoryOrderHints[action.categoryId]
       actions.push action if action
     actions

@@ -514,6 +517,10 @@
       key: 'slug'
     },
     {
+      action: 'setCategoryOrderHint',
+      key: 'categoryOrderHints'
+    },
+    {
       action: 'setDescription'
       key: 'description'
     },


Check for duplicates in activeVars

Check for duplicates in activeVars

--- src/coffeescript_instrumenter.coffee
+++ src/coffeescript_instrumenter.coffee
@@ -162,7 +162,8 @@

   findVariables: (node, vars=[]) ->
     if node instanceof @nodeTypes.Value and node.base instanceof @nodeTypes.Literal and node.base.isAssignable()
-      vars.push node.base.value
+      if vars.indexOf(node.base.value) is -1
+        vars.push node.base.value

     node.eachChild (child) =>
       @findVariables(child, vars)


WIP Topkeys now on top

WIP Topkeys now on top

--- muleview/app/view/MuleChart.coffee
+++ muleview/app/view/MuleChart.coffee
@@ -120,7 +120,7 @@
     @createLegend()
     @graph.render()
     @createTooltips()
-    @createSlider()
+    @createSlider() if @slider

   setZoomHighlight: (toggle, min, max) ->
     return unless @divs?.zoomHighlight # incase the chart wasn't yet rendered
@@ -184,7 +184,6 @@
     $(@divs.legend)[action](300)

   createSlider: ->
-    return unless @slider
     new Rickshaw.Graph.RangeSlider.Preview
       height: 80
       graph: @graph
@@ -287,6 +286,15 @@
     #     data: seriesData[alert.name]
     #     type: "alert"

+    for subKey in (@subKeys || [])
+      series.push
+        key: subKey
+        name: @keyLegendName(subKey)
+        color: palette.color()
+        data: seriesData[subKey]
+        type: "subkey"
+        renderer: "stack"
+
     for topKey in @topKeys
       @hasData ||= seriesData[topKey].length > 0
       series.push
@@ -297,14 +305,6 @@
         key: topKey
         renderer: "line"

-    for subKey in (@subKeys || [])
-      series.push
-        key: subKey
-        name: @keyLegendName(subKey)
-        color: palette.color()
-        data: seriesData[subKey]
-        type: "subkey"
-        renderer: "stack"
     series

   keyLegendName: (key) ->


Test for Express middleware (#62)

Test for Express middleware (#62)

--- tests/middleware.coffee
+++ tests/middleware.coffee
@@ -89,3 +89,25 @@
     c = t.client(zapp.server)
     c.get '/foo.txt', (err, res) ->
       t.equal 'static', res.body, 'intercepted!'
+
+
+  compatible: (t) ->
+    t.expect 1, 2, 3
+    t.wait 3000
+
+    zapp = zappa port++, ->
+      auth = -> (user,pass) ->
+        user is 'hello' and pass is 'world'
+      @get '/', -> 'welcome'
+      @get '/auth', @express.basicAuth(auth), -> 'authenticated'
+
+    c = t.client(zapp.server)
+    c.get '/', (err, res) ->
+      t.equal 1, res.body, 'welcome'
+
+    c.get '/auth', (err, res) ->
+      t.equal 2, res.body, 'Unauthorized'
+
+    a = new Buffer('hello:world').toString('base64')
+    c.get '/auth', headers: {Authorization:'Basic '+a}, (err, res) ->
+      t.equal 3, res.body, 'authenticated'


some debug logging in `initClient`

some debug logging in `initClient`

--- src/client/init.coffee
+++ src/client/init.coffee
@@ -4,11 +4,18 @@
   streamAnchorClicks
   window
   changeBrowserUrl
+  getCookieKeys
+  getRememberedToken
 ) ->
   ->
     console.log 'initClient'

+    console.log 'getCookieKeys()', getCookieKeys()
+    console.log 'getRememberedToken()', getRememberedToken()
+
     anchorClickStream = streamAnchorClicks document
+
+    # TODO break this up into multiple dependencies

     # logging


Number#even and Number#odd

Number#even and Number#odd

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -26,6 +26,20 @@
   equals 5.sign(), 1, "Positive number's sign is 1"
   equals (-3).sign(), -1, "Negative number's sign is -1"
   equals 0.sign(), 0, "Zero's sign is 0"
+
+test "#even", ->
+  [0, 2, -32].each (n) ->
+    ok n.even(), "#{n} is even"
+
+  [1, -1, 2.2, -3.784].each (n) ->
+    equals n.even(), false, "#{n} is not even"
+
+test "#odd", ->
+  [1, 9, -37].each (n) ->
+    ok n.odd(), "#{n} is odd"
+
+  [0, 32, 2.2, -1.1].each (n) ->
+    equals n.odd(), false, "#{n} is not odd"

 test "#times", ->
   n = 5


Update definition of, deprecate --nocolor

--- src/commandline.coffee
+++ src/commandline.coffee
@@ -221,11 +221,11 @@
                 checkstyle, raw), or module, or path to reporter file.')
             .describe("csv", "[deprecated] use --reporter csv")
             .describe("jslint", "[deprecated] use --reporter jslint")
+            .describe("nocolor", "[deprecated] use --color=never")
             .describe("checkstyle", "[deprecated] use --reporter checkstyle")
             .describe("color=<when>",
               "When to colorize the output. <when> can be one of always, never\
               , or auto.")
-            .describe("nocolor", "Don't colorize the output")
             .describe("s", "Lint the source from stdin")
             .describe("q", "Only print errors.")
             .describe("literate",


[trimconfig] Make an effort to maintain the original file's indentation

--- src/commandline.coffee
+++ src/commandline.coffee
@@ -27,10 +27,10 @@
     console.log(arguments...)
     # coffeelint: enable=no_debugger

+jsonIndentation = 2
 logConfig = (config) ->
     filter = (k, v) -> v unless k in ['message', 'description', 'name']
-
-    log(JSON.stringify(config, filter, 4))
+    log(JSON.stringify(config, filter, jsonIndentation))

 # Return the contents of the given file synchronously.
 read = (path) ->
@@ -72,7 +72,10 @@

 # Load a config file given a path/filename
 readConfigFile = (path) ->
-    JSON.parse(stripComments(read(path)))
+    text = read(path)
+    try
+        jsonIndentation = text.split('\n')[1].match(/^\s+/)[0].length
+    JSON.parse(stripComments(text))

 loadConfig = (options) ->
     config = null


Re-enable pending pane items for easier tree-view use

--- atom/config.cson
+++ atom/config.cson
@@ -10,7 +10,6 @@
     includeCompletionsFromAllBuffers: false
     maxVisibleSuggestions: 5
   core:
-    allowPendingPaneItems: false
     closeEmptyWindows: false
     disabledPackages: [
       "archive-view"


Atom - fuzzy-finder dont search all panes, opens in last pane

--- atom/config.cson
+++ atom/config.cson
@@ -23,8 +23,7 @@
     tabLength: 4
   "exception-reporting":
     userId: "82a26126-5c31-4531-b32c-a1877d4677e0"
-  "fuzzy-finder":
-    searchAllPanes: true
+  "fuzzy-finder": {}
   "linter-flake8": {}
   "linter-ui-default":
     panelHeight: 163


add atom-ternjs settings

add atom-ternjs settings

--- atom/config.cson
+++ atom/config.cson
@@ -53,3 +53,4 @@
   "git-history": {}
   "git-log": {}
   "linter-rubocop": {}
+  "atom-ternjs": {}


Latest Atom settings

Latest Atom settings

--- atom/config.cson
+++ atom/config.cson
@@ -25,8 +25,9 @@
       '._*'
       '*.pyc'
     ]
+    'followSymlinks': true
     'themes': [
-      'atom-dark-ui'
+      'one-dark-ui'
       'advanced-railscasts-syntax'
     ]
   'auto-copyright':
@@ -47,9 +48,9 @@
   'autocomplete-plus':
     'fileBlacklist': '.*'
   'atom-color-highlight':
+    'markersAtEndOfLine': true
     'hideMarkersInComments': true
     'hideMarkersInStrings': true
-    'markersAtEndOfLine': true
   'package-generator':
     'createInDevMode': true
   'atom-lint':
@@ -62,6 +63,8 @@
       'railscasts'
     ]
     'enabled': 'detect'
+  'minimap':
+    'v4Preview': true
 '.haml.text':
   'wrap-guide':
     'enabled': false


Convert command creation to new syntax

Convert command creation to new syntax

--- atom/init.coffee
+++ atom/init.coffee
@@ -15,15 +15,13 @@
     todoList = path.join(process.env.HOME, 'Dropbox', 'todo', 'todo.txt')
     atom.workspace.open(todoList)

-atom.workspaceView.command 'custom:screenshot-prep', ->
-  oldWindowDimensions = atom.getWindowDimensions()
-  oldThemes = atom.config.get('core.themes')
+  'custom:screenshot-prep': ->
+    oldWindowDimensions = atom.getWindowDimensions()
+    oldThemes = atom.config.get('core.themes')

-  atom.setWindowDimensions
-    'width': 1024
-    'height': 768
-  atom.config.restoreDefault('core.themes')
+    atom.setWindowDimensions('width': 1024, 'height': 768)
+    atom.config.restoreDefault('core.themes')

-atom.workspaceView.command 'custom:screenshot-restore', ->
-  atom.config.set('core.themes', oldThemes)
-  atom.setWindowDimensions(oldWindowDimensions)
+  'custom:screenshot-restore': ->
+    atom.config.set('core.themes', oldThemes)
+    atom.setWindowDimensions(oldWindowDimensions)


variable naming clarify

variable naming clarify

--- src/main.coffee
+++ src/main.coffee
@@ -44,11 +44,11 @@
         @typeHash = {}
         @nameHash = {}

-    add: (element) ->
-        @typeHash[element.constructor] = element
-
-    get: (type) ->
-        @typeHash[type]
+    add: (instance) ->
+        @typeHash[instance.constructor] = instance
+
+    get: (clazz) ->
+        @typeHash[clazz]

     gets: (types...) ->

@@ -125,16 +125,16 @@
             if bottom.range.top >= absY and top.range.left >= absX
                 return true

-        [leftMarks, rightMarks] = (getMarkBits item for item in [left, right])
-
-        if result = getBitAdd leftMarks.x, rightMarks.x
+        [fooMarks, barMarks] = (getMarkBits item for item in [foo, bar])
+
+        if result = getBitAdd fooMarks.x, barMarks.x
             for i in [0...result.length]
                 list = @grid.getCol i + result.offset
                 for j in [top.point.y...bottom.point.y]
                     break unless list[j].get(State).done
                 return true if j is bottom.point.y

-        if result = getBitAdd leftMarks.y, rightMarks.y
+        if result = getBitAdd fooMarks.y, barMarks.y
             for i in [0...result.length]
                 list = @grid.getRow i + result.offset
                 for j in [left.point.x...right.point.x]


Auto hide time disabled when user writes in text input.

--- src/main.coffee
+++ src/main.coffee
@@ -265,6 +265,7 @@
                         read: -> working_on_value
                         onSubmit: working_on_submit
                         onChange: (new_value)=>
+                            set_auto_hide_time Infinity
                             @setState {writing_working_on:true}
                             working_on_value = new_value
                         onClick: (event)=>


fix(drag): Pass correct drag coordinates from event

- Don't use magic event variable without declaring it

--- src/components/multiselect-list.cjsx
+++ src/components/multiselect-list.cjsx
@@ -210,7 +210,7 @@
   # Public Methods

   itemIdAtPoint: (x, y) ->
-    item = document.elementFromPoint(event.clientX, event.clientY).closest('[data-item-id]')
+    item = document.elementFromPoint(x, y).closest('[data-item-id]')
     return null unless item
     return item.dataset.itemId


Update Comment

Update Comment

--- src/fairy.coffee
+++ src/fairy.coffee
@@ -97,7 +97,7 @@
   # client and the name of the queue, add the queue name into the `QUEUES` set
   # for listing purpose. **Usage:**
   #
-  #   foo = fairy.queue 'foo'
+  #     foo = fairy.queue 'foo'
   #
   queue: (name) ->
     return @queue_pool[name] if @queue_pool[name]
@@ -108,9 +108,9 @@

   # Return named queues whose names are stored in the `QUEUES` set. **Usage:**
   #
-  #   queues = fairy.queues()
-  #   console.log "#{queues.length} queues: ", queues.map (queue) ->
-  #     queue.name
+  #     queues = fairy.queues()
+  #     console.log "#{queues.length} queues: ", queues.map (queue) ->
+  #       queue.name
   #
   queues: (callback) ->
     @redis.smembers @key('QUEUES'), (err, res) =>
@@ -121,8 +121,8 @@
   # `statistics` is an asynchronous method. The only arg of the callback
   # function is an array containing statistics of all queues. **Usage:**
   #
-  #   fairy.statistics (stats) ->
-  #     console.log "Stats of #{stats.length} queues: ", stats
+  #     fairy.statistics (stats) ->
+  #       console.log "Stats of #{stats.length} queues: ", stats
   statistics: (callback) ->
     result = []
     @queues (queues) ->


Classroom: Inherited ClassroomChapterView from KDView.

--- client/app/Applications/Classroom.kdapplication/Views/classroomchapterview.coffee
+++ client/app/Applications/Classroom.kdapplication/Views/classroomchapterview.coffee
@@ -1,4 +1,4 @@
-class ClassroomChapterView extends JView
+class ClassroomChapterView extends KDView

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


pager

pager

--- src/component/List.cjsx
+++ src/component/List.cjsx
@@ -41,6 +41,8 @@
             <Restaurant key={r.id} {...r} />

         nav = [prevPage, nextPage].map (params, idx)=>
+            return if idx is 0 and params.page is 0
+            arrow = if idx is 0 then '←' else '→'
             to = 'list'
             href = @context.router.makePath to, params

@@ -48,7 +50,7 @@
                 @context.router.transitionTo href
                 @props.flux.action.route()

-
<Link to={to} params={params} onClick={delegate} >{params.page}</Link>

+
<Link to={to} params={params} onClick={delegate} ></Link>

@@ -61,7 +63,7 @@

-

+

                         {nav}


Re-ordering lexer rules for fewer regex matches / slightly faster lexing.

--- lexer.coffee
+++ lexer.coffee
@@ -61,14 +61,14 @@
   # `@literalToken` is the fallback catch-all.
   extractNextToken: ->
     return if @identifierToken()
+    return if @commentToken()
+    return if @whitespaceToken()
+    return if @lineToken()
+    return if @heredocToken()
+    return if @stringToken()
     return if @numberToken()
-    return if @heredocToken()
     return if @regexToken()
-    return if @commentToken()
-    return if @lineToken()
-    return if @whitespaceToken()
     return if @jsToken()
-    return if @stringToken()
     return    @literalToken()

   # Tokenizers


fixes #1724: regular expressions beginning with `*`

also normalised capitalisation in a few error messages

--- lexer.coffee
+++ lexer.coffee
@@ -112,7 +112,7 @@
         id  = new String id
         id.reserved = yes
       else if id in RESERVED
-        @error "Reserved word \"#{word}\""
+        @error "reserved word \"#{word}\""

     unless forcedIdentifier
       id  = COFFEE_ALIAS_MAP[id] if id in COFFEE_ALIASES
@@ -199,7 +199,9 @@
     return 0 if prev and (prev[0] in (if prev.spaced then NOT_REGEX else NOT_SPACED_REGEX))
     return 0 unless match = REGEX.exec @chunk
     [regex] = match
-    @token 'REGEX', if regex is '//' then '/(?:)/' else regex
+    if regex.match /^\/\*/ then @error 'regular expressions cannot begin with `*`'
+    if regex is '//' then regex = '/(?:)/'
+    @token 'REGEX', regex
     regex.length

   # Matches multiline extended regular expressions.
@@ -324,7 +326,7 @@
     prev = last @tokens
     if value is '=' and prev
       if not prev[1].reserved and prev[1] in JS_FORBIDDEN
-        @error "Reserved word \"#{@value()}\" can't be assigned"
+        @error "reserved word \"#{@value()}\" can't be assigned"
       if prev[1] in ['||', '&&']
         prev[0] = 'COMPOUND_ASSIGN'
         prev[1] += '='


Port to Process API

Port to Process API

--- components/StateToTransmission.coffee
+++ components/StateToTransmission.coffee
@@ -54,6 +54,7 @@
     datatype: 'array'
     description: 'Array of portal IDs indexed by the light in chain'
     required: yes
+    control: yes
   c.inPorts.add 'state',
     datatype: 'object'
     description: 'Portal state object from Ingress API'
@@ -68,27 +69,28 @@
     datatype: 'object'
     required: no

-  noflo.helpers.WirePattern c,
-    in: 'state'
-    params: ['portals']
-    out: ['light', 'states']
-    forwardGroups: no
-  , (data, groups, out) ->
-    c.previousStates = {} unless c.previousStates
+  c.setUp = (callback) ->
+    # Previous seen state of the portals, keyed by GUID
+    c.previousStates = {}
+    callback()
+  c.tearDown = (callback) ->
+    delete c.previousStates
+    callback()

-    unless data.guid
-      c.error new Error 'Portal data is missing GUID'
+  c.process (input, output) ->
+    return unless input.hasData 'state', 'portals'
+    [state, portals] = input.getData 'state', 'portals'
+    unless state.guid
+      output.done new Error 'Portal data is missing GUID'
       return
-    unless c.params.portals
-      c.error new Error 'Portal list not available'
-      return
-    idx = c.params.portals.indexOf data.guid
+    idx = portals.indexOf state.guid
     if idx is -1
-      c.error new Error "Portal #{data.guid} is not in portals list"
+      output.done new Error "Portal #{state.guid} is not in portals list"
       return

-    out.light.send stateToArray idx, data
-    c.previousStates[data.guid] = data
-    out.states.send c.previousStates
-
-  c
+    output.send
+      light: stateToArray idx, state
+    c.previousStates[state.guid] = state
+    output.send
+      states: c.previousStates
+    output.done()