add inaccessibility message for record video tinymce button

test_plan:
* using a screen reader, navigate to the video record
button in the tinymce editor
* should hear a message that the feature is inaccessible

closes #CNVS-1529

Change-Id: I555d2abaf09b25da2198d8198ff5c6db6578f98f
Reviewed-on: https://gerrit.instructure.com/17958
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Bracken Mosbacker <48b846001bca4166dc3fb7711563afc17525921e@instructure.com>
QA-Review: Adam Phillipps <0e18f44c1fec03ec4083422cb58ba6a09ac4fb2a@instructure.com>

--- app/coffeescripts/editor/editorAccessibility.coffee
+++ app/coffeescripts/editor/editorAccessibility.coffee
@@ -24,5 +24,12 @@
       @$el.find("##{@id_prepend}_forecolor_voice").text(I18n.t('accessibles.forecolor',"Text Color, press down to select"))
       @$el.find("##{@id_prepend}_backcolor_voice").text(I18n.t('accessibles.background_color',"Background Color, press down to select"))

+      @$el.find("##{@id_prepend}_instructure_record").attr('aria-disabled', 'true')
+      @$el.find("##{@id_prepend}_instructure_record").removeAttr('role')
+      @$el.find("##{@id_prepend}_instructure_record_voice").append('<br/>').append(I18n.t('accessibles.record', 'This feature is inaccessible for screen readers.'))
+      @$el.find("##{@id_prepend}_instructure_record img").attr('alt',
+        @$el.find("##{@id_prepend}_instructure_record img").attr('alt') + ", " + I18n.t('accessibles.record', 'This feature is inaccessible for screen readers.'));
+      debugger
+
     _addTitles: ->
       @$iframe.attr 'title', I18n.t('titles.rte_help', 'Rich Text Area. Press ALT F10 for toolbar. Press ALT 0 for help.')


Tests:Helpers: Updated waitForVMRunning helper.

--- tests/src/helpers/helpers.coffee
+++ tests/src/helpers/helpers.coffee
@@ -46,11 +46,15 @@
       .click                  '[testpath=login-button]'
       .pause                  5000

-  waitForVMRunning: (browser) ->
-    vmSelector           = '.vm.running.koding'
-    modalSelector        = '.env-modal.env-machine-state'
-    loaderSelector       = modalSelector + ' .kdloader'
-    buildingLabel        = modalSelector + ' .state-label.building'
+  waitForVMRunning: (browser, machineName) ->
+    vmSelector = '.vm.running.koding'
+
+    if machineName
+      vmSelector   = '[href="/IDE/'+machineName+'/my-workspace"].running.vm'
+
+    modalSelector  = '.env-modal.env-machine-state'
+    loaderSelector = modalSelector + ' .kdloader'
+    buildingLabel  = modalSelector + ' .state-label.building'
     turnOnButtonSelector = modalSelector + ' .turn-on.state-button'

     browser.element 'css selector', vmSelector, (result) =>


Cheat and use GitUtils.add.

Cheat and use GitUtils.add.

--- lib/git-bridge.coffee
+++ lib/git-bridge.coffee
@@ -186,20 +186,7 @@
       callback(new GitNotFoundError)

   @add: (repo, filepath, callback) ->
-    return unless @_checkHealth(callback)
-
-    @process({
-      command: GitCmd,
-      args: ['add', filepath],
-      options: { cwd: repo.getWorkingDirectory() },
-      stdout: (line) -> console.log line
-      stderr: (line) -> console.log line
-      exit: (code) ->
-        if code is 0
-          callback()
-        else
-          callback(new Error("git add failed: exit code #{code}"))
-    })
+    repo.repo.add(filepath)

   @isRebasing: ->
     return unless @_checkHealth (e) ->


Add getPlayer function to controller.

Add getPlayer function to controller.

--- server/bw/battle_controller.coffee
+++ server/bw/battle_controller.coffee
@@ -7,6 +7,10 @@
   constructor: (@battle) ->
     conditions.attach(this)
     @battle.emit('initialize')
+
+  # Returns the player associated with an id
+  getPlayer: (playerId) ->
+    @battle.getPlayer(playerId)

   # Returns all the player ids participating in this battle.
   getPlayerIds: ->


add gitlab commit messages to the message

--- src/scripts/gitlab.coffee
+++ src/scripts/gitlab.coffee
@@ -82,7 +82,8 @@
           if /^0+$/.test(hook.before)
             message = "#{bold(hook.user_name)} pushed a new branch (#{bold(branch)}) to #{bold(hook.repository.name)} #{underline(hook.repository.homepage)}"
           else
-            message = "#{bold(hook.user_name)} pushed #{bold(hook.total_commits_count)} commits to #{bold(branch)} in #{bold(hook.repository.name)} #{underline(hook.repository.homepage + '/compare/' + hook.before.substr(0,9) + '...' + hook.after.substr(0,9))}"
+            message = "#{bold(hook.user_name)} pushed to #{branch} at #{hook.repository.name} (#{underline(hook.repository.homepage + '/compare/' + hook.before.substr(0,9) + '...' + hook.after.substr(0,9))})"
+            message += "\n" + hook.commits.map((commit) -> commit.message).join("\n")
           robot.send user, message
         # not code? must be a something good!
         else


fix for locpix script

fix for locpix script

--- src/scripts/locpix.coffee
+++ src/scripts/locpix.coffee
@@ -16,11 +16,10 @@
 module.exports = (robot) ->
   robot.respond /locpix?(?: me)? (.*)/i, (msg) ->
     q = escape(msg.match[1])
-    msg.http('http://www.loc.gov/pictures/search/?fo=json&q=' + q )
+    msg.http('http://www.loc.gov/pictures/search/?fo=json&fa=displayed:anywhere&q=' + q )
       .get() (err, res, body) ->
         response = JSON.parse(body)
         images = response.results
         if images.length > 0
           image  = msg.random images
-          image  = msg.random images while (/500x500_TGM/.test(image.image.full) or /500x500_look/.test(image.image.full) or /500x500_grouprecord/.test(image.image.full) or /500x500_notdigitized/.test(image.image.full))
           msg.send image.image.full


Add reaction importing and allow  support

--- packages/rocketchat-importer-slack/server.coffee
+++ packages/rocketchat-importer-slack/server.coffee
@@ -296,6 +296,14 @@
 													msgObj.ets = new Date(parseInt(message.edited.ts.split('.')[0]) * 1000)

 												RocketChat.sendMessage @getRocketUser(message.user), msgObj, room, true
+
+									# Process the reactions
+									if RocketChat.models.Messages.findOneById(msgDataDefaults._id)? and message.reactions?.length > 0
+										for reaction in message.reactions
+											for u in reaction.users
+												if @getRocketUser(u)?
+													Meteor.call 'setReaction', ":#{reaction.name}:", createdMsg._id
+
 									@addCountCompleted 1
 			console.log missedTypes
 			@updateProgress Importer.ProgressStep.FINISHING
@@ -322,6 +330,7 @@
 		if message?
 			message = message.replace /<!everyone>/g, '@all'
 			message = message.replace /<!channel>/g, '@all'
+			message = message.replace /<!here>/g, '@here'
 			message = message.replace />/g, '<'
 			message = message.replace /</g, '>'
 			message = message.replace /&/g, '&'


More safety

More safety

--- components/ToFullscale.coffee
+++ components/ToFullscale.coffee
@@ -27,7 +27,7 @@
   fullUrl = url.replace /[-_](small|thumbnail|thumb)/, ''
   # Verify that it exists
   superagent.head fullUrl
-  .end (err, res) =>
+  .end (err, res) ->
     return callback err if err
     newUrl = fullUrl if res and res.statusCode is 200
     out.beginGroup url
@@ -55,6 +55,8 @@
     newUrl = url
     unless url
       return callback new Error 'No image source provided'
+    unless typeof url is 'string'
+      return callback new Error 'URL is not a string'

     if url.indexOf('staticflickr.com') isnt -1
       newUrl = convertFlickr url, callback


Remove console

Remove console

--- apps/home_2/components/followed_artists/view.coffee
+++ apps/home_2/components/followed_artists/view.coffee
@@ -16,7 +16,6 @@

   initialize: ({ @module, @$el, @user }) ->
     # no op
-    console.log 'user', @user

   render: ->
     artists = new Backbone.Collection @module.context.artists


finish implemenation

finish implemenation

--- src/shift-messages.coffee
+++ src/shift-messages.coffee
@@ -2,6 +2,10 @@

 class ShiftMessages extends ReturnValue
   onEnvelope: (envelope) =>
-    return envelope.message
+    { data } = envelope
+
+    message = data.shift()
+
+    return message

 module.exports = ShiftMessages


adjust

adjust

--- config/env/development.coffee
+++ config/env/development.coffee
@@ -33,14 +33,14 @@
   db: 'redis-sailor-twitter-backend',
   host: 'pub-redis-16675.eu-west-1-1.1.ec2.garantiadata.com',
   port: 16675,
-  pass: SAILS_SOCKET_PASSWORD
+  pass: process.env.SAILS_SOCKET_PASSWORD

 session:
   adapter: 'redis'
   db: 'redistogo'
   host: 'jack.redistogo.com'
   port: 10816
-  pass: SAILS_SESSION_PASSWORD
+  pass: process.env.SAILS_SESSION_PASSWORD
   ttl: 150
   prefix: 'sess:'


Redirect '/' to '/#/'

Redirect '/' to '/#/'

--- source/assets/javascripts/application.coffee
+++ source/assets/javascripts/application.coffee
@@ -29,3 +29,7 @@
 #= require directives/slides
 #= require directives/slide
 #= require directives/ytVideo
+
+jQuery ($) ->
+  if window.location.hash is ''
+    window.location = '/#/'


Toggling logic works now.

Toggling logic works now.

--- source/javascripts/games.js.coffee
+++ source/javascripts/games.js.coffee
@@ -30,6 +30,10 @@
     $(".tournament-types .#{this}").removeClass('inactive')
   if config.allTypes()
     $('.tournament-types .all.toggleable').removeClass('inactive')
+  else
+    $('#games-chart .tournament').hide()
+    $.each config.activeTypes, ->
+      $("#games-chart .#{this}").show()

   $('.opponents .toggleable').addClass('inactive')
   if config.activeOpponents is 'all'


Wtf semicolons

Wtf semicolons

--- source/javascripts/suite.js.coffee
+++ source/javascripts/suite.js.coffee
@@ -3,8 +3,8 @@
   # fix for finding focused elements in an inactive window
   jQuery.expr[":"].absfocus = jQuery.expr.createPseudo ->
     return (elem) ->
-      doc = elem.ownerDocument;
-      elem == doc.activeElement && !!(elem.type || elem.href);
+      doc = elem.ownerDocument
+      elem == doc.activeElement && !!(elem.type || elem.href)

   $.fn.selectorText = () ->
     selector = "%#{@[0].tagName.toLowerCase()}"
@@ -41,7 +41,7 @@

   $.fn.pressKey = (k, msg) ->
     if (msg)
-      ok(true, "I press " + msg);
+      ok(true, "I press " + msg)
     $e = $.Event('keydown')
     $e.keyCode = $e.which = k
     $(@).trigger($e)
@@ -238,7 +238,7 @@
       afterLoad: ->
         start()
         equal $.trim(tester.data().$modal.text()), "I'm a cool remote modal!", 'modal loaded successfully'
-    });
+    })
     $trigger.click()
     tester.verifyVisible()

@@ -247,7 +247,7 @@
     $trigger = tester.init({
       afterLoad: ->
         start()
-    });
+    })
     $trigger.click()
     equal tester.data().$modal.find('article').length, 1, 'article is appended to modal'
     equal tester.data().$modal.find('article img').length, 1, 'image is appended to article'
@@ -259,7 +259,7 @@
       afterLoad: ->
         start()
         equal $.trim(tester.data().$modal.text()), "I'm a cool remote modal!", 'modal loaded successfully'
-    }, $('.remote_load'));
+    }, $('.remote_load'))
     $trigger.find('a').click()
     tester.verifyVisible()

@@ -332,4 +332,4 @@
     equal wdz.container, '#foo', 'container is set to #foo'
     equal wdz.animation, 'pop-down', 'animation is set to pop-down'

-)(jQuery)+)(jQuery)


updated the client to handle the new format

--- views/coffeescript/main.coffee
+++ views/coffeescript/main.coffee
@@ -23,13 +23,13 @@

       particleSystem.eachNode (node, point) ->
         w = 80
-        ctx.fillStyle = if node.data.id is 0 then "blue" else "green"
+        ctx.fillStyle = if node.name is 0 then "blue" else "green"
         util.roundRect(ctx, point.x-w/2, point.y-w/2, w, w, 10, 'fill')

         ctx.strokeStyle = 'white'
         ctx.lineWidth = 2
-        ctx.font = "14pt Times"
-        ctx.strokeText(node.data.text, point.x-w/2+10, point.y, w*2, 'fill')
+        ctx.font = "12pt Times"
+        ctx.strokeText(node.data, point.x-w/2+10, point.y, w*2)

     initMouseHandling: ->
       # no-nonsense drag and drop (thanks springy.js)
