remove some RED... magick

remove some RED... magick

--- examples/cli-dashboard.coffee
+++ examples/cli-dashboard.coffee
@@ -41,9 +41,6 @@
     @packet.push i
     @packet.push 0

-    if typeof r is "object"
-      {r,g,b} = r
-
     @packet.push r
     @packet.push g
     @packet.push b
@@ -94,12 +91,20 @@

   createProgram: (cb) ->
     program = []
+
     next = (time=0) -> program.push time
-    set = (i, r, g, b) -> program.push [i, r, g, b]
-    all = (r, g, b) =>
+
+    set = (i, r, g, b) ->
+      if typeof r is "object"
+        {r,g,b} = r
+      program.push [i, r, g, b]
+
+    all = (args...) =>
       for i in [@client.min..@client.max]
-        program.push [i, r, g, b]
+        set i, args...
+
     cb set, all, next, program
+
     return program

@@ -119,24 +124,39 @@

   player = new Player client

-  RED = [255, 0, 0]
-  GREEN = [0, 255, 0]
-  BLUE = [0, 0, 255]
-  BLACK = [0, 0, 0]
-  WHITE = [255, 255, 255]
+  RED =
+    r: 255
+    g: 0
+    b: 0
+  GREEN =
+    r: 0
+    g: 255
+    b: 0
+  BLUE =
+    r: 0
+    g: 0
+    b: 255
+  BLACK =
+    r: 0
+    g: 0
+    b: 0
+  WHITE =
+    r: 255
+    g: 255
+    b: 255

   keyboardKeys =

     o:
       name: "OFF"
       program: player.createProgram (set, all, next) ->
-        all BLACK...
+        all BLACK
         next 1000

     p:
       name: "ON"
       program: player.createProgram (set, all, next) ->
-        all WHITE...
+        all WHITE
         next 1000

     r:
@@ -148,10 +168,10 @@

         for i in [client.min..client.max]
           for j in [0..len]
-            set i-j, BLACK...
+            set i-j, BLACK

           for j in [0..len]
-            set i+j, RED...
+            set i+j, RED

           next 20

@@ -177,17 +197,17 @@
     h:
       name: "fast green blue"
       program: player.createProgram (set, all, next) ->
-        all GREEN...
-        next 100
-        all BLUE...
+        all GREEN
+        next 100
+        all BLUE
         next 100

     j:
       name: "flash red"
       program: player.createProgram (set, all, next) ->
-        all BLACK...
-        next 100
-        all RED...
+        all BLACK
+        next 100
+        all RED
         next 100

     l:
@@ -195,18 +215,18 @@
       program: player.createProgram (set, all, next) ->

         for i in [0..38/2]
-          set i, BLACK...
+          set i, BLACK

         for i in [38/2..38]
-          set i, RED...
+          set i, RED

         next 300

         for i in [0..38/2]
-          set i, RED...
+          set i, RED

         for i in [38/2..38]
-          set i, BLACK...
+          set i, BLACK

         next 300

@@ -218,11 +238,11 @@
         all BLACK

         for i in [client.min..client.max]
-          set i-1, BLACK...
-          set i-2, BLACK...
-          set i, BLUE...
-          set i+1, BLUE...
-          set i+2, BLUE...
+          set i-1, BLACK
+          set i-2, BLACK
+          set i, BLUE
+          set i+1, BLUE
+          set i+2, BLUE
           next 100


Languages injected into app-controller

Languages injected into app-controller

Closes #50

--- annotran/static/scripts/app-controller.coffee
+++ annotran/static/scripts/app-controller.coffee
@@ -3,12 +3,21 @@
 persona = require('../../../../h/h/static/scripts/filter/persona.js')

 class AppControllerExt extends appcontroller
