fix errors

fix errors

--- lib/ewg/haml/renderer.coffee
+++ lib/ewg/haml/renderer.coffee
@@ -42,8 +42,7 @@
       output = hamlc.render(content, context, @config.compiler)
     catch e
       file = content unless file
-      throw new PluginError('EWGHamlRenderer', 'Error compiling '
-                              + file.yellow + ': ' + e, showStack: true)
+      throw new PluginError('EWGHamlRenderer', 'Error compiling ' + file.yellow + ': ' + e, showStack: true)
     output

   renderPartial: (file, locals)=>
@@ -51,8 +50,7 @@

   renderFile: (file, locals) =>
     unless fs.existsSync(file)
-      throw new PluginError('EWGHamlRenderer', 'File not found '
-                              + file.yellow , showStack: true)
+      throw new PluginError('EWGHamlRenderer', 'File not found ' + file.yellow , showStack: true)

     content = fs.readFileSync(file, 'utf8')
     @renderContent(content, extend(true, @lastContext, locals), file)
@@ -71,8 +69,8 @@
     return tmp.replace('.html', '').replace('.haml', '').replace('.', '-')

   replaceExtension: (path) ->
-    path = path.replace('.html','').replace('.haml')
-    path = path + '.' + @config.compiler.extension
+    path = path.replace('.html', '').replace('.haml', '')
+    path = path + @config.compiler.extension
     path.replace('..', '.')

   compileTemplate: (file, cb) =>


Test that nested selectors are merged when the inner selector appears first

--- spec_app/spec/javascripts/up/fragment_spec.js.coffee
+++ spec_app/spec/javascripts/up/fragment_spec.js.coffee
@@ -660,6 +660,34 @@
             $inner = $outer.affix('.inner').text('old inner text')

             replacePromise = up.render('.outer, .inner', url: '/path')
+
+            next =>
+              expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer')
+
+              @respondWith """
+

+                  new outer text
+

+                    new inner text
+

+

+                """
+
+            next =>
+              expect($('.outer')).toBeAttached()
+              expect($('.outer').text()).toContain('new outer text')
+              expect($('.inner')).toBeAttached()
+              expect($('.inner').text()).toContain('new inner text')
+
+            next.await =>
+              promise = promiseState(replacePromise)
+              promise.then (result) => expect(result.state).toEqual('fulfilled')
+
+          it 'replaces a single fragment if a selector is contained by a subsequent selector in the current page', asyncSpec (next) ->
+            $outer = $fixture('.outer').text('old outer text')
+            $inner = $outer.affix('.inner').text('old inner text')
+
+            replacePromise = up.render('.inner, .outer', url: '/path')

             next =>
               expect(@lastRequest().requestHeaders['X-Up-Target']).toEqual('.outer')


fix for the command Add All Commit And Push

--- lib/models/git-commit.coffee
+++ lib/models/git-commit.coffee
@@ -102,7 +102,7 @@
         @destroyActiveEditorView()
         # Refreshing the atom repo status to refresh things like TreeView and
         # diff gutter.
-        git.getRepo()?.refreshStatus?()
+        @repo.refreshStatus()
         # Activate the former active pane.
         @currentPane.activate() if @currentPane.alive
         # Refresh git index to prevent bugs on our methods.


Render link to mention to 'all' without target to user 'all'

--- packages/rocketchat-mentions/client.coffee
+++ packages/rocketchat-mentions/client.coffee
@@ -15,6 +15,13 @@
 				mentions = _.unique mentions
 				mentions = mentions.join('|')
 				msg = msg.replace new RegExp("(?:^|\\s)(@(#{mentions}))(?:\\s|$)", 'g'), (match, mention, username) ->
+					if username is 'all'
+						return match.replace mention, "#{mention}"
+
+					if not message.temp?
+						if not _.findWhere(message.mentions, {username: username})?
+							return match
+
 					return match.replace mention, "#{mention}"

 			message.html = msg


improve visualization of spaces

improve visualization of spaces

--- lib/regex-to-railroad.coffee
+++ lib/regex-to-railroad.coffee
@@ -2,6 +2,28 @@

 {Diagram, Sequence, Choice, Optional, OneOrMore, ZeroOrMore, Terminal,
  NonTerminal, Comment, Skip, Group } = require './railroad-diagrams'
+
+makeLiteral = (text) ->
+  #debugger
+  if text == " "
+    NonTerminal("SP")
+  else
+    parts = text.split /(^ +| {2,}| +$)/
+    sequence = []
+    for part in parts
+      continue unless part.length
+      if /^ +$/.test(part)
+        if part.length == 1
+          sequence.push NonTerminal("SP")
+        else
+          sequence.push OneOrMore(NonTerminal("SP"), Comment("#{part.length} times"))
+      else
+        sequence.push Terminal(part)
+
+    if sequence.length == 1
+      sequence[0]
+    else
+      new Sequence sequence

 rx2rr = (node, options) ->
   switch node.type
@@ -17,13 +39,13 @@
             literal = n.body
         else
           if literal?
-            sequence.push Terminal(literal)
+            sequence.push makeLiteral(literal)
             literal = null

           sequence.push rx2rr n

       if literal?
-        sequence.push Terminal(literal)
+        sequence.push makeLiteral(literal)

       if sequence.length == 1
         sequence[0]
@@ -82,10 +104,7 @@
       NonTerminal("ref #{node.index}")

     when "literal"
-      if node.body == " "
-        NonTerminal("SP")
-      else
-        Terminal(node.body)
+      makeLiteral(node.body)

     when "word"
       NonTerminal("word-character")
@@ -118,16 +137,26 @@
       charset = (x.text for x in node.body)

       if charset.length == 1
+        char = charset[0]
+
+        if char == " "
+          char = "SP"
+
         if node.invert
-          return Terminal("not #{charset[0]}")
+          return NonTerminal("not #{charset[0]}")
         else
           return Terminal(charset[0])
       else
         list = charset[0...-1].join(", ")
+
+        for x,i in list
+          if x == " "
+            list[i] = "SP"
+
         if node.invert
-          return Terminal("not #{list} and #{charset[-1..]}")
+          return NonTerminal("not #{list} and #{charset[-1..]}")
         else
-          return Terminal("#{list} or #{charset[-1..]}")
+          return NonTerminal("#{list} or #{charset[-1..]}")

     when "hex", "octal", "unicode"
       Terminal(node.text)


Break deferred check logic out into helper method

--- lib/resource-combiner.coffee
+++ lib/resource-combiner.coffee
@@ -69,11 +69,7 @@
   (req, res, next) ->
     # Anything that starts with the content path is a potential resource
     if req.url.indexOf(contentPath) == 0
-      resource = pathResourceCache[req.url.substring contentPath.length]
-      if not resource
-        return next()
-
-      sendContent = ->
+      sendContent = (resource) ->
         # WARN: This will need to be redone using md5 or similar if there are multiple servers
         etag = resource.modified.getTime()
         modified = resource.modified
@@ -87,12 +83,20 @@
         else
           responseCache.sendNotModified res

-      if resource.deferred
-        resource.deferred.push (content) ->
-          resource.content = content
-          sendContent()
-        checkComplete resource
+      checkDeferred = (resource) ->
+        if resource.deferred
+          resource.deferred.push (content) ->
+            resource.content = content
+            sendContent resource
+          checkComplete resource
+        else
+          sendContent resource
+
+      path = req.url.substring contentPath.length
+      resource = pathResourceCache[path]
+      if not resource
+        next()
       else
-        sendContent()
+        checkDeferred resource
     else
       next()


:art: switchToLinewise to switch selection temporarily

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -145,7 +145,7 @@
     @selection.insertText(text)
     originalText

-  lineTextForBufferRows: (text) ->
+  lineTextForBufferRows: ->
     {editor} = @selection
     @getRows().map (row) ->
       editor.lineTextForBufferRow(row)
@@ -167,6 +167,12 @@
       when @isLinewise() then 'linewise'
       when not @selection.isEmpty() then 'characterwise'
       else null
+
+  switchToLinewise: (fn) ->
+    @preserveCharacterwise()
+    @expandOverLine(preserveGoalColumn: true)
+    fn()
+    @restoreCharacterwise()

 swrap = (selection) ->
   new SelectionWrapper(selection)


add oracles extract to aram item sets #42

--- lib/sources/lolflavor.coffee
+++ lib/sources/lolflavor.coffee
@@ -45,6 +45,7 @@

       if process_name == 'ARAM'
         data.map = 'HA'
+        data.blocks[0].items.push({count: 1, id: "2047"})
       if process_name != 'ARAM'
         data.map = 'SR' if window.cSettings.locksr
         data.blocks.shift()


Use atom.emitter in specs, too.

Use atom.emitter in specs, too.

--- spec/conflict-marker-spec.coffee
+++ spec/conflict-marker-spec.coffee
@@ -66,7 +66,7 @@

     it 'broadcasts the "merge-conflicts:resolved" event', ->
       event = null
-      atom.on 'merge-conflicts:resolved', (e) -> event = e
+      atom.emitter.on 'merge-conflicts:resolved', (e) -> event = e
       m.conflicts[2].theirs.resolve()

       expect(event.file).toBe(editorView.getModel().getPath())


Fixes isFile() and isDirectory() negative handling

--- src/pathname.coffee
+++ src/pathname.coffee
@@ -123,14 +123,20 @@
   isFile: (cb) ->
     if cb?
       @exists (exists) =>
-        exists and @stat (err, stats) -> cb(err, stats.isFile())
+        if not exists
+          cb(null, false)
+        else
+          @stat (err, stats) -> cb(err, stats.isFile())
     else
       @exists() and @stat().isFile()

   isDirectory: (cb) ->
     if cb?
       @exists (exists) =>
-        exists and @stat (err, stats) -> cb(err, stats.isDirectory())
+        if not exists
+          cb(null, false)
+        else
+          @stat (err, stats) -> cb(err, stats.isDirectory())
     else
       @exists() and @stat().isDirectory()


adding constructor tags to docs

adding constructor tags to docs

--- src/physical.coffee
+++ src/physical.coffee
@@ -11,6 +11,7 @@

   @name Physical
   @module
+  @constructor

   @param {Object} I Instance variables
   @param {Object} self Reference to including object


add autoApprove before decision

add autoApprove before decision

--- src/provider.coffee
+++ src/provider.coffee
@@ -7,24 +7,27 @@

 passport = null

+passby = (req, res, next) ->
+  next()

 class Provider

   @uid = util.uid

   defaultRenderFunction = (req, res)->
+    console.log "render Function on abstract provider"
     res.render('dialog', { transactionID: req.oauth2.transactionID, user: req.user, client: req.oauth2.client })

   constructor: (config, deps)->
     ## normalize parameters
-    throw new Error 'Your argument is invalid: need config object' unless config?
+    throw  new Error 'Your argument is invalid: need config object' unless config?
     @renderFunction = config.renderFunction or defaultRenderFunction

     @passport = passport = deps.passport
     throw new Error 'passport instance must be provided' unless passport

-      ## init oauth server
+    ## init oauth server
     @server = oauth2orize.createServer()

     ## register serialize/deserialize functions (easier for inheritance)
@@ -60,8 +63,11 @@
   authorization: ->
     [
       ensureLogin.ensureLoggedIn()
-      @server.authorization (clientId, redirectURI, done)=> @findClient clientId, redirectURI, done
-      @renderFunction
+      @server.authorization (clientId, redirectURI, done)=>
+        @findClient clientId, redirectURI, done
+
+      if typeof @autoApprove is 'function' then @autoApprove.bind(this) else passby
+      @renderFunction.bind(this)
     ]

   decision: ->
@@ -77,4 +83,4 @@
       @server.errorHandler()
     ]

-module.exports = Provider+module.exports = Provider


Fixed an issue where the create team option buttons were required

--- picoCTF-web/web/coffee/registration.coffee
+++ picoCTF-web/web/coffee/registration.coffee
@@ -12,7 +12,8 @@
 setRequired = ->
     $('#user-registration-form :input').each () ->
         if not $(this).is(':checkbox')