@@ -78,26 +78,29 @@
 class Space
   constructor: (@sys) ->
     @nodes = {}
+    @rels = {}
+    @view = (data) ->
+      for key, value of data
+        console.log "#{key}: #{value}"
+        "#{key}: #{value}"

-  addNode: (node) ->
-    @sys.addNode(@id(node), @props(node)) unless @nodes[@id(node)]
-    @nodes[@id(node)] = 'yes'
-    rels = node['rels']
+  setView: (@view) ->
+
+  addData: (data) ->
+    @addNodes data.nodes
+    @addRels data.rels
+
+  addNodes: (nodes) ->
+    for node in nodes
+      @sys.addNode(node.id, @props(node)) unless @nodes[node.id]
+      @nodes[node.id] = node
+  addRels: (rels) ->
     for rel in rels
-      other_node = rel['other_node']
-      unless @nodes[@id(other_node)]
-        @sys.addNode(@id(other_node), @props(other_node))
-        @sys.addEdge(@id(node), @id(other_node), @props(rel))
-        @nodes[@id(other_node)] = 'no'
+      @sys.addEdge(rel.start_node, rel.end_node, @props(rel)) unless @rels[rel.id]
+      @rels[rel.id] = rel

-  id: (node) ->
-    node['_neo_id']
-
-  props: (node) ->
-    {
-      id: @id(node),
-      text: "id: #{@id(node)}"
-    }
+  props: (obj) ->
+    @view(obj.data)

@@ -107,42 +110,54 @@
   sys.renderer = Renderer("#viewport") # our newly created renderer will have its .init() method called shortly by sys...

   data = {
-    "_neo_id":0,
-    "gemFile": "source :rubygems\ngem 'jruby-openssl'\ngem 'neo4j' #, :path : '/home/andreas/projects/neo4j'\ngem 'birdies-backend', :git : 'git://github.com/andreasronge/birdies-backend.git'   #:path : '/home/andreas/projects/birdies-backend'\n",
+    "nodes": [
+      {"data": {
+        "_neo_id":0,
+        "gemFile": "source :rubygems\ngem",
+        "id":0
+      }},
+      {"data":{"_count__all__classname":53, "_neo_id":5}, "id":5},
+      {"data":{"_count__all__classname":148, "_neo_id":1}, "id":1},
+      {"data":{"_count__all__classname":10, "_neo_id":2}, "id":2},
+      {"data":{"_count__all__classname":1, "_neo_id":6}, "id":6},
+      {"data":{"_count__all__classname":65, "_neo_id":4}, "id":4},
+      {"data":{"_count__all__classname":19, "_neo_id":3}, "id":3}
+    ],
     "rels": [
       {
-        "_neo_id":4,
-        "direction":"outgoing",
-        "other_node":{"_count__all__classname":53, "_neo_id":5},
-        "rel_type":"BirdiesBackend::User"},
+        "data":{"_neo_id":4, "rel_type":"BirdiesBackend::User"},
+        "end_node":5,
+        "id":4,
+        "start_node":0},
       {
-        "_neo_id":0,
-        "direction":"outgoing",
-        "other_node":{"_count__all__classname":148, "_neo_id":1},
-        "rel_type":"Neo4j::Rails::Model"},
+        "data":{"_neo_id":0, "rel_type":"Neo4j::Rails::Model"},
+        "end_node":1,
+        "id":0,
+        "start_node":0},
       {
-        "_neo_id":1,
-        "direction":"outgoing",
-        "other_node":{"_count__all__classname":10, "_neo_id":2},
-        "rel_type":"BirdiesBackend::Link"},
+        "data":{"_neo_id":1, "rel_type":"BirdiesBackend::Link"},
+        "end_node":2,
+        "id":1,
+        "start_node":0},
       {
-        "_neo_id":5,
-        "direction":"outgoing",
-        "other_node":{"_count__all__classname":1, "_neo_id":6},
-        "rel_type":"BirdiesBackend::Tweeters"},
+        "data":{"_neo_id":5, "rel_type":"BirdiesBackend::Tweeters"},
+        "end_node":6,
+        "id":5,
+        "start_node":0},
       {
-        "_neo_id":3,
-        "direction":"outgoing",
-        "other_node":{"_count__all__classname":65, "_neo_id":4},
-        "rel_type":"BirdiesBackend::Tweet"},
+        "data":{"_neo_id":3, "rel_type":"BirdiesBackend::Tweet"},
+        "end_node":4,
+        "id":3,
+        "start_node":0},
       {
-        "_neo_id":2,
-        "direction":"outgoing",
-        "other_node":{"_count__all__classname":19, "_neo_id":3},
-        "rel_type":"BirdiesBackend::Tag"}]}
-
+        "data":{"_neo_id":2, "rel_type":"BirdiesBackend::Tag"},
+        "end_node":3,
+        "id":2,
+        "start_node":0}
+    ]
+  }

   space = new Space(sys)
-  space.addNode(data)
+  space.addData(data)


Updated indentation

Updated indentation

--- src/suites/browser.coffee
+++ src/suites/browser.coffee
@@ -3,17 +3,17 @@

   detectr.defineSuite 'browser',
     'browser-chrome':
-        run: -> contains detectr.Browser.get(), 'chrome'
-        result: 'browser-chrome'
-      'browser-firefox':
-        run: -> contains detectr.Browser.get(), 'firefox'
-        result: 'browser-firefox'
-      'browser-ie':
-        run: -> contains detectr.Browser.get(), 'msie'
-        result: 'browser-ie'
-      'browser-safari':
-        run: -> (contains detectr.Browser.get(), 'safari') and not (contains detectr.Browser.get(), 'chrome')
-        result: 'browser-safari'
-      'browser-opera':
-        run: -> contains detectr.Browser.get(), 'opera'
-        result: 'browser-opera'+      run: -> contains detectr.Browser.get(), 'chrome'
+      result: 'browser-chrome'
+    'browser-firefox':
+      run: -> contains detectr.Browser.get(), 'firefox'
+      result: 'browser-firefox'
+    'browser-ie':
+      run: -> contains detectr.Browser.get(), 'msie'
+      result: 'browser-ie'
+    'browser-safari':
+      run: -> (contains detectr.Browser.get(), 'safari') and not (contains detectr.Browser.get(), 'chrome')
+      result: 'browser-safari'
+    'browser-opera':
+      run: -> contains detectr.Browser.get(), 'opera'
+      result: 'browser-opera'


Removing show tips from startGame()

Removing show tips from startGame()

--- web/scripts/gameManager.coffee
+++ web/scripts/gameManager.coffee
@@ -65,7 +65,7 @@
 #        player = @environment.player
 #        if(! player.games[@environment.key].seenTips)
 #            player.games[@environment.key].seenTips = true
-        @helpTips()
+#        @helpTips()
         return

     interpretGameConfigMap: ->


Adding some conditional stubs for the future.

--- src/toaster/config.coffee
+++ src/toaster/config.coffee
@@ -147,6 +147,12 @@
       else
           browser.amd.base_url = ''

+    else if browser.cjs
+      null
+    else
+      null
+      # validate browser.none?.props
+
     # ...: server - optional
     if server?


Indent code

Indent code

--- src/tools/junction.coffee
+++ src/tools/junction.coffee
@@ -27,5 +27,5 @@
 @exec = (folder, optional)->
   return if optional and fs.FolderExists link
   sh.exec """
-"#{dst()}" "#{link}" "#{fs.BuildPath install2, folder}"
+    "#{dst()}" "#{link}" "#{fs.BuildPath install2, folder}"
   """


Added method Enumerable#eachWithIndex

Added method Enumerable#eachWithIndex

--- lib/ST/Enumerable.coffee
+++ lib/ST/Enumerable.coffee
@@ -56,6 +56,11 @@
         found = false
         'break'
     found
+
+  # Runs callback for each item, with index of item in second argument
+  @method 'eachWithIndex', (fn) ->
+    i = 0
+    @each (item) -> fn.call this, item, i++

   # Returns a new list with callback function applied to each item
   @method 'map', (fn=null) ->


Simplify base presenter; Flip confusing conditionals

--- lib/presenters.coffee
+++ lib/presenters.coffee
@@ -5,28 +5,12 @@
     @response = response
     @predictions = predictions

+  buildPredictionList: ->
+    @formatPrediction(prediction) for prediction in @predictions
+
   formatPrediction: (prediction) ->
     # Example implimentation
-    "#{prediction.stopName}: #{prediction.estimate}"
-
-  generateTitle: ->
-    # Example implimentation
-    "Predictions:"
-
-  generateBody: ->
-    # Example implimentation
-    (@formatPrediction(prediction) for prediction in @predictions)
-
-  respond: ->
-    # Returns [success, response], where
-    # - success:
-    #   true or false
-    #
-    # - response:
-    #   A string representation to return to the client
-    #
-    # Example implimentation
-    [true, @generateBody()]
+    "#{prediction.stop.stopName}: #{prediction.prediction.minutes}"

 class WebPresenter extends Presenter
   formatPrediction: (prediction) ->
@@ -34,37 +18,37 @@
     estimate: prediction.prediction.minutes
     percentComplete: helpers.calculatePercentComplete(prediction.prediction.minutes)

-  generateTitle: ->
+  buildGeneralContext: ->
     routeNumber: @predictions[0].route.number
     routeDirection: @predictions[0].route.direction
     busNumber: @predictions[0].busNumber

   respond: ->
-    unless @predictions.length > 0
-        @response.status(404)
-        @response.render 'error', {msg: 'No predictions available.'}
-    else
-      context = @generateTitle()
-      context.predictions = @generateBody()
+    if @predictions.length > 0
+      context = @buildGeneralContext()
+      context.predictions = @buildPredictionList()
       @response.locals = context
       @response.render 'results', partials: {prediction: '_prediction'}
+    else
+      @response.status(404)
+      @response.render 'error', {msg: 'No predictions available.'}

 class SMSPresenter extends Presenter
   formatPrediction: (prediction) ->
     "In #{prediction.prediction.minutes}m: #{helpers.trimStopName prediction.stop.stopName}"

-  generateTitle: ->
+  getTitle: ->
     "Rt #{@predictions[0].route.number}:"

   respond: ->
-    unless @predictions.length > 0
-      @response.send 200, "No predictions available."
-    else
-      message = @generateBody()
-      message.unshift @generateTitle()
+    if @predictions.length > 0
+      message = @buildPredictionList()
+      message.unshift @getTitle()
       message = message.join "\n"
       truncatedMessage = message.substring 0, 160
       console.warn "Outgoing message > 160 chars (#{message.length})" if truncatedMessage isnt message
       @response.send 200, truncatedMessage
+    else
+      @response.send 200, "No predictions available."

 module.exports = {WebPresenter, SMSPresenter}


corrected documentation

corrected documentation

--- src/zombie/browser.coffee
+++ src/zombie/browser.coffee
@@ -446,7 +446,7 @@
     # Unselects an option.
     #
     # * selector -- CSS selector, field name or text of the field label
-    # * value -- Value (or label) or option to select
+    # * value -- Value (or label) or option to unselect
     #
     # Returns this
     this.unselect = (selector, value)->


Improved readbility of the conditional

Improved readbility of the conditional

--- src/zombie/cookies.coffee
+++ src/zombie/cookies.coffee
@@ -77,15 +77,15 @@
       httpHeader = httpHeader.join(",")
     for cookie in httpHeader.split(/,(?=[^;,]*=)|,$/)
       cookie = Cookie.parse(cookie)
-      continue unless cookie?
-      cookie.domain ||= domain
-      cookie.path   ||= Tough.defaultPath(path)
-      # Delete cookie before setting it, so we only store one cookie (per
-      # domain/path/name)
-      deleteIfExists = @filter((c)-> c.key == cookie.key && c.domain == cookie.domain && c.path == cookie.path)[0]
-      @delete(deleteIfExists)
-      if cookie.TTL() > 0
-        @push(cookie)
+      if cookie
+        cookie.domain ||= domain
+        cookie.path   ||= Tough.defaultPath(path)
+        # Delete cookie before setting it, so we only store one cookie (per
+        # domain/path/name)
+        deleteIfExists = @filter((c)-> c.key == cookie.key && c.domain == cookie.domain && c.path == cookie.path)[0]
+        @delete(deleteIfExists)
+        if cookie.TTL() > 0
+          @push(cookie)
     return


Encode user and escape paths

Encode user and escape paths

--- lib/sshfs-node.coffee
+++ lib/sshfs-node.coffee
@@ -37,7 +37,7 @@

   userOption = ''
   if options && options.user
-    userOption = util.format '%s@', options.user
+    userOption = util.format '%s@', encodeURI(options.user)

   cacheOption = '-o cache=yes'
   if options && options.cache == false
@@ -47,8 +47,8 @@
   if options && options.port
     portOption = util.format '-o port=%d', options.port