-   constructor: (
+  this.$inject = [
+    '$controller', '$document', '$location', '$rootScope', '$route', '$scope',
+    '$window', 'annotationUI', 'auth', 'drafts', 'features', 'groups', 'languages'
+    'identity', 'session'
+  ]
+  constructor: (
      $controller,   $document,   $location,   $rootScope,   $route,   $scope,
      $window,   annotationUI,   auth,   drafts,   features, groups, languages,
      identity,   session
    ) ->
-    super
+
+    super($controller,   $document,   $location,   $rootScope,   $route,   $scope,
+     $window,   annotationUI,   auth,   drafts,   features, groups,
+     identity,   session)
+
     $scope.$root.userListvisible = true
     $scope.$root.editOnly = false
     $scope.$root.userAnnotations = []
@@ -16,6 +25,7 @@
     $scope.$root.pageid = window.location.href
     $scope.$root.pageid = decodeURIComponent($scope.$root.pageid);
     $scope.$root.pageid = encodeURIComponent(encodeURIComponent(getParameterByName("url", $scope.$root.pageid)));
+

     $scope.$root.updateUserList = ->
       # clear the array


Prettier format of 'points per game' field

--- static/script/pages/stats.coffee
+++ static/script/pages/stats.coffee
@@ -9,7 +9,7 @@
     {{= it.wons}}
     {{= it.finished}}
     {{= it.points}}
-    {{= it.ppg}}
+    {{= it.ppg.toFixed(3)}}

 '


Weird JS error

Weird JS error

--- app/assets/javascripts/components/list.coffee
+++ app/assets/javascripts/components/list.coffee
@@ -84,9 +84,9 @@

     node: node
     hidden: false
-    sublists: Array::map.call $item_container.children(".#{CLASSES.SUBLIST}"), (item) ->
+    sublists: [].map.call $item_container.children(".#{CLASSES.SUBLIST}"), (item) ->
       list_mini_constructors.sublist(item)
-    items: Array::map.call $item_container.children(".#{CLASSES.ITEM}"), (item) ->
+    items: [].map.call $item_container.children(".#{CLASSES.ITEM}"), (item) ->
       list_mini_constructors.item(item)


updates for teapot lighting test

updates for teapot lighting test

no longer need to explicitly invalidate shadow
maps when objects move

--- app/assets/jax/controllers/lighting.js.coffee
+++ app/assets/jax/controllers/lighting.js.coffee
@@ -55,9 +55,6 @@
     newPos[0] += e.diffx * 0.1
     newPos[2] -= e.diffy * 0.1
     @teapot.camera.setPosition newPos
-    @point.shadowmap.invalidate()
-    @directional.shadowmap.invalidate()
-    @spot.shadowmap.invalidate()

   update: (timechange) ->
     return unless spot = @spot
@@ -77,7 +74,7 @@
     # it's trivial to orient it within the scene. Rotating about the Y axis causes a
     # horizontal movement.
     spot.rotate rotationDirection*timechange, 0, 1, 0
-
+
     # update player position if s/he's holding a movement key down
     # var speed = 25 * timechange;
     #


load gradebook submissions in display order

closes CNVS-18387

Test plan:
  * make a big course (100+ students)
  * load the gradebook
  * submissions should load in display order, instead of the
    random-looking order they currently come down in

Change-Id: I5f8c6591577514211e297cfb0b9b2a9ed4e256ed
Reviewed-on: https://gerrit.instructure.com/48348
Tested-by: Jenkins
Reviewed-by: Josh Simpson <bfb4987b134a00f73d881fff5113b1d5fa1876bc@instructure.com>
QA-Review: Amber Taniuchi <ef4bfd1e6d278f6bc7d4a478f02be2cb9581a0a5@instructure.com>
Product-Review: Cameron Matheson <1d572acbfa68c7c6e541c7b840d6b622e5c0dc91@instructure.com>

--- app/coffeescripts/gradebook2/Gradebook.coffee
+++ app/coffeescripts/gradebook2/Gradebook.coffee
@@ -465,6 +465,7 @@
     getSubmissionsChunks: =>
       @withAllStudents (allStudentsObj) =>
         allStudents = (s for k, s of allStudentsObj)
+          .sort (a, b) => @localeSort(a.sortable_name, b.sortable_name)
         loop
           students = allStudents[@chunk_start...(@chunk_start+@options.chunk_size)]
           unless students.length


Wait for server before removing shot from coll

--- elements/js/frames.coffee
+++ elements/js/frames.coffee
@@ -107,9 +107,11 @@
   undoShot: ->
     model = @get('Shots').last()
     model.setApiClient @client
-    model.destroy()
-    Backbone.Relational.store.unregister(model)
-    @trigger("update")
+    model.destroy
+      wait: true
+      success: =>
+        Backbone.Relational.store.unregister(model)
+        @trigger("update")

   getState: ->
     scores = @getScores()


drop what;s going on and it works

drop what;s going on and it works

--- scripts/im-out.coffee
+++ scripts/im-out.coffee
@@ -10,14 +10,10 @@
     msg.send "http://mlkshk.com/r/BN75.gif"
   robot.hear /baws/i, (msg) ->
     msg.send "http://mlkshk.com/r/BMPM.gif"
-  robot.hear /it work(s|ed)/i, (msg) ->
-    msg.send "http://mlkshk.com/r/BM7L.gif"
   robot.hear /cat falling off a table/i, (msg) ->
     msg.send "http://mlkshk.com/r/BJNO.gif"
   robot.hear /\bugh/i, (msg) ->
     msg.send "http://i.imgur.com/5qOPW.gif"
-  robot.hear /what.*going on/i, (msg) ->
-    msg.send "http://i.imgur.com/pgG2z.gif"
   robot.hear /you got it/i, (msg) ->
     msg.send "http://28.media.tumblr.com/tumblr_ltp4xqYG2b1qzosj0o1_400.gif"
   robot.hear /brb.*killing/i, (msg) ->


Update Checkins.coffee

Update Checkins.coffee

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -37,10 +37,10 @@
         wasme = [false, false]

Регистрация на занятие

-

+

             Новичкам (кто не решил в курсе ни одной задачи) приходить на занятие нельзя
@@ -62,7 +62,7 @@

             <Alert bsStyle="danger">
-                Занятий 24 ноября и 1 декабря не будет. Ниже запись на 8 декабря.
+                На 8 декабря запись есть только на одно занятие, см. новости.
             </Alert>

             {


Revert nonstandard checkins times

Revert nonstandard checkins times


Hack to hide old dialogue

Hack to hide old dialogue

--- ozaria/site/views/play/level/LevelDialogueView.coffee
+++ ozaria/site/views/play/level/LevelDialogueView.coffee
@@ -126,6 +126,17 @@
   id: 'level-dialogue-view'
   template: template

+#  subscriptions:
+#    'sprite:speech-updated': 'onSpriteDialogue'
+#
+#  events:
+#    'click #back-button': 'onClickBack'
+#
+#  onClickBack: ->
+#    if @tutorial.length
+#      $("#level-dialogue-view").css("visibility", "hidden")
+#      @tour.show(@tutorial.length - 1)
+
   constructor: (options) ->
     super options
     @level = options.level
@@ -153,6 +164,7 @@
     # https://next.ozaria.com/play/level/1upm3l1l2?codeLanguage=python
     if document.URL.indexOf("play/level/1upm3l1l2") > 0
       @tutorial = m3l1l2
+

 #    @character = @level.get('characterPortrait') or 'ghostv'
 #    @tutorial = store.getters['tutorial/allSteps']
@@ -167,6 +179,7 @@
 #    }

   startTutorial: ->
+    $("#level-dialogue-view").css("visibility", "hidden")
     @tour = new Shepherd.Tour({
       defaultStepOptions: {
         classes: 'shepherd-rectangle'
@@ -281,6 +294,11 @@
       console.log(step)
       @animateMessage(tutorialStep.message, '.shepherd-text')
     )
+#
+#    @tour.on('complete', () =>
+#      $("#level-dialogue-view").html(marked(@tutorial[@tutorial.length - 1]))
+#      $("#level-dialogue-view").css("visibility", "visible")
+#    )

     @tour.start()


You didn't declare die

You didn't declare die

--- src/cli.coffee
+++ src/cli.coffee
@@ -11,10 +11,11 @@
     mod = false

   # Try to require Die app
+  Die = require './die'
   if mod
     app = require mod
     # If we actually have a Die app instance, use it
-    if app instanceof require('./die')
+    if app instanceof Die
       return app

   # Return default Die app


Logic block: Fixed typo.

Logic block: Fixed typo.

--- engine/logic_block.coffee
+++ engine/logic_block.coffee
@@ -5,7 +5,7 @@
         scene = @scene
         @context = context = scene.context
         if @init? then @init()
-        if @pre_draw_tick? then cene.pre_draw_callbacks.push(@pre_draw_tick.bind @)
+        if @pre_draw_tick? then scene.pre_draw_callbacks.push(@pre_draw_tick.bind @)
         if @post_draw_tick? then scene.post_draw_callbacks.push(@pre_draw_tick.bind @)

 module.exports = {LogicBlock}


Fix XWS canonical name for PS9 Poe.

Fix XWS canonical name for PS9 Poe.

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -3628,7 +3628,7 @@
         }
         {
             name: "Maarek Stele (TIE Defender)"
-            canonical_name: 'Maarek Stele'.canonicalize()
+            canonical_name: 'maarekstele-swx52'
             id: 193
             unique: true
             faction: "Galactic Empire"


Better redirection flow.

Better redirection flow.

--- server/http/middleware/angular.coffee
+++ server/http/middleware/angular.coffee
@@ -66,7 +66,6 @@
 				]
 				injector = window.angular.element(window.document).injector()
 				injector.invoke invocation
-
 				{$sniffer, socket} = shrub

 				# Don't even try HTML 5 history on the server side.
@@ -86,9 +85,12 @@
 			return fn error if error?
 			fn null, contexts.add id, context

-	navigate = ({shrub, window}, path, body, fn) ->
-
+	navigate = (context, path, body, fn) ->
+
+		{shrub, window} = context
 		{$location, $rootScope, forms} = shrub
+
+		originalUrl = $location.url()

 		# Process any forms.
 		formFn = ->
@@ -110,6 +112,11 @@

 			unlisten = $rootScope.$on 'shrubFinishedRendering', ->
 				unlisten()
+
+				# Catch any path changes.
+				if path isnt $location.url()
+					context.redirect = $location.url()
+
 				formFn()

 			$rootScope.$apply -> $location.path path
@@ -176,6 +183,12 @@
 				# Touch the context to keep it alive.
 				context.touch()

+				# Emit the last HTML generated before the redirect.
+				if context.redirect
+					return process.nextTick ->
+						res.end context.redirect
+						context.redirect = null
+
 				# Prevent a possible race condition that would hang up the
 				# context in between now and render.
 				deferred = Q.defer()
@@ -190,19 +203,20 @@

 				else

-					# GOTO, not gonna lie.
-					do ->
-
-						# Check for any regexs.
-						for key, route of routes
-							if route.regexp?.test path
-
-								# TODO need to extract params to build
-								# redirectTo, small enough mismatch to ignore
-								# for now.
-								return
-
-						# Otherwise.
+					match = false
+
+					# Check for any regexs.
+					for key, route of routes
+						if route.regexp?.test path
+
+							# TODO need to extract params to build
+							# redirectTo, small enough mismatch to ignore
+							# for now.
+							match = true
+							break
+
+					# Otherwise.
+					unless match
 						if routes[null]?
 							return res.redirect routes[null].redirectTo

@@ -210,16 +224,31 @@
 				navigate context, path, body, (delay) ->

 					process.nextTick ->
-
+
 						# Reload the session, server-side JS socket stuff could
 						# have changed it!
 						req.session.reload ->

 							# Don't forget the doctype!
-							res.end """
+							emission = """
 <!doctype html>
 #{window.document.innerHTML}
 """

+							# If a redirect happened on the context, actually
+							# redirect the browser and save the emission for
+							# the next request.
+							if context.redirect
+								res.redirect context.redirect
+								context.redirect = emission
+
+							# Otherwise, just emit.
+							else
+
+								# Emit.
+								res.end emission
+
+							# Let any context expirations take place now that
+							# we've emitted.
 							deferred.resolve()
 							context.promise = null


we might want images -- just not mustached ones

--- scripts/trello.coffee
+++ scripts/trello.coffee
@@ -61,7 +61,7 @@

     trelloIt subject, '510acfdfeca4af654e004e85', msg

-  robot.hear /http(s?):\/\/(.*)/i, (msg) ->
+  robot.hear /^http(s?):\/\/(.*)/i, (msg) ->
     url = msg.match[0]
     unless process.env.TRELLO_API_KEY?
       msg.send "Please specify the Trello API key in TRELLO_API_KEY"
@@ -72,24 +72,24 @@
     ###unless process.env.TRELLO_LIST_ID?
       msg.send "Please specify the Trello List ID in TRELLO_LIST_ID"
       return###
-    ignore = url.match(/\.(png|jpg|jpeg|gif|txt|zip|tar\.bz|js|css)/)
-    unless ignore
-      jsdom = require 'jsdom'
-      jsdom.env(
-        html: msg.match[0]
-        scripts: [
-          'http://code.jquery.com/jquery-1.7.2.min.js'
-        ]
-        done: (errors, window) ->
-          unless errors
-            $ = window.$
-            title = $('title').text()
+    ###ignore = url.match(/\.(png|jpg|jpeg|gif|txt|zip|tar\.bz|js|css)/)
+    unless ignore###
+    jsdom = require 'jsdom'
+    jsdom.env(
+      html: msg.match[0]
+      scripts: [
+        'http://code.jquery.com/jquery-1.7.2.min.js'
+      ]
+      done: (errors, window) ->
+        unless errors
+          $ = window.$
+          title = $('title').text()

-            if title
-              url = url + ' [' + title + ']'
+          if title
+            url = url + ' [' + title + ']'

-            trelloIt url, '510acfdfeca4af654e004e84', msg
-      )
+          trelloIt url, '510acfdfeca4af654e004e84', msg
+    )

 trelloIt = (subject, list, msg) ->
   Trello = require("node-trello");


[#121523735] Fix bug: Can't download documents with large number of cases.

This bug was present on current Chrome. Turned out that the 'download' dialog was using a data URI of the stringified JSON. Appeared to bang up against URI size limits. Converted to a blob URL.
Access webkitURL on older webkit browsers.

--- src/code/views/download-dialog-view.coffee
+++ src/code/views/download-dialog-view.coffee
@@ -35,8 +35,10 @@

   download: (e) ->
     makeBlobURL = (msg) ->
+      wURL = window.URL or window.webkitURL
       blob = new Blob([msg], {type: 'text/plain'})
-      window.URL.createObjectURL(blob)
+      if (wURL)
+        wURL.createObjectURL(blob)

     if @state.trimmedFilename.length > 0
       json = @props.content.getContent()


fix upsert function

fix upsert function

--- backend/db.coffee
+++ backend/db.coffee
@@ -81,9 +81,7 @@
                         if prevDate > newDate
                             newDoc.lastModification =  prevDoc.lastModification

-                    db.put newDoc, (err) ->
-                        log.error err
-                        callback
+                    db.put newDoc, callback

     binaries:
         rows: []


Use proper error logging

Use proper error logging

--- src/ghe.coffee
+++ src/ghe.coffee
@@ -30,7 +30,7 @@
   process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"

   unless token
-    msg.send "token isn't set."
+    robot.logger.error "GitHub Enterprise token isn't set."

   robot.respond /ghe license$/i, (msg) ->
     ghe_license msg,token,url


Keep filename as file open/save tree is navigated [#115354115]

--- src/code/views/file-dialog-tab-view.coffee
+++ src/code/views/file-dialog-tab-view.coffee
@@ -78,8 +78,8 @@
   getInitialState: ->
     @getStateForFolder @props.client.state.metadata?.parent or null

-  componentWillMount: ->
-    @isOpen = @props.dialog.action is 'openFile'
+  isOpen: ->
+    @props.dialog.action is 'openFile'

   filenameChanged: (e) ->
     filename = e.target.value
@@ -89,14 +89,13 @@
       metadata: metadata

   listLoaded: (list) ->
-    @setState
-      list: list
-      metadata: @findMetadata $.trim(@state.filename), list
+    @setState list: list

   getStateForFolder: (folder) ->
+    metadata = if @isOpen() then @state?.metadata or null else @props.client.state.metadata
     folder: folder
-    metadata: @props.client.state.metadata
-    filename: @props.client.state.metadata?.name or ''
+    metadata: metadata
+    filename: metadata?.name or ''
     list: []

   fileSelected: (metadata) ->
@@ -114,7 +113,7 @@
       filename = $.trim @state.filename
       @state.metadata = @findMetadata filename, @state.list
       if not @state.metadata
-        if @isOpen
+        if @isOpen()
           @props.client.alert "#{@state.filename} not found"
         else
           @state.metadata = new CloudMetadata
@@ -156,7 +155,7 @@
       @confirm()

   confirmDisabled: ->
-    (@state.filename.length is 0) or (@isOpen and not @state.metadata)
+    (@state.filename.length is 0) or (@isOpen() and not @state.metadata)

   renderWhenAuthorized: ->
     confirmDisabled = @confirmDisabled()
@@ -166,7 +165,7 @@
       (input {type: 'text', value: @state.filename, placeholder: (tr "~FILE_DIALOG.FILENAME"), onChange: @filenameChanged, onKeyDown: @watchForEnter})
       (FileList {provider: @props.provider, folder: @state.folder, selectedFile: @state.metadata, fileSelected: @fileSelected, fileConfirmed: @confirm, list: @state.list, listLoaded: @listLoaded, client: @props.client})
       (div {className: 'buttons'},
-        (button {onClick: @confirm, disabled: confirmDisabled, className: if confirmDisabled then 'disabled' else ''}, if @isOpen then (tr "~FILE_DIALOG.OPEN") else (tr "~FILE_DIALOG.SAVE"))
+        (button {onClick: @confirm, disabled: confirmDisabled, className: if confirmDisabled then 'disabled' else ''}, if @isOpen() then (tr "~FILE_DIALOG.OPEN") else (tr "~FILE_DIALOG.SAVE"))
         if @props.provider.can 'remove'
           (button {onClick: @remove, disabled: removeDisabled, className: if removeDisabled then 'disabled' else ''}, (tr "~FILE_DIALOG.REMOVE"))
         (button {onClick: @cancel}, (tr "~FILE_DIALOG.CANCEL"))


add metrics for redis get/update

add metrics for redis get/update

--- services/document-updater/app/coffee/RedisManager.coffee
+++ services/document-updater/app/coffee/RedisManager.coffee
@@ -132,6 +132,9 @@
 			if timeSpan > MAX_REDIS_REQUEST_LENGTH
 				error = new Error("redis getDoc exceeded timeout")
 				return callback(error)
+			# record bytes loaded from redis
+			if docLines?
+				metrics.summary "redis.docLines", docLines.length, {status: "get"}
 			# check sha1 hash value if present
 			if docLines? and storedHash?
 				computedHash = RedisManager._computeHash(docLines)
@@ -247,7 +250,8 @@

 			opVersions = appliedOps.map (op) -> op?.v
 			logger.log doc_id: doc_id, version: newVersion, hash: newHash, op_versions: opVersions, "updating doc in redis"
-
+			# record bytes sent to redis in update
+			metrics.summary "redis.docLines", newDocLines.length, {status: "update"}
 			RedisManager._serializeRanges ranges, (error, ranges) ->
 				if error?
 					logger.error {err: error, doc_id}, error.message


Restructuring, adding public methods

Restructuring, adding public methods

--- responsive-elements.coffee
+++ responsive-elements.coffee
@@ -4,13 +4,15 @@
  * Inspired by https://github.com/kumailht/responsive-elements
  *
  * @copyright Corey Worrell 2014
- * @version   1.0.0
+ * @version   1.1.0
 ###

-(->
-	ResponsiveElements = (options) ->
-		# Options
-		opts =
+do ->
+	window.re = do ->
+		b = this
+
+		# Configuration
+		b.config =
 			attr:         'data-respond'
 			widthAttr:    'data-width'
 			refreshRate:  50
@@ -20,13 +22,10 @@
 			end:          900
 			interval:     50

-		for key, value of options
-			opts[key] = value if opts[key]?
-
-		# DOM
-		dom =
+		# DOM elements
+		b.dom =
 			window:   window
-			elements: document.querySelectorAll "[#{opts.attr}]"
+			elements: document.querySelectorAll "[#{b.config.attr}]"

 		# Utilities
 		util =
@@ -55,20 +54,28 @@
 		# Bind listeners
 		bind = ->
 			dom.window.addEventListener 'load', respondAll
-			dom.window.addEventListener 'resize', (util.debounce respondAll, opts.refreshRate)
+			dom.window.addEventListener 'resize', debounceRespondAll
+
+		# Unbind listeners
+		unbind = ->
+			dom.window.removeEventListener 'load', respondAll, false
+			dom.window.removeEventListener 'resize', debounceRespondAll, false

 		# Respond all elements
 		respondAll = (e) ->
 			for element in dom.elements
 				respond element

+		# Respond with a debounce
+		debounceRespondAll = util.debounce respondAll, b.config.refreshRate
+
 		# Respond single element
 		respond = (element) ->
-			params = parseParams (element.getAttribute opts.attr)
+			params = parseParams (element.getAttribute b.config.attr)

 			data = makeData element.offsetWidth, params

-			element.setAttribute opts.widthAttr, (data.join ' ')
+			element.setAttribute b.config.widthAttr, (data.join ' ')

 		# Make responsive data
 		makeData = (width, params) ->
@@ -76,7 +83,7 @@

 			i = if params.interval > params.start then params.interval else ~~(params.start / params.interval) * params.interval

-			width = width * (opts.baseFontSize / opts.rootFontSize)
+			width = width * (b.config.baseFontSize / b.config.rootFontSize)

 			while i <= params.end
 				data.push "gt#{i}" if i < width
@@ -89,25 +96,47 @@
 		# Parse parameters
 		parseParams = (string) ->
 			params  =
-				start:    opts.start
-				end:      opts.end
-				interval: opts.interval
+				start:    b.config.start
+				end:      b.config.end
+				interval: b.config.interval

-			matches = string.match /([a-zA-Z]|[0-9]+)/g
+			matches = string.match /([a-zA-Z]+|[0-9]+)/g

 			if ! matches
 				return params

 			for match, i in matches by 2
-				switch match.toLowerCase()
+				switch match[0].toLowerCase()
 					when 's' then params.start    = +matches[i + 1]
 					when 'e' then params.end      = +matches[i + 1]
 					when 'i' then params.interval = +matches[i + 1]

 			params

-		bind()
+		# Public methods
+		# Set configuration
+		b.setConfig = (config) ->
+			for key, value of config
+				b.config[key] = value if b.config[key]?

-	# Export
-	window.ResponsiveElements = ResponsiveElements
-)()
+			b.dom.elements = document.querySelectorAll "[#{b.config.attr}]"
+
+			b
+
+		# Enable
+		b.enable = ->
+			bind()
+			b
+
+		# Disable
+		b.disable = ->
+			unbind()
+			b
+
+		# Refresh
+		b.refresh = ->
+			respondAll()
+			b
+
+		# Return self
+		b


delete v2

delete v2

--- src/coffee/v2-portal-sku-selector-popup.coffee
+++ src/coffee/v2-portal-sku-selector-popup.coffee
@@ -1,5 +0,0 @@
-$(window).ready ->
-	$.skuSelector "popup" if $("meta[name=vtex-version]").length > 0
-
-$(document).ajaxStop ->
-	$.skuSelector.bindClickHandlers "btn-add-buy-button-asynchronous" if $("meta[name=vtex-version]").length > 0


Fix for generating form ids

Fix for generating form ids

--- _source/client/templates/components/forms/form.coffee
+++ _source/client/templates/components/forms/form.coffee
@@ -21,7 +21,7 @@

   paramaterize: (string) ->
-    if not string is undefined
+    if string isnt undefined
       string = string.trim()
       string = string.replace /[^a-zA-Z0-9-\s]/g, ''
       string = string.replace /[^a-zA-Z0-9-]/g, '-'


Client: Add check for Reset route if found redirect to `/`

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -370,6 +370,10 @@
       firstRoute = KD.getSingleton("router").visitedRoutes.first

       if firstRoute and /^\/Verify/.test firstRoute
+        firstRoute = "/"
+
+
+      if firstRoute and /^\/Reset/.test firstRoute
         firstRoute = "/"

       KD.getSingleton('appManager').quitAll()


a hacky fix for scroll fix

a hacky fix for scroll fix

--- client/landing/app/Applications/Members.kdapplication/ContentDisplays/ContentDisplayControllerMember.coffee
+++ client/landing/app/Applications/Members.kdapplication/ContentDisplays/ContentDisplayControllerMember.coffee
@@ -22,6 +22,11 @@
         domId : 'member-contentdisplay' unless @revivedContentDisplay
     ,options
     super options, data
+
+    @ready =>
+      KD.utils.wait 1000, ->
+        # fka -- find a really true way to do it.
+        KD.getSingleton("windowController").notifyWindowResizeListeners()

   loadView:(mainView)->
     member = @getData()


update atom settings

update atom settings

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -8,7 +8,7 @@
   metrics:
     userId: "f0927718331c072990cb750aafe7bbb7c24695ca"
   editor:
-    fontFamily: "Source Code Pro"
+    fontFamily: "Hack"
     preferredLineLength: 130
     softWrap: true
     invisibles: {}
@@ -28,7 +28,7 @@
     ]
     disabledPackages: [
       "linter-eslint"
-      "linter-jshint"
+      "linter-xo"
     ]
   "spell-check":
     grammars: [
@@ -47,14 +47,23 @@
   "atom-material-ui":
     panelContrast: true
     compactTreeView: true
-  "highlight-selected": {}
+  "highlight-selected":
+    lightTheme: true
   pigments: {}
   linter: {}
   "linter-jshint":
     disableWhenNoJshintrcFileInPath: true
-  "atom-beautify": {}
+  "atom-beautify":
+    _analyticsUserId: "8b9e6b6c-7ca9-4b13-8b9d-ee875e13ba81"
   emmet: {}
   "the-closer":
     closeWindowTogetherWithLastTab: false
   "linter-eslint":
     useGlobalEslint: true
+  "toggle-packages":
+    togglePackages: [
+      "linter-xo"
+      "linter-jshint"
+    ]
+  "status-bar": {}
+  "linter-scss-lint": {}


atom: change project home path

atom: change project home path

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -13,3 +13,4 @@
     'exception-reporting'
     'metrics'
   ]
+  'projectHome': '~/Projects'


close the panel

close the panel

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -26,6 +26,8 @@
     invisibles: {}
     showIndentGuide: true
     softWrapAtPreferredLineLength: true
+  "go-plus":
+    panelDisplayMode: "closed"
   "linter-eslint":
     useGlobalEslint: true
   "release-notes":


fucking atom

fucking atom

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,11 +1,12 @@
 "*":
-  editor:
-    showIndentGuide: true
-    fontFamily: "Menlo"
-    invisibles: {}
-    fontSize: 15
-    softWrapAtPreferredLineLength: true
   core:
+    disabledPackages: [
+      "background-tips"
+      "exception-reporting"
+      "metrics"
+    ]
+    followSymlinks: false
+    hideGitIgnoredFiles: true
     ignoredNames: [
       ".bundle"
       ".git"
@@ -18,13 +19,15 @@
       "unity-ui"
       "glacier-syntax"
     ]
-    hideGitIgnoredFiles: true
-    disabledPackages: [
-      "background-tips"
-      "exception-reporting"
-      "metrics"
-    ]
-    followSymlinks: false
+  editor:
+    fontFamily: "Menlo"
+    invisibles: {}
+    showIndentGuide: true
+    softWrapAtPreferredLineLength: true
+  "go-plus":
+    goPath: "/Users/holman/Code/go"
+  "release-notes":
+    viewedVersion: "0.89.0"
   "spell-check":
     grammars: [
       "text.plain"
@@ -33,7 +36,3 @@
     ]
   welcome:
     showOnStartup: false
-  "release-notes":
-    viewedVersion: "0.89.0"
-  "go-plus":
-    goPath: "/Users/holman/Code/go"


Update Atom settings

Update Atom settings

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -14,10 +14,6 @@
     disabledPackages: [
       "preview-tabs"
       "script"
-    ]
-    themes: [
-      "atom-dark-ui"
-      "base16-ocean-dark-spacegray"
     ]
     projectHome: "/Users/justas/Projects"
   "spell-check":


Fix require uri

Fix require uri

--- src/scripts/modules/browse-content/browse-content.coffee
+++ src/scripts/modules/browse-content/browse-content.coffee
@@ -1,5 +1,5 @@
 define (require) ->
-  subjects = require('cs!/scripts/collections/subjects')
+  subjects = require('cs!collections/subjects')
   BaseView = require('cs!helpers/backbone/views/base')
   template = require('hbs!./browse-content-template')
   require('less!./browse-content')


remove hot schools, not using it now

remove hot schools, not using it now

--- source/javascripts/recruit-map.coffee
+++ source/javascripts/recruit-map.coffee
@@ -66,22 +66,8 @@
     .rollup((d) -> d.length)
     .entries(env.recruits)

-  hotschools = d3.nest()
-    .key((d) -> "#{d.school}:#{d.place_gid}")
-    .rollup((d) -> d.length)
-    .entries(env.recruits)
-
-  for school in hotschools
-    [name, gid] = school.key.split(':')
-    school.place = bygid[gid]
-    school.name = name
-    school
-
   hotspots.sort (a, b) -> d3.descending(a.values, b.values)
   topPlaces = hotspots[0..14].map (spot) -> spot.place = bygid[spot.key]; spot
-
-  hotschools.sort (a, b) -> d3.descending(a.values, b.values)
-  console.log hotschools[0..10]

   for recruit in env.recruits
     coordinates = env.projection [recruit.lat, recruit.lon]


Add usercard on mapper name and link avatar to profile

--- resources/assets/coffee/react/_components/beatmapset-mapping.coffee
+++ resources/assets/coffee/react/_components/beatmapset-mapping.coffee
@@ -25,9 +25,11 @@
 class @BeatmapsetMapping extends React.PureComponent
   render: =>
     user = @props.user ? @props.beatmapset.user
+    userURL = laroute.route 'users.show', user: user.id

     div className: bn,
-      div
+      a
+        href: userURL
         className: 'avatar avatar--beatmapset'
         style:
           backgroundImage: "url(#{user.avatar_url})"
@@ -36,8 +38,9 @@
         div className: "#{bn}__mapper",
           osu.trans 'beatmapsets.show.details.made-by'
           a
-            className: "#{bn}__user"
-            href: laroute.route 'users.show', user: user.id
+            className: "#{bn}__user js-usercard"
+            'data-user-id': user.id
+            href: userURL
             user.username

         @renderDate 'submitted', 'submitted_date'


Toaster: Make backdrop overwritable to "false" when show_ms == 0.

--- src/elements/ConfirmationChoice/Toaster.coffee
+++ src/elements/ConfirmationChoice/Toaster.coffee
@@ -16,12 +16,10 @@
 				v >= 0

 	readOpts: ->
+		if @opts.show_ms == 0 and @opts.backdrop == undefined
+			@opts.backdrop = policy: "modal"
+
 		super()
-		if @_show_ms == 0
-			if not @_backdrop
-				@_backdrop =
-					policy: "modal"
-			console.error "backrop:", @_backdrop

 		@_choices = []


showing the sign in form again right on getting signed in is very unnerving.

--- salvus/page/account.coffee
+++ salvus/page/account.coffee
@@ -910,4 +910,3 @@

 salvus_client.on "signed_in", () ->
     $(".salvus-remember_me-message").hide()
-    $(".salvus-sign_in-form").show()


harald -- misc LaTeX file extensions I wanted to have gray-listed

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -36,7 +36,7 @@
     'class'         : 'java'
     'exe'           : 'cs'

-for ext in misc.split('blg bbl glo idx toc aux log lof ind nav snm gz xyc out ilg')  # gz really synctex.gz
+for ext in misc.split('blg bbl glo idx toc aux log lof ind nav snm gz xyc out ilg fdb_latexmk fls')  # gz really synctex.gz
     masked_file_exts[ext] = 'tex'

 #many languages such as fortran or c++ have a default file name of "a.out." when compiled, so .out extensions are not masked


Expose SVGPath and SVGGroup

Expose SVGPath and SVGGroup

--- framer/Framer.coffee
+++ framer/Framer.coffee
@@ -12,6 +12,8 @@
 Framer.BackgroundLayer = (require "./BackgroundLayer").BackgroundLayer
 Framer.VideoLayer = (require "./VideoLayer").VideoLayer
 Framer.SVGLayer = (require "./SVGLayer").SVGLayer
+Framer.SVGPath = (require "./SVGPath").SVGPath
+Framer.SVGGroup = (require "./SVGGroup").SVGGroup
 Framer.TextLayer = (require "./TextLayer").TextLayer
 Framer.Events = (require "./Events").Events
 Framer.Gestures = (require "./Gestures").Gestures


slightly refactor masked_files code

slightly refactor masked_files code

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -29,6 +29,18 @@
 template_project_branch_single = templates.find(".project-branch-single")
 template_project_collab        = templates.find(".project-collab")
 template_project_linked        = templates.find(".project-linked")
+
+
+exports.masked_file_exts = masked_file_exts =
+    'pyc'           : 'py'
+    'class'         : 'java'
+    'exe'           : 'cs'
+
+for ext in misc.split('blg bbl glo idx toc aux log lof ind nav snm gz xyc out ilg')  # gz really synctex.gz
+    masked_file_exts[ext] = 'tex'
+
+#many languages such as fortran or c++ have a default file name of "a.out." when compiled, so .out extensions are not masked
+

 ##################################################
 # Initialize the modal project management dialogs
@@ -1449,15 +1461,6 @@
                 #console.log("building listing for #{path}...")

                 tm = misc.walltime()
-                masked_file_exts =
-                    'pyc'           : 'py'
-                    'class'         : 'java'
-                    'exe'           : 'cs'
-
-                for ext in misc.split('blg bbl glo idx toc aux log lof ind nav snm gz xyc out ilg')  # gz really synctex.gz
-                    masked_file_exts[ext] = 'tex'
-
-                    #many languages such as fortran or c++ have a default file name of "a.out." when compiled, so .out extensions are not masked

                 masked_file_exts_bad = (key for key of masked_file_exts)
                 masked_file_exts_good = (value for key, value of masked_file_exts)


Use proper relative paths in cache

Use proper relative paths in cache

--- build/tasks/compile-packages-slug-task.coffee
+++ build/tasks/compile-packages-slug-task.coffee
@@ -20,12 +20,12 @@
       pack = {metadata, keymaps: {}, menus: {}}

       for keymapPath in fs.listSync(path.join(moduleDirectory, 'keymaps'), ['.cson', '.json'])
-        keymapPath = path.relative(appDir, keymapPath)
-        pack.keymaps[keymapPath] = CSON.readFileSync(keymapPath)
+        relativePath = path.relative(appDir, keymapPath)
+        pack.keymaps[relativePath] = CSON.readFileSync(keymapPath)

       for menuPath in fs.listSync(path.join(moduleDirectory, 'menus'), ['.cson', '.json'])
-        menuPath = path.relative(appDir, menuPath)
-        pack.menus[menuPath] = CSON.readFileSync(menuPath)
+        relativePath = path.relative(appDir, menuPath)
+        pack.menus[relativePath] = CSON.readFileSync(menuPath)

       packages[metadata.name] = pack


Track positive and negative feedback as different events.

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -36,8 +36,8 @@
 			event_tracking.sendCountly "logs-hints-learn-more"

 		trackLogHintsFeedback = (isPositive, hintId) ->
-			event_tracking.send 'log-hints', (if isPositive then 'feedback-positive' else 'feedback-negative'), hintId
-			event_tracking.sendCountly "log-hints-feedback", { isPositive, hintId }
+			event_tracking.send "log-hints", (if isPositive then "feedback-positive" else "feedback-negative"), hintId
+			event_tracking.sendCountly (if isPositive then "log-hints-feedback-positive" else "log-hints-feedback-negative"), { hintId }

 		$scope.trackLogHintsPositiveFeedback = (hintId) -> trackLogHintsFeedback true, hintId
 		$scope.trackLogHintsNegativeFeedback = (hintId) -> trackLogHintsFeedback false, hintId


Add parsing of datetime as a result for assesment performed as per QDM5.02 3.1.3

--- app/assets/javascripts/datatypes/assessment.js.coffee
+++ app/assets/javascripts/datatypes/assessment.js.coffee
@@ -51,7 +51,7 @@
     new cql.Code(@_reason?.code, @_reason?.code_system)

   ###
-  @returns {Code|Int}
+  @returns {Code|Int|Date}
   ###
   result: ->
     if @_result
@@ -59,6 +59,8 @@
         code_system = @_result.codes[Object.keys(@_result.codes)[0]]
         code = @_result.codes[code_system]
         new cql.Code(code, code_system)
+      else if @_result.units == "UnixTime" # A PhysicalQuantity with unit UnixTime is a TimeStamp
+        CQL_QDM.Helpers.convertDateTime(@_result.scalar)
       else
         parseInt(@_result.scalar)


Prevent progress updates happening more than twice a second.

--- app/assets/javascripts/services/file_upload.js.coffee
+++ app/assets/javascripts/services/file_upload.js.coffee
@@ -6,8 +6,12 @@
       xhr = null
       deferred = $q.defer()

+      lastProgressApply = 0
       progressHandler = (event) ->
         return unless event.lengthComputable
+        now = Date.now()
+        return if now - lastProgressApply < 500
+        lastProgressApply = now
         $rootScope.$apply -> deferred.notify(loaded: event.loaded, total: event.total)

       $q.when($.ajax


typos

typos

--- app/assets/jax/controllers/level_controller.js.coffee
+++ app/assets/jax/controllers/level_controller.js.coffee
@@ -4,31 +4,19 @@
     @movement = { up: 0, down: 0, left: 0, right: 0 }

     # unload old level if exists
-    if @level
-      for i in [0..@level.cols_number*@level.rows_number-1]
-        if @level.objects[i]
-          @world.removeObject @level.objects[i].__unique_id
-          @level.objects[i].dispose()
-          delete @level.objects[i]
-      @world.removeObject @level.__unique_id
-      delete @level.objects
-      delete @level.grid
-      delete @level
+    @unload_level(@level)

-    # load level and add to the scene
+    # load selected level and add it to the world
     @level = Level.find "actual"
     @world.addObject @level

     # Initalize path
     @path = Path.find "actual"

-    # load the components of the level and add on the scene
+    # load the components of the level and add them on the world
     for i in [0..@level.cols_number*@level.rows_number-1]
       if @level.objects[i]
         @world.addObject @level.objects[i]
-
-    #alert(@world.countObjects())
-    #@level.print()

   update: (timechange) ->
     ;
@@ -68,15 +56,26 @@
           [pusher_m-1, pusher_n],
           [pusher_m,   pusher_n+1],
           [pusher_m,   pusher_n-1],
-
           [pusher_m+2, pusher_n],   # if reverse
           [pusher_m-2, pusher_n],   # if reverse
           [pusher_m,   pusher_n+2], # if reverse
           [pusher_m,   pusher_n-2], # if reverse
         ]

-      # visually refresh these 5 positions
+      # visually refresh these 9 positions
       for position in positions
         index = level.cols_number*position[0] + position[1]
         if level.read_pos(position[0], position[1]) != 'E'
           level.display_position(position[0], position[1])
+
+  unload_level: (level) ->
+    if level
+      for i in [0..level.cols_number*level.rows_number-1]
+        if level.objects[i]
+          @world.removeObject level.objects[i].__unique_id
+          level.objects[i].dispose()
+          delete level.objects[i]
+      @world.removeObject level.__unique_id
+      delete level.objects
+      delete level.grid
+      delete level


CSV Export refinements

CSV Export refinements

--- app/coffee/modules/dataset/export/views/layout.coffee
+++ app/coffee/modules/dataset/export/views/layout.coffee
@@ -80,7 +80,13 @@
       # CSV Rows
       for el in jsonExport
         row = []
-        row.push(el[header]|| ' ') for header in allKeys
+
+        for header in allKeys
+          value = el[header]
+          value = value['@id'] if typeof value == 'object' && value['@id']
+          value = 'NULL' if value == undefined
+          row.push(value)
+
         csvOut.push(row.join(', '))

       # Filename and filetype


Fixes issue where other emoji (not thumbup/thumbdown) were not removed

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -44,7 +44,7 @@
   decrementCounter: (emoji) ->
     counter = @findEmojiIcon(emoji).siblings(".counter")
     emojiIcon = counter.parent()
-    if parseInt(counter.text()) > 0
+    if parseInt(counter.text()) > 1
       counter.text(parseInt(counter.text()) - 1)
       emojiIcon.removeClass("active")
       @removeMeFromAuthorList(emoji)


Support documentation for macros

Support documentation for macros

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -482,8 +482,10 @@

   ## Macro declarations

-  declareMacro: (name, macro) ->
+  declareMacro: (name, macro, docs) ->
     name.id = macro.id = @freshId()
+    name.scope = @currentScopeIndex()
+    macro.docs = docs
     addToMap @_scope().macros, name.symbol, macro

   isMacroDeclared: (name) ->
@@ -784,6 +786,7 @@
 callMacroExpand = (ctx, call) ->
   op = _operator call
   op.label = 'keyword'
+  op.scope = ctx.currentScopeIndex()
   macro = ctx.macro op.symbol
   op.id = macro.id
   macro ctx, call
@@ -2516,6 +2519,8 @@
   [maybeType] = rest

   doDeclare = isTypeAnnotation maybeType
+  [docs] = partition isComment, rest
+  typed = isTypeAnnotation maybeType

   if hasName
     macroName = ctx.definitionName()
@@ -2532,21 +2537,19 @@
     macroFn = eval compiledMacro
     if macroFn
       ctx.declareMacro ctx.definitionPattern(), (ctx, call) ->
+        operatorCompile ctx, call if typed
         if splatted
           constantToSource macroFn Immutable.List (_arguments call)
         else
           constantToSource macroFn (_arguments call)...
+      , extractDocs docs
     else
       malformed ctx, ctx.definitionPattern(), 'Macro failed to compile'

-  if isTypeAnnotation maybeType
-    [docs] = partition isComment, rest
+  if typed
     params = (map token_, map _symbol, _terms paramTuple)
     fn_ params, concat [[maybeType], docs, [call_ (token_ macroName), params]]
   else
-    # TODO: docs for macros
-    [docs] = partition isComment, rest
-    extractDocs docs
     jsNoop()

 ms['`'] = ms_quote = (ctx, call) ->
@@ -6070,6 +6073,9 @@
   if not found
     {ctx} = contextWithDependencies reverseModuleDependencies moduleName
     found = lookupInMap ctx._scope(), name # Top scope
+  if not found
+    docs = (lookupInMap ctx._scope().macros, name)?.docs
+    found = {docs} if docs
   found

 # API
@@ -6148,7 +6154,7 @@

 labelDocs = (source, params) ->
   ast = (astizeList source)[1...-1]
-  labelUsedParams ast, params
+  labelUsedParams ast, params if params
   # Strip labels at the top of docs, since those are words
   for word in ast when not ((isForm word) or (word.label is 'param'))
     word.label = null


Fixed sets

Fixed sets

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -2649,7 +2649,7 @@
   {ir: irSetTranslate, items}

 irSetTranslate = (ctx, {items}) ->
-  (jsCall "Immutable.Set.of", items)
+  (jsCall "Immutable.Set.of", (translateIr ctx, items))

 irJsCompatible = (type, expression) ->
@@ -2934,7 +2934,7 @@
   {js: jsNoopTranslate}

 jsNoopTranslate = ->
-  ""
+  "null"

 jsReturn = (arg) ->
@@ -5622,6 +5622,16 @@
   """"""
   "global.Math.PI", Math.PI

+  'sets'
+  """
+  first (macro [set]
+    (: (Fn (Set a) a))
+    (Js.method set "first" {}))
+  x 2
+  y (Set x 1 3)
+  """
+  '(first y)', 2
+
   # TODO: support matching with the same name
   #       to implement this we need the iife to take as arguments all variables
   #       with the same names, since JavaScript shadows it too strongly and


Prevent update settings with same value

Prevent update settings with same value

--- packages/rocketchat-lib/server/models/Settings.coffee
+++ packages/rocketchat-lib/server/models/Settings.coffee
@@ -80,6 +80,7 @@
 	updateValueById: (_id, value) ->
 		query =
 			blocked: { $ne: true }
+			value: { $ne: value }
 			_id: _id

 		update =


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/clamp_bounds.coffee
+++ src/clamp_bounds.coffee
@@ -1,5 +1,5 @@
 ###*
-The ClampBounds module
+The ClampBounds module adds a

 # Health regeneration


Wrapping functions in `expect` testing error

If you are calling `problem.answer` method as is, it's scope is
global and can not access instance variables in `problem` object.
Therfore, this test will be always returning success.

--- assignments/coffeescript/wordy/wordy_test.spec.coffee
+++ assignments/coffeescript/wordy/wordy_test.spec.coffee
@@ -60,9 +60,9 @@

   xit 'too advanced', ->
     problem = new WordProblem('What is 53 cubed?')
-    expect(problem.answer).toThrow(problem.ERROR.tooComplicated)
+    expect(-> problem.answer()).toThrow(problem.ERROR.tooComplicated)

   xit 'irrelevant', ->
     problem = new WordProblem('Who is the president of the United States?')
-    expect(problem.answer).toThrow(problem.ERROR.tooComplicated)
+    expect(-> problem.answer()).toThrow(problem.ERROR.tooComplicated)


Chaning src of webview should always do a load

Previously changing src to the same value won't have any effect, which
does not follow the behavior of browsers.

--- atom/renderer/lib/web-view/web-view-attributes.coffee
+++ atom/renderer/lib/web-view/web-view-attributes.coffee
@@ -138,10 +138,7 @@
   setupMutationObserver: ->
     @observer = new MutationObserver (mutations) =>
       for mutation in mutations
-        oldValue = mutation.oldValue
-        newValue = @getValue()
-        return if oldValue isnt newValue
-        @handleMutation oldValue, newValue
+        @handleMutation mutation.oldValue, @getValue()
     params =
       attributes: true,
       attributeOldValue: true,


Update for Csound 6.17.0

Update for Csound 6.17.0


fix nick name getting

fix nick name getting

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -116,7 +116,7 @@

     room = msg.envelope.room
     msg_data =
-      nick: msg.user.name
+      nick: msg.envelope.user.name
       type: message_type msg
       date: Date.now()
     msg_data['text'] = msg.text if msg instanceof TextMessage


Pass through the complete state object on interactiveGlobal events.

--- src/code/wrapper.coffee
+++ src/code/wrapper.coffee
@@ -11,6 +11,7 @@

 module.exports = class Wrapper
   constructor: (id) ->
+    @globalState = {}
     @updateRuntimeDataSchedule = false
     @updateInterval = 500 # 0.5s
     @loadConfiguration()
@@ -59,9 +60,8 @@

   runtimeHandlers: ->
     "loadInteractiveGlobal": (data) =>
-      key = @globalStateKey
-      data = JSON.parse(data) if typeof data is 'string'
-      myData = data[key]
+      @globalState = JSON.parse(data) if typeof data is 'string'
+      myData = @globalState[@globalStateKey]
       if myData
         @interactivePhone.post 'sendDatasetEvent',
           "eventName": 'dataReset'
@@ -82,7 +82,8 @@
     "getDataset": () =>
       l.info("getDataSet sent by interactive (what to do?)")
     "dataset": (data)=>
-      @runtimePhone.post('interactiveStateGlobal', {"#{@globalStateKey}": data})
+      @globalState[@globalStateKey] = data
+      @runtimePhone.post('interactiveStateGlobal', @globalState)

   interactivePhoneAnswered: ()->
     if @alreadySetupInteractive


naming

naming

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1,55 +1,55 @@
 Bacon = (require "../src/Bacon").Bacon
-describe "later", ->
+describe "Bacon.later", ->
   it "should send single event and end", ->
     expectStreamEvents(
       -> Bacon.later(10, "lol")
       ["lol"])

-describe "sequentially", ->
+describe "Bacon.sequentially", ->
   it "should send given events and end", ->
     expectStreamEvents(
       -> Bacon.sequentially(10, ["lol", "wut"])
       ["lol", "wut"])

-describe "interval", ->
+describe "Bacon.interval", ->
   it "repeats single element indefinitely", ->
     expectStreamEvents(
       -> Bacon.interval(10, "x").take(3)
       ["x", "x", "x"])

-describe "filter", ->
+describe "EventStream.filter", ->
   it "should filter values", ->
     expectStreamEvents(
       -> repeat(10, [1, 2, 3]).take(3).filter(lessThan(3))
       [1, 2])

-describe "map", ->
+describe "EventStream.map", ->
   it "should map with given function", ->
     expectStreamEvents(
       -> repeat(10, [1, 2, 3]).take(3).map(times(2))
       [2, 4, 6])

-describe "takeWhile", ->
+describe "EventStream.takeWhile", ->
   it "should take while predicate is true", ->
     expectStreamEvents(
       -> repeat(10, [1, 2, 3]).takeWhile(lessThan(3))
       [1, 2])

-describe "flatMap", ->
+describe "EventStream.flatMap", ->
   it "should spawn new stream for each value and collect results into a single stream", ->
     expectStreamEvents(
       -> repeat(10, [1, 2]).take(2).flatMap (value) ->
         Bacon.sequentially(100, [value, value])
       [1, 2, 1, 2])

-describe "switch", ->
+describe "EventStream.switch", ->
   it "spawns new streams but collects values from the latest spawned stream only", ->
     expectStreamEvents(
       -> repeat(30, [1, 2]).take(2).switch (value) ->
         Bacon.sequentially(20, [value, value])
       [1, 2, 2])

-describe "merge", ->
+describe "EventStream.merge", ->
   it "merges two streams and ends when both are exhausted", ->
     expectStreamEvents(
       ->
@@ -65,7 +65,7 @@
         left.merge(right).takeWhile(lessThan(2))
       [1])

-describe "delay", ->
+describe "EventStream.delay", ->
   it "delays all events by given delay in milliseconds", ->
     expectStreamEvents(
       ->
@@ -74,13 +74,13 @@
         left.merge(right)
       [1, 2, 3, 4, 5, 6])

-describe "throttle", ->
+describe "EventStream.throttle", ->
   it "throttles input by given delay", ->
     expectStreamEvents(
       -> repeat(10, [1, 2]).take(2).throttle(20)
       [2])

-describe "takeUntil", ->
+describe "EventStream.takeUntil", ->
   it "takes elements from source until an event appears in the other stream", ->
     expectStreamEvents(
       ->
@@ -89,7 +89,7 @@
         src.takeUntil(stopper)
       [1, 2])

-describe "pushStream", ->
+describe "Bacon.pushStream", ->
   it "delivers pushed events", ->
     expectStreamEvents(
       ->
@@ -163,13 +163,13 @@
         prop.sample(30).take(4)
       [1, 2, 2, 2])

-describe "scan", ->
+describe "EventStream.scan", ->
   it "accumulates values with given seed and accumulator function", ->
     expectPropertyEvents(
       -> repeat(10, [1, 2, 3]).take(3).scan(0, add)
       [0, 1, 3, 6])

-describe "subscribe and onValue", ->
+describe "Observable.subscribe and onValue", ->
   it "returns a dispose() for unsubscribing", ->
     s = Bacon.pushStream()
     values = []


Add a test with brackets to control port

--- spec/Component.coffee
+++ spec/Component.coffee
@@ -492,6 +492,44 @@

       sin1.post new IP 'data', 'foo'
       sin2.post new IP 'data', 'bar'
+      sin1.post new IP 'data', 'boo'
+
+    it 'should keep last data-typed IP packet for controls', (done) ->
+      c = new component.Component
+        inPorts:
+          foo: datatype: 'string'
+          bar:
+            datatype: 'string'
+            control: true
+        outPorts:
+          baz: datatype: 'object'
+        process: (input, output) ->
+          return unless input.has 'foo', 'bar'
+          [foo, bar] = input.getData 'foo', 'bar'
+          baz =
+            foo: foo
+            bar: bar
+          output.sendDone
+            baz: baz
+
+      c.inPorts.foo.attach sin1
+      c.inPorts.bar.attach sin2
+      c.outPorts.baz.attach sout1
+
+      sout1.once 'data', (ip) ->
+        chai.expect(ip).to.be.an 'object'
+        chai.expect(ip.type).to.equal 'data'
+        chai.expect(ip.data.foo).to.equal 'foo'
+        chai.expect(ip.data.bar).to.equal 'bar'
+        sout1.once 'data', (ip) ->
+          chai.expect(ip).to.be.an 'object'
+          chai.expect(ip.type).to.equal 'data'
+          chai.expect(ip.data.foo).to.equal 'boo'
+          chai.expect(ip.data.bar).to.equal 'bar'
+          done()
+
+      sin1.post new IP 'data', 'foo'
+      sin2.send new IP 'data', 'bar'
       sin1.post new IP 'data', 'boo'

     it 'should isolate packets with different scopes', (done) ->


Update index.coffee

Update index.coffee

--- examples/index.coffee
+++ examples/index.coffee
@@ -1,4 +1,4 @@
-Config = require "../"
+Config = require "../lib"
 fs = require "fs"
 path = require "path"


vmUI: minor

vmUI: minor

--- client/app/MainApp/sidebar/sidebarview.coffee
+++ client/app/MainApp/sidebar/sidebarview.coffee
@@ -101,7 +101,7 @@
       icon      : yes
       iconClass : "plus-orange"
       cssClass  : "clean-gray create-vm"
-      callback  : KD.singletons.vmController.createNewVM
+      callback  : KD.getSingleton('vmController').createNewVM

     @environmentButton = new KDButtonView
       title     : "Environments"


Don't throw exception on closed guest window

--- atom/browser/lib/guest-window-manager.coffee
+++ atom/browser/lib/guest-window-manager.coffee
@@ -55,14 +55,16 @@
     [url, frameName, options] = args
     event.returnValue = createGuest event.sender, url, options

-ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, args...) ->
-  guest = removeGuest args...
+ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', (event, embedderId, guestId) ->
+  return unless BrowserWindow.windows.has guestId
+  guest = removeGuest embedderId, guestId
   guest.destroy()

 ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestId, method, args...) ->
-  guest = BrowserWindow.windows.get guestId
-  guest[method] args...
+  return unless BrowserWindow.windows.has guestId
+  BrowserWindow.windows.get(guestId)[method] args...

 ipc.on 'ATOM_SHELL_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestId, method, args...) ->
+  return unless BrowserWindow.windows.has guestId
   guest = BrowserWindow.windows.get guestId
-  guest.webContents?[method] args...
+  BrowserWindow.windows.get(guestId).webContents?[method] args...


Add specs around coffee-refactor:rename event

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -31,7 +31,7 @@

   describe "when '.coffee' file is opened", ->

-    [ editorView, editor, activationPromise, watcher ] = []
+    [ editorView, editor, activationPromise, watcher, errorView, referenceView ] = []

     beforeEach ->
       { editorView, editor } = openFile 'fibonacci.coffee'
@@ -43,14 +43,37 @@
       waitsForPromise ->
         activationPromise
       runs ->
-        expect(atom.workspaceView.find(".#{ErrorView.className}")).toExist()
-        expect(atom.workspaceView.find(".#{ReferenceView.className}")).toExist()
+        errorView = atom.workspaceView.find ".#{ErrorView.className}"
+        referenceView = atom.workspaceView.find ".#{ReferenceView.className}"
+        expect(errorView).toExist()
+        expect(referenceView).toExist()

     it "activates watcher", ->
       waitsForPromise ->
         activationPromise
       runs ->
         expect(watcher.ripper).toBeDefined()
+
+    it "starts highlighting", ->
+      waitsForPromise ->
+        activationPromise
+      runs ->
+        expect(referenceView.find('.marker').length).toEqual 4
+
+    it "not starts selecting references", ->
+      waitsForPromise ->
+        activationPromise
+      runs ->
+        expect(editor.getCursors().length).toEqual 1
+
+    describe "when 'coffee-refactor:rename' event is triggered", ->
+
+      it "starts selecting references", ->
+        atom.workspaceView.trigger 'coffee-refactor:rename'
+        waitsForPromise ->
+          activationPromise
+        runs ->
+          expect(editor.getCursors().length).toEqual 4

   describe "when '.litcoffee' file is opened", ->


Require helpers

Require helpers

--- spec/main-spec.coffee
+++ spec/main-spec.coffee
@@ -1,3 +1,5 @@
+require('./helpers')
+
 fs = require('fs')
 glob = require('glob')
 deref = require('../lib')


WIP media uploading

WIP media uploading


Sidebar: Store workspace channel identifier

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -630,8 +630,12 @@

     @latestWorkspaceData = data

-    localStorage         = KD.getSingleton("localStorageController").storage "IDE"
-    minimumDataToStore   = machineLabel: (machine.slug or machine.label), workspaceSlug: workspace.slug
+    localStorage = KD.getSingleton("localStorageController").storage "IDE"
+
+    minimumDataToStore =
+      machineLabel     : machine.slug or machine.label
+      workspaceSlug    : workspace.slug
+      channelId        : data.channelId

     localStorage.setValue 'LatestWorkspace', minimumDataToStore


Use localized date formats

Use localized date formats

--- src/scripts/helpers/handlebars/date.coffee
+++ src/scripts/helpers/handlebars/date.coffee
@@ -1,40 +1,20 @@
 define (require) ->
   Handlebars = require('Handlebars')

-  MONTHS = [
-    'January'
-    'February'
-    'March'
-    'April'
-    'May'
-    'June'
-    'July'
-    'August'
-    'September'
-    'October'
-    'November'
-    'December'
-  ]
-
-  DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
-
   convertDate = (date) ->
-    month = MONTHS[date.getMonth()].substr(0,3)
-    day = date.getDate()
-    year = date.getFullYear()
-
-    return "#{month} #{day}, #{year}"
+    options = {year: 'numeric', month: 'short', day: 'numeric'}
+    return date.toLocaleDateString(navigator.language, options)

   Handlebars.registerHelper 'date', (period) ->
-    date = new Date()
-
     if period instanceof Date
       html = convertDate(period)
     else
+      date = new Date()
       switch period
         when 'year' then html = date.getFullYear()
-        when 'month' then html = MONTHS[date.getMonth()]
-        when 'day' then html = DAYS[date.getDay()]
+        when 'month' then html = date.toLocaleDateString(navigator.language, {month: "long"})
+        when 'day' then html = date.toLocaleDateString(navigator.language, {day: "numeric"})
+        when 'weekday' then html = date.toLocaleDateString(navigator.language, {weekday: "long"})
         else html = convertDate(new Date(period))

     return new Handlebars.SafeString(html.toString())


Client: Activity: Added getCreateModalProps method.

--- client/app/lib/components/sidebarsection/index.coffee
+++ client/app/lib/components/sidebarsection/index.coffee
@@ -34,6 +34,18 @@
     kd.utils.stopDOMEvent event
     @setState isModalOpen: yes

+
+  getCreateChannelModalProps: ->
+
+    isOpen             : @state.isModalOpen
+    account            : @state.account
+    onAbort            : @bound 'onClose'
+    onClose            : @bound 'onClose'
+    title              : @props.modalProps.title
+    className          : @props.modalProps.className
+    buttonConfirmTitle : @props.modalProps.buttonConfirmTitle
+
+
   renderAddChannelModal: ->

     <CreateChannelModal {...@getCreateChannelModalProps()} />


Update imgur regex to match more url variants

--- bots/Imgur.coffee
+++ bots/Imgur.coffee
@@ -31,7 +31,7 @@
   ARGS[1] += sep # add the path separator if it is missing from the end

 folder = "#{ARGS[1]}#{ARGS[0]}#{sep}"
-matches = ARGS[0].match /^(?:(?:(?:https?:\/\/)?imgur.com\/)?(?:\/?a\/)(\w+)(?:\?\w+)?)|(\w+)$/i
+matches = ARGS[0].match /^(?:(?:(?:(?:https?:\/\/)?imgur\.com)?(?:\/?a\/)(\w+)|(\w+))(?:(?:\?|#|\/)(?:.+)?)?)$/i
 ARGS[0] = matches[1] ?= matches[2]

 casper.echo "Fetching album #{ARGS[0]} from Imgur."


collaboration: controller minor

collaboration: controller minor

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -617,9 +617,7 @@
       @whenRealtimeReady =>
         @setSocialChannel channel
         @createChatPaneView channel
-
-        @chat.ready =>
-          @stateMachine.transition 'Active'
+        @chat.ready => @stateMachine.transition 'Active'

       @activateRealtimeManager doc


collaboration: nullify references before dispose

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -256,6 +256,13 @@
     @myWatchMap        = refs.watchMap
     @mySnapshot        = refs.snapshot

+    @rtm.once 'RealtimeManagerDidDispose', =>
+      @participants      = null
+      @changes           = null
+      @permissions       = null
+      @broadcastMessages = null
+      @myWatchMap        = null
+      @mySnapshot        = null

   registerCollaborationSessionId: ->


collaboration: controller move participant end to its own method

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -689,11 +689,28 @@
     else
       @endCollaborationForParticipant
         success: =>
-          # TODO: move this into its own method.
-          @removeParticipant nick()
-          @removeMachineNode()
-          sharedSuccessFn()
-          kd.utils.defer @bound 'quit'
+          @modal?.destroy()
+          @handleCollaborationEndedForParticipant()
+
+
+  handleCollaborationEndedForParticipant: ->
+
+    # TODO: fix explicit state checks.
+    return  unless @stateMachine.state in ['Active', 'Ending']
+
+    # TODO: fix implicit emit.
+    @rtm.once 'RealtimeManagerWillDispose', =>
+      @chat.emit 'CollaborationEnded'
+      @chat.destroy()
+      @chat = null
+      @statusBar.emit 'CollaborationEnded'
+      @removeParticipant nick()
+      @removeMachineNode()
+
+    @rtm.once 'RealtimeManagerDidDispose', =>
+      kd.utils.defer @bound 'quit'
+
+    @cleanupCollaboration()

   endCollaborationForHost: (callbacks) ->


Unblocked when sending email.

Unblocked when sending email.

--- src/AccountsUi.coffee
+++ src/AccountsUi.coffee
@@ -112,11 +112,14 @@
     Accounts.urls.verifyEmail = (token) -> Meteor.absoluteUrl('verify-email/' + token)

   _.extend AccountsUi,
-    sendEmail: (email) ->
+    createEmail: (email) ->
       config = AccountsUi.config()
       email = _.extend({
         from: config.email.fromAddress
       }, email)
+
+    sendEmail: (email) ->
+      @unblock()
       Email.send(email)

     sendEmailToAdmin: (email) ->


更新校車表計算

更新校車表計算

簡單測試過 不太確定是否完全正確


Add .ban and .kickban to kamaln7/banhammer

--- src/plugins/kamaln7/banhammer/banhammer.coffee
+++ src/plugins/kamaln7/banhammer/banhammer.coffee
@@ -4,6 +4,8 @@
     @client = bot.client
     @commands =
       'kick': 'onCommandKick'
+      'ban': 'onCommandBan'
+      'kickban': 'onCommandKickBan'

   onCommandKick: (from, to, message, args) ->
     if args.length < 3
@@ -27,9 +29,66 @@
         @bot.reply from, to, "Sorry, but I'm not in #{channel}.", 'notice'
         return

-      # Kick the user
-      command = ['KICK', channel, nick].concat reason
-      console.log command
+      @kick channel, nick, reason
+
+  onCommandBan: (from, to, message, args) ->
+    if args.length < 3
+      @bot.reply from, to, "Usage: #{args[0]} <channel> <user>", 'notice'
+      return
+
+    @client.whois from, (whois) =>
+      return if not whois.account
+
+      channel = args[1].toLowerCase()
+      nick = args[2].toLowerCase()
+      account = whois.account.toLowerCase()
+
+      if not ~@config.admins.indexOf account.toLowerCase()
+        @bot.reply from, to, 'You are not authorized to do that.', 'notice'
+        return
+
+      if not ~Object.keys(@bot.client.chans).indexOf channel
+        @bot.reply from, to, "Sorry, but I'm not in #{channel}.", 'notice'
+        return
+
+      @ban channel, nick
+
+  onCommandKickBan: (from, to, message, args) ->
+    if args.length < 3
+      @bot.reply from, to, "Usage: #{args[0]} <channel> <user>", 'notice'
+      return
+
+    @client.whois from, (whois) =>
+      return if not whois.account
+
+      args.shift()
+      channel = args.shift().toLowerCase()
+      nick = args.shift().toLowerCase()
+      reason = args.join(' ')
+      account = whois.account.toLowerCase()
+
+      if not ~@config.admins.indexOf account.toLowerCase()
+        @bot.reply from, to, 'You are not authorized to do that.', 'notice'
+        return
+
+      if not ~Object.keys(@bot.client.chans).indexOf channel
+        @bot.reply from, to, "Sorry, but I'm not in #{channel}.", 'notice'
+        return
+
+      @kick channel, nick, reason
+      @ban channel, nick
+
+  kick: (channel, user, reason) ->
+    command = ['KICK', channel, user].concat reason
+    @client.send.apply @client, command
+
+  ban: (channel, user) ->
+    @client.whois user, (whois) =>
+      return if not whois.host
+
+      hostmask = "*!#{whois.user}@#{whois.host}"
+
+      command = ['MODE', channel, '+b', hostmask]
       @client.send.apply @client, command

 module.exports =


Permissions: code clean up

Permissions: code clean up

--- client/Dashboard/views/permissionview.coffee
+++ client/Dashboard/views/permissionview.coffee
@@ -1,11 +1,13 @@
 class GroupPermissionsView extends JView

-  constructor:->
   constructor: (options={}, data)->
+    options.cssClass = "permissions-view"
+    super options, data

-    super
+    @loader     = new KDLoaderView showLoader: yes
+    @loaderText = new KDView partial: 'Loading Permissions...'

-    @setClass "permissions-view"
+    @addPermissionsView()

     group = @getData()


Added user_validation xsolla hook

Added user_validation xsolla hook

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -1252,6 +1252,17 @@
                     message: "Invalid signature"
             return
         data = JSON.parse(req.body)
+        if data.notification_type == "user_validation"
+            userId = data.user.id
+            user = await User.findById(userId)
+            if user
+                res.status(204).send('')
+            else
+                res.status(400).json
+                    error:
+                        code: "INVALID_USER",
+                        message: "Invalid user"
+            return
         if data.notification_type == "refund"
             res.status(204).send('')
             return


ComputeProvider: remove old compute ui

ComputeProvider: remove old compute ui

--- client/Main/providers/computeprovider.coffee
+++ client/Main/providers/computeprovider.coffee
@@ -34,108 +34,26 @@
       KD.singletons.mainController.emit "renderStacks"

-class ComputeProvider.UI
-
-  requiresLogin = do -> ({ message }, fn) -> (args...)->
-
-    return unless KD.isLoggedIn()
-      new KDNotificationView
-        title: message
-
-    fn args...

-  @showProvidersModal = requiresLogin
-    message: "You need to login to create a new machine."
-  , (stack)->
-
-    new KDModalView
-      title    : 'Add Virtual Machine'
-      cssClass : 'provider-modal'
-      view     : new ProviderView {stack}
-      width    : 800
-      height   : 600
-      overlay  : yes

-  @generateAddCredentialFormFor = (provider)->
-
-    fields          =
-      title         :
-        label       : "Title"
-        placeholder : "title for this credential"
-
-    Providers = ComputeProvider.providers
-
-    Object.keys(Providers[provider].credentialFields).forEach (field)->
-      fields[field] = _.clone Providers[provider].credentialFields[field]
-      fields[field].required = yes
-
-    return form = new KDFormViewWithFields
-      cssClass     : "form-view"
-      fields       : fields
-      buttons      :
-
-        Save       :
-          title    : "Add credential"
-          type     : "submit"
-          style    : "solid green medium"
-          loader   : color : "#444444"
-          callback : -> @hideLoader()
-
-        Cancel     :
-          style    : "solid medium"
-          type     : "button"
-          callback : -> form.emit "Cancel"
-
-      callback     : (data)->
-
-        { Save } = @buttons
-        Save.showLoader()
-
-        { title } = data
-        delete data.title
-
-        KD.remote.api.JCredential.create {
-          provider, title, meta: data
-        }, (err, credential)=>
-
-          Save.hideLoader()
-
-          unless KD.showError err
-            @emit "CredentialAdded", credential

-  @generateCreateInstanceForm: ->

-    form = new KDFormViewWithFields

-      cssClass          : "form-view"

-      fields            :

-        title           :
-          label         : "Title"
-          placeholder   : "title for this instance"
-          validate      :
-            rules       :
-              required  : yes
-            messages    :
-              required  : "Title is required"

-      buttons           :
-
-        Save            :
-          title         : "Create Instance"
-          type          : "submit"
-          style         : "solid green medium"
-          loader        : color : "#444444"
-          callback      : -> @hideLoader()
-
-        Cancel          :
-          style         : "solid medium"
-          type          : "button"
-          callback      : -> form.emit "Cancel"
-
-      callback          : (data)->
-        form.emit "Submit", data+    # kayt = null
+    # KD.singletons.kontrol.fetchKite(
+    #   query:
+    #     username:"gokmen"
+    #     name: "klient"
+    #     environment: "public-host"
+    # ).then (oskite)->
+    #   log {oskite}
+    #   kayt = oskite
+    #   oskite.connect()
+    #   oskite.tell "exec", [command : "ls /"]
+    # .then log, warn


Review : rename callback into listener

Review : rename callback into listener

--- client/app/components/compose_toolbox.coffee
+++ client/app/components/compose_toolbox.coffee
@@ -26,8 +26,8 @@

     triggerAction: (type) ->
         @setState action : type
-        if (callback = @props[type]) and _.isFunction callback
-            callback()
+        if (listener = @props[type]) and _.isFunction listener
+            listener?()

     render: ->
         div className: 'composeToolbox',


Show chapter on Introduction content

Show chapter on Introduction content

For Introduction pages, TOC will not display chapter, but page content
should.

--- src/scripts/modules/media/header/header.coffee
+++ src/scripts/modules/media/header/header.coffee
@@ -28,7 +28,7 @@

       downloads = @model.get('downloads')
       pageDownloads = currentPage?.get?('downloads')
-      chapter = currentPage.chapter ? ''
+      chapter = currentPage.chapter ? currentPage._parent?.get('chapter') ? ''

       return {
         currentPage: currentPage


Adding a method of executing scripts fetched via AJAX in IE8+. Also adds inputtype-specific handlers so that javascript can be executed when an inputtype is encountered.

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -1,4 +1,5 @@
 class @Problem
+
   constructor: (element) ->
     @el = $(element).find('.problems-wrapper')
     @id = @el.data('problem-id')
@@ -28,22 +29,69 @@
   render: (content) ->
     if content
       @el.html(content)
-      @bind()
+      @executeProblemScripts () =>
+        @setupInputTypes()
+        @bind()
     else
       $.postWithPrefix "#{@url}/problem_get", (response) =>
         @el.html(response.html)
-        @executeProblemScripts()
-        @bind()
-
-  executeProblemScripts: ->
-    @el.find(".script_placeholder").each (index, placeholder) ->
-      s = $("<script>")
-      s.attr("type", "text/javascript")
-      s.attr("src", $(placeholder).attr("data-src"))
+        @executeProblemScripts () =>
+          @setupInputTypes()
+          @bind()
+
+  # TODO add hooks for problem types here by inspecting response.html and doing
+  # stuff if a div w a class is found
+
+  setupInputTypes: =>
+    @el.find(".capa_inputtype").each (index, inputtype) =>
+      classes = $(inputtype).attr('class').split(' ')
+      for cls in classes
+        setupMethod = @inputtypeSetupMethods[cls]
+        setupMethod(inputtype) if setupMethod?
+
+  executeProblemScripts: (callback=null) ->
+
+    placeholders = @el.find(".script_placeholder")
+
+    if placeholders.length == 0
+      callback()
+      return
+
+    completed      = (false for i in [1..placeholders.length])
+    callbackCalled = false
+
+    # This is required for IE8 support.
+    completionHandlerGeneratorIE = (index) =>
+      return () ->
+        if (this.readyState == 'complete' || this.readyState == 'loaded')
+          #completionHandlerGenerator.call(self, index)()
+          completionHandlerGenerator(index)()
+
+    completionHandlerGenerator = (index) =>
+      return () =>
+        allComplete = true
+        completed[index] = true
+        for flag in completed
+          if not flag
+            allComplete = false
+            break
+        if allComplete and not callbackCalled
+          callbackCalled = true
+          callback() if callback?
+
+    placeholders.each (index, placeholder) ->
+      s = document.createElement('script')
+      s.setAttribute('src', $(placeholder).attr("data-src"))
+      s.setAttribute('type', "text/javascript")
+
+      s.onload             = completionHandlerGenerator(index)
+
+      # s.onload does not fire in IE8; this does.
+      s.onreadystatechange = completionHandlerGeneratorIE(index)

       # Need to use the DOM elements directly or the scripts won't execute
       # properly.
-      $('head')[0].appendChild(s[0])
+      $('head')[0].appendChild(s)
       $(placeholder).remove()

   ###
@@ -107,6 +155,9 @@
         @render(response.html)
         @updateProgress response

+  # TODO this needs modification to deal with javascript responses; perhaps we
+  # need something where responsetypes can define their own behavior when show
+  # is called.
   show: =>
     if !@el.hasClass 'showed'
       Logger.log 'problem_show', problem: @id
@@ -156,3 +207,20 @@
     @$(".CodeMirror").each (index, element) ->
       element.CodeMirror.save() if element.CodeMirror.save
     @answers = @$("[id^=input_#{@element_id.replace(/problem_/, '')}_]").serialize()
+
+  inputtypeSetupMethods:
+    javascriptinput: (element) =>
+
+      data = $(element).find(".javascriptinput_data")
+
+      params        = JSON.parse(data.attr("data-params"))
+      submission    = JSON.parse(data.attr("data-submission"))
+      evaluation    = JSON.parse(data.attr("data-evaluation"))
+      problemState  = JSON.parse(data.attr("data-problem_state"))
+      displayClass  = eval(data.attr("data-display_class"))
+
+      container = $(element).find(".javascriptinput_container")
+      submissionField = $(element).find(".javascriptinput_input")
+
+      display = new displayClass(problemState, submission, evaluation, container, submissionField, params)
+      display.render()


More result log

More result log

--- server/lib/download.coffee
+++ server/lib/download.coffee
@@ -19,7 +19,7 @@
                 gzip: true,
                 timeout: options?.timeout || 7 * 1000
             })
-            extract = page.substring(0, 50)
+            extract = page.substring(0, 500)
             logger.info "Downloaded #{href} -> `#{extract}...`"
             return page
         catch e


Check for favicon existence

Check for favicon existence

So server tests can be run without building first.

--- server/routes/admin.coffee
+++ server/routes/admin.coffee
@@ -19,7 +19,10 @@
   new hbs.handlebars.SafeString JSON.stringify(context)

 app.set 'views', "#{__dirname}/../views"
-app.use(favicon(__dirname + '/../../public/favicon.ico'))
+
+faviconFile = "#{__dirname}/../../public/favicon.ico"
+app.use favicon faviconFile if fs.existsSync faviconFile
+
 app.use express.static "#{__dirname}/../../public/", maxAge: 86400000 * 7 # One week

 app.set 'plugins', plugins.load()


static selection helper methods

static selection helper methods

--- client/lanes/components/grid/Selections.cjsx
+++ client/lanes/components/grid/Selections.cjsx
@@ -1,7 +1,7 @@
 class CheckBox extends Lanes.React.BaseComponent
     d: -> @props.query.results.xtraData(@props.row)

-    onChange: (ev) -> #, me, results, index) ->
+    onChange: (ev) ->
         @d().selected = ev.target.checked
         @props.selections.onChange?(@props)
         @forceUpdate()
@@ -9,7 +9,8 @@
     render: ->
         selected = @d().selected
         selected = @props.selections.selectionDefault unless selected?
-        <input type="checkbox" checked={selected} onChange={@onChange} />
+        attrs = @props.selections.attributesGetter?(@props) || {}
+        <input type="checkbox" checked={selected} onChange={@onChange} {...attrs} />

@@ -23,9 +24,17 @@
         false == this.xtraData(indx)?.selected

 class Lanes.Components.Grid.Selections
-    constructor: (options) ->
+
+    @setRow: (row, xd, selected) ->
+        xd.selected = selected
+
+    @isSelected: (row, xd, selected) ->
+        !(xd && false == xd.selected)
+
+
+    constructor: (options = {}) ->
         _.extend(@, DEFAULTS)
-        @onChange = options.onChange
+        _.extend(@, _.pick(options, 'onChange', 'attributesGetter'))
         @choices = {}
         _.bindAll(@, 'onColumnClick')
         @component = _.partial(@component, _, @)
@@ -33,8 +42,9 @@
     onColumnClick: (ev, props) ->
         unless ev.target.tagName is 'INPUT'
             input = ev.target.querySelector('input')
-            xd = props.query.results.xtraData(props.rowNum)
-            xd.selected = input.checked = !input.checked
+            unless input.disabled
+                xd = props.query.results.xtraData(props.rowNum)
+                xd.selected = input.checked = !input.checked
         @onChange?(props)
         ev.stopPropagation()


Allow calling initialize_janus directly

Allow calling initialize_janus directly

--- client/tawk.coffee
+++ client/tawk.coffee
@@ -149,7 +149,9 @@
   audio = if @props.audio? then @props.audio else true

   if not tawk.janus_initialized
-    initialize_janus(tawk.id, tawk.space)
+    initialize_janus
+      audio: true
+      video: true
     tawk.janus_initialized = true

   # Have to make sure we get all connections to choose
@@ -502,58 +504,99 @@
 # Tell Janus to publish our video stream to the server
 # Note that the hook when we get a local stream is actually
 # a separate callback to janus.attach
-publish_local_stream = (audio) ->
+window.publish_local_stream = ({audio, video}) ->
   plugin_handle.createOffer
     media:
       audioRecv: false
       videoRecv: false
       audioSend: audio
-      videoSend: true
+      videoSend: video
     success: (jsep) ->
       plugin_handle.send
         message:
           request: "configure"
           audio: audio
-          video: true
+          video: video
         jsep: jsep
     error: (error) ->
       if audio
-        console.error 'no_camera', 'No camera allowed', 'Trying audio-only mode'
-        publish_local_stream false
+        audio = false
+        console.error 'no_camera', 'No camera allowed', 'Trying audio-only mode', error
+        publish_local_stream()
       else
-        console.error 'no_camera', 'No camera or microphone allowed', 'You are a listener'
-
-new_remote_feed = (janus, feed) ->
-  remote_feed = null
-  {id, space} = JSON.parse feed.display
-  janus.attach
-    plugin: "janus.plugin.videoroom"
-    onremotestream: (stream) -> recieved_stream(stream, id)
-    error: console.error
-    success: (ph) ->
-      remote_feed = ph
-      remote_feed.send
-        message:
-          request: "join"
-          room: 1234
-          ptype: "listener"
-          feed: feed.id
-    onmessage: (msg, jsep) ->
-      if jsep
-        remote_feed.createAnswer
-          jsep: jsep
-          error: console.error
-          media:
-            audioSend: false
-            videoSend: false
-          success: (jsep) ->
-            remote_feed.send
-              jsep: jsep
-              message:
-                request: "start"
-                room: 1234
-
-initialize_janus = () ->
+        console.error 'no_camera', 'No camera or microphone allowed', 'You are a listener', error
+
+###
+Connect to tawk's video server (Janus). Automatically subscribe
+to all remote streams in the same space,
+and by default automatically publish local stream (though this is configurable).
+
+State imported:
+tawk.space (string, required) -> The room to subscribe to. Corresponds to <space-id>
+    in https://tawk.space/<space-id>. Note that you probably
+    don't want to have overlap between spaces used in dom.TAWK
+    and directly in initialize_janus. This function subscribes
+    to all streams in the space, but dom.TAWK only renders streams
+    that are in a groups. The end result is that if you call the lower
+    level function you get all the streams, but using dom.TAWK you only
+    get a subset of them.
+tawk.id (string, required): An identifier for the connection that must be unique
+    in the given space. It is used to export stream information
+    for every user.
+
+State exported:
+tawk['streams/' + id] -> {
+  url: A blob url for the stream. It can be used in audio or video tags with src: url
+  volume: On a scale from 0-100, how loud is the user speaking.
+      Volume tries to reflect human speech, and ignore static background noise.
+}
+There will be one piece of state exported per remote *and* local stream.
+
+Params:
+audio (boolean, default=true): Whether to subscribe to and publish audio
+video (boolean, default=true): Whether to subscribe to and publish video
+on_join (function({audio, video}), default=window.publish_local_stream):
+    A callback for when we have connected to Janus and its videoroom plugin.
+    The default callback is to immediately publish your local stream and ask
+    the user for permission to their camera and/or microphone. You can
+    override this callback if you do not wish to immediately publish the local stream.
+    The callback takes {audio, video}, which are the same values passed
+    into initialize_janus, and represent whether the caller wants to publish
+    audio and video.
+###
+window.initialize_janus = ({audio = true, video = true, on_join = window.publish_local_stream}) ->
+  new_remote_feed = (janus, feed) ->
+    remote_feed = null
+    {id, space} = JSON.parse feed.display
+    janus.attach
+      plugin: "janus.plugin.videoroom"
+      onremotestream: (stream) -> recieved_stream(stream, id)
+      error: console.error
+      success: (ph) ->
+        remote_feed = ph
+        remote_feed.send
+          message:
+            request: "join"
+            room: 1234
+            ptype: "listener"
+            feed: feed.id
+      onmessage: (msg, jsep) ->
+        if jsep
+          remote_feed.createAnswer
+            jsep: jsep
+            error: console.error
+            media:
+              audioRecv: audio
+              videoRecv: video
+              audioSend: false
+              videoSend: false
+            success: (jsep) ->
+              remote_feed.send
+                jsep: jsep
+                message:
+                  request: "start"
+                  room: 1234
+
   Janus.init
     callback: ->
       if not Janus.isWebrtcSupported()
@@ -590,7 +633,9 @@
               # The plugin_handle.send call to join as a publisher succeeded.
               # We can now send our video to everybody
               if msg["videoroom"] == "joined"
-                publish_local_stream true
+                on_join
+                  audio: audio
+                  video: video

               if jsep
                 plugin_handle.handleRemoteJsep


do not log private messages

do not log private messages

--- src/scripts/hubot-simple-logger.coffee
+++ src/scripts/hubot-simple-logger.coffee
@@ -131,7 +131,11 @@
         date = new Tempus()
         room = res.message.user.room || 'general'
         user = res.message.user.name || res.message.user.id || 'unknown'
-        log_message(logs_root,date,type,room,{ 'message' : res.message.text , 'user' : user })
+        if res.message.user.room?
+            log_message(logs_root,date,type,room,{ 'message' : res.message.text , 'user' : user })
+        else
+            robot.logger.debug "Private message: #{res.message.text} will not show in logs."
+            return

     # Add a listener that matches all messages and calls log_message with redis and robot instances and a Response object
     robot.listeners.push new Listener(robot, ((msg) -> return true), (res) -> _log_message(res))


Change to read canonical uuid from page json (#2005)

* Change to read canonical uuid from page json

* Code to handle single page that could be in a book and page that is not in a book

* Fixed URL layout

* Added host and 3rd scenario

* Fixed 3rd scenario

--- src/scripts/modules/media/media.coffee
+++ src/scripts/modules/media/media.coffee
@@ -7,6 +7,7 @@
   Content = require('cs!models/content')
   BaseView = require('cs!helpers/backbone/views/base')
   MainPageView = require('cs!modules/main-page/main-page')
+  settings = require('settings')

   ContentsView = require('cs!modules/media/tabs/contents/contents')

@@ -25,7 +26,23 @@
   return class MediaView extends BaseView
     key = []
     canonical: () ->
-      return linksHelper.getModelPath(@model, false)
+      console.debug "model: ", @model
+      urlScheme = window.location.protocol+"//"
+      uuid = @model.attributes.currentPage?.attributes?.canonical
+      mediatype = @model.attributes.mediaType
+      if uuid
+        pageId = @model.attributes.currentPage?.id
+      else
+        uuid = @model.attributes?.canonical
+        pageId = @model.id
+        if not uuid and mediatype is 'application/vnd.org.cnx.collection'
+          uuid =  @model.attributes.id
+          pageId = @model.attributes.currentPage?.id
+      if not uuid
+        url = "#{urlScheme}#{location.host}#{settings.root}contents/#{pageId}"
+      else
+        url = "#{urlScheme}#{location.host}#{settings.root}contents/#{uuid}:#{pageId}"
+      return url

     template: template
     regions:


Fixes reset to use DRY save() method

Fixes reset to use DRY save() method

--- src/scripts/one_four_twentyfour.coffee
+++ src/scripts/one_four_twentyfour.coffee
@@ -323,13 +323,14 @@
       player = k
       return_str += @playerstats(v)
     msg.reply return_str
-
-  reset: ->
-    @playerdata = []
-    @robot.brain.data.playerdata = []
+
+  reset: (msg, player) ->
+    delete @playerdata[player.id]
+    msg.reply "Your stats have been reset."
+    @save()

 module.exports = (robot) ->
-
+
   buddha = new BuddhaLounge robot

   robot.hear /buddha start|dice start|bdstart/i, (msg) ->
@@ -342,7 +343,7 @@
     buddha.stats msg

   robot.hear /buddha reset|dice reset/i, (msg) ->
-    buddha.reset msg
-
+    buddha.reset msg, msg.message.user
+
   robot.hear /(buddha take|dice take|bdt) ([\w .-]+)/i, (msg) ->
     buddha.take msg, msg.message.user, msg.match[2]


add ucmdb global id

add ucmdb global id

--- lib/sm-slack.coffee
+++ lib/sm-slack.coffee
@@ -36,7 +36,7 @@

   formatRecord: (record)->
-    slackform =["IncidentID", "RequestedBy","Status", "ContactPerson","Phase", "Company", "Location","PrimaryAffectedService","MajorIncident",  "AffectedCI","Escalated", "IncidentManager", "Category","Impact","SubCategory","Urgency","Area","Priority","AssignmentGroup","Source", "Assignee","CompletionCode", "Solution", "OpenTime", "ClosedTime", "ClosedBy" ]
+    slackform =["IncidentID", "RequestedBy","Status", "ContactPerson","Phase", "Company", "Location","PrimaryAffectedService","PrimaryAffectedServiceID","MajorIncident",  "AffectedCI","AffectedCIGlobalID","Escalated", "IncidentManager", "Category","Impact","SubCategory","Urgency","Area","Priority","AssignmentGroup","Source", "Assignee","CompletionCode", "Solution", "OpenTime", "ClosedTime", "ClosedBy" ]
     fields = []
     if record["Title"] != undefined && record["Description"]!=undefined
       r =


Accidentally a callback

Accidentally a callback

--- resources/assets/coffee/react/_components/comments-manager.coffee
+++ resources/assets/coffee/react/_components/comments-manager.coffee
@@ -81,7 +81,7 @@
     @setState
       comments: osu.updateCollection @state.comments, [comment]
       users: osu.updateCollection @state.users, [comment.user, comment.editor]
-      commentableMeta = _.concat @state.commentableMeta, [comment.commentable_meta]
+      commentableMeta: _.concat @state.commentableMeta, [comment.commentable_meta]

   addVote: (_event, {id}) =>


use event.value instead of event.target.value

Fix need for latest jsdom where value was renamed nodeValue

--- src/server/browser_server/index.coffee
+++ src/server/browser_server/index.coffee
@@ -320,7 +320,7 @@
         return if @browserLoading
         @broadcastEvent('DOMCharacterDataModified',
                         event.target.__nodeID,
-                        event.target.value)
+                        event.value)

     WindowMethodCalled : (event) ->
         return if @browserLoading


sorted

sorted

--- code/linode.coffee
+++ code/linode.coffee
@@ -97,6 +97,8 @@
   @list: (verbose, callback) ->
     @client.call 'linode.list', null, (err, res) =>
       list = _.map res, _convert_to_instance
+      # Sort by name.
+      list = _.sortBy list, 'name'
       requestArray = _.map list, (instance) ->
         { api_action: 'linode.config.list', LinodeID: instance.id }
       request


Refactor helper function for li comment

Refactor helper function for li comment

--- code/linode.coffee
+++ code/linode.coffee
@@ -67,17 +67,15 @@

   @get_comment: (name, callback) ->
     @get name, (instance) =>
-      @client.call 'linode.config.list',
-          'LinodeID': instance.id
-        , (err, res) ->
-          callback err, res[0].Comments
+      @_get_config instance, (err, config) =>
+        callback err, config.Comments

   @set_comment: (name, comment, callback) ->
     @get name, (instance) =>
-      @_get_linode_config_id instance, (err, ConfigID) =>
+      @_get_config instance, (err, config) =>
         @client.call 'linode.config.update',
             'LinodeID': instance.id
-            'ConfigID': ConfigID
+            'ConfigID': config.ConfigID
             'Comments': comment
           , (err, res) ->
             callback err, res
@@ -137,12 +135,6 @@
   @_get_config_linode_plan: (callback) =>
     @_get_plan @config.ram, @config.disk_size, callback

-  @_get_linode_config_id: (instance, callback) =>
-    @client.call 'linode.config.list',
-        'LinodeID': instance.id
-      , (err, res) ->
-        callback err, res[0].ConfigID
-
   @_linode_create: (plan_id, callback) =>
     @client.call 'linode.create',
       'DatacenterID': 7
@@ -256,6 +248,12 @@
        else
          callback err, public_record.IPADDRESS, null

+  @_get_config: (instance, callback) =>
+    @client.call 'linode.config.list',
+        'LinodeID': instance.id
+      , (err, res) ->
+        callback err, res[0]
+
 # Convert the Linode API JSON representation for a linode server
 # into an instance of the Linode class.
 _convert_to_instance = (l) =>


Add setting saving logic.

Add setting saving logic.

--- services/web/public/coffee/ide/FeatureOnboardingController.coffee
+++ services/web/public/coffee/ide/FeatureOnboardingController.coffee
@@ -1,16 +1,22 @@
 define [
 	"base"
 ], (App) ->
-	App.controller "FeatureOnboardingController", ($scope, $modal, event_tracking) ->
+	App.controller "FeatureOnboardingController", ($scope, settings) ->
 		$scope.isFeatureSettingDefined = window.userSettings.syntaxValidation?;

 		$scope.innerStep = 1

 		$scope.turnCodeCheckOn = () ->
+			settings.saveProjectSettings({ syntaxValidation: true })
 			navToInnerStep2()

 		$scope.turnCodeCheckOn = () ->
+			settings.saveProjectSettings({ syntaxValidation: false })
 			navToInnerStep2()
+
+		$scope.dismiss = () ->
+			# TODO Toggle logic.
+			$scope.isFeatureSettingDefined = false

 		navToInnerStep2 = () ->
 			$scope.innerStep = 2


Convert Rewind Buffer to using data chunks, not mp3 frames

Brings rewind buffer in line with the direct outputs, allowing cleaner use
as a burst for shoutcast / livemp3 listeners.  Will also help scaling.

--- src/streammachine/rewind_buffer.coffee
+++ src/streammachine/rewind_buffer.coffee
@@ -21,7 +21,7 @@

         @stream = stream

-        @framesPerSec = null
+        @secsPerChunk = null
         @max = null
         @burst = null

@@ -37,28 +37,20 @@

         # -- set up header and frame functions -- #

-        @lastHeader = null
-        @headerFunc = (data,header) => @lastHeader = data
-
-        @frameFunc = (frame) =>
-            # make sure we don't get a frame before header
-            if @lastHeader
-                # if we're at max length, shift off a frame (or more, if needed)
-                while @buffer.length > @max
-                    @buffer.shift()
-
-                # take the new frame, add the header back in, and push it to the buffer
-                buf = new Buffer( @lastHeader.length + frame.length )
-                @lastHeader.copy(buf,0)
-                frame.copy(buf,@lastHeader.length)
-                @buffer.push buf
-
-                # loop through all connected listeners and pass the frame buffer at
-                # their offset.
-                bl = @buffer.length
-                for l in @listeners
-                    # we'll give them whatever is at length - offset
-                    l.writeFrame @buffer[ bl - 1 - l._offset ]
+        @dataFunc = (chunk) =>
+            # if we're at max length, shift off a chunk (or more, if needed)
+            while @buffer.length > @max
+                @buffer.shift()
+
+            # push the chunk on the buffer
+            @buffer.push chunk
+
+            # loop through all connected listeners and pass the frame buffer at
+            # their offset.
+            bl = @buffer.length
+            for l in @listeners
+                # we'll give them whatever is at length - offset
+                l.writeFrame @buffer[ bl - 1 - l._offset ]

         # -- look for stream connections -- #

@@ -67,38 +59,32 @@
             # -- disconnect from old source -- #

             if @source
-                @source.removeListener "header", @headerFunc
-                @source.removeListener "frame", @frameFunc
+                @source.removeListener "data", @dataFunc

             # -- compute initial stats -- #

             source.once "header", (data,header) =>
-                if @framesPerSec && @framesPerSec == @stream.source.framesPerSec
+                if @secsPerChunk && @secsPerChunk == @stream.source.emit_duration
                     # reconnecting, but rate matches so we can keep using
                     # our existing buffer.
                     @log.debug "Rewind buffer validated new source.  Reusing buffer."

                 else
-                    if @framesPerSec
+                    if @secsPerChunk
                         # we're reconnecting, but didn't match rate...  we
                         # should wipe out the old buffer
                         @buffer = []

                     # compute new frame numbers
-                    @framesPerSec   = @stream.source.framesPerSec
-                    @max            = Math.round @framesPerSec * @options.seconds
-                    @burst          = Math.round @framesPerSec * @options.burst
+                    @secsPerChunk   = @stream.source.emit_duration
+                    @max            = Math.round @options.seconds / @secsPerChunk
+                    @burst          = Math.round @options.burst / @secsPerChunk

                     console.log "Rewind's max buffer length is ", @max

-            # headers and data get sent separately. we need to grab the header so
-            # we can lump it back together with the frame data later
-            @lastHeader = null
-            source.on "header", @headerFunc
+            # connect our data listener
+            source.on "data", @dataFunc

-            # frame listener will be used to fill our buffer with mp3 frames
-            source.on "frame", @frameFunc
-
             # keep track of our source
             @source = source

@@ -106,13 +92,13 @@

     bufferedSecs: ->
         # convert buffer length to seconds
-        Math.round @buffer.length / @framesPerSec
+        Math.round @buffer.length / @secsPerChunk

     #----------

     checkOffset: (offset) ->
         # we're passed offset in seconds. we'll convert it to frames
-        offset = Math.round Number(offset) * @framesPerSec
+        offset = Math.round Number(offset) / @secsPerChunk

         console.log "asked about offset of ", offset

@@ -145,7 +131,7 @@
         if offset > @buffer.length
             offset = @buffer.length

-        length = Math.round(length*@framesPerSec)
+        length = Math.round(length/@secsPerChunk)
         if length > offset
             length = offset