-            $(this).prop('required', $(this).is(":visible"))
+            if not $(this).is(':radio')
+                $(this).prop('required', $(this).is(":visible"))

 checkEligibility = ->


Write todo for hydrator.

Write todo for hydrator.

--- src/OVirtResponseHydrator.coffee
+++ src/OVirtResponseHydrator.coffee
@@ -18,9 +18,93 @@
 # - Investigates for embedded collections links and process them.
 # - Exports all other "plain" properties as hashes.
 #
-# @todo Implement properties hydration
-# @todo Deal with subcollections
-# @todo Implement resource links hydration
+#
+# Currently oVirt API responses has a following structure:
+# ---------------------------------------------------------
+#
+# ### API
+#
+# * Collections (as a links)
+# * Special objects (collection members with special rel's)
+# * Links to resources (not in current version)
+# * Properties
+#
+# ### Collections
+#
+# * Array of resources
+#
+# ### Resources
+#
+# * ID and hreg attributes that identifies resource
+# * Subcollections (same as collections) as a links
+# * Special objects as mentioned before.
+# * Links to resources
+# * Actions (special links).
+# * Properties.
+#
+#
+# Hydration tasks
+# ----------------
+# Assuming that collections and resource links and resources are top-level
+# objects.
+#
+# ### Hydrate top level element attributes
+#
+# - Save attributes in corresponding property if existed.
+#
+# ### Hydrate collections
+#
+# - Detect collection links.
+# - Detect search options.
+# - Instantiate collection objects.
+# - Detect special objects inks and put them into collection.
+# - Save results in collection property.
+#
+# ### Hydrate resource links
+#
+# - Detect links to resources
+# - Instantiate resource objects in link mode.
+# - Save results in resource links property.
+#
+# ### Hydrate resources
+#
+# - Detect resources.
+# - Delegate resource hydration to other hydrator instance.
+# - Save results in resources property.
+#
+# ### Hydrate actions
+#
+# - Detect actions
+# - Instantiates actions objects
+#
+# ### Hydrate properties
+#
+# - Detect properties
+# - Save them in corresponding property.
+#
+#
+# Export tasks
+# -------------
+#
+# - Export attributes.
+# - Export collections.
+# - Export resources.
+# - Export resource links.
+# - Export properties.
+#
+#
+# Utility tasks
+# --------------
+#
+# - Constructor should be able to create targets from strings and constructors.
+# - Get element attributes for custom hash.
+# - Detect whether element has no children.
+# - Retrieve element's children (but not attributes).
+# - Merge attributes with children (for plain properties).
+# - Retrieve merged version of element.
+# - Detect whether element is a link (it has href and id or rel property).
+# - Detect resource hrefs.
+# - Detect hrefs that leads to collections.
 #
 class OVirtResponseHydrator
   # Static properties


check in-progress stories when stories have been updated

--- src/scripts/routes/iterations_route.coffee
+++ src/scripts/routes/iterations_route.coffee
@@ -9,18 +9,19 @@
     controller.set 'model', model

     if model.get 'length'
-      stories = model.get 'firstObject.stories'
-      @checkInProgressStories stories
+      @checkInProgressStories model
       @controllerFor('application').on 'settingsUpdated', =>
-        @checkInProgressStories stories
+        @checkInProgressStories model

     projectModel = @modelFor 'project'
     App.pivotal.listenForProjectUpdates projectModel
-    App.pivotal.on 'projectUpdated', ->
-      App.pivotal.getIterations(projectModel.id).then (iterations)->
+    App.pivotal.on 'projectUpdated', =>
+      App.pivotal.getIterations(projectModel.id).then (iterations)=>
         controller.set 'model', iterations
+        @checkInProgressStories controller.get('model')

-  checkInProgressStories: (stories)->
+  checkInProgressStories: (model)->
+    stories = model.get 'firstObject.stories'
     storiesInProgress = _.filter stories, (story)->
       _.contains inProgressStoryTypes, story.current_state
     inProgressMax = App.settings.getValue 'inProgressMax', 5


add function to ensure all quota allocations are valid...

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -1883,12 +1883,14 @@
     # Ensure that all upgrades applied by the given user to projects are consistent,
     # truncating any that exceed their allotment.  NOTE: Unless there is a bug,
     # the only way the quotas should ever exceed their allotment would be if the
-    # user is trying to cheat.
+    # user is trying to cheat... *OR* a subscription was cancelled or ended.
     ensure_user_project_upgrades_are_valid: (opts) =>
         opts = defaults opts,
             account_id : required
-            fix        : true       # if true, will fix projects in database whose quotas exceed the alloted amount; it is the caller's responsibility to say actually change them.
+            fix        : true       # if true, will fix projects in database whose quotas exceed the alloted amount; it is the caller's responsibility to actually change them.
             cb         : required   # cb(err, excess)