-  # sshfs -o IdentityFile=~/.ssh/id_rsa user@localhost:/ ~/mnt/localhost_mount/
-  command = util.format 'sshfs %s %s %s -o StrictHostKeyChecking=no %s%s:/ %s', identityOption, cacheOption, portOption, userOption, host, mountpoint
+  # sshfs -o IdentityFile=~/.ssh/id_rsa user@localhost:"/" "~/mnt/localhost_mount/"
+  command = util.format 'sshfs %s %s %s -o StrictHostKeyChecking=no %s%s:"/" "%s"', identityOption, cacheOption, portOption, userOption, host, mountpoint
   sshfs.exec command, callback

 ###*


no reload for code block setting change

no reload for code block setting change

--- lib/style-text.coffee
+++ lib/style-text.coffee
@@ -8,7 +8,7 @@
   italic: before: "_", after: "_"
   keystroke: before: "<kbd>", after: "</kbd>"
   strikethrough: before: "~~", after: "~~"
-  codeblock: config.get("codeblock")
+  codeblock: -> config.get("codeblock")

 module.exports =
 class StyleText
@@ -17,6 +17,7 @@

   constructor: (style) ->
     @style = styles[style]
+    @style = @style() if typeof styles[style] == "function"

   display: ->
     @editor = atom.workspace.getActiveEditor()


Less buggy transposing method.

Less buggy transposing method.

--- static/coffee/song.coffee
+++ static/coffee/song.coffee
@@ -58,14 +58,14 @@
         .off()
         .on 'change', ->

-            val = parseInt($(this).val(), 10)
+            val = parseInt $(this).val(), 10
             increment = val - previous
             previous = val

             $('#song').fadeOut FADE_TIME, ->

                 $ '.verse.chords'
-                    .each (i) ->
+                    .each ->
                         line = $(@).text()
                         line = transposeLine line, increment
                         $(@).html line
@@ -107,6 +107,7 @@
             $ '.verse.chords, .verse.lyrics'
                 .fadeIn FADE_TIME

+        # Show lyrics
         # Hide chords
         when 1
             $ '.verse.chords'
@@ -117,14 +118,14 @@

 changeTheme = (url) ->

-    # Make sure the value stored in localStorage actually is a theme we can use
-    # and 'select' the theme in the dropdown box.
+    # Make sure the value stored in localStorage actually is a theme we can use.
     el = $('option', '#themeSelector').filter(->
         $(this).val() is url
     )[0]

     return unless el?

+    # Set it as the selected value.
     $ el
         .attr 'selected', true

@@ -135,14 +136,7 @@

 transposeLine = (line, increment) ->
-    splitted = line.trim().split /\s+/
-
-    $.each splitted, (i) ->
-        original = @
-        transposed = transposeChord original, increment
-        line = line.replace original, transposed
-
-    line
+    line.replace /\S+/g, (match) -> transposeChord match, increment

 # This method is based mostly off of this SO answer http://stackoverflow.com/a/7936871.


adds mapping to pull are.na channel into contract view

--- maps/contracts.coffee
+++ maps/contracts.coffee
@@ -4,56 +4,13 @@
     "abstract": "Autonomous Killer Robots have been described as the third revolution in warfare, after gunpowder and nuclear arms. Armed unmanned quadcopters, all-terrain vehicles, submarmines, or androids, for example, they use Artificial Intelligence (AI) technology to select targets based on pre-defined criteria and engage them without human intervention, often with the intent to kill. Insure our saftey from this future.",
     "contract": "KBOT",
     "title": "Autonomous Killer Robots Immunity"
+    "channel_id": 32844
   SDRV:
     "image": "googlecar.jpg",
     "abstract": "Cars that drive themselves, using sensors to perceive and interpret the environment around them to navigate our existing roads, could be safer and more energy and time efficient, allowing commuters to perform other tasks while in transit. They would increase mobility for those who can't drive themselves and would transform other transport functions, such as long-distance trucking, and infrastructures, like traffic lights.",
     "contract": "SDRV",
     "title": "Self-Driving Vehicles"
-  GENC:
-    "image": "Dewey-Hagborg_face.jpg",
-    "abstract": "Your body is constantly shedding information that can be traced back to you. Whereas fingerprints allowed for unique identification, today hair, semen and other DNA-carrying organic samples can be developed into 3D recreations of your physical form, opening the door to a new surveilance techniques. Genetic Cloaking is a form of counter-bio-surveilance that masks your genetic identity.",
-    "contract": "GENC",
-    "title": "Genetic Cloaking"
-  SMRT:
-    "image": "",
-    "abstract": "As more and more of the objects around us begin to have embedded computers and connect to the Internet and one another, they will vacuum up data about every aspect of our lives and send it to the companies and governments that monitor them. The Smart Things Code of Conduct is a set of laws to regulate the collection, transfer and use of this data.",
-    "contract": "SMRT",
-    "title": "Smart Things Code of Conduct"
-  BSIC:
-    "image": "",
-    "abstract": "",
-    "contract": "BSIC",
-    "title": "Basic Universal Income"
-  FRED:
-    "image": "",
-    "abstract": "",
-    "contract": "FRED",
-    "title": "Free College Education"
-  PROD:
-    "image": "",
-    "abstract": "More information leads to more anxiety. The more connected we are, the more we are expected to know at all times, and the more our connection anxiety grows. Legalizing chemical doping–drugs–is one way to make this anxiety bareable while maintaining our productivity, from the track to the field to the cubicle to the boardroom.",
-    "contract": "PROD",
-    "title": "Legal Professional Doping"
-  DRTY:
-    "image": "",
-    "abstract": "",
-    "contract": "DRTY",
-    "title": "Direct Democracy"
-  DIGU:
-    "image": "",
-    "abstract": "",
-    "contract": "DIGU",
-    "title": "Digital Worker Union"
-  KBOT:
-    "image": "bigdog.jpg",
-    "abstract": "Autonomous Killer Robots have been described as the third revolution in warfare, after gunpowder and nuclear arms. Armed unmanned quadcopters, all-terrain vehicles, submarmines, or androids, for example, they use Artificial Intelligence (AI) technology to select targets based on pre-defined criteria and engage them without human intervention, often with the intent to kill. Insure our saftey from this future.",
-    "contract": "KBOT",
-    "title": "Autonomous Killer Robots Immunity"
-  SDRV:
-    "image": "googlecar.jpg",
-    "abstract": "Cars that drive themselves, using sensors to perceive and interpret the environment around them to navigate our existing roads, could be safer and more energy and time efficient, allowing commuters to perform other tasks while in transit. They would increase mobility for those who can't drive themselves and would transform other transport functions, such as long-distance trucking, and infrastructures, like traffic lights.",
-    "contract": "SDRV",
-    "title": "Self-Driving Vehicles"
+    "channel_id": 32845
   GENC:
     "image": "Dewey-Hagborg_face.jpg",
     "abstract": "Your body is constantly shedding information that can be traced back to you. Whereas fingerprints allowed for unique identification, today hair, semen and other DNA-carrying organic samples can be developed into 3D recreations of your physical form, opening the door to a new surveilance techniques. Genetic Cloaking is a form of counter-bio-surveilance that masks your genetic identity.",


formview lazydom adjustment

formview lazydom adjustment

--- client/Framework/components/forms/KDFormView.coffee
+++ client/Framework/components/forms/KDFormView.coffee
@@ -12,17 +12,21 @@

     return inputs

+
   ###
   INSTANCE LEVEL
   ###
   constructor:(options = {}, data)->

+    options.tagName      = "form"
+    options.cssClass     = KD.utils.curryCssClass "kdformview", options.cssClass
     options.callback   or= noop     # a Function
     options.customData or= {}       # an Object of key/value pairs
     options.bind       or= "submit" # a String of space separated event names

     super options,data

+    @unsetClass "kdview"
     @valid = null
     @setCallback options.callback
     @customData = {}
@@ -32,10 +36,6 @@
     @emit 'inputWasAdded', child  if child instanceof KDInputView

     super
-
-  setDomElement:()->
-    cssClass = @getOptions().cssClass ? ""
-    @domElement = $ "<form class='kdformview #{cssClass}'></form>"

   getCustomData:(path)->
     if path


activity: deprecate unnecessary variable

--- client/Social/Activity/views/activityactions.coffee
+++ client/Social/Activity/views/activityactions.coffee
@@ -6,7 +6,6 @@

     super options, data

-    activity = @getData()

     @commentLink  = new CustomLinkView title: "Comment"

@@ -17,7 +16,7 @@
       click       : (event) =>
         KD.utils.stopDOMEvent event
         @getDelegate().emit "CommentCountClicked", this
-    , activity
+    , data

     @shareLink = new CustomLinkView
       title : "Share"
@@ -44,7 +43,7 @@

         KD.mixpanel "Activity share, click"

-    @likeLink = new ActivityLikeLink null, activity
+    @likeLink = new ActivityLikeLink null, data

     @loader = new KDLoaderView
       cssClass      : 'action-container'
@@ -56,7 +55,6 @@

   attachListeners: ->

-    activity    = @getData()
     commentList = @getDelegate()

     commentList.on 'BackgroundActivityStarted',  @loader.bound 'show'


Client: Activity: Feed: Added new action types for feed messages search

--- client/activity/lib/flux/actions/actiontypes.coffee
+++ client/activity/lib/flux/actions/actiontypes.coffee
@@ -155,6 +155,11 @@
   'UPDATE_CHANNEL_FAIL'
   'UPDATE_CHANNEL_SUCCESS'

+  'SET_CHANNEL_MESSAGES_SEARCH_QUERY'
+  'CHANNEL_MESSAGES_SEARCH_BEGIN'
+  'CHANNEL_MESSAGES_SEARCH_FAIL'
+  'CHANNEL_MESSAGES_SEARCH_SUCCESS'
+
   'SET_CHANNEL_RESULT_STATE'

   'SOCIAL_SHARE_LINK_CREATED'


Client: Activity: Added EDIT_MESSAGE actiontypes

--- client/activity/lib/flux/actions/actiontypes.coffee
+++ client/activity/lib/flux/actions/actiontypes.coffee
@@ -6,6 +6,10 @@
   'CREATE_MESSAGE_BEGIN'
   'CREATE_MESSAGE_SUCCESS'
   'CREATE_MESSAGE_FAIL'
+
+  'EDIT_MESSAGE_BEGIN'
+  'EDIT_MESSAGE_SUCCESS'
+  'EDIT_MESSAGE_FAIL'

   'REMOVE_MESSAGE_BEGIN'
   'REMOVE_MESSAGE_SUCCESS'


Reloaded admin modal if any introduction record modified or created.

--- client/app/MainApp/sidebar/modals/adminmodal.coffee
+++ client/app/MainApp/sidebar/modals/adminmodal.coffee
@@ -394,7 +394,8 @@
     buttons.Update.show()

   initIntroductionTab: ->
-    @modalTabs.forms["Introduction"].addSubView new IntroductionAdmin()
+    parentView = @modalTabs.forms["Introduction"]
+    parentView.addSubView new IntroductionAdmin { parentView }

 class IntroductionAdmin extends JView
@@ -430,6 +431,11 @@
     @on "IntroductionItemDeleted", (snippet) =>
       @snippets.splice @snippets.indexOf(snippet), 1
       @notFoundText.show() if @snippets.length is 0
+
+  reload: ->
+    parentView = @getOptions().parentView
+    parentView.addSubView new IntroductionAdmin { parentView }
+    @destroy()

   fetchData: ->
     KD.remote.api.JIntroSnippet.fetchAll (err, snippets) =>
@@ -451,6 +457,8 @@
       delegate: @
     }
     , data
+
+    @form.on "IntroductionFormNeedsReload", => @reload()

   viewAppended: ->
     super
@@ -578,13 +586,20 @@
       Title           :
         label         : "Title"
         type          : "text"
-        placeholder   : "Title of the introduction"
+        placeholder   : "Title of introduction group"
         defaultValue  : data.title
       ExpiryDate      :
         label         : "Expiry Date"
         type          : "text"
-        placeholder   : "Best before"
+        placeholder   : "Best before (YYYY-MM-DD)"
         defaultValue  : data.expiryDate
+        tooltip       :
+          title       : "Date format should be YYYY-MM-DD. e.g. 2013-05-03"
+          placement   : "right"
+          direction   : "center"
+          offset      :
+            top       : 2
+            left      : 0
       Overlay         :
         label         : "Overlay"
         type          : "select"
@@ -657,43 +672,22 @@
   insertParent: ->
     KD.remote.api.JIntroSnippet.create @createPostData(), =>
       @buttons["Save"].hideLoader()
-      log "new created"
+      @emit "IntroductionFormNeedsReload"

   insertChild: ->
     @parentData.addChild @createPostData(), =>
       @buttons["Save"].hideLoader()
-      log "child created"
+      @emit "IntroductionFormNeedsReload"

   updateParent: ->
     @parentData.update @createPostData(), =>
-      log "parent updated"
+      @emit "IntroductionFormNeedsReload"

   updateChild: ->
     data = @createPostData()
     data.oldIntroId = @parentData.introId
     @parentData.introductionItem.getData().updateChild data, =>
-      log "child updated"
-
-    # {
-    #   introTitle : postData.IntroTitle
-    #   introId    : postData.IntroId
-    #   snippet    : postData.Snippet
-    #   oldIntroId : @getData().IntroId
-    # }
-    # @getData().snippet.updateChild @createPostData(), =>
-      # introId    = "EDITED"
-      # snippet    = "alert"
-      # introTitle = "TITLE"
-      # @getDelegate().getData().updateChild {
-      #   oldIntroId : @getData().introId
-      #   introId
-      #   introTitle
-      #   snippet
-      # }, => @setData {
-      #     introTitle
-      #     introId
-      #     snippet
-      #   }
+      @emit "IntroductionFormNeedsReload"

   createPostData: ->
     {inputs}     = @


client: fix softlayer_username variable

client: fix softlayer_username variable

--- client/app/lib/providers/templates/softlayer.coffee
+++ client/app/lib/providers/templates/softlayer.coffee
@@ -7,7 +7,7 @@
 {
   "provider": {
     "softlayer": {
-      "username": "softlayer_username",
+      "username": "${var.softlayer_username}",
       "api_key": "${var.softlayer_api_key}"
     }
   },


fixed #24

fixed #24

--- menus/go-outline.cson
+++ menus/go-outline.cson
@@ -2,7 +2,7 @@
 'context-menu':
   'atom-text-editor': [
     {
-      'label': 'Toggle go-outline'
+      'label': 'Toggle Go Outline'
       'command': 'go-outline:toggle'
     }
   ]
@@ -10,11 +10,15 @@
   {
     'label': 'Packages'
     'submenu': [
-      'label': 'go-outline'
+      'label': 'Go Outline'
       'submenu': [
         {
-          'label': 'Show/Hide go-outline'
+          'label': 'Show/Hide Go Outline'
           'command': 'go-outline:toggle'
+        },
+        {
+          'label': 'Focus Symbol Filter'
+          'command': 'go-outline:focus-filter'
         }
       ]
     ]


Ensure user is null

Ensure user is null

--- components/related_auction_results/test/view.coffee
+++ components/related_auction_results/test/view.coffee
@@ -40,6 +40,8 @@
       @view.$('.rar-title').first().text().should.equal 'MADONNA PAINTING (1985)'

     it 'does not have the price for logged out users', ->
+      @view.user = null
+      @view.render()
       @view.$('.rar-price').length.should.equal 0

     it 'has the price if you are logged in', ->


Fix html parser

Fix html parser

--- scripts/models/content/inherits/container.coffee
+++ scripts/models/content/inherits/container.coffee
@@ -42,8 +42,10 @@
     results = []
     $html = $(html)

-    $('> nav > ol > li', $html).each (index, $el) ->
-      $node = $el.children.eq(0)
+    $html.find('> ol').find('> li').each (index, el) ->
+      $el = $(el)
+
+      $node = $el.children().eq(0)

       if $node.is('a')
         id = $node.attr('href')


Remove comment

Remove comment

--- admin/static/coffee/app.coffee
+++ admin/static/coffee/app.coffee
@@ -218,7 +218,6 @@
 # Tweaking the value of server.host or server.port can trigger errors for testing
 $ ->
     window.r = require('rethinkdb')
-    #DataExplorerView.Container.TermBase = r.expr(1).constructor.__super__.constructor.__super__

     render_loading()
     bind_dev_tools()


Fix error when checkForUnearnedAchievements was run from campaign editor

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -900,7 +900,7 @@
     true

   checkForUnearnedAchievements: ->
-    return unless @campaign
+    return unless @campaign and currentView.sessions

     # Another layer attempting to make sure users unlock levels properly.


Integrated two views

Integrated two views

--- core/app/backbone/views/feed_activities_view.coffee
+++ core/app/backbone/views/feed_activities_view.coffee
@@ -1,14 +1,7 @@
 window.ReactFeedActivities = React.createBackboneClass
-  render: ->
-    _div [id:"feed_activity_list"],
-      ReactActivities
-        model: @model()
+  displayName: 'ReactFeedActivities'

-ReactActivities = React.createBackboneClass
-  displayName: 'ReactActivities'
-
-  # this function sets the correct state after loading is done, tries to load more if applicable
-  # and sets empty state if we are not loading and we have no items
+  # this function loads more activities, if we're almost at the bottom of the list
   checkScrolledPosition: ->
     pixels_under_fold = $(document).height() - ($(window).scrollTop() + $(window).height())

@@ -24,7 +17,7 @@
     $(window).off "scroll", @checkScrolledPosition

   render: ->
-    _div [],
+    _div [id:"feed_activity_list"],
       _div ["js-activities-list"],
         @model().map (model) =>
           switch model.get("action")


Make sure showing success dialogs works correctly

--- spec/command-installer-spec.coffee
+++ spec/command-installer-spec.coffee
@@ -41,6 +41,19 @@
     expect(appDelegate.confirm).toHaveBeenCalledWith({
       message: "Failed to install shell commands"
       detailedMessage: "another error"
+    })
+
+  it "shows a success dialog when installing commands interactively succeeds", ->
+    appDelegate = jasmine.createSpyObj("appDelegate", ["confirm"])
+    installer = new CommandInstaller("2.0.2", appDelegate)
+    spyOn(installer, "installAtomCommand").andCallFake (__, callback) -> callback()
+    spyOn(installer, "installApmCommand").andCallFake (__, callback) -> callback()
+
+    installer.installShellCommandsInteractively()
+
+    expect(appDelegate.confirm).toHaveBeenCalledWith({
+      message: "Commands installed."
+      detailedMessage: "The shell commands `atom` and `apm` are installed."
     })

   describe "when using a stable version of atom", ->


Fix tests

Fix tests

--- spec/project-find-view-spec.coffee
+++ spec/project-find-view-spec.coffee
@@ -388,8 +388,7 @@

         runs ->
           expect(projectFindView.errorMessages).not.toBeVisible()
-          expect(projectFindView.infoMessages).toBeVisible()
-          expect(projectFindView.infoMessages.find('li').text()).toContain 'Replaced'
+          expect(projectFindView.descriptionLabel.text()).toContain 'Replaced'

           sampleJsContent = fs.readFileSync(sampleJs, 'utf8')
           expect(sampleJsContent.match(/items/g)).toBeFalsy()
@@ -412,7 +411,7 @@
           runs ->
             expect(atom.project.scan).not.toHaveBeenCalled()
             expect(atom.beep).toHaveBeenCalled()
-            expect(projectFindView.infoMessages.find('li').text()).toBe "Nothing replaced"
+            expect(projectFindView.descriptionLabel.text()).toBe "Nothing replaced"

       describe "when the search text has changed since that last search", ->
         beforeEach ->
@@ -437,7 +436,7 @@
           runs ->
             expect(atom.project.scan).not.toHaveBeenCalled()
             expect(atom.beep).toHaveBeenCalled()
-            expect(projectFindView.infoMessages.find('li').text()).toBe "Nothing replaced"
+            expect(projectFindView.descriptionLabel.text()).toBe "Nothing replaced"

       describe "when the text in the search box triggered the results", ->
         beforeEach ->
@@ -462,7 +461,7 @@
             expect(resultsView).toBeVisible()
             expect(resultsView.find("li > ul > li")).toHaveLength(0)

-            expect(projectFindView.infoMessages.find('li').text()).toBe "Replaced 13 results in 2 files"
+            expect(projectFindView.descriptionLabel.text()).toBe "Replaced 13 results in 2 files"

             sampleJsContent = fs.readFileSync(sampleJs, 'utf8')
             expect(sampleJsContent.match(/items/g)).toBeFalsy()


Ignore unregistered accounts in findSuggestion implementation of JAccount

--- workers/social/lib/social/models/account/index.coffee
+++ workers/social/lib/social/models/account/index.coffee
@@ -558,6 +558,8 @@
         ],
       _id     :
         $nin  : blacklist
+      type    :
+        $in   : ['registered', null]
     },{
       skip
       limit
@@ -702,8 +704,8 @@

   # returns troll users ids
   @getExemptUserIds: (callback)->
-    JAccount.someData {isExempt:true}, {_id:1}, (err, cursor)->
-      cursor.toArray (err, data)->
+    JAccount.someData {isExempt:true}, {_id:1}, (err, cursor)->
+      cursor.toArray (err, data)->
         if err
           return callback err, null
         callback null, (i._id for i in data)


Define collaboration user kick method

Define collaboration user kick method

--- workers/social/lib/social/models/collaboration.coffee
+++ workers/social/lib/social/models/collaboration.coffee
@@ -74,6 +74,13 @@
     setUsers client, workspaceId, options, callback

+  @kick = secure (client, workspaceId, target, callback) ->
+
+    asUser  = no
+    options = {target, asUser}
+    setUsers client, workspaceId, options, callback
+
+
   setUsers = (client, workspaceId, options, callback) ->

     JWorkspace.one _id: workspaceId, (err, workspace) ->


add fetchMachinesByUsername function to machinehelper.coffee

--- workers/social/testhelper/models/machinehelper.coffee
+++ workers/social/testhelper/models/machinehelper.coffee
@@ -1,9 +1,10 @@
-_         = require 'underscore'
-hat       = require 'hat'
-{ daisy } = require 'bongo'
+_          = require 'underscore'
+hat        = require 'hat'
+{ daisy }  = require 'bongo'
+{ expect } = require 'chai'

-JUser     = require '../../lib/social/models/user/index'
-JMachine  = require '../../lib/social/models/computeproviders/machine'
+JUser      = require '../../lib/social/models/user/index'
+JMachine   = require '../../lib/social/models/computeproviders/machine'

 { reviveClient } = require '../../../social/lib/social/models/computeproviders/computeutils'

@@ -67,9 +68,17 @@
   daisy queue

+fetchMachinesByUsername = (username, callback) ->
+
+  JMachine.fetchByUsername username, (err, machines) ->
+    expect(err).to.not.exist
+    callback machines
+
+
 module.exports = {
   createUserAndMachine
   generateMachineParams
+  fetchMachinesByUsername
   generateMachineParamsByAccount
 }


bump timeout

bump timeout

--- angular/test/protractor.coffee
+++ angular/test/protractor.coffee
@@ -2,7 +2,7 @@
 paths         = require('../tasks/paths')

 exports.config =
-  allScriptsTimeout: 30000
+  allScriptsTimeout: 60000
   directConnect: true
   # seleniumAddress: 'http://localhost:4444/wd/hub'
   capabilities:
@@ -13,7 +13,7 @@
     isVerbose: false
     showColors: true
     includeStackTrace: true
-    defaultTimeoutInterval: 30000
+    defaultTimeoutInterval: 60000
   onPrepare: ->
     jasmine.getEnv().addReporter new screenshooter
       dest: paths.protractor.screenshots


Correct examples base url for ngdoc imports

--- assets/js/services/importer.coffee
+++ assets/js/services/importer.coffee
@@ -5,7 +5,7 @@
 #= require ./../services/notifier

 # Use the identifier {{VERSION}} which will be substituted with the example's stated version
-NGDOC_BASE_URL = "http://ci.angularjs.org/job/angular.js-pete/485/artifact/build/docs/examples/"
+NGDOC_BASE_URL = "http://code.angularjs.org/{{VERSION}}/docs/examples/"

 plunkerRegex = ///
   ^


Cleanup UserPassword model andd add some documentation to it.

--- api/models/UserPassword.coffee
+++ api/models/UserPassword.coffee
@@ -1,3 +1,8 @@
+# UserPassword.coffee
+#
+# @description :: Accepts and validates incoming POST request for user password.
+# @docs        :: http://sailsjs.org/documentation/concepts/models-and-orm/models
+
 module.exports =
   # Allow only whitelisted attributes.
   schema: true
@@ -56,8 +61,3 @@

     # Continue.
     cb()
-
-  # Remove empty values from model.
-  # afterValidate: (values, cb) ->
-    # delete values[i] for i of values when !values[i]
-    # cb()


Minor cleanup

Minor cleanup

--- src/components/player/PlayerSpaceship.coffee
+++ src/components/player/PlayerSpaceship.coffee
@@ -14,8 +14,6 @@
     ]

     @onHit 'ShipSolid', (hits) ->
-      console.log('on edge!')
-
       delta = @motionDelta()
       xCorrection = 0
       yCorrection = 0


avoid blocking when fetching redis keys

avoid blocking when fetching redis keys

use scan instead of keys method

--- app/coffee/RedisManager.coffee
+++ app/coffee/RedisManager.coffee
@@ -33,6 +33,23 @@
 	getDocIdsWithHistoryOps: (project_id, callback = (error, doc_ids) ->) ->
 		rclient.smembers docsWithHistoryOpsKey(project_id), callback

+	# iterate over keys asynchronously using redis scan (non-blocking)
+	_getKeys: (pattern, callback) ->
+		cursor = 0  # redis iterator
+		keySet = {} # use hash to avoid duplicate results
+		# scan over all keys looking for pattern
+		doIteration = (cb) ->
+			rclient.scan cursor, "MATCH", pattern, "COUNT", 1000, (error, reply) ->
+				return callback(error) if error?
+				[cursor, keys] = reply
+				for key in keys
+					keySet[key] = true
+				if cursor == '0'  # note redis returns string result not numeric
+					return callback(null, Object.keys(keySet))
+				else
+					doIteration()
+		doIteration()
+
 	# extract ids from keys like DocsWithHistoryOps:57fd0b1f53a8396d22b2c24b
 	_extractIds: (keyList) ->
 		ids = (key.split(":")[1] for key in keyList)