+        dbg = @_dbg("ensure_user_project_upgrades_are_valid(account_id='#{opts.account_id}')")
+        dbg()
         excess = stripe_data = project_upgrades = undefined
         async.series([
             (cb) =>
@@ -1911,6 +1913,7 @@
                 excess = require('smc-util/upgrades').available_upgrades(stripe_data, project_upgrades).excess
                 if opts.fix
                     fix = (project_id, cb) =>
+                        dbg("fixing project_id='#{project_id}' with excess #{JSON.stringify(excess[project_id])}")
                         upgrades = undefined
                         async.series([
                             (cb) =>
@@ -1939,6 +1942,39 @@
         ], (err) =>
             opts.cb(err, excess)
         )
+
+    # Loop through every user of cocalc that is connected with stripe (so may have a subscription),
+    # and ensure that any upgrades that have applied to projects are valid.  It is important to
+    # run this periodically or there is a really natural common case where users can cheat:
+    #    (1) they apply upgrades to a project
+    #    (2) their subscription expires
+    #    (3) they do NOT touch upgrades on any projects again.
+    ensure_all_user_project_upgrades_are_valid: (opts) =>
+        opts = defaults opts,
+            cb : required
+        dbg = @_dbg("ensure_all_user_project_upgrades_are_valid")
+        locals = {}
+        async.series([
+            (cb) =>
+                @_query
+                    query : "SELECT account_id FROM accounts"
+                    where : "stripe_customer_id IS NOT NULL"
+                    cb    : all_results 'account_id', (err, account_ids) =>
+                        locals.account_ids = account_ids
+                        cb(err)
+            (cb) =>
+                m = 0
+                n = locals.account_ids.length
+                dbg("got #{n} accounts with stripe")
+                f = (account_id, cb) =>
+                    m += 1
+                    dbg("#{m}/#{n}")
+                    @ensure_user_project_upgrades_are_valid
+                        account_id : account_id
+                        cb         : cb
+                # We only do 1 at a time, since there is no hurry.
+                async.mapLimit(locals.account_ids, 1, f, cb)
+        ], opts.cb)

     # Return the sum total of all user upgrades to a particular project
     get_project_upgrades: (opts) =>


should have done this in last commit

should have done this in last commit

--- src/smc-hub/postgres-server-queries.coffee
+++ src/smc-hub/postgres-server-queries.coffee
@@ -126,6 +126,7 @@
             smc_git_rev  : undefined
             uptime       : undefined
             start_time   : undefined
+            id           : undefined  # ignored
             cb           : undefined
         @_query
             query       : 'INSERT INTO webapp_errors'


Use callback to avoid test failure on travis.

--- src/spec/integration/streaming.spec.coffee
+++ src/spec/integration/streaming.spec.coffee
@@ -31,9 +31,10 @@
     .catch (err) -> done(_.prettify err)

   describe '#constructor', ->
-    it 'should initialize', ->
+    it 'should initialize', (done) ->
       expect(-> new Streaming()).toBeDefined()
       expect(@streaming).toBeDefined()
+      done()

   describe '#processStream', ->
     it 'should create one new category', (done) ->


hiding product form on submit to hide input manipulation

--- app/assets/javascripts/admin/stores.js.coffee
+++ app/assets/javascripts/admin/stores.js.coffee
@@ -187,6 +187,7 @@

             .on 'submit', 'form', (e) ->
                 e.preventDefault()
+                ($ @).addClass(hide)

                 verb = if @id is 'new_product' then 'created' else 'updated'
                 $.ajax
@@ -201,5 +202,6 @@
                             reset_product_form()
                             fetch_and_render_product_widgets()
                         else
+                            ($ @).removeClass(hide)
                             ($ '.form-errors-wrapper').html(Mustache.render(templates.form_error_template, {errors: response.errors}))
                             $window.scrollTop(($ '.form-errors-wrapper'))


fix input-connected

fix input-connected

--- app/assets/javascripts/input-connected.coffee
+++ app/assets/javascripts/input-connected.coffee
@@ -70,8 +70,9 @@
         values = component.val()

         name = component.attr('name')
-        name = if name? then name.replace(' ', '_').split('[')[0] else "id"
-        name = if Array.isArray(values) then "#{ name }s" else name
+        #name = if name? then name.replace(' ', '_').split('[')[0] else "id"
+        name = if name? then name else "id"
+        #name = if Array.isArray(values) then "#{ name }s" else name

         if mode == "remote"
           params = { "#{ name }": values }


OX-403 Implement and use extra methods to build GUI

--- app/assets/javascripts/oxalis/view/gui.coffee
+++ app/assets/javascripts/oxalis/view/gui.coffee
@@ -20,51 +20,51 @@

     _.extend(this, new EventMixin())

-    data = @model.user
+    @user = @model.user
     # create GUI
     modelRadius = @model.route.getActiveNodeRadius()
     @qualityArray = ["high", "medium", "low"]

     @datasetPostfix = _.last(@model.binary.dataSetName.split("_"))
-    @datasetPosition = @initDatasetPosition(data.briConNames)
+    @datasetPosition = @initDatasetPosition(@user.briConNames)

     somaClickingAllowed = settings.somaClickingAllowed

     @settings =

-      rotateValue : data.rotateValue
-      moveValue3d : data.moveValue3d
-      mouseRotateValue : data.mouseRotateValue
-      crosshairSize : data.crosshairSize
+      rotateValue : @user.rotateValue
+      moveValue3d : @user.moveValue3d
+      mouseRotateValue : @user.mouseRotateValue
+      crosshairSize : @user.crosshairSize

-      lockZoom : data.lockZoom
-      inverseX : data.inverseX
-      inverseY : data.inverseY
-      dynamicSpaceDirection : data.dynamicSpaceDirection
-
-      moveValue : data.moveValue
-      routeClippingDistance : data.routeClippingDistance
-      displayCrosshairs : data.displayCrosshair
-
-      fourBit : data.fourBit
-      briConNames : data.briConNames
-      brightness : data.brightness[@datasetPosition]
-      contrast : data.contrast[@datasetPosition]
+      lockZoom : @user.lockZoom
+      inverseX : @user.inverseX
+      inverseY : @user.inverseY
+      dynamicSpaceDirection : @user.dynamicSpaceDirection
+
+      moveValue : @user.moveValue
+      routeClippingDistance : @user.routeClippingDistance
+      displayCrosshair : @user.displayCrosshair
+
+      fourBit : @user.fourBit
+      briConNames : @user.briConNames
+      brightness : @user.brightness[@datasetPosition]
+      contrast : @user.contrast[@datasetPosition]
       resetBrightnessAndContrast : => @resetBrightnessAndContrast()
-      interpolation : data.interpolation
-      quality : @qualityArray[data.quality]
-
-      displayPrevXY : data.displayPreviewXY
-      displayPrevYZ : data.displayPreviewYZ
-      displayPrevXZ : data.displayPreviewXZ
-      nodesAsSpheres : data.nodesAsSpheres
+      interpolation : @user.interpolation
+      quality : @qualityArray[@user.quality]
+
+      displayPrevXY : @user.displayPreviewXY
+      displayPrevYZ : @user.displayPreviewYZ
+      displayPrevXZ : @user.displayPreviewXZ
+      nodesAsSpheres : @user.nodesAsSpheres

       activeTreeID : @model.route.getActiveTreeId()
       newTree : => @trigger "createNewTree"
       deleteActiveTree : => @trigger "deleteActiveTree"

       activeNodeID : @model.route.getActiveNodeId()
-      newNodeNewTree : if somaClickingAllowed then data.newNodeNewTree else false
+      newNodeNewTree : if somaClickingAllowed then @user.newNodeNewTree else false
       deleteActiveNode : => @trigger "deleteActiveNode"
       radius : if modelRadius then modelRadius else 10 * @model.scaleInfo.baseVoxel
       comment : ""
@@ -76,7 +76,7 @@
       @model.user.briConNames.push(@datasetPostfix)
       @model.user.brightness.push(@settings.brightness)
       @model.user.contrast.push(@settings.contrast)
-      @dataSetPosition = data.briConNames.length - 1
+      @dataSetPosition = @user.briConNames.length - 1

     @gui = new dat.GUI(autoPlace: false, width : 280, hideable : false, closed : true)
@@ -84,120 +84,57 @@
     container.append @gui.domElement

     fControls = @gui.addFolder("Controls")
-    (fControls.add @settings, "lockZoom")
-                          .name("Lock Zoom")
-                          .onChange((v) => @setBoolean("lockZoom", v))
-    (fControls.add @settings, "inverseX")
-                          .name("Inverse X")
-                          .onChange((v) => @setBoolean("inverseX", v))
-    (fControls.add @settings, "inverseY")
-                          .name("Inverse Y")
-                          .onChange((v) => @setBoolean("inverseY", v))
-    (fControls.add @settings, "dynamicSpaceDirection")
-                          .name("d/f-Switching")
-                          .onChange((v) => @setBoolean("dynamicSpaceDirection", v))
+    @addCheckbox(fControls, @user, "lockZoom", "Lock Zoom")
+    @addCheckbox(fControls, @settings, "inverseX", "Inverse X")
+    @addCheckbox(fControls, @settings, "inverseY", "Inverse Y")
+    @addCheckbox(fControls, @settings, "dynamicSpaceDirection", "d/f-Switching")

     fFlightcontrols = @gui.addFolder("Flighcontrols")
-    (fFlightcontrols.add @settings, "mouseRotateValue", 0.001, 0.02)
-                          .step(0.001)
-                          .name("Mouse Rotation")
-                          .onChange((v) => @setNumber("mouseRotateValue", v))
-    (fFlightcontrols.add @settings, "rotateValue", 0.001, 0.08)
-                          .step(0.001)
-                          .name("Keyboard Rotation Value")
-                          .onChange((v) => @setNumber("rotateValue", v))
-    (fFlightcontrols.add @settings, "moveValue3d", 0.1, 10)
-                          .step(0.1)
-                          .name("Move Value")
-                          .onChange((v) => @setNumber("moveValue3d", v))
-    (fFlightcontrols.add @settings, "crosshairSize", 0.1, 1)
-                          .step(0.01)
-                          .name("Crosshair size")
-                          .onChange((v) => @setNumber("crosshairSize", v))
-
-
-    fView = @gui.addFolder("Planes")
-    (fView.add @settings, "moveValue", 0.1, 10)
-                          .step(0.1)
-                          .name("Move Value")
-                          .onChange((v) => @setNumber("moveValue", v))
-    scale = @model.scaleInfo.baseVoxel
-    (fView.add @settings, "routeClippingDistance", 1, 1000 * scale)
-                          .name("Clipping Distance")
-                          .onChange((v) => @setNumber("routeClippingDistance", v))
-    (fView.add @settings, "displayCrosshairs")
-                          .name("Show Crosshairs")
-                          .onChange((v) => @setBoolean("displayCrosshair", v))
-
-    fView = @gui.addFolder("Voxel")
-    (fView.add @settings, "fourBit")
-                          .name("4 Bit")
-                          .onChange((v) => @setBoolean("fourBit", v))
+    @addSlider(fFlightcontrols, @settings, "mouseRotateValue", 0.001, 0.02, 0.001, "Mouse Rotation")
+    @addSlider(fFlightcontrols, @settings, "rotateValue", 0.001, 0.08, 0.001, "Keyboard Rotation Value")
+    @addSlider(fFlightcontrols, @settings, "moveValue3d", 0.1, 10, 0.1, "Move Value")
+    @addSlider(fFlightcontrols, @settings, "crosshairSize", 0.1, 1, 0.01, "Crosshair size")
+
+    fPlanes = @gui.addFolder("Planes")
+    @addSlider(fPlanes, @settings, "moveValue", 0.1, 10, 0.1, "Move Value")
+    @addSlider(fPlanes, @settings, "routeClippingDistance", 1, 1000 * @model.scaleInfo.baseVoxel, 1, "Clipping Distance")
+    @addCheckbox(fPlanes, @settings, "displayCrosshair", "Show Crosshairs")
+
+    fVoxel = @gui.addFolder("Voxel")
+    @addCheckbox(fVoxel, @settings, "fourBit", "4 Bit")
     @brightnessController =
-    (fView.add @settings, "brightness", -256, 256)
-                          .step(5)
-                          .name("Brightness")
-                          .onChange( => @setBrightnessAndContrast())
+      @addSlider(fVoxel, @settings, "brightness", -256, 256, 5, "Brightness", @setBrightnessAndContrast)
     @contrastController =
-    (fView.add @settings, "contrast", 0.5, 5)
-                          .step(0.1)
-                          .name("Contrast")
-                          .onChange( => @setBrightnessAndContrast())
-    (fView.add @settings, "resetBrightnessAndContrast")
-                          .name("Reset To Default")
-    (fView.add @settings, "interpolation")
-                          .name("Interpolation")
-                          .onChange((v) => @setBoolean("interpolation", v))
-    (fView.add @settings, "quality", @qualityArray)
+      @addSlider(fVoxel, @settings, "contrast", 0.5, 5, 0.1, "Contrast", @setBrightnessAndContrast)
+    @addFunction(fVoxel, @settings, "resetBrightnessAndContrast", "Reset To Default")
+    @addCheckbox(fVoxel, @settings, "interpolation", "Interpolation")
+    (fVoxel.add @settings, "quality", @qualityArray)
                           .name("Quality")
                           .onChange((v) => @setQuality(v))

     fSkeleton = @gui.addFolder("Skeleton View")
-    (fSkeleton.add @settings, "displayPrevXY")
-                          .name("Display XY-Plane")
-                          .onChange((v) => @setBoolean("displayPreviewXY", v))
-    (fSkeleton.add @settings, "displayPrevYZ")
-                          .name("Display YZ-Plane")
-                          .onChange((v) => @setBoolean("displayPreviewYZ", v))
-    (fSkeleton.add @settings, "displayPrevXZ")
-                          .name("Display XZ-Plane")
-                          .onChange((v) => @setBoolean("displayPreviewXZ", v))
+    @addCheckbox(fSkeleton, @settings, "displayPrevXY", "Display XY-Plane")
+    @addCheckbox(fSkeleton, @settings, "displayPrevYZ", "Display YZ-Plane")
+    @addCheckbox(fSkeleton, @settings, "displayPrevXZ", "Display XZ-Plane")

     fTrees = @gui.addFolder("Trees")
-    @activeTreeIdController =
-    (fTrees.add @settings, "activeTreeID")
-                          .min(1)
-                          .step(1)
-                          .name("Active Tree ID")
-                          .onFinishChange( (value) => @trigger "setActiveTree", value)
+    @activeTreeIdController = @addNumer(fTrees, @settings, "activeTreeID", 1, 1, "Active Tree ID")
     if somaClickingAllowed
-      (fTrees.add @settings, "newNodeNewTree")
-                            .name("Soma clicking mode")
-                            .onChange((v) => @setBoolean("newNodeNewTree", v))
+      @addCheckbox(fTrees, @settings, "newNodeNewTree", "Soma clicking mode")
     else
       @setNewNodeNewTree(false)
-    (fTrees.add @settings, "newTree")
-                          .name("Create New Tree")
-    (fTrees.add @settings, "deleteActiveTree")
-                          .name("Delete Active Tree")
+    @addFunction(fTrees, @settings, "newTree", "Create New Tree")
+    @addFunction(fTrees, @settings, "deleteActiveTree", "Delete Active Tree")

     fNodes = @gui.addFolder("Nodes")
-    @activeNodeIdController =
-    (fNodes.add @settings, "activeNodeID")
-                          .min(1)
-                          .step(1)
-                          .name("Active Node ID")
-                          .onFinishChange( (value) => @trigger "setActiveNode", value)
+    @activeNodeIdController = @addNumer(fNodes, @settings, "activeNodeID", 1, 1, "Active Node ID")
     @commentController =
     (fNodes.add @settings, "comment")
                           .name("Comment")
                           .onChange(@setComment)
-    (fNodes.add @settings, "prevComment")
-                          .name("Previous Comment")
-    (fNodes.add @settings, "nextComment")
-                          .name("Next Comment")
-    (fNodes.add @settings, "deleteActiveNode")
-                          .name("Delete Active Node")
+    @addFunction(fNodes, @settings, "prevComment", "Previous Comment")
+    @addFunction(fNodes, @settings, "nextComment", "Next Comment")
+    @addFunction(fNodes, @settings, "deleteActiveNode", "Delete Active Node")

     #fControls.open()
     #fView.open()
@@ -237,6 +174,28 @@
     @createTooltips()

+  addCheckbox : (folder, object, propertyName, displayName) =>
+    return (folder.add object, propertyName)
+                          .name(displayName)
+                          .onChange((v) => @set(propertyName, v,  Boolean))
+
+  addSlider : (folder, object, propertyName, start, end, step, displayName, onChange) =>
+    return (folder.add object, propertyName, start, end)
+                          .step(step)
+                          .name(displayName)
+                          .onChange(if onChange? then onChange else(v) => @set(propertyName, v, Number))
+
+  addFunction : (folder, object, propertyName, displayName) =>
+    return (folder.add object, propertyName)
+                          .name(displayName)
+
+  addNumer : (folder, object, propertyName, min, step, displayName) =>
+    return (folder.add object, propertyName)
+                          .min(min)
+                          .step(step)
+                          .name(displayName)
+                          .onChange((v) => @set(propertyName, v, Number))
+
   saveNow : =>
     @model.user.pushImpl()
     @model.route.pushNow()
@@ -271,11 +230,8 @@
     stringPos = Math.round(globalPos[0]) + ", " + Math.round(globalPos[1]) + ", " + Math.round(globalPos[2])
     $("#trace-position-input").val(stringPos)

-  setNumber : (name, value) =>
-    @model.user.setValue( name, (Number) value)
-
-  setBoolean : (name, value) =>
-    @model.user.setValue( name, (Boolean) value)
+  set : (name, value, type) =>
+    @model.user.setValue( name, (type) value)

   setBrightnessAndContrast : =>
     @model.binary.updateLookupTable(@settings.brightness, @settings.contrast)
@@ -301,7 +257,7 @@
     for i in [0..(@qualityArray.length - 1)]
       if @qualityArray[i] == value
         value = i
-    @setNumber("quality", value)
+    @set("quality", value, Number)

   setComment : (value) =>
     @model.route.setComment(value)


Very fancy job server task indicators

Very fancy job server task indicators

--- app/assets/javascripts/plugins/borg.js.coffee
+++ app/assets/javascripts/plugins/borg.js.coffee
@@ -7,7 +7,7 @@
   appendTo: (@$container) ->

   createTask: (host) ->
-    new ContainerView(@$container, host)
+    new LightsTaskView(@$container, host)

   getTask: (host) ->
     @tasks[host] ||= @createTask(host)
@@ -69,23 +69,25 @@
     @capistranoTask = "jobs:restart"

   createTask: (host) ->
-    task = super(host)
-    task.addCustomClass("task-job-server")
-    task
+    new JobServerRestartTaskView(@$container, host)

   parse: (parser) ->
     parser.eachMessage (log) =>
       if match = log.output.match(/sv-multi sending graceful_quit to (\d+) services/)
         @getTask(log.host).update
           numLights: match[1]
-      else if match = log.output.match(/ok: run: [\-\w\d]+: \(pid \d+\)/)
+      else if match = log.output.match(/ok: run: ([\-\w\d]+): \(pid \d+\)/)
         task = @getTask(log.host)
-        task.update
-          numDone: task.numDone + 1
+        task.addStatus("up", match[1])
+        task.update {}
+      else if match = log.output.match(/Timeout: ([\-\w\d]+) still running after (\d+) seconds|timeout: run: ([\-\w\d]+): \(pid \d+\) (\d+)s/)
+        task = @getTask(log.host)
+        task.addStatus("partial", "timeout (#{match[2]||match[4]}s): #{match[1]||match[3]}")
+        task.update {}
     null

-class ContainerView
+class LightsTaskView
   TEMPLATE = $.trim """

@@ -100,12 +102,9 @@

   constructor: (@$container, host) ->
     @$element = $(TEMPLATE)
-    title = host.split('.')[0]
-    @$element.find('.task-lights-title').text(title)
-    @insertSorted(@$element, title)
-
-  addCustomClass: (className) ->
-    @$element.addClass(className)
+    @$title = host.split('.')[0]
+    @$element.find('.task-lights-title').text(@$title)
+    @insertSorted(@$element, @$title)

   insertSorted: (toInsert, title) ->
     inserted = false
@@ -119,8 +118,7 @@
         return false
     toInsert.appendTo(@$container) unless inserted

-  update: (attrs) ->
-    $.extend(this, attrs)
+  genBoxes: ->
     boxes = document.createDocumentFragment();
     for i in [1..(+@numLights)]
       status = if i <= @numDone
@@ -130,11 +128,36 @@
       else
         'neutral'
       $('').addClass("task-lights-box box-#{status}").appendTo(boxes)
-    @$element.find('.task-lights-boxes').empty().append(boxes)
+    boxes
+
+  update: (attrs) ->
+    $.extend(this, attrs)
+    @$element.find('.task-lights-boxes').empty().append(@genBoxes())
     this

   fail: ->
     @$element.addClass('task-failed')
+
+class JobServerRestartTaskView extends LightsTaskView
+  constructor: (container, host) ->
+    super container, host
+    @$element.addClass("task-job-server")
+    @statuses = []
+
+  genBoxes: ->
+    boxes = document.createDocumentFragment();
+    for status in @statuses
+      box = $('').addClass("task-lights-box box-#{status[0]}")
+      box.attr('title',status[1])
+      box.appendTo(boxes)
+    for i in [0...(@numLights-@statuses.length)]
+      $('').addClass("task-lights-box box-neutral").appendTo(boxes)
+    boxes
+
+  addStatus: (status, note) ->
+    @statuses.push([status, note])
+
+

 restartWidget = new ContainersRestartWidget()


Remove Animation

Remove Animation

--- app/assets/javascripts/static_pages.js.coffee
+++ app/assets/javascripts/static_pages.js.coffee
@@ -5,19 +5,19 @@
 # Responsive Action
 ResponsiveWindowWidth = 767

-#Turn on or off touch based menu sliding ("on"/"off")
-touchToggle = "off"
+# #Turn on or off touch based menu sliding ("on"/"off")
+# touchToggle = "off"

-#Swipe Variables
-maxTime = 1000
-maxDistance = 50
-startX = 0
-startTime = 0
+# #Swipe Variables
+# maxTime = 1000
+# maxDistance = 50
+# startX = 0
+# startTime = 0

-touch = "ontouchend" of document
-startEvent = (if (touch) then "touchstart" else "mousedown")
-moveEvent = (if (touch) then "touchmove" else "mousemove")
-endEvent = (if (touch) then "touchend" else "mouseup")
+# touch = "ontouchend" of document
+# startEvent = (if (touch) then "touchstart" else "mousedown")
+# moveEvent = (if (touch) then "touchmove" else "mousemove")
+# endEvent = (if (touch) then "touchend" else "mouseup")

 clearMenu = (e) ->
 	$(".menu").removeClass "menu-open-mobile"
@@ -31,25 +31,25 @@
 	else
 		$('.menu').css "width", "20%"

-closeMenu = (e) ->
-	if ($(window).width() < ResponsiveWindowWidth)
-		$(".menu").toggleClass "menu-open-mobile"
-		$('.siteContainer').toggleClass "site-open-mobile"
-	else
-		$(".menu").toggleClass "menu-open-browser"
-		$('.siteContainer').toggleClass "site-open-browser"
-	$('body').css "position", "relative"
-	$(".menu").data "state", "closed"
+# closeMenu = (e) ->
+# 	if ($(window).width() < ResponsiveWindowWidth)
+# 		$(".menu").toggleClass "menu-open-mobile"
+# 		$('.siteContainer').toggleClass "site-open-mobile"
+# 	else
+# 		$(".menu").toggleClass "menu-open-browser"
+# 		$('.siteContainer').toggleClass "site-open-browser"
+# 	$('body').css "position", "relative"
+# 	$(".menu").data "state", "closed"

-openMenu = (e) ->
-	if ($(window).width() <= ResponsiveWindowWidth)
-		$(".menu").toggleClass "menu-open-mobile"
-		$('.siteContainer').toggleClass "site-open-mobile"
-		$('body').css "position", "fixed"
-	else
-		$(".menu").toggleClass "menu-open-browser"
-		$('.siteContainer').toggleClass "site-open-browser"
-	$(".menu").data "state", "open"
+# openMenu = (e) ->
+# 	if ($(window).width() <= ResponsiveWindowWidth)
+# 		$(".menu").toggleClass "menu-open-mobile"
+# 		$('.siteContainer').toggleClass "site-open-mobile"
+# 		$('body').css "position", "fixed"
+# 	else
+# 		$(".menu").toggleClass "menu-open-browser"
+# 		$('.siteContainer').toggleClass "site-open-browser"
+# 	$(".menu").data "state", "open"

 jQuery ->
 	if ($(window).width() < ResponsiveWindowWidth)
@@ -68,24 +68,24 @@
 		else if $(".menu").data("state") == "closed"
 			openMenu ->

-	#Turn on or off touch based menu sliding ("on"/"off")
-	if touchToggle == "on"
-		$(".siteContainer").bind startEvent, (e) ->
-			startTime = e.timeStamp
-			startX = (if e.originalEvent.touches then e.originalEvent.touches[0].pageX else e.pageX)
+	# #Turn on or off touch based menu sliding ("on"/"off")
+	# if touchToggle == "on"
+	# 	$(".siteContainer").bind startEvent, (e) ->
+	# 		startTime = e.timeStamp
+	# 		startX = (if e.originalEvent.touches then e.originalEvent.touches[0].pageX else e.pageX)

-		$(".siteContainer").bind endEvent, (e) ->
-			startTime = 0
-			startX = 0
+	# 	$(".siteContainer").bind endEvent, (e) ->
+	# 		startTime = 0
+	# 		startX = 0

-		$(".siteContainer").bind moveEvent, (e) ->
-			currentX = (if e.originalEvent.touches then e.originalEvent.touches[0].pageX else e.pageX)
-			currentDistance = (if (startX is 0) then 0 else Math.abs(currentX - startX))
-			currentTime = e.timeStamp
-			if startTime != 0 && currentTime - startTime < maxTime && currentDistance > maxDistance
-				if currentX < startX
-					if $(".menu").data("state") == "open"
-						closeMenu ->
-				if currentX > startX
-					if $(".menu").data("state") == "closed"
-						openMenu ->+	# 	$(".siteContainer").bind moveEvent, (e) ->
+	# 		currentX = (if e.originalEvent.touches then e.originalEvent.touches[0].pageX else e.pageX)
+	# 		currentDistance = (if (startX is 0) then 0 else Math.abs(currentX - startX))
+	# 		currentTime = e.timeStamp
+	# 		if startTime != 0 && currentTime - startTime < maxTime && currentDistance > maxDistance
+	# 			if currentX < startX
+	# 				if $(".menu").data("state") == "open"
+	# 					closeMenu ->
+	# 			if currentX > startX
+	# 				if $(".menu").data("state") == "closed"
+	# 					openMenu ->


Skip TP deletion tests when etherpad missing

--- plugins/twinklepad/test/padlib.spec.coffee
+++ plugins/twinklepad/test/padlib.spec.coffee
@@ -153,6 +153,7 @@
         done()

   it "trashes a twinklepad", (done) ->
+    return done() if skip_tests
     tp_schema.TwinklePad.findOne {pad_name: "tptest_1"}, (err, doc) ->
       doc.sharing.group_id = _.find(session.groups, (g) -> g.slug == "three-members").id
       doc.save (err, doc) ->
@@ -182,6 +183,7 @@
           done()

   it "untrashes a twinklepad", (done) ->
+    return done() if skip_tests
     tp_schema.TwinklePad.findOne {pad_name: "tptest_1"}, (err, doc) ->
       api_methods.trash_entity session2, {
         application: "twinklepad"
@@ -209,6 +211,7 @@
         done()

   it "requests deletion", (done) ->
+    return done() if skip_tests
     tp_schema.TwinklePad.findOne {pad_name: "tptest_1"}, (err, doc) ->
       # Ensure we have multiple events
       www_schema.Event.find {entity: doc._id}, (err, events) ->
@@ -247,6 +250,7 @@
           done()

   it "confirms deletion", (done) ->
+    return done() if skip_tests
     tp_schema.TwinklePad.findOne {pad_name: "tptest_1"}, (err, doc) ->
       www_schema.DeletionRequest.findOne {entity: doc.id}, (err, dr) ->
         api_methods.confirm_deletion session2, dr._id, (err, notices) ->


Get path to analyse

Get path to analyse

--- src/tankipas.coffee
+++ src/tankipas.coffee
@@ -28,3 +28,7 @@
     .option "-u, --user <user>", "use only the commits of the given user."
     .parse process.argv

+# --- get path
+
+sRepoPath = path.resolve process.cwd(), ( tankipas.args[ 0 ] ? "." )
+


Fix transition stopping by introducing -1 flag

--- src/commands/Transition.coffee
+++ src/commands/Transition.coffee
@@ -171,9 +171,9 @@
       return position
     else
       range[15] += diff
-      if range[7] && Math.abs(range[6]) < @REST_THRESHOLD
+      if range[7] > 0 && Math.abs(range[6]) < @REST_THRESHOLD
         @clean(range)
-        range[7] = 0
+        range[7] = -1
         if position != goal
           return goal
       return


Nodejs Web Server Tests: refactor account.test existing test cases

--- workers/social/lib/social/models/account.test.coffee
+++ workers/social/lib/social/models/account.test.coffee
@@ -1,36 +1,21 @@
-{ argv }                      = require 'optimist'
-{ expect }                    = require 'chai'
-{ env : { MONGO_URL } }       = process
-
-KONFIG                        = require('koding-config-manager').load("main.#{argv.c}")
-
-Bongo                         = require 'bongo'
-JUser                         = require './user'
-mongo                         = MONGO_URL or "mongodb://#{ KONFIG.mongo }"
-JAccount                      = require './account'
-JSession                      = require './session'
-TestHelper                    = require '../../../testhelper'
-
-{ daisy }                     = Bongo
-{ generateUserInfo
+{ daisy
+  expect
+  withDummyClient
+  withConvertedUser
   generateDummyClient
-  generateCredentials
-  generateRandomEmail
   generateRandomString
-  generateRandomUsername
-  generateDummyUserFormData } = TestHelper
+  checkBongoConnectivity } = require '../../../testhelper'
+
+JUser    = require './user'
+JGroup   = require './group'
+JAccount = require './account'
+JSession = require './session'

 # making sure we have db connection before tests
 beforeTests = -> before (done) ->

-  bongo = new Bongo
-    root   : __dirname
-    mongo  : mongo
-    models : ''
-
-  bongo.once 'dbClientReady', ->
-    done()
+  checkBongoConnectivity done

 # here we have actual tests
@@ -40,92 +25,30 @@

     it 'should pass error if fields contain invalid key', (done) ->

-      client       = null
-      account      = null
-      fields       = { someInvalidField : 'someInvalidField' }
-      userFormData = generateDummyUserFormData()
-
-      queue = [
-
-        ->
-          # generating dummy client
-          generateDummyClient { group : 'koding' }, (err, client_) ->
-            expect(err).to.not.exist
-            client  = client_
-            account = client.connection.delegate
-            queue.next()
-
-        ->
-          # registering user
-          JUser.convert client, userFormData, (err, data) ->
-            expect(err).to.not.exist
-            # set credentials
-            { account, newToken }      = data
-            client.sessionToken        = newToken
-            client.connection.delegate = account
-            queue.next()
-
-        ->
-          # expecting error when using unallowed field
-          account.modify client, fields, (err) ->
-            expect(err?.message).to.be.equal 'Modify fields is not valid'
-            queue.next()
-
-        -> done()
-
-      ]
-
-      daisy queue
+      withConvertedUser ({ client, account }) ->
+        fields = { someInvalidField : 'someInvalidField' }
+        account.modify client, fields, (err) ->
+          expect(err?.message).to.be.equal 'Modify fields is not valid'
+          done()

     it 'should update given fields correctly', (done) ->

-      fields =
-        'profile.about'     : 'newAbout'
-        'profile.lastName'  : 'newLastName'
-        'profile.firstName' : 'newFirstName'
-
-      client        = null
-      account       = null
-      userFormData  = generateDummyUserFormData()
-
-      queue = [
-
-        ->
-          # generating dummy client
-          generateDummyClient { group : 'koding' }, (err, client_) ->
-            expect(err).to.not.exist
-            client  = client_
-            account = client.connection.delegate
-            queue.next()
-
-        ->
-          # registering user
-          JUser.convert client, userFormData, (err, data) ->
-            expect(err).to.not.exist
-            # set credentials
-            { account, newToken }      = data
-            client.sessionToken        = newToken
-            client.connection.delegate = account
-            queue.next()
-
-        ->
-          # expecting account to be modified
-          account.modify client, fields, (err, data) ->
-            expect(err).to.not.exist
-            queue.next()
-
-        ->
+      withConvertedUser ({ client, account }) ->
+
+        fields =
+          'profile.about'     : 'newAbout'
+          'profile.lastName'  : 'newLastName'
+          'profile.firstName' : 'newFirstName'
+
+        # expecting account to be modified
+        account.modify client, fields, (err, data) ->
+          expect(err).to.not.exist
+
           # expecting account's values to be changed
           for key, value of fields
             expect(account.getAt key).to.be.equal value
-          queue.next()
-
-        -> done()
-
-      ]
-
-      daisy queue
+          done()

   describe '#createSocialApiId()', ->
@@ -134,236 +57,115 @@

       it 'should return -1', (done) ->

-        client        = null
-        account       = null
-        userFormData  = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            # generating dummy client
-            generateDummyClient { group : 'koding' }, (err, client_) ->
+        withDummyClient ({ client, account }) ->
+          # expecting unregistered account to return -1
+          account.createSocialApiId (err, socialApiId) ->
+            expect(err).to.not.exist
+            expect(socialApiId).to.be.equal -1
+            done()
+
+
+
+    describe 'when account type is not unregistered', ->
+
+      it 'should return socialApiId if socialApiId is already set', (done) ->
+
+        withConvertedUser ({ client, account }) ->
+          socialApiId = '12345'
+
+          queue = [
+
+            ->
+              # setting social api id
+              account.update { $set : { socialApiId : socialApiId } }, (err) ->
+                expect(err).to.not.exist
+                queue.next()
+
+            ->
+              # expecting createsocialApiId method to return accountId
+              account.createSocialApiId (err, socialApiId_) ->
+                expect(err).to.not.exist
+                expect(socialApiId_).to.be.equal socialApiId
+                queue.next()
+
+            -> done()
+
+          ]
+
+          daisy queue
+
+
+      it 'should create social api id if account\'s socialApiId is not set', (done) ->
+
+        withConvertedUser ({ client, account }) ->
+
+          queue = [
+
+            ->
+              # unsetting account's socialApiId
+              account.socialApiId = null
+              account.update { $unset : { 'socialApiId' : 1 } }, (err) ->
+                expect(err).to.not.exist
+                expect(account.getAt 'socialApiId').to.not.exist
+                queue.next()
+
+            ->
+              # creating new social api id
+              account.createSocialApiId (err, socialApiId_) ->
+                expect(err).to.not.exist
+                expect(socialApiId_).to.exist
+
+                # expecting account's social api id to be set
+                expect(account.getAt 'socialApiId').to.exist
+                expect(account.socialApiId).to.exist
+                queue.next()
+
+            -> done()
+
+          ]
+
+          daisy queue
+
+
+  describe '#fetchMyPermissions()', ->
+
+    describe 'when group does not exist', ->
+
+      it 'should return error', (done) ->
+
+        withDummyClient { group : 'someInvalidGroup' }, ({ client, account }) ->
+          # expecting error when client's group does not exist
+          account.fetchMyPermissions client, (err, permissions) ->
+            expect(err?.message).to.be.equal 'group not found'
+            done()
+
+
+    describe 'when group exists', ->
+
+      describe 'if account is valid', ->
+
+        it 'should return client\'s permissions', (done) ->
+
+          withDummyClient ({ client, account }) ->
+            # expecting to be able to get permissions
+            account.fetchMyPermissions client, (err, permissions) ->
               expect(err).to.not.exist
-              client  = client_
-              account = client.connection.delegate
-              queue.next()
-
-          ->
-            # expecting unregistered account to return -1
-            account.createSocialApiId (err, socialApiId) ->
+              expect(permissions).to.exist
+              expect(permissions).to.be.an 'object'
+              done()
+
+
+      describe 'if group slug is not defined', ->
+
+        it 'should set the slug as koding and return permissions', (done) ->
+
+          withDummyClient ({ client, account }) ->
+            # expecting to be able to get permissions
+            account.fetchMyPermissions client, (err, permissions) ->
               expect(err)          .to.not.exist
-              expect(socialApiId)  .to.be.equal -1
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
-
-
-    describe 'when account type is not unregistered', ->
-
-      it 'should return socialApiId if socialApiId is already set', (done) ->
-
-        client        = null
-        account       = null
-        socialApiId   = '12345'
-        userFormData  = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            # generating dummy client
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client  = client_
-              account = client.connection.delegate
-              queue.next()
-
-          ->
-            # registering user
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              # set credentials
-              { account, newToken }      = data
-              client.sessionToken        = newToken
-              client.connection.delegate = account
-              queue.next()
-
-          ->
-            # setting social api id
-            account.update { $set : { socialApiId : socialApiId } }, (err) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            # expecting createsocialApiId method to return accountId
-            account.createSocialApiId (err, socialApiId_) ->
-              expect(err)          .to.not.exist
-              expect(socialApiId_) .to.be.equal socialApiId
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
-
-
-      it 'should create social api id if account\'s socialApiId is not set', (done) ->
-
-        client        = null
-        account       = null
-        userFormData  = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            # generating dummy client
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client  = client_
-              account = client.connection.delegate
-              queue.next()
-
-          ->
-            # registering user
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              # set credentials
-              { account, newToken }      = data
-              client.sessionToken        = newToken
-              client.connection.delegate = account
-              queue.next()
-
-          ->
-            # unsetting account's socialApiId
-            account.socialApiId = null
-            account.update { $unset : { 'socialApiId' : 1 } }, (err) ->
-              expect(err)                          .to.not.exist
-              expect(account.getAt 'socialApiId')  .to.not.exist
-              queue.next()
-
-          ->
-            # creating new social api id
-            account.createSocialApiId (err, socialApiId_) ->
-              expect(err)           .to.not.exist
-              expect(socialApiId_)  .to.exist
-              queue.next()
-
-          ->
-            # expecting account's social api id to be set
-            expect(account.getAt 'socialApiId')  .to.exist
-            expect(account.socialApiId)          .to.exist
-            queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
-
-
-  describe '#fetchMyPermissions()', ->
-
-    describe 'when group does not exist', ->
-
-      it 'should return error', (done) ->
-
-        client        = null
-        account       = null
-        userFormData  = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            # generating dummy client
-            generateDummyClient { group : 'someInvalidGroup' }, (err, client_) ->
-              expect(err).to.not.exist
-              client  = client_
-              account = client.connection.delegate
-              queue.next()
-
-          ->
-            # expecting error when client's group does not exist
-            account.fetchMyPermissions client, (err, permissions) ->
-              expect(err?.message).to.be.equal 'group not found'
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
-
-
-    describe 'when group exists', ->
-
-      describe 'if account is valid', ->
-
-        it 'should return client\'s permissions', (done) ->
-
-          client        = null
-          account       = null
-          userFormData  = generateDummyUserFormData()
-
-          queue = [
-
-            ->
-              # generating dummy client
-              generateDummyClient { group : 'koding' }, (err, client_) ->
-                expect(err).to.not.exist
-                client  = client_
-                account = client.connection.delegate
-                queue.next()
-
-            ->
-              # expecting to be able to get permissions
-              account.fetchMyPermissions client, (err, permissions) ->
-                expect(err)          .to.not.exist
-                expect(permissions)  .to.exist
-                expect(permissions)  .to.be.an 'object'
-                queue.next()
-
-            -> done()
-
-          ]
-
-          daisy queue
-
-      describe 'if group slug is not defined', ->
-
-        it 'should set the slug as koding and return permissions', (done) ->
-
-          client        = null
-          account       = null
-          userFormData  = generateDummyUserFormData()
-
-          queue = [
-
-            ->
-              # generating dummy client
-              generateDummyClient { group : 'koding' }, (err, client_) ->
-                expect(err).to.not.exist
-                client               = client_
-                client.context.group = null
-                account              = client.connection.delegate
-                queue.next()
-
-            ->
-              # expecting to be able to get permissions
-              account.fetchMyPermissions client, (err, permissions) ->
-                expect(err)          .to.not.exist
-                expect(permissions)  .to.exist
-                expect(permissions)  .to.be.an 'object'
-                queue.next()
-
-            -> done()
-
-          ]
-
-          daisy queue
+              expect(permissions)  .to.exist
+              expect(permissions)  .to.be.an 'object'
+              done()

   describe '#leaveFromAllGroups()', ->

@@ -383,43 +185,15 @@
       # before running test cases creating a group
       before (done) ->

-        adminUserFormData = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            # generating admin client to create group
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              adminClient = client_
-              queue.next()
-
-          ->
-            # registering admin client
-            JUser.convert adminClient, adminUserFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account, newToken } = data
-
-              # set credentials
-              adminClient.sessionToken        = newToken
-              adminClient.connection.delegate = account
-              adminClient.context.group       = groupSlug
-              adminAccount                    = account
-              queue.next()
-
-          ->
-            JGroup = require './group'
-            # creating a new group
-            JGroup.create adminClient, groupData, adminAccount, (err, group_) ->
-              expect(err).to.not.exist
-              group = group_
-              queue.next()
-
-          -> done()
-
-        ]
-
-        daisy queue
+        withConvertedUser ({ client, account }) ->
+          adminClient  = client
+          adminAccount = account
+
+          # creating a new group
+          JGroup.create adminClient, groupData, adminAccount, (err, group_) ->
+            expect(err).to.not.exist
+            group = group_
+            done()

       it 'admin should have more than one group', (done) ->
@@ -429,59 +203,42 @@
           expect(groups).to.have.length.above(1)
           done()

-      it 'standart user should be able to leave from all groups', (done) ->
-        client       = null
-        account      = null
-        userFormData = generateDummyUserFormData()
-
-        queue = [
-
-          ->
-            generateDummyClient { group : 'koding' }, (err, client_) ->
-              expect(err).to.not.exist
-              client = client_
-              queue.next()
-
-          ->
-            # registering admin client
-            JUser.convert client, userFormData, (err, data) ->
-              expect(err).to.not.exist
-              { account, newToken } = data
-
-              client.sessionToken        = newToken
-              client.connection.delegate = account
-              client.context.group       = 'koding'
-              queue.next()
-
-          ->
-            JUser = require './user'
-            JUser.addToGroup account, group.slug, userFormData.email, null, (err) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            account.fetchAllParticipatedGroups client, (err, groups) ->
-              expect(err).to.not.exist
-              expect(groups).to.have.length.above(1)
-              queue.next()
-
-          ->
-            account.leaveFromAllGroups client, (err) ->
-              expect(err).to.not.exist
-              queue.next()
-
-          ->
-            account.fetchAllParticipatedGroups client, (err, groups) ->
-              expect(err).to.not.exist
-              expect(groups).to.have.length(1)
-              queue.next()
-
-          ->
-            done()
-
-        ]
-
-        daisy queue
+
+      it 'standard user should be able to leave from all groups', (done) ->
+
+        withConvertedUser ({ client, account, userFormData }) ->
+
+          queue = [
+
+            ->
+              JUser.addToGroup account, group.slug, userFormData.email, null, (err) ->
+                expect(err).to.not.exist
+                queue.next()
+
+            ->
+              account.fetchAllParticipatedGroups client, (err, groups) ->
+                expect(err).to.not.exist
+                expect(groups).to.have.length.above(1)
+                queue.next()
+
+            ->
+              account.leaveFromAllGroups client, (err) ->
+                expect(err).to.not.exist
+                queue.next()
+
+            ->
+              account.fetchAllParticipatedGroups client, (err, groups) ->
+                expect(err).to.not.exist
+                expect(groups).to.have.length(1)
+                queue.next()
+
+            ->
+              done()
+
+          ]
+
+          daisy queue
+

 beforeTests()


FIX: layout update when value changes

FIX: layout update when value changes

--- source/Art/Engine/Forms/TextInputElement.coffee
+++ source/Art/Engine/Forms/TextInputElement.coffee
@@ -112,8 +112,8 @@

         handlerMap.keyPress? e
         {props} = e
-        @handleEvent "enter", value:@value if props.key == "Enter"
-        @handleEvent "escape", value:@value if props.key == "Escape"
+        @handleEvent "enter",  value: @value if props.key == "Enter"
+        @handleEvent "escape", value: @value if props.key == "Escape"

   _unregister: ->
     @_canvasElementToFocusOnBlur?.focusCanvas()
@@ -134,6 +134,7 @@
       setter: (v) ->
         v = if v? then "#{v}" else ""
         unless @domElement.value == v
+          @_elementChanged true
           @domElement.value = v

     color:


Updates spec

Updates spec

--- test/brush-javascript.spec.coffee
+++ test/brush-javascript.spec.coffee
@@ -2,12 +2,12 @@

 chai = require 'chai'
 expect = chai.expect
-parser = require 'syntaxhighlighter-parser'
+parser = require 'parser'

 (typeof window isnt 'undefined' and window or global)
   .SyntaxHighlighter =
-    Highlighter: require('syntaxhighlighter-brush').Brush
-    regexLib: require('syntaxhighlighter-common').regexLib
+    Highlighter: require('brush-base')
+    regexLib: require('regex-lib')
     brushes: {}

 {Brush} = require '..'


Clean router

Clean router

--- admin/static/coffee/router.coffee
+++ admin/static/coffee/router.coffee
@@ -23,8 +23,6 @@
         'servers/:id/': 'server'
         'dashboard': 'dashboard'
         'dashboard/': 'dashboard'
-        'resolve_issues': 'resolve_issues'
-        'resolve_issues/': 'resolve_issues'
         'logs': 'logs'
         'logs/': 'logs'
         'dataexplorer': 'dataexplorer'
@@ -42,9 +40,6 @@

     update_active_tab: (route) =>
         @navbar.set_active_tab route
-
-    set_stats_call: (url) =>
-        return

     index_tables: ->
         clear_modals()
@@ -64,25 +59,14 @@
         @current_view = new DashboardView.DashboardContainer
         @container.html @current_view.render().el

-    resolve_issues: ->
-        @set_stats_call ''
-        log_router '/resolve_issues'
-        clear_modals()
-        @current_view.remove()
-        @current_view = new ResolveIssuesView.Container
-        @container.html @current_view.render().el
-
     logs: ->
-        @set_stats_call ''
-        log_router '/logs'
+        #TODO
         clear_modals()
         @current_view.remove()
         @current_view = new LogView.Container
         @container.html @current_view.render().el

     dataexplorer: ->
-        @set_stats_call ''
-        log_router '/dataexplorer'
         clear_modals()
         @current_view.remove()
         @current_view = new DataExplorerView.Container


asteroid tracking: fix done button (does not enable after all frames complete)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -190,8 +190,19 @@
           @el.find(".asteroid-frame-complete-#{@currFrameIdx+1}").prop 'checked', true
           @asteroidMarkedInFrame[@currFrameIdx] = true

+        # enable 'done' button only if all frames marked
+        numFramesComplete = 0
+        for status in @asteroidMarkedInFrame
+          console.log status
+          if status is true
+            numFramesComplete++
+
+        if numFramesComplete is 4
+          @doneButton.prop 'disabled', false
+
         # DEBUG CODE
         # console.log @asteroidMarkedInFrame
+

       tool.controls.controller.setMark(@currFrameIdx)


comment debug code to simulate new subject (oops!)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -318,7 +318,7 @@
     @startLoading()

   onSubjectSelect: (e, subject) =>
-    Subject.current.classification_count = 0 # DEBUG CODE: fake brand new subject
+    # Subject.current.classification_count = 0 # DEBUG CODE: fake brand new subject
     if @subjectUnseen()
       @notification.html translate('span', 'classifier.subjectUnseenMessage', class: 'bold-text green-text')
       @notification.fadeIn()


Fix current lists

Fix current lists

--- app/controllers/lists.item.coffee
+++ app/controllers/lists.item.coffee
@@ -14,7 +14,7 @@
     @list.bind "update", @render
     @list.bind "destroy", @remove
     Task.bind "create update destroy", @update
-    @list.bind "changeList", @active
+    @list.bind "changeList", @current

   render: =>
     @list.count = Task.active(@list.id).length
@@ -25,23 +25,21 @@
         task = ui.draggable.data("item")
         task.updateAttribute("list", @list.id)

-    @active()
+    @current()
     @
-
-  click: =>
-    @el.parent().parent().find(".current").removeClass("current")
-    @el.addClass("current")
-    List.trigger "changeList", @list
-    true

   update: =>
     # Only update if the task was in the list
     # if task.list is @list.id
     @render()

-  active: =>
+  click: ->
+    List.trigger "changeList", @list
+
+  current: =>
     if List.current?.id is @list.id
-      @el.addClass "active"
+      @el.parent().parent().find(".current").removeClass("current")
+      @el.addClass "current"

   remove: ->
     @el.remove()


simplify message

simplify message

--- static/index.coffee
+++ static/index.coffee
@@ -101,7 +101,7 @@
   edit = (content) ->
     content ?= getDomContent()
     $list.hide()
-    $linenosCode.html "#{metaKeyName}+s to Save - Shift+#{metaKeyName}+s to Save As".replace /(.)/g, '$1
'
+    $linenosCode.html "Esc - #{metaKeyName}+s - Shift+#{metaKeyName}+s".replace /(.)/g, '$1
'
     $editorCode.html(content).attr('contentEditable', 'true').focus()
     $editor.addClass('editing')
     $window.off 'keydown', disableSave


Use UUID v4 implementation of kelektiv/node-uuid

Source code found in:
https://github.com/kelektiv/node-uuid/tree/v3.3.3

Copy files:
* https://github.com/kelektiv/node-uuid/blob/v3.3.3/lib/rng-browser.js
* https://github.com/kelektiv/node-uuid/blob/v3.3.3/lib/bytesToUuid.js
* https://github.com/kelektiv/node-uuid/blob/v3.3.3/v4.js

--- src/helpers/uuid_helper.coffee
+++ src/helpers/uuid_helper.coffee
@@ -1,35 +1,88 @@
 ###
-Tiny library to generate version 4 UUIDs
+Library to generate version 4 UUIDs
+
+Code taken from:
+  https://github.com/kelektiv/node-uuid/tree/v3.3.3

 References:
   http://www.ietf.org/rfc/rfc4122.txt
-  https://github.com/kelektiv/node-uuid
-  https://gist.github.com/jed/982883
 ###
 define ->
-  cryptoObj = window.crypto or window.msCrypto # for IE 11
+  UUID =
+    # _cryptoObj, _rng(), _whatwgRNG() and _mathRNG() were taken from:
+    # https://github.com/kelektiv/node-uuid/blob/v3.3.3/lib/rng-browser.js
+    _cryptoObj: window.crypto or window.msCrypto # for IE 11

-  if cryptoObj
-    random = ->
-      # Divide a random UInt32 by the maximum value (2^32 -1) to get a result between 0 and 1
-      # https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
-      cryptoObj.getRandomValues(new Uint32Array(1))[0] / 4294967295
-  else
-    random = Math.random
+    # Unique ID creation requires a high quality random # generator.  In the
+    # browser this is a little complicated due to unknown quality of Math.random()
+    # and inconsistent support for the `crypto` API.  We do the best we can via
+    # feature-detection
+    _rng: () -> if UUID._cryptoObj then UUID._whatwgRNG() else UUID._mathRNG()

-  # algorithm from https://gist.github.com/jed/982883
-  UUID =
-    generate: (placeholder) ->
-      if placeholder
-        # if the placeholder was passed,
-        # return a random number from 0 to 15
-        # unless placeholder is 8,
-        # in which case a random number from 8 to 11
-        # in hexadecimal
-        (placeholder ^ random() * 16 >> placeholder/4).toString(16)
-      else
-        # or otherwise a template string replacing
-        # zeroes, ones, and eights with random hex digits
-        '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, UUID.generate)
+    # WHATWG crypto RNG - http://wiki.whatwg.org/wiki/Crypto
+    _whatwgRNG: () ->
+      rnds8 = new Uint8Array(16);
+
+      UUID._cryptoObj.getRandomValues(rnds8)
+
+      rnds8
+
+    # Math.random()-based (RNG)
+
+    # If all else fails, use Math.random().  It's fast, but is of unspecified
+    # quality.
+    _mathRNG: () ->
+      rnds = new Array(16);
+
+      for i in [0...16]
+        r = Math.random() * 0x100000000 if (i & 0x03) == 0
+        rnds[i] = r >>> (i & 0x03) << 3 & 0xff
+
+      rnds
+
+    # _bytesToUuid() and _byteToHex were taken from:
+    # https://github.com/kelektiv/node-uuid/blob/v3.3.3/lib/bytesToUuid.js
+    #
+    # Convert array of 16 byte values to UUID string format of the form:
+    # XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
+    _byteToHex: do ->
+      byteToHex = []
+      byteToHex[i] = (i + 0x100).toString(16).substr(1) for i in [0...256]
+
+    _bytesToUuid: (buf, offset) ->
+      i = offset or 0
+      bth = UUID._byteToHex
+      # join used to fix memory issue caused by concatenation: https://bugs.chromium.org/p/v8/issues/detail?id=3175#c4
+      [
+        bth[buf[i++]], bth[buf[i++]]
+        bth[buf[i++]], bth[buf[i++]], '-'
+        bth[buf[i++]], bth[buf[i++]], '-'
+        bth[buf[i++]], bth[buf[i++]], '-'
+        bth[buf[i++]], bth[buf[i++]], '-'
+        bth[buf[i++]], bth[buf[i++]]
+        bth[buf[i++]], bth[buf[i++]]
+        bth[buf[i++]], bth[buf[i++]]
+      ].join ''
+
+    # generate() was taken from:
+    # https://github.com/kelektiv/node-uuid/blob/v3.3.3/v4.js
+    generate: (options, buf, offset) ->
+      i = buf and offset or 0
+
+      if typeof options == 'string'
+        buf = if options == 'binary' then new Array(16) else null
+        options = null
+      options = options or {}
+
+      rnds = options.random or (options.rng or UUID._rng)()
+
+      # Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
+      rnds[6] = rnds[6] & 0x0f | 0x40
+      rnds[8] = rnds[8] & 0x3f | 0x80
+
+      # Copy bytes to buffer, if provided
+      (buf[i + ii] = rnds[ii] for ii in [0...16]) if buf
+
+      buf or UUID._bytesToUuid(rnds)

   return UUID


remove unnecessary .toString

remove unnecessary .toString

--- src/components/navbar/index.cjsx
+++ src/components/navbar/index.cjsx
@@ -29,7 +29,7 @@
     if @isMounted()
       {courseId} = @context.router.getCurrentParams()

-      unless @state.course?.id.toString() is courseId
+      unless @state.course?.id is courseId
         course = CourseStore.get(courseId)
         @setState({course})


moved label below tabs

moved label below tabs

--- src/components/scores/index.cjsx
+++ src/components/scores/index.cjsx
@@ -223,6 +223,13 @@

     data = @getStudentRowData()

+    periodNav =
+      <CoursePeriodsNavShell
+        handleSelect={@selectPeriod}
+        handleKeyUpdate={@setPeriodIndex}
+        intialActive={period_id}
+        courseId={courseId} />
+
     rowGetter = (rowIndex) =>
       @renderStudentRow(data.rows[rowIndex])

@@ -230,11 +237,7 @@

           Student Scores
           <ScoresExport courseId={courseId} className='pull-right'/>
-          <CoursePeriodsNavShell
-            handleSelect={@selectPeriod}
-            handleKeyUpdate={@setPeriodIndex}
-            intialActive={period_id}
-            courseId={courseId} />
+          {periodNav}

             <Table
               onColumnResizeEndCallback={@onColumnResizeEndCallback}
@@ -253,13 +256,9 @@

     else

+        {periodNav}
         No Assignments Yet
-        <CoursePeriodsNavShell
-          handleSelect={@selectPeriod}
-          handleKeyUpdate={@setPeriodIndex}
-          intialActive={period_id}
-          courseId={courseId} />
-

+


remove commented code

remove commented code

--- src/jquery-select-areas.coffee
+++ src/jquery-select-areas.coffee
@@ -95,8 +95,6 @@
     # register mouse events
     @container
       .on 'mousedown', (e) =>
-        #click_y = e.pageY
-        #click_x = e.pageX
         # relative to container
         click_x = e.pageX - @container.offset().left
         click_y = e.pageY - @container.offset().top


Kill Controller.viewContext. This was a dumb idea.

--- src/controller/controller.coffee
+++ src/controller/controller.coffee
@@ -170,8 +170,6 @@

     view

-  @accessor 'viewContext', -> @viewContext ||= new Batman.View(controller: this)
-
   scrollToHash: (hash = @get('params')['#'])-> Batman.DOM.scrollIntoView(hash)

   _resetActionFrames: -> @_actionFrames = []


fix #55 - restore focus when esc goto popu

--- lib/goto/goto-select-list-view.coffee
+++ lib/goto/goto-select-list-view.coffee
@@ -30,12 +30,14 @@
   * Hides the list view.
  ###
  cancelled: ->
+   @previouslyFocusedElement.focus()
    @panel.hide()

  ###*
   * Shows the list view.
  ###
  show: ->
+   @storeFocusedElement()
    @panel ?= atom.workspace.addModalPanel(item: this)
    @panel.show()
    @keyBindings = atom.keymaps.findKeyBindings(target: atom.views.getView(atom.workspace))


getPixel Implemented, wooo\!

getPixel Implemented, wooo\!

--- web/scripts/paintGameState.coffee
+++ web/scripts/paintGameState.coffee
@@ -63,21 +63,22 @@
         return

     drawPixel: (x, y, color) ->
-        @commands.push {
-            key: 'drawPixel',
-            exec: @_drawPixel.bind @, x, y, color
-        }
-        return
-
-    _drawPixel: (x, y, color) ->
         if not @gameManager.config.game.characterBase.hasOwnProperty color
             return
         char = @gameManager.generateCharacter color,
                 x, y, false
-        if @picture[x][y]
+        char.color = color
+        @picture[x][y] = char
+        @commands.push {
+            key: 'drawPixel',
+            exec: @_drawPixel.bind @, x, y, color, char
+        }
+        return
+
+    _drawPixel: (x, y, color, char) ->
+        if @picture[x][y]?
             @visual.removeCharacter @gameManager.config.visual, @picture[x][y].visual
         @visual.pushCharacter @gameManager.config.visual, char.visual
-        char.color = color
         @picture[x][y] = char
         return


webgl: revert line glyph for now

webgl: revert line glyph for now

--- bokehjs/src/coffee/renderer/glyph/line.coffee
+++ bokehjs/src/coffee/renderer/glyph/line.coffee
@@ -1,5 +1,4 @@
 _ = require "underscore"
-gloo2 = require "gloo2"
 Glyph = require "./glyph"
 hittest = require "../../common/hittest"

@@ -8,22 +7,10 @@
   _index_data: () ->
     @_xy_index()

-  _map_data: () ->
-    if @_gl?
-        return  # performance
+  _render: (ctx, indices, {sx, sy}) ->
+    drawing = false
+    @visuals.line.set_value(ctx)  # Set before gl, because it uses it too

-  _set_data: () ->
-    @_data_changed = true  # notify gl, lazy upload
-
-  _render: (ctx, indices, {sx, sy}) ->
-
-    @visuals.line.set_value(ctx)  # Set before gl, because it uses it too
-
-    if ctx.glcanvas
-        if not @_render_gl(ctx, indices)
-          return
-
-    drawing = false
     for i in indices
       if !isFinite(sx[i]+sy[i]) and drawing
         ctx.stroke()
@@ -40,53 +27,6 @@

     if drawing
       ctx.stroke()
-
-  _render_gl: (ctx, indices) ->
-    gl = ctx.glcanvas.gl
-    if not @_gl?
-      @_gl = setup_gl(gl)
-
-    # Get transform, and verify that its linear
-    if (Math.abs((dx[1] - dx[0]) - (dx[2] - dx[1])) > 1e-6 ||
-        Math.abs((dy[1] - dy[0]) - (dy[2] - dy[1])) > 1e-6)
-      return true
-
-    # No gl if line has features that we do no support
-    # If we implement agg quality lines with shaders we can probably also add
-    # stippling and caps (Nicolas Rougier has done much work on this stuff)
-    console.log(@visuals.line.dash.value())
-    if @visuals.line.dash.value().length
-      return true
-    if ctx.lineCap != 'butt'
-      return true
-
-    if @_data_changed
-      @_data_changed = false
-      @_gl.vbo_x.set_size(@x.length * 4)  # size in bytes
-      @_gl.vbo_y.set_size(@y.length * 4)
-      @_gl.vbo_x.set_data(0, new Float32Array(@x))
-      @_gl.vbo_y.set_data(0, new Float32Array(@y))
-
-    # Get RGBA as a tuple
-    # We query this (and other) values from the ctx, so we have the appropriate
-    # defaults, and colors like "red" will be transformed to hex.
-    colorparts = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(ctx.strokeStyle)
-    color = [parseInt(colorparts[1], 16)/255, parseInt(colorparts[2], 16)/255, parseInt(colorparts[3], 16)/255]
-    color.push(ctx.globalAlpha)
-
-    # Set line width - note that only width 1 is guaranteed to be supported
-    # We make width-1 lines a bit wider, otherwise they turn vague due to aa
-    lineWidth = if ctx.lineWidth > 1 then ctx.lineWidth else ctx.lineWidth + 0.15
-    ctx.glcanvas.gl.lineWidth(lineWidth)
-
-    @_gl.prog.set_uniform('u_color', 'vec4', color)
-    @_gl.prog.set_uniform('u_canvas_size', 'vec2', [ctx.glcanvas.width, ctx.glcanvas.height])
-    @_gl.prog.set_uniform('u_offset', 'vec2', [dx[0], dy[0]])
-    @_gl.prog.set_uniform('u_scale', 'vec2', [dx[1]-dx[0], dy[1]-dy[0]])
-
-    # todo: use indices
-    console.log(indices)
-    @_gl.prog.draw(gl.LINE_STRIP, [0, @x.length])

   _hit_point: (geometry) ->
     ### Check if the point geometry hits this line glyph and return an object
@@ -165,40 +105,6 @@
   visuals: ['line']

-setup_gl = (gl) ->
-  # This function sets up the visualization to render a line
-
-  VERT = """
-  precision mediump float;
-  attribute float a_x;
-  attribute float a_y;
-  uniform vec2 u_canvas_size;
-  uniform vec2 u_offset;
-  uniform vec2 u_scale;
-  void main() {
-      vec2 pos = vec2(a_x, a_y) * u_scale + u_offset; // in pixels
-      pos /= u_canvas_size;  // in 0..1
-      gl_Position = vec4(pos*2.0-1.0, 0.0, 1.0);
-      gl_Position.y *= -1.0;
-  }"""
-
-  FRAG = """
-  precision mediump float;
-  uniform vec4 u_color;
-  void main() {
-      gl_FragColor = u_color;
-      gl_FragColor.a = 1.0;
-  }"""
-
-  prog = new gloo2.Program(gl)
-  prog.set_shaders(VERT, FRAG)
-  vbo_x = new gloo2.VertexBuffer(gl)
-  prog.set_attribute('a_x', 'float', [vbo_x, 0, 0])
-  vbo_y = new gloo2.VertexBuffer(gl)
-  prog.set_attribute('a_y', 'float', [vbo_y, 0, 0])
-  return {'gl': gl, 'prog': prog, 'vbo_x', vbo_x, 'vbo_y': vbo_y}
-
-
 module.exports =
   Model: Line
   View: LineView


Remove Olark; fixes #1

Remove Olark; fixes #1

--- hamwerk/client/todos.coffee
+++ hamwerk/client/todos.coffee
@@ -175,13 +175,13 @@
     sel = class_id: class_id
     if class_id is ""
         sel = {}
-
+
     return _(Offline.smart.assignments().find(sel).fetch()).chain()
            .sortBy((obj) -> new Date(obj.due).getTime())
            .sortBy("done")
            .groupBy("done")
            .pairs()
-           .map(([truthiness, list]) -> _.sortBy(list, (obj) ->
+           .map(([truthiness, list]) -> _.sortBy(list, (obj) ->
                 if truthiness is "true"
                     Math.abs(DateOMatic.msDifferential(new Date(obj.due)))
                 else
@@ -231,7 +231,7 @@
         Session.set "editing_itemname", this._id
         Deps.flush() # update DOM before focus
         activateInput(tmpl.find("#assignment-input"))
-
+
     "dblclick abbr": (evt, tmpl) ->
         Session.set "editing_due_date", @_id
         Deps.flush()
@@ -290,71 +290,8 @@
 loadIntercom = (force = no) ->
     return Meteor.setTimeout((-> loadIntercomActual(force)), 250)

-loadOlark = ->
-    window.olark || ((c) ->
-        l = `window.location.protocol == "https:" ? "https:" : "http:"`
-        nt = ->
-            window[c.name] = ->
-                (a.s = a.s || []).push(arguments)
-            a = window[c.name]._ = {}
-            q = c.methods.length
-            while (q--)
-                ((n) ->
-                    window[c.name][c.methods[q]] = ->
-                        window[c.name]("call", n, arguments)
-                )(c.methods[q])
-            a.l = c.loader
-            a.i = nt
-            a.p = 0: +new Date
-            a.P = (u) -> a.p[u] = new Date - a.p[0]
-
-            s = ->
-                a.P("load")
-                window[c.name]("load")
-            `window.addEventListener ? window.addEventListener("load", s, false) : window.attachEvent("onload", s)`
-            ld = ->
-                p = -> ""
-                m = document.body
-                if (!m)
-                    return Meteor.setTimeout(ld, 100)
-                a.P(1)
-                n = document.createElement("div")
-                v = n.appendChild(document.createElement(c.name))
-                b = document.createElement("iframe")
-                n.style.display = "none"
-                m.insertBefore(n, m.firstChild).id = c.name
-                b.frameBorder = "0"
-                b.id = c.name + "-loader"
-                if /MSIE[ ]+6/.test(navigator.userAgent)
-                    b.src = "javascript:false"
-                b.allowTransparency = "true"
-                v.appendChild(b)
-                try
-                    b.contentWindow.document.open()
-                catch
-                    c.domain = document.domain;
-                    o = "javascript:var d=document.open();d.domain='#{document.domain}';";
-                    b.src = o + "void(0);"
-                try
-                    t = b.contentWindow.document
-                    t.write(p())
-                    t.close()
-                catch
-                    b.src = o + 'd.write("' + p().replace(/"/g, String.fromCharCode(92) + '"') + '");d.close();'
-                a.P(2)
-            ld()
-        nt()
-    )({
-        loader: "static.olark.com/jsclient/loader0.js",
-        name: "olark",
-        methods: ["configure", "extend", "declare", "identify"]
-    })
-    # custom configuration goes here (www.olark.com/documentation)
-    olark.identify('8586-972-10-1233');
-
 Meteor.startup ->
     Backbone.history.start pushState: true
-    loadOlark()
     Meteor.call "hash", (error, user_hash) ->
         window.intercomSettings =
             # TODO: The current logged in user's email address.


Fix regex when only one word for name

Fix regex when only one word for name

--- src/core/javascripts/controllers/base.js.coffee
+++ src/core/javascripts/controllers/base.js.coffee
@@ -244,9 +244,10 @@
     $scope.bb.client_defaults = prms.client if prms.client

     if $scope.bb.client_defaults && $scope.bb.client_defaults.name
-      result = $scope.bb.client_defaults.name.match(/^(\S+)\s(.*)/).slice(1)
-      $scope.bb.client_defaults.first_name =  result[0]
-      $scope.bb.client_defaults.last_name =  result[1]
+      match = $scope.bb.client_defaults.name.match(/^(\S+)(?:\s(\S+))?/)
+      if match
+        $scope.bb.client_defaults.first_name =  match[1]
+        $scope.bb.client_defaults.last_name = match[2] if match[2]?

     if prms.clear_member
       $scope.bb.clear_member = prms.clear_member


UI/slideshow better animation lock to prevent doubleclicking

--- ui/slideshow/src/slideshow.coffee
+++ ui/slideshow/src/slideshow.coffee
@@ -106,10 +106,14 @@
   # @return {Slideshow} this
   #
   slideTo: (index)->
+    return if @__sliding; @__sliding = true
+
     items = @items()

     if index isnt @currentIndex && @currentIndex isnt null && items[index] && @currentItem
       @_slide(index, items[index], @currentItem)
+    else
+      @__sliding = false

     if items[index] or @currentIndex is null
       @currentIndex = index
@@ -152,8 +156,6 @@

   # makes the actual sliding effect
   _slide: (index, item, cur_item)->
-    return if @__sliding; @__sliding = true
-
     box_size = @_end_size(item)
     old_size = cur_item.size()
     end_size = box_size.item_size
@@ -185,7 +187,7 @@

   # calculates the end size of the whole block
   _end_size: (item)->
-    @__clone or= @clone().style(visibility: 'hidden').insertTo(@, 'after')
+    @__clone or= @clone().style(visibility: 'hidden').insertTo(@, 'before')
     @__clone.style(display: '')

     item = item.clone().style(display: 'block', position: 'relative')


pjax request selects correct tab, loads requests on all pages with updates

--- public/js/main.coffee
+++ public/js/main.coffee
@@ -14,11 +14,22 @@

   # Set equal heights for 3-column area
   $("#pitch").equalHeights()
-
+
+  #########################################
+  # Updates
+  #########################################
+
   # PJAX-ify the tabs
-  $('a#tab-replies').pjax('#timeline')
-  $('a#tab-timeline').pjax('#timeline')
-  $('a#tab-world').pjax('#timeline')
+  $('#navigation a').pjax('#content .updates').click ->
+    # Set clicked tab to be given active class when pjax returns
+    $('#navigation a').removeClass("next")
+    $(this).addClass("next");
+
+  $("#content .updates").bind "end.pjax", pjaxSuccess = ->
+    # Set active class appropriately
+    $('#navigation li').removeClass("active")
+    $('#navigation a.next').parent().addClass("active")
+

   #########################################
   # Update Form


Added projector auto shutdown feature

Added projector auto shutdown feature

--- tp6/src/script/views/projector_pane.coffee
+++ tp6/src/script/views/projector_pane.coffee
@@ -1,6 +1,7 @@
 slinky_require('../core.coffee')

 Tp.ProjectorPaneView = Backbone.View.extend
+
   initialize: () ->
     $.get '../script/templates/projector_module.html', (template) =>
       $.template "projector-pane-template", template
@@ -16,6 +17,8 @@
     Tp.devices.volume.bind "change:volume", @audioLevelChanged
     Tp.sources.bind "change", @sourceChanged
     Tp.room.bind "change:source", @sourceChanged
+
+    @timer = null

   render: () ->
     ($.tmpl "projector-pane-template", {
@@ -45,15 +48,22 @@
     $('.mute-button').click(@muteButtonClicked)

-
   projectorChanged: () ->
     state = Tp.devices.projector.get('state')
     console.log("Projector now " + state)
     text = if (["on", "muted", "warming"].indexOf(state) != -1) then "off" else "on"
+    @autoOff(state)
     $('.power-button .label').html("turn " + text)
     $('.status-image').attr 'src', '../images/projector/' + state + '.png'
     $('.source-image').css 'visibility', if text == "off" then "visible" else "hidden"
     $('.screen-image-overlay').css 'opacity', if text == "off" then 0 else 0.4
+
+  autoOff: (state) ->
+    if state == "on"
+      callback = -> Tp.devices.projector.state_set 'power', false
+      @timer = setTimeout callback, 10800000 # Three hour timer
+    else if state == "off"
+      if @timer then clearTimeout(@timer)

   sourceChanged: () ->
     state = Tp.room.get('source')


Minor bugfix, unclear if it makes any difference

--- src/goo/loaders/handlers/MaterialHandler.coffee
+++ src/goo/loaders/handlers/MaterialHandler.coffee
@@ -94,7 +94,8 @@
 					if promises?.length
 						return RSVP.all(promises).then (textures)->
 							for texture in textures
-								object.setTexture(texture.type, texture.texture)
+								if texture.texture?
+									object.setTexture(texture.type, texture.texture)

 							return object
 						.then null, (err)->


Adds mathjax rendered modifier class

Adds mathjax rendered modifier class

Needed to add hover effects, click handlers to the middle of rendered expressions

--- math/expression_to_string_spec.coffee
+++ math/expression_to_string_spec.coffee
@@ -4,10 +4,12 @@
   beforeEach ->
     @to_string = ttm.require("lib/math/expression_to_string").toString
     @to_html_string = ttm.require("lib/math/expression_to_string").toHTMLString
-    @builder = ttm.require("lib/math/build_expression_from_javascript_object").buildExpression
+
+    builder_lib = ttm.require('lib/math/build_expression_from_javascript_object')
+    @builder = builder_lib.build(@comps).builderFunction()
+
   it "converts a simple addition expression", ->
     expect(@to_string(@builder(1, '+', 2))).toEqual "1 + 2"
-

   it "correctly displays a complicated decimal number", ->
     expect(@to_string(@builder(Math.PI))).toEqual "3.1416"


delete square marks on mouseup

delete square marks on mouseup

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -183,6 +183,11 @@
     return null if ! @state.uncommittedMark?

     mark = @state.uncommittedMark
+
+    # if this mark is square, delete it
+    if mark.width == mark.height
+      @setUncommittedMark null
+      return

     # Instantiate appropriate marking tool:
     # AMS: think this is going to markingTools[mark._toolIndex]


Datepicker cleanup   * Fixed spacing issues   * Removed Locale stuff   * Added dateFormat   * Fixed first/last day of week loop

--- app/assets/javascripts/exo/views/datepicker.js.coffee
+++ app/assets/javascripts/exo/views/datepicker.js.coffee
@@ -4,6 +4,7 @@
     hidden: false
     autoRender: true
     headerFormat: "MMMM YYYY"
+    dateFormat: "MM/DD/YYYY"
     firstDayOfWeek: 1
     linkTemplate: null

@@ -13,15 +14,15 @@
       collision: 'fit'

     events:
-      "mousedown .datepicker-prev": (e) ->
+      "mouseup .datepicker-prev": (e) ->
         e.preventDefault()
         @prev()

-      "mousedown .datepicker-next": (e) ->
+      "mouseup .datepicker-next": (e) ->
         e.preventDefault()
         @next()

-      "mousedown .day": (e) ->
+      "mouseup .day": (e) ->
         e.preventDefault()
         @select(e.target.getAttribute("data-datestamp"))

@@ -55,13 +56,14 @@

       @monthHeader = $('
' + @now.format(@headerFormat) + '
')

-      dayLabels = _.map(Locale.get('Date.days_abbr'), (str) -> '' + str.substr(0, 1) + '')
+      dayLabels = _.map(["Sun", "Mon", "Tue", "Wed", "Thurs", "Fri", "Sat"], (str) -> '' + str.substr(0, 1) + '')
       dayLabels = dayLabels.concat(dayLabels.splice(0, @firstDayOfWeek))
-
-      $(document.createElement("div")).addClass('datepicker-header-inner').append(prevBtn)
+      $(document.createElement("div"))
+            .addClass('datepicker-header-inner')
+            .append(prevBtn)
             .append(this.monthHeader)
             .append(nextBtn)
-            .append('
'+ dayLabels.join('') +'
')
+            .append('
' + dayLabels.join('') + '
')
             .appendTo(@$header)

       @$header
@@ -80,12 +82,12 @@
       @range[0] = moment(d)
       @$body.empty()

-      for j in [0..6]
-        for i in [0..7]
+      for j in [0..5]
+        for i in [0..6]
           classes = ['day']

           classes.push('first') if i == 0
-          classes.push('last')  if i ==6
+          classes.push('last')  if i == 6

           if @now.month() != d.month()
             classes.push('not-in-month')
@@ -94,11 +96,11 @@
             classes.push('today')

            E = $(document.createElement('a')).attr({
-            'data-datestamp': d.format('YYYY-MM-DD'),
+            'data-datestamp': d.format(@dateFormat),
             'class': classes.join(' ')
           }).html(d.date())

-          dE.appendTo(@$body)
+          E.appendTo(@$body)
           d.add('days', 1)

       @range[1] = d.clone()
@@ -134,12 +136,12 @@

       options = _.extend({ silent: false, toggle: true }, options)

-      if date.format('YYYY-MM-DD') != @now.format('YYYY-MM-DD')
+      if date.format(@dateFormat) != @now.format(@dateFormat)
         @showDate(date)

       @clear() if(options.toggle)

-      elem = @$body.find('[data-datestamp="'+ date.format('YYYY-MM-DD') +'"]').addClass('highlight')
+      elem = @$body.find('[data-datestamp="' + date.format(@dateFormat) + '"]').addClass('highlight')

       if !options.silent
         @trigger('select', date, elem)