@@ -40,7 +57,7 @@

 	# this will only work on single node redis, not redis cluster
 	getProjectIdsWithHistoryOps: (callback = (error, project_ids) ->) ->
-		rclient.keys docsWithHistoryOpsKey("*"), (error, project_keys) ->
+		RedisManager._getKeys docsWithHistoryOpsKey("*"), (error, project_keys) ->
 			return callback(error) if error?
 			project_ids = RedisManager._extractIds project_keys
 			callback(error, project_ids)
@@ -49,7 +66,7 @@
 	getAllDocIdsWithHistoryOps: (callback = (error, doc_ids) ->) ->
 		# return all the docids, to find dangling history entries after
 		# everything is flushed.
-		rclient.keys rawUpdatesKey("*"), (error, doc_keys) ->
+		RedisManager._getKeys rawUpdatesKey("*"), (error, doc_keys) ->
 			return callback(error) if error?
 			doc_ids = RedisManager._extractIds doc_keys
 			callback(error, doc_ids)


Update de.coffee

Update de.coffee

Add translations for email verification.

--- t9n/de.coffee
+++ t9n/de.coffee
@@ -103,6 +103,11 @@
       "User not found": "Benutzer nicht gefunden"
       "Verify email link expired": "Link zur E-Mail Verifizierung ist abgelaufen"
       "Verify email link is for unknown address": "Link zur Verifizierung ist für eine unbekannte E-Mail Adresse"
+      "Verification email lost?" : "Verifizierungsemail verloren?"
+      "Send again" : "Erneut senden"
+      "Send the verification email again" : "Verifizierungsemail erneut senden"
+      "Send email again" : "Email erneut senden"
+      "A new email has been sent to you. If the email doesn't show up in your inbox, be sure to check your spam folder." : "Eine neue Email wurde an Sie verschickt. Sollte sich die Email nicht in Ihrem Posteingang befinden, prüfen Sie bitten Ihren Spamordner."

       #---- match
       "Match failed": "Abgleich fehlgeschlagen"


Switch off custom map labels for now

Switch off custom map labels for now

--- app/controllers/profile.coffee
+++ app/controllers/profile.coffee
@@ -83,11 +83,11 @@

   onMouseEnterFavorite: ({currentTarget}) =>
     favID = $(currentTarget).attr 'data-favorite'
-    @labels[favID].el.addClass 'hovering'
+    # @labels[favID].el.addClass 'hovering'

   onMouseLeaveFavorite: ({currentTarget}) =>
     favID = $(currentTarget).attr 'data-favorite'
-    @labels[favID].el.removeClass 'hovering'
+    # @labels[favID].el.removeClass 'hovering'

   onClickReveal: ({currentTarget}) =>
     favRoot = $(currentTarget).closest '[data-favorite]'


fix: type error

fix: type error

--- app/helpers/device-type.coffee
+++ app/helpers/device-type.coffee
@@ -6,7 +6,7 @@

   currentDevice = params[0]

-  if currentDevice is ENUMS.DEVICE_TYPE.NO_PREFERENCES
+  if currentDevice is ENUMS.DEVICE_TYPE.NO_PREFERENCE
     "No Preferences"
   else if currentDevice is ENUMS.DEVICE_TYPE.PHONE_REQUIRED
     "Phone Required"


Note when obsolete files should be removed.

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -133,8 +133,8 @@
         files:
           'build/factlink.start_annotating.min.js':   ['build/factlink.start_annotating.js']
           'build/factlink.stop_annotating.min.js':    ['build/factlink.stop_annotating.js']
-          'build/factlink.start_highlighting.min.js': ['build/factlink.start_highlighting.js'] #Obsolete!
-          'build/factlink.stop_highlighting.min.js':  ['build/factlink.stop_highlighting.js'] #Obsolete!
+          'build/factlink.start_highlighting.min.js': ['build/factlink.start_highlighting.js'] #Obsolete; remove 2014-04-01
+          'build/factlink.stop_highlighting.min.js':  ['build/factlink.stop_highlighting.js'] #Obsolete; remove 2014-04-01
           'build/factlink_loader_basic.min.js':       ['build/factlink_loader_basic.js']
           'build/factlink_loader_publishers.min.js':  ['build/factlink_loader_publishers.js']
           'build/factlink_loader_bookmarklet.min.js': ['build/factlink_loader_bookmarklet.js']


Fixing mouse events set on groups not working (due to `ignoreEvents` prop not being forwarded to child elements).

--- framer/SVGGroup.coffee
+++ framer/SVGGroup.coffee
@@ -57,5 +57,6 @@
 	@defineGroupProxyProp "strokeWidth", _.isNumber, parseInt
 	@defineGroupProxyProp "strokeWidthMultiplier", _.isNumber, parseInt
 	@defineGroupProxyProp "color", Color.validColorValue, Color.toColor
+	@defineGroupProxyProp "ignoreEvents", _.isBoolean, (value) -> !!value

 exports.SVGGroup = SVGGroup


Final backwards compatibility adjustment 😞

--- framer/SVGLayer.coffee
+++ framer/SVGLayer.coffee
@@ -18,7 +18,7 @@
 			# Backwards compatibility for old Vekter exporter that would
 			# set backgroundColor instead of color
 			options.color ?= options.backgroundColor
-			delete options.backgroundColor
+			options.backgroundColor = null
 		super options
 		@updateGradientSVG()


Improve end-of-level fade/pause behavior (#664)

--- app/lib/surface/Surface.coffee
+++ app/lib/surface/Surface.coffee
@@ -144,7 +144,7 @@
       @handleEvents
     })
     @countdownScreen = new CountdownScreen camera: @camera, layer: @screenLayer, showsCountdown: @world.showsCountdown
-    unless @options.levelType is 'game-dev'
+    if @options.levelType in ['ladder', 'hero-ladder', 'course-ladder']
       @playbackOverScreen = new PlaybackOverScreen camera: @camera, layer: @screenLayer, playerNames: @options.playerNames
       @normalStage.addChildAt @playbackOverScreen.dimLayer, 0  # Put this below the other layers, actually, so we can more easily read text on the screen.
     @initCoordinates()
@@ -341,11 +341,13 @@
   setPaused: (paused) ->
     # We want to be able to essentially stop rendering the surface if it doesn't need to animate anything.
     # If pausing, though, we want to give it enough time to finish any tweens.
+    clearTimeout @surfacePauseTimeout if @surfacePauseTimeout
+    clearTimeout @surfaceZoomPauseTimeout if @surfaceZoomPauseTimeout
+    return if @options.levelType in ['game-dev']
+    return unless @handleEvents  # Don't do this within the level editor
     performToggle = =>
       createjs.Ticker.framerate = if paused then 1 else @options.frameRate
       @surfacePauseTimeout = null
-    clearTimeout @surfacePauseTimeout if @surfacePauseTimeout
-    clearTimeout @surfaceZoomPauseTimeout if @surfaceZoomPauseTimeout
     @surfacePauseTimeout = @surfaceZoomPauseTimeout = null
     if paused
       @surfacePauseTimeout = _.delay performToggle, 2000
@@ -375,10 +377,10 @@
     )

     if (not @world.indefiniteLength) and @lastFrame < @world.frames.length and @currentFrame >= @world.totalFrames - 1
+      @updatePaths()  # TODO: this is a hack to make sure paths are on the first time the level loads
       @ended = true
       @setPaused true
       Backbone.Mediator.publish 'surface:playback-ended', {}
-      @updatePaths()  # TODO: this is a hack to make sure paths are on the first time the level loads
     else if @currentFrame < @world.totalFrames and @ended
       @ended = false
       @setPaused false


bongo: handle an error if the konstructor doesn't exist

--- node_modules_koding/bongo/lib/bongo.coffee
+++ node_modules_koding/bongo/lib/bongo.coffee
@@ -365,6 +365,9 @@
       else if method is 'authenticateUser' then @authenticateUser args... # TODO: this doesn't need to be specialcase. C.T.
       else if method?.method
         konstructor = Base.constructors[method.constructorName]
+        unless konstructor?
+          @handleError "No such constructor! #{method.constructorName}"
+          return
         switch method.type
           when 'static'
             if konstructor.hasSharedMethod method.method


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -1,5 +1,6 @@
 module "ActiveBounds"

-test "should remove objects outside of active"
+test "should remove objects outside of activeBounds", ->
+  obj =

 module()


Set dashboard to use toolsets

Set dashboard to use toolsets

--- app/modules/tool_loader.coffee
+++ app/modules/tool_loader.coffee
@@ -15,6 +15,9 @@
   else
     Manager.set 'tools', tools.projects[project]

-  Ubret.Loader Manager.get('tools'), cb
+  if location.port > 3333
+    Ubret.Loader Manager.get('tools'), cb
+  else
+    Ubret.ToolsetLoader Manager.get('project'), cb

 module.exports = ToolLoader


Select: Fixed the 'ui' attribute for options with object values.

--- src/elements/Select/Select.coffee
+++ src/elements/Select/Select.coffee
@@ -119,7 +119,10 @@
 					first_value_opt = opt

 				if @_ui
-					opt.ui = "#{@_ui}.option:#{opt.value}"
+					uiValue = opt.value
+					if CUI.util.isPlainObject(opt.value)
+						uiValue = idx
+					opt.ui = "#{@_ui}.option:#{uiValue}"

 			# auto - select first opt, if value unset
 			# and no empty text allowed


fixed contentWidth watcher

fixed contentWidth watcher

--- gilbox-kinetic-slider.coffee
+++ gilbox-kinetic-slider.coffee
@@ -182,7 +182,7 @@
         contElm.css 'width', contentWidth + 'px'

       calcContentWidth()
-      scope.$watch('contentWidth', calcContentWidth) unless scope.contentWidth?
+      scope.$watch('contentWidth', calcContentWidth) if scope.contentWidth?

       onWinResize = ->
         calcContentWidth()


Remove unused API calls for genome

Remove unused API calls for genome

--- web/scripts/app.coffee
+++ web/scripts/app.coffee
@@ -486,10 +486,7 @@

         # Lookup variant form.
         @get '/lookup_variant', ->
-            @app.api.genome
-                success: (genome) =>
-                    @show 'lookup', {chromosomes: genome.chromosomes}, {subpage: 'variant'}
-                error: (code, message) => @error message
+            @show 'lookup', {}, {subpage: 'variant'}

         # Lookup variant.
         @post '/lookup_variant', ->
@@ -519,10 +516,7 @@

         # Lookup variants by transcript form.
         @get '/lookup_transcript', ->
-            @app.api.genome
-                success: (genome) =>
-                    @show 'lookup', {chromosomes: genome.chromosomes}, {subpage: 'transcript'}
-                error: (code, message) => @error message
+            @show 'lookup', {}, {subpage: 'transcript'}

         # List samples.
         @get '/samples', ->


Add EJS options to JS Beautify

Add EJS options to JS Beautify

--- src/beautifiers/js-beautify.coffee
+++ src/beautifiers/js-beautify.coffee
@@ -11,6 +11,7 @@
     Handlebars: true
     Mustache: true
     JavaScript: true
+    EJS: true
     JSON: true
     CSS:
       indent_size: true


hub/customize: silly mistake

hub/customize: silly mistake


Revert "Merge pull request #71 from RallySoftware/S52265"

This reverts commit 8ee5842a860bc02a7266e25848c0bad99245d2a2, reversing
changes made to 826942c3f37e6a913b1da0a63ba19f9737cffacf.

--- kanban/KanbanAppSpec.coffee
+++ kanban/KanbanAppSpec.coffee
@@ -357,8 +357,7 @@

     assertPolicyCmpConfig: (settingsKey, policy) ->
       column = @app.down('rallycardboard').getColumns()[0]
-      plugin = _.find(column.plugins, {ptype: 'rallycolumnpolicy'});
-      prefConfigSettings = plugin.policyCmpConfig.prefConfig.settings
+      prefConfigSettings = column.policyCmpConfig.prefConfig.settings
       expect(Ext.Object.getKeys(prefConfigSettings)[0]).toBe settingsKey
       expect(prefConfigSettings[settingsKey]).toBe policy
-      expect(plugin.policyCmpConfig.policies).toBe policy+      expect(column.policyCmpConfig.policies).toBe policy


Calculate & update the tiles correctly

Calculate & update the tiles correctly

--- app/scripts/controllers.coffee
+++ app/scripts/controllers.coffee
@@ -172,12 +172,21 @@

           # index by status_group for convenience
           statusMap = _.object(_.pluck(data,"status_group"), data)
-
           $scope.status = statusMap

-          #FIXME: join with population data
+          # ensure all three statusses are always represented
+          empty = {count: 0, percent: 0}
+          statusses = ["functional", "not functional", "needs repair"]
+          statusses.forEach((x) -> statusMap[x] = statusMap[x] || empty)
+
+          # the population covered
+          # FIXME: needs pop data for percentage
+          funPop = statusMap.functional.waterpoints[0].population
+          popCover = {count: funPop, percent: 0}
+
           $scope.tiles = _.pairs(_.pick(statusMap,'functional','needs repair'))
-          $scope.tiles.push(['population cover', {count: statusMap["functional"].waterpoints.population, percent: 0}])
+          $scope.tiles.push(['population cover', popCover])
+
       if changed == 'region'
         getLGA()
         getWard()


Use $q.all for getting regions and pop data

--- app/scripts/dcplotsctrl.coffee
+++ app/scripts/dcplotsctrl.coffee
@@ -62,26 +62,28 @@
       modalSpinner.open()

       # get all regions
-      $http
-        .get('/api/waterpoints/values/region', cache: true)
-        .success (data, status, headers, config) ->
-          $scope.regions = data.sort()
-          $scope.region = $scope.regions[3]
-
-          # FIXME:
-          # unfortunately, for some reason, not all dc charts manage to pickup the
-          # correct dimensions on their own. Manually set a resize renderlet to run once
-          # all the charts have rendered.
-          dc.renderlet () ->
-            resizeCharts()
-            modalSpinner.close()
-            # only keep the renderlet once
-            dc.renderlet null
-
-          # ensure we have the population data
-          populationData.then (data) ->
-            popData = data
-            setupCharts $scope.region
+      $q.all([
+        $http.get('/api/waterpoints/values/region', cache: true)
+        populationData
+      ]).then((results) ->
+        regs = results[0].data
+        popData = results[1]
+
+        $scope.regions = regs.sort()
+        $scope.region = $scope.regions[3]
+
+        # FIXME:
+        # unfortunately, for some reason, not all dc charts manage to pickup the
+        # correct dimensions on their own. Manually set a resize renderlet to run once
+        # all the charts have rendered.
+        dc.renderlet () ->
+          resizeCharts()
+          modalSpinner.close()
+          # only keep the renderlet once
+          dc.renderlet null
+
+        setupCharts $scope.region
+      )

     # what value to use for year 0
     YEAR_ZERO=1950


wip wip

wip wip

--- scratch.coffee
+++ scratch.coffee
@@ -1,5 +1,26 @@
-# f :: Number * String -> String = (n, s) ->
-#   a :: Number = s
+# struct Hash<K, V> {
+#   get :: K -> V
+#   set :: K * V -> ()
+# }

-a :: Number
-a = "hogehoge"+# hash :: Hash<String, Number> =
+#   get: (key) -> @[key]
+#   set: (key, val) -> @[key] = val
+# # hash :: Hash<String, Number>
+# # hash =
+# #   get: (key) -> @[key]
+# #   set: (key, val) -> @[key] = val
+
+# hash.set "", 1
+# hash.get 1
+
+struct Hash<K, V> {
+  get :: K -> V
+  set :: K * V -> ()
+}
+hash :: Hash<String, Number> = {
+  get: (key) -> @[key]
+  set: (key, val) -> @[key] = val
+}
+hash.set "a", 1
+hash.get 3


Moved to ff 10 as required.

Moved to ff 10 as required.

--- app/assets/javascripts/app/lib/app_post/browser.coffee
+++ app/assets/javascripts/app/lib/app_post/browser.coffee
@@ -14,8 +14,8 @@
       console.log('Browser not supported')
       return false

-    # disable Firefox 6 and older
-    else if data.browser == 'Firefox' && data.version <= 6
+    # disable Firefox 9 and older
+    else if data.browser == 'Firefox' && data.version <= 9
       @message(data)
       console.log('Browser not supported')
       return false


move additional extensions into material raingular

--- lib/assets/javascripts/extensions/array.coffee
+++ lib/assets/javascripts/extensions/array.coffee
@@ -1,69 +1,130 @@
-Array.prototype.empty = ->
-  this.length == 0
-Array.prototype.present = ->
-  this.length != 0
-Array.prototype.min = ->
-  return Math.min.apply(null,this)
-Array.prototype.max = ->
-  return Math.max.apply(null,this)
-Array.prototype.railsMap = (func)->
+Array::dup        =       -> return @slice(0)
+Array::empty      =       -> @length == 0
+Array::first      =       -> @[0]
+Array::intersects = (arr) -> @intersection(arr).length > 0
+Array::last       =       -> @[@length - 1]
+Array::min        =       -> return Math.min.apply(null,@)
+Array::max        =       -> return Math.max.apply(null,@)
+Array::present    =       -> @length != 0
+Array::remove_all =       -> @splice(0,@length)
+
+Array::add = (arr) ->
+  (@push(item) unless @includes(item)) for item in arr
+  @
+Array::allIndicesOf = (arg) ->
+  res = []
+  until res.last() == -1 || res.length > 10
+    start = if (res.last() > -1) then res.last() + 1 else 0
+    res.push(@indexOf(arg,start))
+  res[0..-2]
+Array::compact = ->
+  arr = []
+  arr.push(i) if !!i or i == false for i in @
+  arr
+Array::drop = (entry)->
+  if (entry || {}).hasOwnProperty('id')
+    index = @pluck('id').indexOf(entry.id)
+  else
+    index = @indexOf(entry)
+  return @ unless index > -1
+  @splice(index,1)
+Array::find = (id) ->
+  index = @pluck('id').indexOf(id)
+  @[index]
+Array::flatten = ->
+  arr = []
+  for l in @
+    if Array.isArray(l)
+      for i in l.flatten()
+        arr.push i
+    else
+      arr.push l
+  arr
+Array::includes = (entry)->
+  return @indexOf(entry) > -1 unless entry
+  if entry instanceof Date
+    (@map (obj) -> obj.toDateString()).includes(entry.toDateString())
+  else if entry.hasOwnProperty('id')
+    return @pluck('id').includes(entry.id)
+  else
+    return @indexOf(entry) > -1
+Array::index = (obj) ->
+  return unless (obj || {}).hasOwnProperty('id')
+  @pluck('id').indexOf(obj.id)
+Array::$inject = (action) ->
+  operators = {
+    '+': (a,b) -> a + b
+    '-': (a,b) -> a - b
+    '*': (a,b) -> a * b
+    '/': (a,b) -> a / b
+    '&': (a,b) -> a && b
+    '|': (a,b) -> a || b
+    'merge': (a,b) ->
+      res = new Object(a)
+      res[key] = value for key,value of b
+      res
+  }
+  result = null
+  for item,index in @
+    result ||= item
+    continue if index == 0
+    result = operators[action](result,item)
+  result
+Array::intersection = (arr) ->
+  res = []
+  (res.push(val) if arr.includes(val)) for val in @
+  return res
+Array::merge = (arg) ->
+  @push(i) for i in arg
+  @
+Array::pluck = (property) ->
+  return [] if !(@ && property)
+  property = String(property)
+  return @map (object) ->
+    object = Object(object)
+    return object[property] if (object.hasOwnProperty(property))
+    return ''
+Array::railsMap = (func)->
   args = func.match(/\|(.*)\|,(.*)/) || []
   throw 'Invalid syntax "|a|, a.b"' unless args.length == 3
   arr = []
-  for obj in this
+  for obj in @
     eval args[1] + '= obj'
     if args[2].includes(':')
       arr.push eval "(" + args[2] + ")"
     else
       arr.push eval args[2]
   arr
-Array.prototype.compact = ->
-  arr = []
-  for i in this
-    arr.push(i) if !!i or i == false
+Array::reject = (func) ->
+  arr=[]
+  (arr.push(item) unless func(item)) for item in @
   arr
-Array.prototype.flatten = ->
-  arr = []
-  for l in this
-    if Array.isArray(l)
-      for i in l.flatten()
-        arr.push i
-    else
-      arr.push l
+Array::remove = (arr) ->
+  indices = []
+  for item in arr
+    indices.push(@indexOf(item)) if @indexOf(item) > -1
+  (while indices.length > 0
+    @splice(indices.drop(indices.max())[0],1)).flatten()
+Array::select = (func) ->
+  arr=[]
+  (arr.push(item) if func(item)) for item in @
   arr
-Array.prototype.sum = ->
+Array::sum = ->
   total = 0
-  for i in this
-    total += parseFloat(i) if i
+  (total += parseFloat(i) if i) for i in @
   total
-Array.prototype.includes = (entry)->
-  return @.indexOf(entry) > -1 unless entry
-  if entry instanceof Date
-    (@.map (obj) -> obj.toDateString()).includes(entry.toDateString())
-  else if entry.hasOwnProperty('id')
-    return @.pluck('id').includes(entry.id)
-  else
-    return @.indexOf(entry) > -1
-Array.prototype.drop = (entry)->
-  if (entry || {}).hasOwnProperty('id')
-    index = @.pluck('id').indexOf(entry.id)
-  else
-    index = @.indexOf(entry)
-  return this unless index > -1
-  @.splice(index,1)
-Array.prototype.pluck = (property) ->
-  return [] if !(this && property)
-  property = String(property)
-  return this.map (object) ->
-    object = Object(object)
-    return object[property] if (object.hasOwnProperty(property))
-    return ''
-Array.prototype.unique = (filterOn) ->
+Array::update = (obj) ->
+  return unless (obj || {}).hasOwnProperty('id')
+  @[@index(obj)] = obj
+Array::union = (arr) ->
+  dup = @slice(0)
+  dup.add(arr)
+Array::unique = (filterOn) ->
   equiv = (first,second) ->
     return true if first == second
     return !first && !second
   newItems = []
-  for item in this
+  for item in @
     item = item[filterOn] if filterOn
     for newItem in newItems
       isDuplicate = false
@@ -72,9 +133,7 @@
         break
     newItems.push(item) if (!isDuplicate)
   return newItems
-Array.prototype.dup = ->
-  return @.slice(0)
-Array.prototype.where = (obj) ->
+Array::where = (obj) ->
   equiv = (first,second) ->
     return true if first == second
     if !isNaN(first) && !isNaN(second)
@@ -87,29 +146,3 @@
       addEntry = addEntry && equiv(entry[key], value)
     result.push(entry) if addEntry
   result
-Array.prototype.intersection = (arr) ->
-  res = []
-  for val in @
-    res.push(val) if arr.includes(val)
-  return res
-Array.prototype.intersects = (arr) ->
-  @.intersection(arr).length > 0
-Array.prototype.find = (id) ->
-  index = @.pluck('id').indexOf(id)
-  @[index]
-Array.prototype.index = (obj) ->
-  return unless (obj || {}).hasOwnProperty('id')
-  @.pluck('id').indexOf(obj.id)
-Array.prototype.update = (obj) ->
-  return unless (obj || {}).hasOwnProperty('id')
-  @[@.index(obj)] = obj
-Array::reject = (func) ->
-  arr=[]
-  for item in @
-    arr.push(item) unless func(item)
-  arr
-Array::select = (func) ->
-  arr=[]
-  for item in @
-    arr.push(item) if func(item)
-  arr


Pretty-print all pre on task page

Pretty-print all pre on task page

--- app/static/coffee/views.coffee
+++ app/static/coffee/views.coffee
@@ -627,6 +627,8 @@
             @minOffset = @$el.find('.js-first-violation').offset().top
             @minOffset -= @$el.find('.js-violations-menu').height()
             $(window).scroll $.proxy @onScroll,  @
+            @$el.find('pre').addClass 'prettyprint'
+            prettyPrint()

         onScroll: ->
             if $(window).scrollTop() > @minOffset


fix issue with document type not being interpreted properly

--- app/assets/javascripts/hctbox/html_validator.js.coffee
+++ app/assets/javascripts/hctbox/html_validator.js.coffee
@@ -31,11 +31,11 @@
   # http://stackoverflow.com/questions/6088972/get-doctype-of-an-html-as-string-with-javascript
   getDoctype: ->
     node = document.doctype;
-    html = "<!DOCTYPE " +
+    '<!DOCTYPE ' +
       node.name +
       (node.publicId ? ' PUBLIC "' + node.publicId + '"' : '') +
       (!node.publicId && node.systemId ? ' SYSTEM' : '') +
-      (node.systemId ? ' "' + node.systemId + '"' : '')
+      (node.systemId ? ' "' + node.systemId + '"' : '') +
       '>'


remove warning

remove warning

--- app/assets/javascripts/lib/workflow-methods-mixin.cjsx
+++ app/assets/javascripts/lib/workflow-methods-mixin.cjsx
@@ -375,7 +375,6 @@
               @setState
                 notice: null
                 taskKey: @getActiveWorkflow().first_task
-        console.warn "NO MORE SUBJECT SETS"
       return

     # console.log "Mark#index Advancing to subject_set_index #{new_subject_set_index} (of #{@state.subjectSets.length}), subject_index #{new_subject_index} (of #{@state.subjectSets[new_subject_set_index].subjects.length})"


#191 Uncomment identity mapping, because it breaks rgb datasets

--- app/assets/javascripts/oxalis/model/binary/cube.coffee
+++ app/assets/javascripts/oxalis/model/binary/cube.coffee
@@ -214,12 +214,12 @@
       cube[bucketIndex] = bucketData

       # Generate identity mapping
-      mappingIndex = @getMappingIndexByZoomedAddress(address)
-      if not @mappings[mappingIndex]?
-        mapping = new Array( 1 << @LOCAL_ID_SIZE_P )
-        for i in [0...mapping.length]
-          mapping[i] = i
-        @mappings[mappingIndex] = mapping
+      #mappingIndex = @getMappingIndexByZoomedAddress(address)
+      #if not @mappings[mappingIndex]?
+      #  mapping = new Array( 1 << @LOCAL_ID_SIZE_P )
+      #  for i in [0...mapping.length]
+      #    mapping[i] = i
+      #  @mappings[mappingIndex] = mapping

       @setArbitraryBucketByZoomedAddress(address, bucketData) if address[3] <= @ARBITRARY_MAX_ZOOMSTEP
       @trigger("bucketLoaded", address)


Fix #842: Only every safe if updates are allowed

--- app/assets/javascripts/oxalis/model/statelogger.coffee
+++ app/assets/javascripts/oxalis/model/statelogger.coffee
@@ -72,6 +72,9 @@

   pushImpl : (notifyOnFailure) ->

+    if not @allowUpdate
+      return new $.Deferred().resolve().promise()
+
     @concatUpdateTracing()
     @committedDiffs = @committedDiffs.concat(@newDiffs)
     @newDiffs = []


Focus current menu item to return focus to content and improve accessibility

--- app/assets/javascripts/uploadcare/widget/dialog.coffee
+++ app/assets/javascripts/uploadcare/widget/dialog.coffee
@@ -404,6 +404,7 @@
             .removeClass("uploadcare--menu__item_current")
             .filter(".uploadcare--menu__item_tab_#{tab}")
             .addClass("uploadcare--menu__item_current")
+            .focus()

       className = "uploadcare--tab"
       @panel.find(".#{className}")


Add test cases for iterator.seek.

Add test cases for iterator.seek.

--- test/iterator-test.coffee
+++ test/iterator-test.coffee
@@ -58,6 +58,40 @@
               assert.equal expectVal, val
               iterator.next if ++i <= 200 then next else done
       next()
+
+  it 'should seek to a key', (done) ->
+    i = 100
+    next = (err) ->
+      assert.ifError err
+      expectKey = "#{i}"
+      expectVal = "Hello #{i}"
+      iterator.seek expectKey, (err) ->
+        assert.ifError err
+        iterator.key (err, key) ->
+          assert.ifError err
+          assert.equal expectKey, key
+          iterator.value (err, val) ->
+            assert.ifError err
+            assert.equal expectVal, val
+            if ++i <= 100 then next() else done()
+    next()
+
+  it 'should seek to a key (buffer)', (done) ->
+    i = 100
+    next = (err) ->
+      assert.ifError err
+      expectKey = "#{i}"
+      expectVal = "Hello #{i}"
+      iterator.seek new Buffer(expectKey, 'ascii'), (err) ->
+        assert.ifError err
+        iterator.key (err, key) ->
+          assert.ifError err
+          assert.equal expectKey, key
+          iterator.value (err, val) ->
+            assert.ifError err
+            assert.equal expectVal, val
+            if ++i <= 100 then next() else done()
+    next()

   it 'should not get invalid key', (done) ->
     iterator.seek '201', (err) ->


Fix mentions cursor

Fix mentions cursor

--- assets/javascripts/views/posts_feed/mentions.js.coffee
+++ assets/javascripts/views/posts_feed/mentions.js.coffee
@@ -26,8 +26,10 @@
     }
     @fetch()

-  fetchSuccess: (res, xhr) =>
+  fetchSuccess: (res, xhr, params, options) =>
     super
+
+    return if options.append

     link_header = new TentStatus.PaginationLinkHeader xhr.getResponseHeader('Link')
     @updateProfileCursor(link_header)


clear the roll value accumulator at the right time

--- lib/DiceInterpretter.coffee
+++ lib/DiceInterpretter.coffee
@@ -20,6 +20,7 @@
       when ','
         (left, right) =>
           @interpret_subroll left
+
           @interpret_node right
       when '#'
         (times, ast) =>
@@ -57,7 +58,6 @@
         throw "Bad unary operation: #{op}"

   roll: (dice, sides) =>
-    console.log dice
     return ZERO if dice == 0
     if dice < 0
       throw new DiceError "Cannot roll a negative number of dice."
@@ -80,12 +80,11 @@
       exception

   interpret_subroll: (ast) ->
+    result = @interpret_node ast
+    @results.push
+      result: result
+      rolls: @dice_rolled
     @dice_rolled = []
-    result = @interpret_node ast
-    if result?
-      @results.push
-        result: result
-        rolls: @dice_rolled
     result

   interpret_node: (node) ->


Contact form events and indentation fixed

--- src/javascripts/contact_us.coffee
+++ src/javascripts/contact_us.coffee
@@ -1,86 +1,87 @@
 jQuery ($) ->
   $("aside.contact-us .write-to-us").click (event) ->
-        event.preventDefault()
-        $trigger = $(@)
+    event.preventDefault()
+    $trigger = $(@)
+
+    # Clone form template
+    $form = $("#contact-us-form-template").clone()
+    $form.removeAttr("id")

-  # Clone form template
-  $form = $("#contact-us-form-template").clone()
-  $form.removeAttr("id")
+    # Post the form w/ Ajax on submit
+    $form.submit (event) ->
+      event.preventDefault()
+      $form.find("input[type=submit]").val("Skickar meddelande ...").attr("disabled", "disabled")
+      $.ajax
+        type: "POST"
+        url: $trigger.attr('data-action')
+        data: $form.serialize() + "&contactid=#{$trigger.attr('data-contact-id')}"
+        success: (data) ->
+          $form.replaceWith(data)
+        error: (x, y, z) ->
+          # Server error or timeout, nothing to do
+          # FIXME: Uncomment the real error message
+          $form.after('
Ett fel inträffade, vänligen försök senare eller skicka ditt meddelande till nedanstående e-postadress.
')

-  # Post the form w/ Ajax on submit
-  $form.submit (event) ->
-  event.preventDefault()
-  $form.find("input[type=submit]").val("Skickar meddelande ...").attr("disabled", "disabled")
-  $.ajax
-    type: "POST"
-    url: $trigger.attr('data-action')
-    data: $form.serialize() + "&contactid=#{$trigger.attr('data-contact-id')}"
-    success: (data) ->
-      $form.replaceWith(data)
-    error: (x, y, z) ->
-      # Server error or timeout, nothing to do
-      # FIXME: Uncomment the real error message
-      $form.after('
Ett fel inträffade, vänligen försök senare eller skicka ditt meddelande till nedanstående e-postadress.
')
+    # Replace the trigger w/ the form
+    $trigger.replaceWith($form.show())

-  # Replace the trigger w/ the form
-  $trigger.replaceWith($form.show())
-
-  # Scroll to top of form
-  $('html, body').animate
-  scrollTop: $form.offset().top - 45, 100
+    # Scroll to top of form
+    $('html, body').animate
+      scrollTop: $form.offset().top - 45
+    , 100

   # District selector for Contact us
   if $("aside.contact-us.multi-district").length
-  $.cookie.json = true
+    $.cookie.json = true

-  # The form
-  $chooseDistrict = $("#choose-district")
+    # The form
+    $chooseDistrict = $("#choose-district")

-  # Prevent the form for being submited
-  $chooseDistrict.submit -> event.preventDefault()
+    # Prevent the form for being submited
+    $chooseDistrict.submit -> event.preventDefault()

-  # Selectbox
-  $selectDistrict = $chooseDistrict.find("select")
+    # Selectbox
+    $selectDistrict = $chooseDistrict.find("select")

-  showDistrictContact = (district) ->
-  # Hide all contact cards
-  $("aside.contact-us.multi-district .vcard").hide()
+    showDistrictContact = (district) ->
+      # Hide all contact cards
+      $("aside.contact-us.multi-district .vcard").hide()

-  # Show selected contact card
-  $("#district-#{district}").show()
+      # Show selected contact card
+      $("#district-#{district}").show()

-  # Set district in select menu
-  $selectDistrict.val district
+      # Set district in select menu
+      $selectDistrict.val district

-  # Set selected district in cookie
-  $.cookie('city-district', district, { expires: 365, path: '/' } )
+      # Set selected district in cookie
+      $.cookie('city-district', district, { expires: 365, path: '/' } )

-  # Select district from cookie on load
-  storedDistrict = $.cookie('city-district')
-  if !!storedDistrict
-    showDistrictContact storedDistrict
-  else
-     $("aside.contact-us.multi-district .vcard").hide()
+    # Select district from cookie on load
+    storedDistrict = $.cookie('city-district')
+    if !!storedDistrict
+      showDistrictContact storedDistrict
+    else
+      $("aside.contact-us.multi-district .vcard").hide()

-  # District selector is changed by user or address search
-  selectDistrict.change ->
-  showDistrictContact $(@).val()
+    # District selector is changed by user or address search
+    $selectDistrict.change ->
+      showDistrictContact $(@).val()

-			# Autocomplete for street addresses
-			# Get address suggestions w/ districts from SBK's map service
-			$chooseDistrict.find("input").autocomplete
-				source: (request, response) ->
-					$.ajax
-						url: "//kartor.malmo.se/api/v1/district_from_address/"
-						dataType: "jsonp"
-						data:
-							q: request.term
-							items: 10
-							group_by: "district"
-						success: (data) ->
-							response $.map data.addresses, (item) ->
-								label: item.name
-								district: item.towndistrict
-				minLength: 2
-				select: (event, ui) ->
-					showDistrictContact ui.item.district.toLowerCase()+    # Autocomplete for street addresses
+    # Get address suggestions w/ districts from SBK's map service
+    $chooseDistrict.find("input").autocomplete
+      source: (request, response) ->
+        $.ajax
+          url: "//kartor.malmo.se/api/v1/district_from_address/"
+          dataType: "jsonp"
+          data:
+            q: request.term
+            items: 10
+            group_by: "district"
+          success: (data) ->
+            response $.map data.addresses, (item) ->
+              label: item.name
+              district: item.towndistrict
+      minLength: 2
+      select: (event, ui) ->
+        showDistrictContact ui.item.district.toLowerCase()


destroy db after tests

destroy db after tests

--- test/request_tests.coffee
+++ test/request_tests.coffee
@@ -47,9 +47,9 @@

     after helpers.closeApp

-    ###after (done) ->
+    after (done) ->
         db.destroy ->
-            db.create done###
+            db.create done

     describe "View creation", ->
         describe "Install an application which has access to every docs", ->


Split platform specific keybindings

Split platform specific keybindings

--- keymaps/grammar-selector.cson
+++ keymaps/grammar-selector.cson
@@ -1,3 +1,5 @@
-'.editor':
-  'meta-L': 'grammar-selector:show'
+'.platform-darwin .editor':
+  'cmd-L': 'grammar-selector:show'

+'.platform-win32 .editor':
+  'ctrl-L': 'grammar-selector:show'


Remove unused url's path

Remove unused url's path

--- lib/broadcast-server.coffee
+++ lib/broadcast-server.coffee
@@ -28,7 +28,7 @@
     @startSocketIOServer()

     if !isRestart and atom.config.get 'broadcast.automaticallyOpenInBrowser'
-      @openUrlInBrowser url + '/index.html'
+      @openUrlInBrowser url

     console.log "Broadcast started at #{url}"


Removing duplicate code

Removing duplicate code

--- src/processors/job/job.processor.spec.coffee
+++ src/processors/job/job.processor.spec.coffee
@@ -19,6 +19,17 @@
     json: body
   }

+mockFailedNotificationWith = (badStatusCode) ->
+  errorMessage = "it's a trap!"
+  bodyExpected =
+    statusCode: badStatusCode
+    message: errorMessage
+    success: no
+
+  _nockAPI badStatusCode, errorMessage
+  _notificationsApiNock bodyExpected
+
+
 _notificationsApiNock = (bodyExpected) ->
   nock NOTIFICATIONS_URL
     .post "/jobs/#{JOB_ID}/operations", (body) -> _.omit(body, "request").should.be.eql bodyExpected
@@ -32,36 +43,15 @@
   context "when API response with bad status code", ->
     it "and dequeue counter is lower than MAX_DEQUEUE_COUNT, should throw an exception", ->
       _nockAPI(500, "something went wrong!")
-
       _processJob().should.be.rejected()

     it "and dequeue counter is greater than MAX_DEQUEUE_COUNT, should notify for fail to notificationsApi", ->
-      errorMessage = "it's a trap!"
-      badStatusCode = 500
-
-      bodyExpected =
-        statusCode: badStatusCode
-        message: errorMessage
-        success: no
-
-      _nockAPI badStatusCode, errorMessage
-
-      _notificationsApiNock bodyExpected
-
+      mockFailedNotificationWith 500
       _processJob(dequeueCount: 6)
       .tap -> nock.isDone().should.be.ok()

     it "equal to 400 then is success but should notify for fail to notificationsApi", ->
-      errorMessage = "it's a trap!"
-      badStatusCode = 400
-
-      bodyExpected =
-        statusCode: badStatusCode
-        message: errorMessage
-        success: no
-
-      _nockAPI badStatusCode, errorMessage
-      _notificationsApiNock bodyExpected
+      mockFailedNotificationWith 400

       _processJob()
       .tap -> nock.isDone().should.be.ok()


Add `choice` method in `rand` (#3682)

Add `choice` method in `rand` (#3682)

As in python random module. It can be useful for levels instead "someArray[@world.rand.rand(someArray.length)]".

--- app/lib/world/rand.coffee
+++ app/lib/world/rand.coffee
@@ -48,6 +48,9 @@
       arr[j] = arr[i]
       arr[i] = t
     arr
+
+  choice: (arr) =>
+    return arr[@rand arr.length]

 module.exports = Rand


Update zh-HANS.coffee

Update zh-HANS.coffee

--- app/locale/zh-HANS.coffee
+++ app/locale/zh-HANS.coffee
@@ -700,7 +700,7 @@
     more_info_2: "教师论坛"
     more_info_3: "是个与其他使用CodeCombat的教育工作者联系的良好平台。"

-#  teachers_quote:
+  teachers_quote:
     name: "申请表"
     title: "申请一个班级"
     subtitle: "在教室，兴趣小组，学校或社区使用CodeCombat"


Zoom is not reset on dragend event in markers

--- src/elements/Map/LeafletMap.coffee
+++ src/elements/Map/LeafletMap.coffee
@@ -59,7 +59,7 @@
 			)

 			@__selectedMarker.on('dragend', () =>
-				@__map.setView(@getSelectedMarkerPosition(), @_zoom)
+				@__map.setView(@getSelectedMarkerPosition())
 				@_onMarkerSelected?(@getSelectedMarkerPosition())
 			)


fix summary popups

fix summary popups

--- src/scripts/controllers/mapController.coffee
+++ src/scripts/controllers/mapController.coffee
@@ -95,6 +95,7 @@
       "end": @onMissionEnd
       "mode": @updateVehicleMessage
       "arm": @updateVehicleMessage
+      "update": @onMissionUpdate
       "mystery": @updateVehicleMessage
       "text": @updateVehicleMessage

@@ -114,15 +115,14 @@
     )

   onLive: (data) =>
-    # FIXME - not sure if I need apply... (or how to optimize it)
     # Apply should be called around the function to be guarded... -kevinh
     @scope.$apply(() =>
       key = @vehicleKey(data.missionId)
-      @updateVehicle(key, data)
-
-      # Grow (or create) our bounds
-      if @boundsFactory.expand(data.payload.lat, data.payload.lon)
-        @scope.bounds = @boundsFactory.bounds
+      @updateVehicle(key, data.payload.lat, data.payload.lon)
+
+      # Grow (or create) our bounds - We don't really use this feature much any more because starting with the whole world looks better
+      #if @boundsFactory.expand(data.payload.lat, data.payload.lon)
+      #  @scope.bounds = @boundsFactory.bounds
     )

   onAttitude: (data) =>
@@ -138,20 +138,40 @@
         #@log.debug("Can't set angle to #{v.iconAngle} because angular-leaflet is dumb and separates options from markers")
     )

-  onMissionStart: (data) ->
-    # creating empty markers is bad - causes null ref in leaflet
-    # @scope.vehicleMarkers[@vehicleKey(data.missionId)] ?= {}
+  # Oops - mission start and update message are formatted slightly differently FIXME
+  onMissionStart: (data) =>
+    @scope.$apply(() =>
+      key = @vehicleKey(data.mission.id)
+      lat = data.mission.latitude
+      lon = data.mission.longitude
+      if lat? && lon?
+        v = @updateVehicle(key, lat, lon)
+        angular.extend(v.payload, data.mission)
+    )
+
+  onMissionUpdate: (data) =>
+    @scope.$apply(() =>
+      key = @vehicleKey(data.missionId)
+      payload = data.payload
+      lat = payload.mission.latitude
+      lon = payload.mission.longitude
+      if lat? && lon?
+        v = @updateVehicle(key, lat, lon)
+        angular.extend(v.payload, payload.mission)
+    )

   onMissionEnd: (data) =>
     vehicleKey = @vehicleKey(data.missionId)
     delete @scope.vehicleMarkers[vehicleKey]
     delete @scope.vehiclePaths[vehicleKey]

-  updateVehicle: (vehicleKey, data) =>
+  # Returns the marker
+  updateVehicle: (vehicleKey, lat, lon) =>
     # We don't add new vehicles to the hashtable until fully inited (because I think angular has hooks watching for writes to @scope)
     v = @scope.vehicleMarkers[vehicleKey] ? {}
-    v.lat = data.payload.lat
-    v.lng = data.payload.lon
+    v.payload ?= {} # Provide a placeholder empty set of payload fields
+    v.lat = lat
+    v.lng = lon
     v.focus = false
     v.draggable = false
     # TODO: icons need to be better
@@ -162,14 +182,21 @@
         iconAnchor: [17.5, 17.5] # point of the icon which will correspond to marker's location
         popupAnchor: [0, -17.5] # point from which the popup should open relative to the inconAnchor
     @scope.vehicleMarkers[vehicleKey] = v
-    @motionTracking(vehicleKey, {lat: data.payload.lat, lng: data.payload.lon})
+    @motionTracking(vehicleKey, {lat: lat, lng: lon})
+    v

   updateVehicleMessage: (data) =>
-    # TODO: its really ugly and its doing nothing
-    # (kevinh - if we receive a message before we know the vehicle loc just drop it because having a marker without a loc freaks out leaflet)
-    vehicleKey = @vehicleKey(data.missionId)
-    if @scope.vehicleMarkers[vehicleKey]?
-      @scope.vehicleMarkers[vehicleKey].message ?= "lat: #{@scope.vehicleMarkers[vehicleKey].lat} - lon: #{@scope.vehicleMarkers[vehicleKey].lng} - #{JSON.stringify(data.payload)}"
+    @scope.$apply(() =>
+      # TODO: its really ugly and its doing nothing
+      # (kevinh - if we receive a message before we know the vehicle loc just drop it because having a marker without a loc freaks out leaflet)
+      vehicleKey = @vehicleKey(data.missionId)
+      marker = @scope.vehicleMarkers[vehicleKey]
+      if marker?
+        # We merge the misc payload fields into one dictionary - showing the latest combination of all data
+        angular.extend(marker.payload, data.payload)
+        p = marker.payload
+        marker.message = "User: #{p.userName}, summary: #{p.summaryText} - #{p.flightDuration / 60} minutes"
+    )

   motionTracking: (vehicleKey, latlng) =>
     v = @scope.vehiclePaths[vehicleKey] ? { color: '#f76944', weight: 7, latlngs: []}


Prerender HTML for performance and fix contextual links

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -2,6 +2,7 @@
   $ = require('jquery')
   _ = require('underscore')
   Mathjax = require('mathjax')
+  router = require('cs!router')
   EditableView = require('cs!helpers/backbone/views/editable')
   template = require('hbs!./body-template')
   require('less!./body')
@@ -23,6 +24,7 @@
         type: 'aloha'

     events:
+      'click a': 'changePage'
       'click [data-type="solution"] > .ui-toggle-wrapper > .ui-toggle': 'toggleSolution'
       'click .solution              > .ui-toggle-wrapper > .ui-toggle': 'toggleSolution'

@@ -43,53 +45,84 @@
       @setupModelListener()
       @render()

+    # Perform mutations to the HTML before loading it on to the page for better performance
+    renderDom: () ->
+      $temp = $('
').html(@getTemplate())
+
+      if @owner.get('loaded') and @model?.get('loaded') and @model?.get('active')
+        # Converts a TERP link to an OST-hosted iframe
+        $temp.find('a[href*="#terp-"]').each () ->
+          terpCode = $(this).attr('href').match(/#terp\-(.*)/)[1]
+          $(this).replaceWith("
+                               ")
+
+        # Remove the module title and abstract TODO: check if it is still necessary
+        $temp.children('[data-type="title"]').remove()
+        $temp.children('[data-type="abstract"]').remove()
+
+        # Wrap title and content elements in header and section elements, respectively
+        $temp.find('.example, .exercise, .note,
+                  [data-type="example"], [data-type="exercise"], [data-type="note"]').each (index, el) ->
+          $el = $(el)
+          $contents = $el.contents().filter (i, node) ->
+            return !$(node).is('.title, [data-type="title"]')
+          $contents.wrapAll('
')
+          $title = $el.children('.title, [data-type="title"]')
+          $title.wrap('')
+          # Add an attribute for the parents' `data-label`
+          # since CSS does not support `parent(attr(data-label))`.
+          # When the title exists, this attribute is added before it
+          $title.attr('data-label-parent', $el.attr('data-label'))
+          # Add a class for styling since CSS does not support `:has(> .title)`
+          # NOTE: `.toggleClass()` explicitly requires a `false` (not falsy) 2nd argument
+          $el.toggleClass('ui-has-child-title', $title.length > 0)
+
+        # Wrap solutions in a div so "Show/Hide Solutions" work
+        $temp.find('.solution, [data-type="solution"]')
+        .wrapInner('
')
+        .prepend('''
+

+            <button class="btn-link ui-toggle" title="Show/Hide Solution"></button>
+
''')
+
+        $temp.find('figure:has(> figcaption)').addClass('ui-has-child-figcaption')
+
+        # Move all figure captions below the figure
+        $temp.find('figcaption').each (i, el) ->
+          $(el).parent().append(el)
+
+        # Convert links to maintain context in a book, if appropriate
+        $temp.find('a').each (i, el) =>
+          $el = $(el)
+          page = @owner.getPage($el.attr('href').substr(10))
+
+          if page
+            pageNumber = page.getPageNumber()
+            $el.attr('href', "/contents/#{@owner.getVersionedId()}:#{pageNumber}")
+            $el.data('page', pageNumber)
+
+      @$el?.html($temp.html())
+
     onRender: () ->
       if not @model?.get('active') then return

+      # MathJax rendering must be done after the HTML has been added to the DOM
       MathJax?.Hub.Queue(['Typeset', MathJax.Hub], @$el.get(0))

-      # Converts a TERP link to an OST-hosted iframe
-      @$el.find('a[href*="#terp-"]').each () ->
-        terpCode = $(this).attr('href').match(/#terp\-(.*)/)[1]
-        $(this).replaceWith("
-                             ")
+    changePage: (e) ->
+      $el = $(e.currentTarget)
+      href = $el.attr('href')

-      # Remove the module title and abstract TODO: check if it is still necessary
-      @$el.children('[data-type="title"]').remove()
-      @$el.children('[data-type="abstract"]').remove()
+      if href.indexOf("/contents/#{@owner.getVersionedId()}:") is 0
+        e.preventDefault()
+        e.stopPropagation()

-      # Wrap title and content elements in header and section elements, respectively
-      @$el.find('.example, .exercise, .note,
-                [data-type="example"], [data-type="exercise"], [data-type="note"]').each (index, el) ->
-        $el = $(el)
-        $contents = $el.contents().filter (i, node) ->
-          return !$(node).is('.title, [data-type="title"]')
-        $contents.wrapAll('
')
-        $title = $el.children('.title, [data-type="title"]')
-        $title.wrap('')
-        # Add an attribute for the parents' `data-label`
-        # since CSS does not support `parent(attr(data-label))`.
-        # When the title exists, this attribute is added before it
-        $title.attr('data-label-parent', $el.attr('data-label'))
-        # Add a class for styling since CSS does not support `:has(> .title)`
-        # NOTE: `.toggleClass()` explicitly requires a `false` (not falsy) 2nd argument
-        $el.toggleClass('ui-has-child-title', $title.length > 0)
+        @owner.setPage($el.data('page'))

-      # Wrap solutions in a div so "Show/Hide Solutions" work
-      @$el.find('.solution, [data-type="solution"]')
-      .wrapInner('
')
-      .prepend('''
-

-          <button class="btn-link ui-toggle" title="Show/Hide Solution"></button>
-
''')
-
-      @$el.find('figure:has(> figcaption)').addClass('ui-has-child-figcaption')
-
-      # Move all figure captions below the figure
-      @$el.find('figcaption').each (i, el) ->
-        $(el).parent().append(el)
+        router.navigate href, {trigger: false}, () => #@trackNav()
+        #@scrollToTop()

     toggleSolution: (e) ->
       $solution = $(e.currentTarget).closest('.solution, [data-type="solution"]')


Tiny formatting tweak

Tiny formatting tweak

--- tests/request.spec.coffee
+++ tests/request.spec.coffee
@@ -263,11 +263,10 @@
 					if requestsSeen <= 2
 						Promise.reject(new Error('low-level network error'))
 					else
-						Promise.resolve(
+						Promise.resolve
 							body: result: 'success'
 							headers:
 								'Content-Type': 'application/json'
-						)

 			it 'should fail by default', ->
 				promise = request.send


Color the maximize button using the tab color scheme.

--- src/main/webapp/coffeescripts-hidden/load.coffee
+++ src/main/webapp/coffeescripts-hidden/load.coffee
@@ -296,6 +296,8 @@
       anchorTab.addClass("mobile-optimized-page")

     anchorTab.find(".maximize").text("Subscribe")
+    anchorTab.find(".maximize").attr("style", colorSchemeStyle)
+
     anchorTab.find(".minimize").text("Close")
   else
     anchorTab.addClass "desktop"