better logging

better logging

--- lib/main.coffee
+++ lib/main.coffee
@@ -70,9 +70,7 @@

     {editor, point, URI: lastURI} = @lastEditor.getInfo()
     if not @isLocked() and (lastURI isnt item.getURI())
-      # @history.add editor, point, lastURI
       @saveHistory editor, point, lastURI, dumpMessage: "[Pane item changed] save history"
-      # @history.dump "[Pane item changed] save history"

     @lastEditor.set item
     @debug "set LastEditor #{path.basename(item.getURI())}"
@@ -83,9 +81,7 @@
     return unless URI = editor.getURI()

     if @needRemember(oldBufferPosition, newBufferPosition, cursor)
-      # @history.add editor, oldBufferPosition, URI
       @saveHistory editor, oldBufferPosition, URI, dumpMessage: "[Cursor moved] save history"
-      # @history.dump "[Cursor moved] save history"

   # Throttoling save to history once per 500ms.
   # When activePaneItem change and cursorMove event happened almost at once.
@@ -139,7 +135,7 @@
       # Jump to different pane
       options =
         searchAllPanes: settings.get('searchAllPanes')
-
+
         # initialLine: point.row
         # initialColumn: point.column


Use Workspace::registerOpener

Use Workspace::registerOpener

--- lib/main.coffee
+++ lib/main.coffee
@@ -4,7 +4,7 @@

 module.exports =
   activate: ->
-    atom.project.registerOpener (uriToOpen) ->
+    atom.workspace.registerOpener (uriToOpen) ->
       return unless uriToOpen is viewUri
       @deprecationCopView = new DeprecationCopView(uriToOpen)


Make use of coffeescript's existential operator

--- lib/main.coffee
+++ lib/main.coffee
@@ -32,23 +32,23 @@
 ###
 check = (options, callback) ->
   # get options
-  token = options.token.trim()
-  repo = options.repo.trim()
-  owner = options.owner.trim()
-  currentVersion = options.currentVersion.trim()
+  token = options.token ? ''
+  repo = options.repo ? ''
+  owner = options.owner ? ''
+  currentVersion = options.currentVersion ? ''
   fetchTags = options.fetchTags || false

   # check if required options are defined
-  if token is undefined or token is ''
+  if token is ''
     callback('no token specified', null)
     return
-  if repo is undefined or repo is ''
+  if repo is ''
     callback('no repository specified', null)
     return
-  if owner is undefined or owner is ''
+  if owner is ''
     callback('no owner specified', null)
     return
-  if currentVersion is undefined or currentVersion is ''
+  if currentVersion is ''
     callback('no current version given', null)
     return


painter knows about player stats

painter knows about player stats

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -139,6 +139,7 @@
     @width = @canvas.width
     @height = @canvas.height
     @context = @canvas.getContext "2d"
+    @winner = []
     @color = '#00FFFF'
     @padding = 1
     @buildings = []
@@ -153,6 +154,7 @@
       @redraw_gorillas()
     else
       @empty = false
+      @clear()
       @draw_the_sun()
       @draw_buildings()
       @draw_gorillas()
@@ -220,17 +222,16 @@
       @f

   update_score: ->
-    console.log @player_1.games_won()
-    $('#score label').text(@player_1.games_won() + '>Score<' + @player_2.games_won())
+    console.log @winner
+    player_1 = @winner.filter((x) => x == 1).length
+    player_2 = @winner.filter((x) => x == 2).length
+
+    $('#score label').text(player_1 + '>Score<' + player_2)

   animate_banana:(player) ->
     @timeout = setTimeout (=>
       @draw_scene()
       if @banana_hit_gorilla(player) == true
-        @update_score()
-        @player_1.reset_state()
-        @player_2.reset_state()
-        @next_player_turn(player)
         return
       if @banana_has_collided(player) == true
         @next_player_turn(player)
@@ -260,7 +261,8 @@
     if @player_2.check_colission(x, y) || @player_1.check_colission(x, y)
       dead_player = if @player_2.dead == true then @player_2 else @player_1
       winner = if @player_2.dead == false then @player_2 else @player_1
-      winner.win()
+      @winner.push winner.player_number
+      @update_score()
       @timeout = setTimeout (=>
         @start_time = new Date()
         winner.animate = true
@@ -278,14 +280,20 @@

   animate_win:(player, @start_time) ->
     @timeout = setTimeout (=>
-      return unless player.animate == true and player.animations < 10
+      unless player.animate == true and player.animations < 12
+        @empty = true
+        @buildings = []
+        @draw_scene()
+        @next_player_turn(player)
+        return
+
       now = new Date()
       time = now - @start_time
       @draw_scene()
       player.animate_win()
       @animate_win(player, @start_time)
       ),
-      750
+      900

   next_player_turn:(player) ->
     next_player = if player.player_number == 2 then 1 else 2
@@ -350,17 +358,6 @@
       return true
     false

-  win: ->
-    @wins++
-
-  games_won: ->
-    @wins
-
-  reset_state: ->
-    @animations = 0
-    @dead = false
-    @aniamate = false
-
   right_hand_image: ->
     image = new Image()
     image.src = 'images/gorilla-right-hand-up.png'


fix error with empty mailbox

fix error with empty mailbox

--- server/controllers/mailboxes.coffee
+++ server/controllers/mailboxes.coffee
@@ -44,9 +44,9 @@
             return next err if err
             Mailbox.getCounts req.mailbox.id, (err, counts) ->
                 return next err if err
-                {total, recent, unread} = counts[req.mailbox.id]
-                req.mailbox.nbTotal = total
-                req.mailbox.nbUnread = unread
+                mailboxCounts = counts[req.mailbox.id]
+                req.mailbox.nbTotal = mailboxCounts?.total or 0
+                req.mailbox.nbUnread = mailboxCounts?.unread or 0
                 res.send req.mailbox


add isloading to mapdetailctrl

add isloading to mapdetailctrl

--- scripts/src/map/controllers.coffee
+++ scripts/src/map/controllers.coffee
@@ -74,8 +74,11 @@

 class MapMarkerDetailCtrl
         constructor: (@$scope, @Marker) ->
+                @$scope.isLoading = true
+
                 @$scope.marker = @Marker.get({markerId: 1}, (aMarker, getResponseHeaders) => # FIXME: HARDCODED VALUE
                         console.debug("marker loaded")
+                        @$scope.isLoading = false
                 )

 MapMarkerDetailCtrl.$inject = ['$scope', "Marker"]


stop animation if banana is outside boundaries

--- views/gorillas.coffee
+++ views/gorillas.coffee
@@ -173,7 +173,8 @@
     if player == 2
       angle = -angle
       force = -force
-    this['player_' + player].grab_banana(force, angle)
+    player = this['player_' + player]
+    player.grab_banana(force, angle)
     @timeout = setTimeout (=>
       @animate_banana(player)
       ),
@@ -182,11 +183,15 @@
   animate_banana:(player) ->
     @timeout = setTimeout (=>
       @draw_scene()
-      this['player_' + player].throw_banana()
+      return if @within_boundaries(player.banana.x(), player.banana.y()) == false
+      player.throw_banana()

       @animate_banana(player)
       ),
       150
+
+  within_boundaries:(x, y) ->
+    return false if x < 0 || x > @width || y > @height || y < 0

 class Gorilla
   constructor:(@context) ->
@@ -214,13 +219,19 @@

 class Banana
   constructor:(@context, @initx, @inity, @force, @angle) ->
-    @x = 0
-    @y = 0
+    @projection_x = 0
+    @projection_y = 0
     @g = 9.8
     @calculate_initial_position()

+  x: ->
+    @initx + @projection_x
+
+  y: ->
+    @inity - @projection_y
+
   draw: ->
-    @context.drawImage @image(), @initx+@x, @inity-@y
+    @context.drawImage @image(), @x(), @y()

   draw_frame: ->
     @draw()
@@ -232,9 +243,9 @@
     @dy = @force/Math.sin(radian)

   calculate_projection:() ->
-    @x += @dx
+    @projection_x += @dx
     @dy -= @g
-    @y += @dy
+    @projection_y += @dy

   image:() ->
     image = new Image()


:lipstick:

:lipstick:

--- src/browser/squirrel-update.coffee
+++ src/browser/squirrel-update.coffee
@@ -75,18 +75,16 @@
         callback(new Error('Registry query for PATH failed'))

   getPath (error, envPath) ->
-    if error?
-      callback(error)
-    else
-      console.log envPath
-      segments = envPath.split(';')
-      if segments.indexOf(binFolder) is -1
-        segments.push(binFolder)
+    return callback(error) if error?

-        console.log 'updating'
-        console.log segments.join(';')
-        args = ['add', environmentKeyPath, '/v', 'Path', '/d', segments.join(';'), '/f']
-        spawnReg(args, callback)
+    segments = envPath.split(';')
+    return callback() unless segments.indexOf(binFolder) is -1
+
+    segments.push(binFolder)
+    console.log 'updating'
+    console.log segments.join(';')
+    args = ['add', environmentKeyPath, '/v', 'Path', '/d', segments.join(';'), '/f']
+    spawnReg(args, callback)

 exports.spawn = spawnUpdate


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -13,6 +13,7 @@
   equals "String".constantize(), String, "look up a constant"
   equals "Math".constantize(), Math, "look up a constant"
   equals "Number".constantize(), Number, "look up a constant"
+  equals "Math.TAU".constantize(), Math.TAU, ""

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"


call preventDefault to stop scroll-to-top behaviour

--- src/components/sections-chooser.cjsx
+++ src/components/sections-chooser.cjsx
@@ -51,6 +51,7 @@
   isAnySelected:                -> _.any @props.chapter.children, @isSectionSelected
   toggleSectionSelections: (ev) ->
     ev.stopPropagation()
+    ev.preventDefault()
     selected = not @isAnySelected()
     newSelections = {}
     newSelections[section.id] = selected for section in @props.chapter.children


Turn off mini-graph icons

Turn off mini-graph icons

--- src/code/stores/graph-store.coffee
+++ src/code/stores/graph-store.coffee
@@ -29,7 +29,7 @@
     SimulationStore.actions.setDuration.listen             @resetSimulation.bind(@)
     SimulationStore.actions.simulationFramesCreated.listen @updateSimulationData.bind(@)

-    @codapStandaloneMode = true
+    @codapStandaloneMode = false

   resetSimulation: ->
     for node in @getNodes()


Add header to materials collection React component

--- app/assets/javascripts/components/materials_collection/materials_collection.js.coffee
+++ app/assets/javascripts/components/materials_collection/materials_collection.js.coffee
@@ -1,4 +1,4 @@
-{div, span, a, i} = React.DOM
+{div, span, a, i, h1} = React.DOM

 shuffle = (a) ->
   idx = a.length
@@ -10,6 +10,11 @@
   a

 window.MaterialsCollectionClass = React.createClass
+  getDefaultProps: ->
+    randomize: false
+    limit: Infinity
+    header: null
+
   getInitialState: ->
     materials: []
     truncated: true
@@ -45,18 +50,22 @@
     )

   render: ->
+    headerVisible = @props.header && @state.materials.length > 0
     (div {},
+      if headerVisible
+        (h1 {className: 'collection-header'}, @props.header)
       (SMaterialsList {materials: @getMaterialsList()})
       @renderTruncationToggle()
     )

 window.MaterialsCollection = React.createFactory MaterialsCollectionClass

+# Supported options: limit, randomize, header
+# Keep API backward compatible, so accept either 'limit' option as the last argument or hash.
 Portal.renderMaterialsCollection = (collectionId, selectorOrElement, limitOrOptions = Infinity) ->
-  # Keep API backward compatible, so accept either 'limit' option as the last argument or hash.
-  options = if typeof limitOrOptions == 'number'
-              {limit: limitOrOptions}
-            else
-              limitOrOptions
-  ReactDOM.render MaterialsCollection(collection: collectionId, limit: options.limit, randomize: options.randomize),
-    jQuery(selectorOrElement)[0]
+  props = if typeof limitOrOptions == 'number'
+            {limit: limitOrOptions}
+          else
+            limitOrOptions
+  props.collection = collectionId
+  ReactDOM.render MaterialsCollection(props), jQuery(selectorOrElement)[0]


add ripple UI enabling parameter to browser window opened by serve

--- src/steroids/Serve.coffee
+++ src/steroids/Serve.coffee
@@ -19,7 +19,11 @@

     startLocationURL = URL.parse(startLocation)

-    url = @baseURL + startLocationURL.path
+    # add ripple ui enabler parameter
+    url = URL.parse(@baseURL + startLocationURL.path)
+    url.query = {} unless url.query?
+    url.query["enableripple"] = "cordova"
+    url = URL.format(url)

     serveServer = Server.start
       port: @port


Flee if weak personality properly handles no enemy

--- src/definitions/personalities.coffee
+++ src/definitions/personalities.coffee
@@ -93,7 +93,7 @@
 	tick: (creature) ->
 		enemy = creature.findNearest 10, (e) -> e.type is 'creature'

-		creature.moveAwayFrom enemy
+		creature.moveAwayFrom enemy if enemy?

 		12


Refetch user to make sure groups are uptodate

--- app/backbone/views/users/react_user_groups_edit.coffee
+++ app/backbone/views/users/react_user_groups_edit.coffee
@@ -31,6 +31,7 @@
     group.save members: [ @props.user.get('username') ],
       success: (o)=>
         Factlink.notificationCenter.success "Group #{o.get('groupname')} created."
+        currentSession.user().fetch()
       error: (o)=>
         Factlink.notificationCenter.error  "Group #{o.get('groupname')} could not be created!"


I guess this has to be committed

I guess this has to be committed

--- server/app/controllers/Game.coffee
+++ server/app/controllers/Game.coffee
@@ -60,6 +60,7 @@

 			mediator.on '!game:new', (cb) =>
 				log.info 'attempting to create new game'
+				@savegamename = 'savegame-' + Date.now() + '.json'
 				teams = [
 						name: ''
 					,


new server port

new server port

--- server/config/server-config.coffee
+++ server/config/server-config.coffee
@@ -1,4 +1,4 @@
 module.exports =
-	port: 8080,
+	port: 3000,
 	secret: 'test',
 	cookieSecret: 'test'


fix catching empty configuration

fix catching empty configuration

--- src/javascripts/configuration.coffee
+++ src/javascripts/configuration.coffee
@@ -63,7 +63,7 @@
     )

   setConfigurations: (viaJSON) =>
-    return unless @configuration.podcast
+    return unless @configuration.episode
     @app.podcast = new Podcast(@app, @configuration.podcast || {})

     @app.extensionOptions = @configuration.extensions || {}


removed all nonsense launcher stuff

removed all nonsense launcher stuff


Form: Fixed option override for labeled inputs.

--- src/lib/components/forms/Form.coffee
+++ src/lib/components/forms/Form.coffee
@@ -64,9 +64,16 @@
     {name, label} = options

     if label
-      labeledInput   = new spark.components.LabeledInput
-        labelOptions : options
-        inputOptions : options
+      labelOptions = {}
+      inputOptions = {}
+
+      # Copy objects to make them separate since objects are passed by reference
+      for key, value of options
+        inputOptions[key] = value
+        labelOptions[key] = value
+
+      allOptions   = { labelOptions, inputOptions }
+      labeledInput = new spark.components.LabeledInput allOptions

       @inputsContainer.appendView labeledInput
       @inputs.push input = labeledInput.input


cleanup

cleanup

--- chrome/content/zotero-better-bibtex/preferences.coffee
+++ chrome/content/zotero-better-bibtex/preferences.coffee
@@ -26,8 +26,8 @@
   return "http://localhost:#{serverPort}/better-bibtex/#{url}"

 Zotero.BetterBibTeX.pref.styleChanged = (index) ->
-  listbox = document.getElementById('better-bibtex-abbrev-style')
-  selectedItem = if index != 'undefined' then listbox.getItemAtIndex(index) else listbox.selectedItem
+  stylebox = document.getElementById('better-bibtex-abbrev-style')
+  selectedItem = if index != 'undefined' then stylebox.getItemAtIndex(index) else stylebox.selectedItem
   styleID = selectedItem.getAttribute('value')
   Zotero.BetterBibTeX.pref.set('auto-abbrev.style', styleID)
   Zotero.BetterBibTeX.keymanager.journalAbbrevCache = Object.create(null)
@@ -56,8 +56,8 @@

   styles = (style for style in Zotero.Styles.getVisible() when style.usesAbbreviation)

-  listbox = document.getElementById('better-bibtex-abbrev-style')
-  fillList = listbox.children.length is 0
+  stylebox = document.getElementById('better-bibtex-abbrev-style')
+  fillList = stylebox.children.length is 0
   selectedStyle = Zotero.BetterBibTeX.pref.get('auto-abbrev.style')
   selectedIndex = -1
   for style, i in styles
@@ -65,13 +65,13 @@
       itemNode = document.createElement('listitem')
       itemNode.setAttribute('value', style.styleID)
       itemNode.setAttribute('label', style.title)
-      listbox.appendChild(itemNode)
+      stylebox.appendChild(itemNode)
     if style.styleID is selectedStyle then selectedIndex = i
   if selectedIndex is -1 then selectedIndex = 0
   @styleChanged(selectedIndex)

   window.setTimeout((->
-    listbox.ensureIndexIsVisible(selectedIndex)
-    listbox.selectedIndex = selectedIndex
+    stylebox.ensureIndexIsVisible(selectedIndex)
+    stylebox.selectedIndex = selectedIndex
     return), 0)
   return


admin js - added button action import-csv, updated poll $tmpl

--- app/assets/javascripts/admin/index.js.coffee
+++ app/assets/javascripts/admin/index.js.coffee
@@ -1,59 +1,11 @@
 jQuery ->
-  $.template "newChoice", "
<input id=\"poll_choices_attributes_${count}_content\" name=\"poll[choices_attributes][${count}][content]\" size=\"30\" type=\"text\">
<input id=\"poll_choices_attributes_${count}_image\" name=\"poll[choices_attributes][${count}][image]\" type=\"file\"> [-]"
-  $("a.add").live "click", (e) ->
-    choice = count: $(".choice").size() + 1
-    $.tmpl("newChoice", choice).appendTo "#choices"
-
-  $("a.remove").live "click", (e) ->
-    $(this).parents(".choice").remove()
-
-  $(".locate").click (e) ->
-    e.preventDefault()
-    form = $("#" + $(this).attr("data-form-id"))
-    address = $("#event_location_attributes_address", form).val()
-    latitude = $("#event_location_attributes_latitude", form)
-    longitude = $("#event_location_attributes_longitude", form)
-    callback = ((loc) ->
-      latitude.val loc.lat()
-      longitude.val loc.lng()
-      form.submit()
-    )
-    fp.maps.find address, callback
-
-  $(".location_update_form").bind("ajax:beforeSend", ->
-    $(".locate", this).html "Loading..."
-  ).bind("ajax:success", (evt, data, status, xhr) ->
-    event = $("#event_" + data._id)
-    $(".event-latitude", event).html data.location.latitude
-    $(".event-longitude", event).html data.location.longitude
-  ).bind("ajax:error", (evt, data, status, xhr) ->
-    alert "An error occured getting the coordinates for this event."
-  ).bind "ajax:complete", (evt, data, status, xhr) ->
-    $(".locate", this).html "Locate"
-
-  $(".datetimepicker").datetimepicker
-    ampm: true
-    dateFormat: "yy-mm-dd"
-    timeFormat: "hh:mm:ss TT"
-
-  $("#submit-event").click (e) ->
-    e.preventDefault()
-    address = $("#location_address").val()
-    if address is ""
-      alert "You must provide an address!"
-      return
-    callback = ((loc) ->
-      $("#location_latitude").val loc.lat()
-      $("#location_longitude").val loc.lng()
-      $(".event-form").submit()
-    )
-    fp.maps.find address, callback
   # Admin - Project dashboard
   $(".table tr").hover (->
     $("td .actions", this).show()
   ), ->
     $("td .actions", this).hide()

+  # Buttons
   $(".delete").bind("ajax:beforeSend", ->
     $(this).parents("table").siblings(".alert").empty()
   ).bind("ajax:success", ->
@@ -112,4 +64,62 @@
       $(this).text("Activate").removeClass("deactivate").addClass("activate").attr "href", newLink
       $(this).parents("tr").removeClass("active").addClass "inactive"
     ).bind "ajax:error", ->
-      $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."+      $(this).parents("table").siblings(".alert").append "An Error has occured. Please try again."
+
+  # Admin - Poll form
+  $.template "newChoice", "
 <input id=\"poll_choices_attributes_${count}_content\" name=\"poll[choices_attributes][${count}][content]\" size=\"30\" type=\"text\">  <input id=\"poll_choices_attributes_${count}_image\" name=\"poll[choices_attributes][${count}][image]\" type=\"file\">
"
+
+  $("a.add").live "click", (e) ->
+    choice = count: $(".choice").size() + 1
+    $.tmpl("newChoice", choice).appendTo "#choices .controls"
+
+  $("a.remove").live "click", (e) ->
+    $(this).parents(".choice").remove()
+
+  # Admin - Event form
+  $(".import").click (e) ->
+    e.preventDefault
+    $(".import-csv").toggle()
+
+  $(".locate").click (e) ->
+    e.preventDefault()
+    form = $("#" + $(this).attr("data-form-id"))
+    address = $("#event_location_attributes_address", form).val()
+    latitude = $("#event_location_attributes_latitude", form)
+    longitude = $("#event_location_attributes_longitude", form)
+    callback = ((loc) ->
+      latitude.val loc.lat()
+      longitude.val loc.lng()
+      form.submit()
+    )
+    fp.maps.find address, callback
+
+  $(".location_update_form").bind("ajax:beforeSend", ->
+    $(".locate", this).html "Loading..."
+  ).bind("ajax:success", (evt, data, status, xhr) ->
+    event = $("#event_" + data._id)
+    $(".event-latitude", event).html data.location.latitude
+    $(".event-longitude", event).html data.location.longitude
+  ).bind("ajax:error", (evt, data, status, xhr) ->
+    alert "An error occured getting the coordinates for this event."
+  ).bind "ajax:complete", (evt, data, status, xhr) ->
+    $(".locate", this).html "Locate"
+
+  $("#submit-event").click (e) ->
+    address = $("#location_address").val()
+    if address is ""
+      alert "You must provide an address!"
+      return
+    callback = ((loc) ->
+      $("#location_latitude").val loc.lat()
+      $("#location_longitude").val loc.lng()
+      $(".event-form").submit()
+    )
+    fp.maps.find address, callback
+    e.preventDefault()
+
+  # Datetime picker plugin
+  $(".datetimepicker").datetimepicker
+    ampm: true
+    dateFormat: "yy-mm-dd"
+    timeFormat: "hh:mm:ss TT"


Remove console.log

Remove console.log

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -2,4 +2,3 @@
 #= require_self

 Turbolinks.pagesCached(0)
-console.log 'yeah hi'


remove js-routes from manifest file

remove js-routes from manifest file

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -1,5 +1,4 @@
 #= require jquery
 #= require jquery_ujs
-#= require js-routes
 #= require bootstrap
 #= require_tree .


correct typo

correct typo

--- app/assets/javascripts/interface_test.coffee
+++ app/assets/javascripts/interface_test.coffee
@@ -21,7 +21,7 @@
     # fake annotation_lifecycle functionallity
     window[$staging.data('interface-type-name')] = new AnnotationLifecycleFaker()

-    # render templat with test payload & append to DOM
+    # render template with test payload & append to DOM
     mustacheParsedTemplate = Mustache.render(
       $staging.data('template'),
       $staging.data('test-payload')


activity: fix infinite scrollview problem for SingleActivityPane

--- client/Social/Activity/views/singleactivitypane.coffee
+++ client/Social/Activity/views/singleactivitypane.coffee
@@ -5,6 +5,8 @@
     options.cssClass = KD.utils.curry 'single-activity', options.cssClass

     super options, data
+
+    @scrollView.wrapper.off 'LazyLoadThresholdReached'

   viewAppended: ->


Instantiate "own" type machine list for managed VMs

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -615,7 +615,13 @@
         stackEnvironment = stack.machines.map (machine) ->
           environmentMap[machine._id]

-        @createMachineList 'stack', { title, stack }, stackEnvironment
+        type = switch
+          when title is 'Managed VMs' then 'own'
+          else 'stack'
+
+        options = { title, stack }
+
+        @createMachineList type, options, stackEnvironment

       inProgress = no


ActivitySidebar: Comment out watchMachineState method.

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -683,29 +683,29 @@
     # localStorage.setValue 'LatestWorkspace', minimumDataToStore

-  watchMachineState: (data) ->
-
-    {workspace, machine} = data
-
-    @watchedMachines  or= {}
-
-    {Running} = Machine.State
-
-    return  if @watchedMachines[machine._id]
-
-    callback = (state) =>
-      return  if state.status isnt Running
-
-      machine.status.state = Running
-      delete @watchedMachines[machine._id]
-
-      if data is @latestWorkspaceData
-        @selectWorkspace data
-
-    kd.getSingleton 'computeController'
-      .on "public-#{machine._id}", callback
-
-    @watchedMachines[machine._id] = yes
+  # watchMachineState: (data) ->
+
+  #   {workspace, machine} = data
+
+  #   @watchedMachines  or= {}
+
+  #   {Running} = Machine.State
+
+  #   return  if @watchedMachines[machine._id]
+
+  #   callback = (state) =>
+  #     return  if state.status isnt Running
+
+  #     machine.status.state = Running
+  #     delete @watchedMachines[machine._id]
+
+  #     if data is @latestWorkspaceData
+  #       @selectWorkspace data
+
+  #   kd.getSingleton 'computeController'
+  #     .on "public-#{machine._id}", callback
+
+  #   @watchedMachines[machine._id] = yes

   fetchMachines: (callback) ->


I don't know git enough, it seems

I don't know git enough, it seems

--- spec/server/frobnicator.spec.coffee
+++ spec/server/frobnicator.spec.coffee
@@ -1,26 +1,26 @@
-f = require '../../src/server/frobnicator'
-frob = f.frob
-eyes = require 'eyes'
-
-describe 'Frobnicator', ->
-    it 'keeps an empty object', ->
-        expect(frob {}, {}).toEqual {}
-    describe 'list mapper', ->
-        fooSpec = foos: f.listToIndex 'foo', 'id'
-        it 'does a simple mapping', ->
-            input = foos: foo: [{ id: 1}, { id:2 }]
-            expect(frob input, fooSpec).toEqual foos: { '1': { id: 1}, '2': {id: 2}}
-        it 'does an empty mapping if the property is the empty list', ->
-            input = foos: foo: []
-            expect(frob input, fooSpec).toEqual foos: {}
-        it 'throws if element is not an array', ->
-            input = foos: foo: 'bar'
-            expect(-> frob input, fooSpec).toThrow()
-        it 'throws if an unexpected element exists', ->
-            input = foos: { foo: [], bar: true }
-            expect(-> frob input, fooSpec).toThrow()
-        it 'throws if an element does not contain the correct key', ->
-            input = foos: foo: [ { bar: true } ]
-            expect(-> frob input, fooSpec).toThrow()
-        it 'has a useful toString()', ->
-            expect(f.listToIndex('el', 'id').toString()).toEqual('ListToIndex(el, id)')
+f = require '../../src/server/frobnicator'
+frob = f.frob
+eyes = require 'eyes'
+
+describe 'Frobnicator', ->
+    it 'keeps an empty object', ->
+        expect(frob {}, {}).toEqual {}
+    describe 'list mapper', ->
+        fooSpec = foos: f.listToIndex 'foo', 'id'
+        it 'does a simple mapping', ->
+            input = foos: foo: [{ id: 1}, { id:2 }]
+            expect(frob input, fooSpec).toEqual foos: { '1': { id: 1}, '2': {id: 2}}
+        it 'does an empty mapping if the property is the empty list', ->
+            input = foos: foo: []
+            expect(frob input, fooSpec).toEqual foos: {}
+        it 'throws if element is not an array', ->
+            input = foos: foo: 'bar'
+            expect(-> frob input, fooSpec).toThrow()
+        it 'throws if an unexpected element exists', ->
+            input = foos: { foo: [], bar: true }
+            expect(-> frob input, fooSpec).toThrow()
+        it 'throws if an element does not contain the correct key', ->
+            input = foos: foo: [ { bar: true } ]
+            expect(-> frob input, fooSpec).toThrow()
+        it 'has a useful toString()', ->
+            expect(f.listToIndex('el', 'id').toString()).toEqual('ListToIndex(el, id)')


Tests for up.fragment.subtree()

Tests for up.fragment.subtree()

--- spec/unpoly/fragment_spec.js.coffee
+++ spec/unpoly/fragment_spec.js.coffee
@@ -292,6 +292,76 @@
         result = up.fragment.closest($element[0], '.match')
         expect(result).toBe($grandMother[0])

+    describe 'up.fragment.subtree()', ->
+
+      it 'returns all descendants of the given root matching the given selector', ->
+        $element = $fixture('.element')
+        $matchingChild = $element.affix('.child.match')
+        $matchingGrandChild = $matchingChild.affix('.grand-child.match')
+        $otherChild = $element.affix('.child')
+        $otherGrandChild = $otherChild.affix('.grand-child')
+        results = up.fragment.subtree($element[0], '.match')
+        expect(results).toEqual [$matchingChild[0], $matchingGrandChild[0]]
+
+      it 'includes the given root if it matches the selector', ->
+        $element = $fixture('.element.match')
+        $matchingChild = $element.affix('.child.match')
+        $matchingGrandChild = $matchingChild.affix('.grand-child.match')
+        $otherChild = $element.affix('.child')
+        $otherGrandChild = $otherChild.affix('.grand-child')
+        results = up.fragment.subtree($element[0], '.match')
+        expect(results).toEqual [$element[0], $matchingChild[0], $matchingGrandChild[0]]
+
+      it 'does not return ancestors of the root, even if they match', ->
+        $parent = $fixture('.parent.match')
+        $element = $parent.affix('.element')
+        results = up.fragment.subtree($element[0], '.match')
+        expect(results).toEqual []
+
+      it 'returns an empty list if neither root nor any descendant matches', ->
+        $element = $fixture('.element')
+        $child = $element.affix('.child')
+        results = up.fragment.subtree($element[0], '.match')
+        expect(results).toEqual []
+
+      it 'ignores descendants that are being destroyed', ->
+        element = fixture('.element')
+        activeChild = e.affix(element, '.child')
+        destroyingChild = e.affix(element, '.child.up-destroying')
+
+        results = up.fragment.subtree(element, '.child')
+
+        expect(results).toEqual [activeChild]
+
+      it 'supports non-standard selector extensions like :has()', ->
+        element = fixture('.element')
+        matchingChild = e.affix(element, '.child')
+        requiredGrandChild = e.affix(matchingChild, '.grand-child')
+        otherChild = e.affix(element, '.child.up-destroying')
+
+        results = up.fragment.subtree(element, '.child:has(.grand-child)')
+
+        expect(results).toEqual [matchingChild]
+
+      it 'matches descendants in a detached tree', ->
+        detachedTree = fixture('.element')
+        matchingChild = e.affix(detachedTree, '.child')
+        otherChild = e.affix(detachedTree, '.other-child')
+
+        results = up.fragment.subtree(detachedTree, '.child')
+
+        expect(results).toEqual [matchingChild]
+
+      it 'matches descendants in a detached tree when the current layer is an overlay (bugfix)', ->
+        makeLayers(2)
+
+        detachedTree = fixture('.element')
+        matchingChild = e.affix(detachedTree, '.child')
+        otherChild = e.affix(detachedTree, '.other-child')
+
+        results = up.fragment.subtree(detachedTree, '.child')
+
+        expect(results).toEqual [matchingChild]

     describe 'up.render()', ->


Automate cmdize sources

Automate cmdize sources

--- webpack/cmdize.coffee
+++ webpack/cmdize.coffee
@@ -2,28 +2,24 @@
 # Wrap .js into .bat
 # http://www.dostips.com/forum/viewtopic.php?p=37780#p37780
 #
-z = require "combined-stream2"
 fs = require "fs"
-path = require "path"
-
-js2bat = """
-0</*! ::
-@echo off
-cscript //nologo //e:javascript "%~f0" %*
-goto :EOF */0;
-
-"""

 module.exports =
 me = (options)->

 me::apply = (compiler)->
   compiler.plugin "done", (compilation)->
-    x = z.create()
-    x.append new Buffer js2bat
-    x.append input = fs.createReadStream 'tmp/nvms.js'
-    x.append new Buffer "\n"
-    x.pipe fs.createWriteStream 'tmp/nvms.bat'
+    for k, z of compilation.compilation.assets
+      dst = z.existsAt
+      continue unless /[.]js$/.test dst
+      fs.unlink dst
+      dst = dst.replace /[.].*?$/, '.bat'
+      fs.writeFile dst, """
+0</*! ::
+@echo off
+cscript //nologo //e:javascript "%~f0" %*
+goto :EOF */0;
+#{do z.source}

-    input.on 'end', ->
-      fs.unlink 'tmp/nvms.js'
+      """
+    return


Traduction de l'anglais vers l'arabe 0.3

Ajout de traducton

--- app/locale/ar.coffee
+++ app/locale/ar.coffee
@@ -108,7 +108,7 @@
     logging_in: "جاري تسجيل الدخول"
     log_out: "تسجيل الخروج"
     forgot_password: "نسيت رمز الدخول?"
-#    authenticate_gplus: "مصادقة G+"
+    authenticate_gplus: "مصادقة G+"
     load_profile: "تحميل صفحة غوغل بلس"
     finishing: "الانتهاء"
     sign_in_with_facebook: "سجل الدخول بواسطة فيسبوك"
@@ -178,10 +178,10 @@
     version_history: "تاريخ النسخة"
     version_history_for: "تاريخ النسخة لل: "
     select_changes: "اختر تغيريين."
-#    undo_prefix: "Undo"
-#    undo_shortcut: "(Ctrl+Z)"
-#    redo_prefix: "Redo"
-#    redo_shortcut: "(Ctrl+Shift+Z)"
+    undo_prefix: "فك"
+    undo_shortcut: "(Ctrl+Z)"
+    redo_prefix: "Redo"
+    redo_shortcut: "(Ctrl+Shift+Z)"
     play_preview: "شاهد مقطع فيديو عن المستوى الحالي"
     result: "نتيجة"
     results: "نتائج"
@@ -227,7 +227,7 @@

   play_level:
     done: "انتهاء"
-#    home: "Home" # Not used any more, will be removed soon.
+    home: "Home" # Not used any more, will be removed soon.
     level: "مستوى" # Like "Level: Dungeons of Kithgard"
     skip: "الغاء"
     game_menu: "قائمة اللعبة"
@@ -240,25 +240,25 @@
     incomplete: "غير مكتمل"
     timed_out: "تم انتهاء الوقت"
     failing: "فشل"
-#    action_timeline: "Action Timeline"
-#    click_to_select: "Click on a unit to select it."
-#    control_bar_multiplayer: "Multiplayer"
-#    control_bar_join_game: "Join Game"
-#    reload: "Reload"
-#    reload_title: "Reload All Code?"
-#    reload_really: "Are you sure you want to reload this level back to the beginning?"
-#    reload_confirm: "Reload All"
-#    victory: "Victory"
-#    victory_title_prefix: ""
-#    victory_title_suffix: " Complete"
-#    victory_sign_up: "Sign Up to Save Progress"
-#    victory_sign_up_poke: "Want to save your code? Create a free account!"
-#    victory_rate_the_level: "Rate the level: " # Only in old-style levels.
-#    victory_return_to_ladder: "Return to Ladder"
-#    victory_play_continue: "Continue"
-#    victory_saving_progress: "Saving Progress"
-#    victory_go_home: "Go Home" # Only in old-style levels.
-#    victory_review: "Tell us more!" # Only in old-style levels.
+    action_timeline: "عمل الجدول الزمني"
+    click_to_select: "انقر على وحدة لتحديده."
+    control_bar_multiplayer: "متعددة"
+    control_bar_join_game: "تاريخ لعبة"
+    reload: "تحديث"
+    reload_title: "تحديث الصفحة كل رمز؟"
+    reload_really: "هل أنت متأكد أنك تريد تحميل هذا المستوى مرة أخرى إلى البداية؟"
+    reload_confirm: "تحديث جميع"
+    victory: "فوز"
+    victory_title_prefix: ""
+    victory_title_suffix: " كامل"
+    victory_sign_up: "اشترك لإنقاذ التقدم"
+    victory_sign_up_poke: "تريد حفظ التعليمات البرمجية الخاصة بك؟ إنشاء حساب مجاني!"
+    victory_rate_the_level: "معدل المستوى: " # Only in old-style levels.
+    victory_return_to_ladder: "العودة إلى سلم"
+    victory_play_continue: "استمر"
+    victory_saving_progress: "توفير التقدم"
+    victory_go_home: "اذهب للمنزل" # Only in old-style levels.
+    victory_review: "إخبرنا المزيد!" # Only in old-style levels.
 #    victory_hour_of_code_done: "Are You Done?"
 #    victory_hour_of_code_done_yes: "Yes, I'm finished with my Hour of Code™!"
 #    victory_experience_gained: "XP Gained"


Added Tips

Added Tips

Added english translations for the tips added to the loading tips.

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -322,6 +322,8 @@
     tip_lines_of_code: "Measuring programming progress by lines of code is like measuring aircraft building progress by weight. — Bill Gates"
     tip_source_code: "I want to change the world but they would not give me the source code."
     tip_javascript_java: "Java is to JavaScript what Car is to Carpet. - Chris Heilmann"
+    tip_google: "Have a problem you can't solve? Google it!"
+    tip_adding_evil: "Adding a pinch of evil."

   game_menu:
     inventory_tab: "Inventory"


Fix a few more Hebrew translations

Fix a few more Hebrew translations

--- app/locale/he.coffee
+++ app/locale/he.coffee
@@ -7,9 +7,9 @@
     im_a_teacher: "אני מורה"
     im_a_student: "אני תלמיד"
     learn_more: "מידע נוסף"
-    classroom_in_a_box: "כיתה שלמה בקופסה ללימוד מדעי המחשב."
-    codecombat_is: "CodeCombat היא פלטפורמה לתלמידים, המיועדת ללימוד מדעי המחשב תוך כדי חוויית משחק אמיתית."
-    our_courses: "הקורסים שלנו נבדקו במיוחד כדי להבטיח התאמתם לסביבה הכיתתית, אפילו אצל מורים עם ניסיון מועט בתכנות או ללא ניסיון כלל."
+    classroom_in_a_box: "כיתה ללימוד מדעי מחשב בתוך קופסא."
+    codecombat_is: "CodeCombat היא פלטפורמה לתלמידים, ללימוד מדעי המחשב, תוך כדי חויית משחק מחשב אמיתי."
+    our_courses: "הקורסים שלנו נבנו לאפשר הגעה לרמת מצויינו, גם עבור מורים חסרי ניסיון או בעלי מעט ניסיון קודם בתחום התכנות."
 #    watch_how: "Watch how CodeCombat is transforming the way people learn computer science."
     top_screenshots_hint: "התלמידים כותבים קוד, ורואים את השינויים שלהם מתעדכנים בזמן אמת"
     designed_with: "תוכנן מתוך חשיבה על מורים"


EM show/hide Quick Guide

EM show/hide Quick Guide

--- app/assets/javascripts/em.js.coffee
+++ app/assets/javascripts/em.js.coffee
@@ -8,4 +8,14 @@
 	showActive reports

 	window.onhashchange = -> showActive reports
+
+	$('a.show-quick-guide').click ->
+		$('.em-quick-guide').show()
+		$('a.hide-quick-guide').show()
+		$(this).hide()
+
+	$('a.hide-quick-guide').click ->
+		$('.em-quick-guide').hide()
+		$('a.show-quick-guide').show()
+		$(this).hide()
 )


Update hu.coffee

Update hu.coffee

--- app/locale/hu.coffee
+++ app/locale/hu.coffee
@@ -3,7 +3,7 @@
     loading: "Töltés..."
     saving: "Mentés..."
     sending: "Küldés..."
-#    send: "Send"
+    send: "Küldés indítása"
     cancel: "Mégse"
     save: "Mentés"
 #    publish: "Publish"
@@ -69,7 +69,7 @@
   login:
     sign_up: "Regisztráció"
     log_in: "Bejelentkezés"
-#    logging_in: "Logging In"
+    logging_in: "Bejelentkezés"
     log_out: "Kijelentkezés"
     recover: "meglévő fiók visszaállítása"

@@ -194,8 +194,8 @@

   account_profile:
 #    settings: "Settings"
-#    edit_profile: "Edit Profile"
-#    done_editing: "Done Editing"
+    edit_profile: "Szerkeszd meg a profilodat"
+    done_editing: "Szerkesztés kész"
     profile_for_prefix: "Profil "
     profile_for_suffix: " számára"
 #    featured: "Featured"
@@ -203,14 +203,14 @@
     looking_for: "Keres:"
     last_updated: "Legutóbb napra-készre hozva:"
     contact: "Kapcsolat"
-#    active: "Looking for interview offers now"
-#    inactive: "Not looking for offers right now"
+    active: "Azonnali interjú ajánlatokat keresek"
+    inactive: "Most éppen nem keresek interjú ajánlatokat"
 #    complete: "complete"
-#    next: "Next"
-#    next_city: "city?"
+    next: "Következő"
+    next_city: "Város?"
 #    next_country: "pick your country."
-#    next_name: "name?"
-#    next_short_description: "write a short description."
+    next_name: "Név?"
+    next_short_description: "adj egy rövid leírást."
 #    next_long_description: "describe your desired position."
 #    next_skills: "list at least five skills."
 #    next_work: "chronicle your work history."
@@ -219,7 +219,7 @@
 #    next_links: "add any personal or social links."
 #    next_photo: "add an optional professional photo."
 #    next_active: "mark yourself open to offers to show up in searches."
-#    example_blog: "Blog"
+    example_blog: "Blog"
 #    example_personal_site: "Personal Site"
 #    links_header: "Personal Links"
 #    links_blurb: "Link any other sites or profiles you want to highlight, like your GitHub, your LinkedIn, or your blog."


Update ko.coffee

Update ko.coffee

--- app/locale/ko.coffee
+++ app/locale/ko.coffee
@@ -512,7 +512,7 @@
     fork_title: "새 버전 가져오기"
     fork_creating: "포크 생성중..."
 #    randomize: "Randomize"
-#    more: "More"
+    more: "더 보기"
     wiki: "위키"
     live_chat: "실시간 채팅"
     level_some_options: "다른 옵션들?"
@@ -782,10 +782,10 @@
     simulate_all: "리셋하고 시뮬레이션 하기"
 #    games_simulated_by: "Games simulated by you:"
 #    games_simulated_for: "Games simulated for you:"
-#    games_simulated: "Games simulated"
-#    games_played: "Games played"
+    games_simulated: "시뮬레이션 실행된 게임"
+    games_played: "플레이한 게임"
 #    ratio: "Ratio"
-#    leaderboard: "Leaderboard"
+    leaderboard: "상위권 순위 차트"
 #    battle_as: "Battle as "
 #    summary_your: "Your "
 #    summary_matches: "Matches - "
@@ -793,27 +793,27 @@
 #    summary_losses: " Losses"
 #    rank_no_code: "No New Code to Rank"
 #    rank_my_game: "Rank My Game!"
-#    rank_submitting: "Submitting..."
+    rank_submitting: "제출중..."
 #    rank_submitted: "Submitted for Ranking"
-#    rank_failed: "Failed to Rank"
+    rank_failed: "순위 매기기 실패"
 #    rank_being_ranked: "Game Being Ranked"
-#    rank_last_submitted: "submitted "
+    rank_last_submitted: "제출 완료"
 #    help_simulate: "Help simulate games?"
 #    code_being_simulated: "Your new code is being simulated by other players for ranking. This will refresh as new matches come in."
 #    no_ranked_matches_pre: "No ranked matches for the "
 #    no_ranked_matches_post: " team! Play against some competitors and then come back here to get your game ranked."
 #    choose_opponent: "Choose an Opponent"
-#    select_your_language: "Select your language!"
-#    tutorial_play: "Play Tutorial"
-#    tutorial_recommended: "Recommended if you've never played before"
-#    tutorial_skip: "Skip Tutorial"
+    select_your_language: "언어를 고르세요!"
+    tutorial_play: "튜토리얼 보기"
+    tutorial_recommended: "전에 플레이해본 적이 없으시다면 튜토리얼을 보시는 걸 권장합니다."
+    tutorial_skip: "튜토리얼 넘기기"
 #    tutorial_not_sure: "Not sure what's going on?"
 #    tutorial_play_first: "Play the Tutorial first."
 #    simple_ai: "Simple AI"
 #    warmup: "Warmup"
-#    vs: "VS"
+    vs: "VS"
 #    friends_playing: "Friends Playing"
-#    log_in_for_friends: "Log in to play with your friends!"
+    log_in_for_friends: "로그인하시고 친구들과 게임을 즐기세요!"
 #    social_connect_blurb: "Connect and play against your friends!"
 #    invite_friends_to_battle: "Invite your friends to join you in battle!"
 #    fight: "Fight!"
@@ -824,8 +824,8 @@
 #    tournament_rules: "Tournament Rules"
 #    tournament_blurb: "Write code, collect gold, build armies, crush foes, win prizes, and upgrade your career in our $40,000 Greed tournament! Check out the details"
 #    tournament_blurb_blog: "on our blog"
-#    rules: "Rules"
-#    winners: "Winners"
+    rules: "규칙"
+    winners: "승리자"

 #  ladder_prizes:
 #    title: "Tournament Prizes"
@@ -844,7 +844,7 @@
 #    credits: "credits"
 #    one_month_coupon: "coupon: choose either Rails or HTML"
 #    one_month_discount: "discount, 30% off: choose either Rails or HTML"
-#    license: "license"
+    license: "라이센스"
 #    oreilly: "ebook of your choice"

 #  multiplayer_launch:
@@ -866,7 +866,7 @@
     connection_failure: "연결 실패"
 #    unauthorized: "You need to be signed in. Do you have cookies disabled?"
     forbidden: "권한이 필요합니다."
-#    not_found: "Not found."
+    not_found: "찾을 수 없습니다."
 #    not_allowed: "Method not allowed."
     timeout: "서버 타임아웃"
 #    conflict: "Resource conflict."
@@ -876,20 +876,20 @@

 #  resources:
 #    your_sessions: "Your Sessions"
-#    level: "Level"
+    level: "레벨"
 #    social_network_apis: "Social Network APIs"
 #    facebook_status: "Facebook Status"
-#    facebook_friends: "Facebook Friends"
+    facebook_friends: "페이스북 친구들"
 #    facebook_friend_sessions: "Facebook Friend Sessions"
-#    gplus_friends: "G+ Friends"
+    gplus_friends: "구글 플러스 친구들"
 #    gplus_friend_sessions: "G+ Friend Sessions"
-#    leaderboard: "Leaderboard"
+    leaderboard: "상위권 순위 차트"
 #    user_schema: "User Schema"
-#    user_profile: "User Profile"
+    user_profile: "유저 프로필"
 #    patches: "Patches"
 #    patched_model: "Source Document"
 #    model: "Model"
-#    system: "System"
+    system: "시스템"
 #    component: "Component"
 #    components: "Components"
 #    thang: "Thang"
@@ -899,7 +899,7 @@
 #    article: "Article"
 #    user_names: "User Names"
 #    thang_names: "Thang Names"
-#    files: "Files"
+    files: "파일들"
 #    top_simulators: "Top Simulators"
 #    source_document: "Source Document"
 #    document: "Document"


Update uk.coffee (#5743)

Update uk.coffee (#5743)

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -617,7 +617,7 @@
     reload_title: "Перезавантажити весь код?"
     reload_really: "Ви впевнені, що хочете перезавантажити цей рівень і почати спочатку?"
     reload_confirm: "Перезавантажити все"
-#    restart_really: "Are you sure you want to restart the level? You'll loose all the code you've written."
+    restart_really: "Чи впевнені ви, що хочете перезапустити цей рівень? Весь ваш сирець буде втрачено."
     restart_confirm: "Так, перезавантажити"
     test_level: "Протестувати рівень"
     victory: "Перемога"


Add checkbox to opt out of printing Juggler QR code.

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -250,6 +250,9 @@

                     Print color <input type="checkbox" class="toggle-color-print" />
+
+
+                    Include List Juggler QR code <input type="checkbox" class="toggle-juggler-qrcode" checked="checked" />

                     <button class="btn select-simple-view">Simple</button>
@@ -678,7 +681,7 @@
                     @printable_container.find('.printable-body').toggleClass 'bw', @list_modal.find('.toggle-print-color').prop('checked')
             # Add List Juggler QR code
             query = @permalink.attr('href').split(/\?/)[1]
-            if query?
+            if query? and @list_modal.find('.toggle-juggler-qrcode').prop('checked')
                 @printable_container.find('.printable-body').append $.trim """

Bringing this list to a tournament?  Have the TO scan this QR code to register this list with List Juggler!


spec: "new-window" event of BrowserWindow

--- spec/api-browser-window-spec.coffee
+++ spec/api-browser-window-spec.coffee
@@ -156,3 +156,20 @@
       w.on 'onbeforeunload', ->
         done()
       w.loadUrl 'file://' + path.join(fixtures, 'api', 'close-beforeunload-empty-string.html')
+
+  describe 'new-window event', ->
+    it 'emits when window.open is called', (done) ->
+      w.webContents.once 'new-window', (e, url, frameName) ->
+        e.preventDefault()
+        assert.equal url, 'http://host'
+        assert.equal frameName, 'host'
+        done()
+      w.loadUrl "file://#{fixtures}/pages/window-open.html"
+
+    it 'emits when link with target is called', (done) ->
+      w.webContents.once 'new-window', (e, url, frameName) ->
+        e.preventDefault()
+        assert.equal url, 'http://host/'
+        assert.equal frameName, 'target'
+        done()
+      w.loadUrl "file://#{fixtures}/pages/target-name.html"


Updated ukrainian translation - chunk 1

Updated ukrainian translation - chunk 1

added: watch_how, map, get_course_for_class, request_licenses, subscriber_heroes, brain_pop_challenge, back_to_classroom, teacher_button, get_more_codecombat, complete_subscription,sign_in_to_continue

modified:subscription_required,
цікаво, як гарно перекласти Challenge...

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -10,7 +10,7 @@
     classroom_in_a_box: "Класна кімната \"з коробки\" для того, щоб навчати комп'ютерним наукам."
     codecombat_is: "CodeCombat - це платформа для учнів, створена, щоб опановувати комп'ютерні науки під час захоплюючої гри."
     our_courses: "Наша програма була протестована, щоб бути ефективною в класі, навіть для вчителів з мінімальним досвідом програмування."
-#    watch_how: "Watch how CodeCombat is transforming the way people learn computer science."
+    watch_how: "Подивіться, як CodeCombat трансформує то, як люди вивчають комп'ютерні науки"
     top_screenshots_hint: "Учні пишуть код і бачать усі зміни наживо"
     designed_with: "Зважаючи на потреби вчителя"
     real_code: "Справжній код"
@@ -66,7 +66,7 @@
     want_coco: "Хочете CodeCombat у вашій школі?"

   nav:
-#    map: "Map"
+    map: "Карта"
     play: "Грати" # The top nav bar entry where players choose which levels to play
     community: "Спільнота"
     courses: "Курси"
@@ -117,8 +117,8 @@

   play:
     play_as: "Грати за" # Ladder page
-#    get_course_for_class: "Assign Game Development and more to your classes!"
-#    request_licenses: "Contact our school specialists for details."
+    get_course_for_class: "Призначте розробку ігор та інше для ваших класів!"
+    request_licenses: "Зверніться до наших шкільних фахівців для подробиць."
     compete: "Змагайтесь!" # Course details page
     spectate: "Спостерігати" # Ladder page
     players: "гравці" # Hover over a level on /play
@@ -138,10 +138,10 @@
     next: "Далі" # Go from choose hero to choose inventory before playing a level
     change_hero: "Змінити героя" # Go back from choose inventory to choose hero
     buy_gems: "Придбати самоцвіти"
-    subscription_required: "Потрібен абонемет"
+    subscription_required: "Потрібна підписка"
     subscribers_only: "Тільки для підписників!"
     subscribe_unlock: "Придбайте підписку для розблокування!"
-#    subscriber_heroes: "Subscribe today to immediately unlock Amara, Hushbaum, and Hattori!"
+    subscriber_heroes: "Підпишіться сьогодні, щоб відразу розблокувати Amara, Hushbaum та Hattori!"
     subscriber_gems: "Підпишіться сьогодні, щоб придбати цього героя за самоцвіти!"
     anonymous: "Гравець-анонім"
     level_difficulty: "Складність: "
@@ -154,11 +154,11 @@
     campaign_multiplayer: "Арени для мультиплеєра"
     campaign_multiplayer_description: "... в яких Ви програмуєте віч-на-віч із іншими гравцями."
     brain_pop_done: "Ви перемогли Оґрів за допомогою коду! Ви виграли!"
-#    brain_pop_challenge: "Challenge yourself to play again using a different programming language!"
-#    replay: "Replay"
-#    back_to_classroom: "Back to Classroom"
-#    teacher_button: "For Teachers"
-#    get_more_codecombat: "Get More CodeCombat"
+    brain_pop_challenge: "Випробуйте себе, щоб грати знову, використовуючи іншу мову програмування!"
+    replay: "Переграти"
+    back_to_classroom: "Повернутися до класу"
+    teacher_button: "для вчителів"
+    get_more_codecombat: "Отримайте більше від CodeCombat"

   code:
     if: "якщо" # Keywords--these translations show up on hover, so please translate them all, even if it's kind of long. (In the code editor, they will still be in English.)
@@ -233,12 +233,12 @@
     signup_switch: "Хочете створити акаунт?"

   signup:
-#    complete_subscription: "Complete Subscription"
+    complete_subscription: "Завершити підписку"
     create_student_header: "Створити акаунт учня"
     create_teacher_header: "Створити акаунт вчителя"
     create_individual_header: "Створити індивідуальний акаунт"
     email_announcements: "Отримувати анонси про нові рівні та інші новинки CodeCombat!"
-#    sign_in_to_continue: "Sign in or create an account to continue"
+    sign_in_to_continue: "Увійдіть або створить акаунт для того щоб продовжити"
 #    teacher_email_announcements: "Keep me updated on new teacher resources, curriculum, and courses!"
     creating: "Створення акаунту..."
     sign_up: "Реєстрація"


Remove transferPropsTo from markdown editor

--- app/components/markdown-editor.cjsx
+++ app/components/markdown-editor.cjsx
@@ -8,11 +8,14 @@
     previewing: false
     value: @props.defaultValue ? ''

+  getDefaultProps: ->
+    rows: '5'
+
   render: ->
     previewing = @props.previewing ? @state.previewing

     <div className={['markdown-editor', @props.className].join ' '} data-previewing={@state.previewing or null}>
-      {@transferPropsTo <textarea className="markdown-editor-input" value={@props.value ? @state.value} onChange={@handleChange} />}
+      <textarea className="markdown-editor-input" rows={@props.rows} value={@props.value ? @state.value} onChange={@handleChange} />

       <Markdown className="markdown-editor-preview">{@state.value}</Markdown>


Fix ship total formatting in simple view. #196

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -254,7 +254,7 @@

                     Print color <input type="checkbox" class="toggle-color-print" />

-
+
                     Include List Juggler QR code <input type="checkbox" class="toggle-juggler-qrcode" checked="checked" />

@@ -2083,7 +2083,7 @@
                 table_html += upgrade.toTableRow()

         # if @getPoints() != @pilot.points
-        table_html += """
	Ship Total: #{@getPoints()}
"""
+        table_html += """
Ship Total: #{@getPoints()}
"""

         table_html += '

'
         table_html


Fix height issue

Fix height issue

--- src/layout/inspect_behavior.coffee
+++ src/layout/inspect_behavior.coffee
@@ -67,7 +67,5 @@
 exports.inspect_behavior = (sprite) ->
   rows = make.rows 'behavior', programs(sprite)
   my.extend rows, {
-    height: my.row.size
     y: (world) -> world.index * my.row.spacing
-
   }


Uncommented out some headers in uk.coffee localization.

--- app/locale/uk.coffee
+++ app/locale/uk.coffee
@@ -33,12 +33,12 @@
     twitter_follow: "Фоловити"
     employers: "Зайняті"

-#  versions:
+  versions:
     save_version_title: "Зберегти нову версію"
     new_major_version: "Зберегти основну версію"
     cla_prefix: "Для збереження змін, спочатку треба погодитись з нашим"
-#    cla_url: "CLA"
-#    cla_suffix: "."
+    cla_url: "CLA"
+    cla_suffix: "."
     cla_agree: "Я Згоден"

   login:
@@ -47,7 +47,7 @@
     log_out: "Вийти"
     recover: "відновити акаунт"

-#  recover:
+  recover:
     recover_account_title: "Відновити акаунт"
     send_password: "Вислати пароль відновлення"

@@ -101,7 +101,7 @@
     learn_more: "Узнати, як стати Дипломатом"
     subscribe_as_diplomat: "Записатися у Дипломати"

-#  wizard_settings:
+  wizard_settings:
     title: "Налаштування"
     customize_avatar: "Налаштувати аватар"
     clothes: "Одяг"
@@ -144,11 +144,11 @@
   account_profile:
     edit_settings: "Змінити налаштування"
     profile_for_prefix: "Профіль для "
-#    profile_for_suffix: ""
+    profile_for_suffix: ""
     profile: "Профіль"
     user_not_found: "Користувача не знайдено. Будь ласка, перевірте URL."
     gravatar_not_found_mine: "Ми не можемо знайти ваш профіль, пов'язаний з:"
-#    gravatar_not_found_email_suffix: "."
+    gravatar_not_found_email_suffix: "."
     gravatar_signup_prefix: "Зареєструйтеся на "
     gravatar_signup_suffix: " щоб продовжувати"
     gravatar_not_found_other: "Нажаль, немає профіля, що пов'язаний з електронною адресою цієї людини."
@@ -172,7 +172,7 @@
     reload_title: "Перезавантажити весь код?"
     reload_really: "Ви впевнені, що хочете перезавантажити цей рівень і почати спочатку?"
     reload_confirm: "Перезавантажити все"
-#    victory_title_prefix: ""
+    victory_title_prefix: ""
     victory_title_suffix: " закінчено"
     victory_sign_up: "Підписатися на оновлення"
     victory_sign_up_poke: "Хочете отримувати останні новини на email? Створіть безкоштовний акаунт, і ми будемо тримати вас в курсі!"
@@ -202,17 +202,17 @@
     spell_saved: "Заклинання збережено"
     skip_tutorial: "Пропустити (esc)"

-#  admin:
+  admin:
 #    av_title: "Admin Views"
 #    av_entities_sub_title: "Entities"
     av_entities_users_url: "користувачі"
 #    av_entities_active_instances_url: "Active Instances"
-#    av_other_sub_title: "Ынше"
+    av_other_sub_title: "Ынше"
 #    av_other_debug_base_url: "Base (for debugging base.jade)"
     u_title: "Список користувачів"
     lg_title: "Останні ігри"

-#  editor:
+  editor:
 #    main_title: "CodeCombat Editors"
 #    main_description: "Build your own levels, campaigns, units and educational content. We provide all the tools you need!"
 #    article_title: "Article Editor"
@@ -252,7 +252,7 @@
 #    edit_btn_preview: "Preview"
 #    edit_article_title: "Edit Article"

-#  general:
+  general:
     and: "та"
     name: "Ім’я"
 #    body: "Body"
@@ -265,7 +265,7 @@
     email: "Email"
     message: "Повідомлення"

-#  about:
+  about:
     who_is_codecombat: "Хто є CodeCombat?"
     why_codecombat: "Чому CodeCombat?"
     who_description_prefix: "Взагалом розпочався CodeCombat у 2013. Ми також створили "
@@ -287,7 +287,7 @@
 #    jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
 #    michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."

-#  legal:
+  legal:
     page_title: "Юридичні нотатки"
 #    opensource_intro: "CodeCombat is free to play and completely open source."
 #    opensource_description_prefix: "Check out "


ensure both props and options default to empty

--- client/lanes/testing/Helpers.coffee
+++ client/lanes/testing/Helpers.coffee
@@ -1,5 +1,6 @@
-Lanes.Test.renderComponent = (component, {props, options}) ->
-    options ||= {}
+Lanes.Test.renderComponent = (component, args = {}) ->
+    props   = args.props || {}
+    options = args.options || {}
     wrapper = React.createClass
         makePath: Lanes.emptyFn
         childContextTypes:


Don't error out when the only response is result: true

--- assets/scripts/app/store/rest_adapter.coffee
+++ assets/scripts/app/store/rest_adapter.coffee
@@ -32,3 +32,9 @@

   ajax: ->
     Travis.ajax.ajax.apply(this, arguments)
+
+  sideload: (store, type, json, root) ->
+    if json && json.result
+      return
+    else
+      @_super.apply this, arguments


Search completion; refactor regexp parsing (simplification).

--- background_scripts/completion_engines.coffee
+++ background_scripts/completion_engines.coffee
@@ -21,7 +21,7 @@

 # A base class for common regexp-based matching engines.
 class RegexpEngine
-  constructor: (args...) -> @regexps = args
+  constructor: (args...) -> @regexps = args.map (regexp) -> new RegExp regexp
   match: (searchUrl) -> Utils.matchesAnyRegexp @regexps, searchUrl

 # Several Google completion engines package XML responses in this way.
@@ -34,7 +34,7 @@
 class Google extends GoogleXMLRegexpEngine
   # Example search URL: http://www.google.com/search?q=%s
   constructor: (regexps = null) ->
-    super regexps ? new RegExp "^https?://[a-z]+\.google\.(com|ie|co\.uk|ca|com\.au)/"
+    super regexps ? "^https?://[a-z]+\.google\.(com|ie|co\.uk|ca|com\.au)/"

   getUrl: (queryTerms) ->
     Utils.createSearchUrl queryTerms,
@@ -59,12 +59,12 @@
 # then strip "map of" from the resulting suggestions.
 class GoogleMaps extends GoogleWithPrefix
   # Example search URL: https://www.google.com/maps?q=%s
-  constructor: -> super "map of", new RegExp "https?://[a-z]+\.google\.(com|ie|co\.uk|ca|com\.au)/maps"
+  constructor: -> super "map of", "https?://[a-z]+\.google\.(com|ie|co\.uk|ca|com\.au)/maps"

 class Youtube extends GoogleXMLRegexpEngine
   # Example search URL: http://www.youtube.com/results?search_query=%s
   constructor: ->
-    super new RegExp "^https?://[a-z]+\.youtube\.com/results"
+    super "^https?://[a-z]+\.youtube\.com/results"

   getUrl: (queryTerms) ->
     Utils.createSearchUrl queryTerms,
@@ -73,7 +73,7 @@
 class Wikipedia extends RegexpEngine
   # Example search URL: http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s
   constructor: ->
-    super new RegExp "^https?://[a-z]+\.wikipedia\.org/"
+    super "^https?://[a-z]+\.wikipedia\.org/"

   getUrl: (queryTerms) ->
     Utils.createSearchUrl queryTerms,
@@ -84,13 +84,13 @@

 class Bing extends RegexpEngine
   # Example search URL: https://www.bing.com/search?q=%s
-  constructor: -> super new RegExp "^https?://www\.bing\.com/search"
+  constructor: -> super "^https?://www\.bing\.com/search"
   getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://api.bing.com/osjson.aspx?query=%s"
   parse: (xhr) -> JSON.parse(xhr.responseText)[1]

 class Amazon extends RegexpEngine
   # Example search URL: http://www.amazon.com/s/?field-keywords=%s
-  constructor: -> super new RegExp "^https?://www\.amazon\.(com|co.uk|ca|com.au)/s/"
+  constructor: -> super "^https?://www\.amazon\.(com|co.uk|ca|com.au)/s/"
   getUrl: (queryTerms) ->
     Utils.createSearchUrl queryTerms,
       "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s"
@@ -98,15 +98,14 @@

 class DuckDuckGo extends RegexpEngine
   # Example search URL: https://duckduckgo.com/?q=%s
-  constructor: -> super new RegExp "^https?://([a-z]+\.)?duckduckgo\.com/"
-  getUrl: (queryTerms) ->
+  constructor: -> super "^https?://([a-z]+\.)?duckduckgo\.com/"
   getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "https://duckduckgo.com/ac/?q=%s"
   parse: (xhr) ->
     suggestion.phrase for suggestion in JSON.parse xhr.responseText

 class Webster extends RegexpEngine
   # Example search URL: http://www.merriam-webster.com/dictionary/%s
-  constructor: -> super new RegExp "^https?://www.merriam-webster.com/dictionary/"
+  constructor: -> super "^https?://www.merriam-webster.com/dictionary/"
   getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://www.merriam-webster.com/autocomplete?query=%s"
   parse: (xhr) -> JSON.parse(xhr.responseText).suggestions


Improve error handling if options.inc.php does not exist

--- file/acp/be.bastelstu.wcf.nodePush/app.coffee
+++ file/acp/be.bastelstu.wcf.nodePush/app.coffee
@@ -34,10 +34,12 @@
 process.title = "nodePush #{config.inbound.host}:#{config.inbound.port}"

 unless config.signerKey?
-	options_inc_php = fs.readFileSync "#{__dirname}/../../options.inc.php"
-	unless matches = /define\('SIGNER_SECRET', '(.*)'\);/.exec options_inc_php
-		throw new Error "Cannot find signer secret"
-
+	try
+		options_inc_php = fs.readFileSync "#{__dirname}/../../options.inc.php"
+		unless matches = /define\('SIGNER_SECRET', '(.*)'\);/.exec options_inc_php
+			throw new Error "options.inc.php does not contain the SIGNER_SECRET option."
+	catch e
+		throw new Error "Cannot find signer secret: #{e}"
 	config.signerKey = matches[1].replace("\\'", "'").replace("\\\\", "\\")
 	debug "Extracted #{config.signerKey} as Signer key"


Clearer Block metric tracking. Require 2 second visibility.


Fix status code coloring bug.

Fix status code coloring bug.

--- main.coffee
+++ main.coffee
@@ -73,10 +73,10 @@
 morgan.token 'coloredStatus', (req, res) ->
 	color = (x) -> x
 	status = res.statusCode
-	if status >= 200 and status <= 300 then color = chalk.green
-	if status >= 300 and status <= 400 then color = chalk.blue
-	if status >= 400 and status <= 500 then color = chalk.yellow
-	if status >= 500 and status <= 600 then color = chalk.red
+	if status >= 200 and status < 300 then color = chalk.green
+	if status >= 300 and status < 400 then color = chalk.blue
+	if status >= 400 and status < 500 then color = chalk.yellow
+	if status >= 500 and status < 600 then color = chalk.red
 	return color(status)
 morgan.token 'uu', (req, res) ->
 	name = req.uonline?.username or '-'


Add title case filter

Add title case filter

--- www/coffee/app.coffee
+++ www/coffee/app.coffee
@@ -3,6 +3,16 @@
   'ionic'
   'starter.controllers'
 ])
+
+.filter 'titleCase', ->
+  (input) ->
+    words = input.split(' ')
+    i = 0
+    while i < words.length
+      words[i] = words[i].charAt(0).toUpperCase() + words[i].slice(1)
+      i++
+    words.join ' '
+

 .factory '$localStorage', ['$window', ($window) ->
   return {


Fix spec

Fix spec

--- spec/command-installer-spec.coffee
+++ spec/command-installer-spec.coffee
@@ -20,7 +20,7 @@

       installDone = false
       installError = null
-      installer.install commandFilePath, (error) ->
+      installer.install commandFilePath, false, (error) ->
         installDone = true
         installError = error


Refactored code duplications

Refactored code duplications

--- wzk/ui/ac/base.coffee
+++ wzk/ui/ac/base.coffee
@@ -16,7 +16,7 @@
   @param {wzk.dom.Dom} dom
 ###
 wzk.ui.ac.buildSelectAutoCompleteNative = (select, dom) ->
-  ac = wzk.ui.ac.buildSelectAutocomplete select, dom
+  ac = wzk.ui.ac.buildSelectAutoComplete select, dom
   dataProvider = new wzk.ui.ac.NativeDataProvider()
   dataProvider.load select, dom, (data) ->
     ac.load data
@@ -27,10 +27,8 @@
   @param {wzk.net.XhrFactory} xhrFac
 ###
 wzk.ui.ac.buildSelectAutoCompleteRest = (select, dom, xhrFac) ->
-  ac = wzk.ui.ac.buildSelectAutocomplete select, dom
-  dataProvider = new wzk.ui.ac.RestDataProvider()
-  dataProvider.addExtraField '_obj_name'
-  dataProvider.load select, xhrFac, (data) ->
+  ac = wzk.ui.ac.buildSelectAutoComplete select, dom
+  wzk.ui.ac.buildRestDataProvider select, xhrFac, (data) ->
     ac.load data

 ###*
@@ -51,25 +49,30 @@
 ###
 wzk.ui.ac.buildExtSelectboxFromSelectRest = (select, dom, xhrFac) ->
   selectbox = wzk.ui.ac.buildExtSelectboxFromSelect select, dom
-
-  dataProvider = new wzk.ui.ac.RestDataProvider()
-  dataProvider.addExtraField '_obj_name'
-  dataProvider.load select, xhrFac, (data) ->
+  wzk.ui.ac.buildRestDataProvider select, xhrFac, (data) ->
     selectbox.decorate(select, data)

 ###*
-  @protected
+  @param {HTMLSelectElement} select
+  @param {wzk.net.XhrFactory} xhrFac
+  @param {function(Object)} onLoad
+###
+wzk.ui.ac.buildRestDataProvider = (select, xhrFac, onLoad) ->
+  dataProvider = new wzk.ui.ac.RestDataProvider()
+  dataProvider.addExtraField '_obj_name'
+  dataProvider.load select, xhrFac, onLoad
+
+###*
   @param {HTMLSelectElement} select
   @param {wzk.dom.Dom} dom
 ###
-wzk.ui.ac.buildSelectAutocomplete = (select, dom) ->
+wzk.ui.ac.buildSelectAutoComplete = (select, dom) ->
   renderer = new wzk.ui.ac.Renderer(dom, null, null, new wzk.ui.ac.PictureCustomRenderer(dom))
   ac = new wzk.ui.ac.SelectAutoComplete dom, renderer
   ac.decorate select
   ac

 ###*
-  @protected
   @param {HTMLSelectElement} select
   @param {wzk.dom.Dom} dom
 ###


ComputeProvider:[client] add more helpers, tidy up the UI

--- client/Main/providers/computeprovider.coffee
+++ client/Main/providers/computeprovider.coffee
@@ -48,17 +48,17 @@
           label              : "Project Id"
           placeholder        : "project id in gce"
         clientSecretsContent :
-          label              : "Client secrets"
+          label              : "Secrets"
           placeholder        : "content of the client_secrets.xxxxx.json"
           type               : "textarea"
         privateKeyContent    :
           label              : "Private Key"
           placeholder        : "content of the xxxxx-privatekey.pem"
           type               : "textarea"
-        zone                 :
-          label              : "Zone"
-          placeholder        : "google zone"
-          defaultValue       : "us-central1-a"
+        # zone                 :
+        #   label              : "Zone"
+        #   placeholder        : "google zone"
+        #   defaultValue       : "us-central1-a"

     engineyard               :
       title                  : "EngineYard Credential"
@@ -89,6 +89,7 @@
       cssClass     : 'vendor-modal'
       view         : new VendorView
       width        : 800
+      height       : 600
       overlay      : yes
       buttons      :
         create     :
@@ -144,3 +145,10 @@

   @credentialsFor = (vendor, callback)->
     KD.remote.api.JCredential.some { vendor }, callback
+
+  @fetchAvailable = (options, callback)->
+    KD.remote.api.ComputeProvider.fetchAvailable options, callback
+
+  @create = (options, callback)->
+    KD.remote.api.ComputeProvider.create options, callback
+


test/credentials: update credentials

test/credentials: update credentials

--- client/test/lib/dashboard/credentials.coffee
+++ client/test/lib/dashboard/credentials.coffee
@@ -32,9 +32,6 @@
       .url credentialsUrl
       .waitForElementVisible sectionSelector, 20000

-
-  showAndRemoveCredential: (browser) ->
-
     sectionSelector = '.kdview.kdtabpaneview.credentials'
     credential = '.kdview.kdlistitemview.kdlistitemview-default.credential-item'
     showButton = "#{credential} .custom-link-view.HomeAppView--link.primary"
@@ -64,3 +61,4 @@
       .waitForElementVisible removeCredentialButton, 20000
       .click removeCredentialButton
       .pause 2000
+      .end()


adapt patterns, tests to new macro syntax


jupyter sync: keyboard shortcut for saving (support it)

--- src/smc-webapp/editor_jupyter.coffee
+++ src/smc-webapp/editor_jupyter.coffee
@@ -54,11 +54,12 @@
     - set_cursors
     - get
     - event:
+       - 'ready'
+       - 'error'
        - 'change'
-       - 'ready'
-       - 'cursor'
-       - 'error'
+       - 'cursor' - cursor info
        - 'info'   - user requests info (clicking on jupyter logo)
+       - 'save'   - user requests save

 States:

@@ -231,6 +232,16 @@

         if not require('./feature').IS_MOBILE
             @frame.$("#site").css("padding-left", "20px")
+
+        if not @read_only
+            $(@iframe[0].contentWindow.document).keydown (evt) =>
+                if evt.ctrlKey or evt.metaKey or evt.altKey
+                    if evt.keyCode == 83 # s
+                        @emit('save')
+                        evt.preventDefault()
+                        evt.stopPropagation()
+                        return false
+

     monkey_patch_logo: () =>
         @frame.$("#ipython_notebook").find("a").click () =>
@@ -537,7 +548,7 @@

     show: (width) =>
         @iframe?.attr('width', width).maxheight()
-        setTimeout((()=>@iframe?.maxheight()), 1)   # set it one time more the next render loop.
+        setTimeout((()=>@iframe?.maxheight()), 1)   # set it one time more in the next render loop.

     line_to_cell: (line) =>
         obj = JSON.parse(line)
@@ -758,16 +769,27 @@
                 if @dom.read_only
                     # DOM gets extra info about @read_only status of file from jupyter notebook server.
                     @read_only = true
+                else
+                    # not read only, so make "command/control+s" save.
+                    #@dom.frame.keydown (e) =>
+                    #    console.log("frame keydown ", e)
                 cb()
         @dom = new JupyterWrapper(@notebook, @server_url, @filename, @read_only, @project_id, done)

     init_buttons: () =>
+        # info button
         @element.find("a[href=#info]").click(@info)
+
+        # time travel/history
         @element.find("a[href=#history]").click(@show_history_viewer)
+
+        # save button
         if @read_only
             @element.find("a[href=#save]").addClass('disabled')
         else
             @save_button = @element.find("a[href=#save]").click(@save)
+
+        # publish button
         @publish_button = @element.find("a[href=#publish]").click(@publish_ui)

     init_dom_events: () =>
@@ -776,6 +798,7 @@
             @dom.on 'cursor', (x) =>
                 @syncstring._syncstring.set_cursor_locs(x.locs, x.caused)
             @syncstring._syncstring.on('cursor_activity', @render_cursor)
+            @dom.on('save', @save)

     render_cursor: (account_id) =>
         if account_id == salvus_client.account_id


webapp/custom software: always show project capabilities in settings

--- src/smc-webapp/project_settings.cjsx
+++ src/smc-webapp/project_settings.cjsx
@@ -1217,11 +1217,11 @@

                     <HideDeletePanel key='hidedelete' project={@props.project} />
                     {<SSHPanel key='ssh-keys' project={@props.project} user_map={@props.user_map} account_id={@props.account_id} /> if @props.kucalc == 'yes'}
-                    {<ProjectCapabilitiesPanel
+                    <ProjectCapabilitiesPanel
                         name={name}
                         key={'capabilities'}
                         project={@props.project}
-                     /> if @props.kucalc == 'yes'}
+                    />
                 </Col>
                 <Col sm={6}>
                     <CurrentCollaboratorsPanel key='current-collabs'  project={@props.project} user_map={@props.user_map} />


Add Angular 2.x templates from Rado

Add Angular 2.x templates from Rado

--- assets/js/directives/toolbar.coffee
+++ assets/js/directives/toolbar.coffee
@@ -77,6 +77,9 @@

1.3.x

1.4.x

+

+
2.0.x (ES6)

+
2.0.x (ES5)


code cleanup

code cleanup

--- components/tooltip/KDTooltip.coffee
+++ components/tooltip/KDTooltip.coffee
@@ -41,6 +41,11 @@
     else
       @hide()

+    KDView.appendToDOMBody @
+    @getSingleton('windowController').addLayer @
+    @addListeners()
+
+  addListeners:->
     @on 'mouseenter', =>
       @avoidDestroy = yes

@@ -59,8 +64,6 @@
     @on 'ReceivedClickElsewhere', =>
       @delayedDestroy 0

-    KDView.appendToDOMBody @
-    @getSingleton('windowController').addLayer @

   setView:(newView)->
     return unless newView
@@ -151,13 +154,8 @@
     # @getOptions
     o = @translateCompassDirections o if o.gravity

-    if o.animate
-      @show()
-      @setClass 'in'
-
-    else
-      @show()
-
+    @setClass 'in' if o.animate
+    @show()
     @setPosition(o)

   getCorrectPositionCoordinates:(o={},positionValues,callback=noop)->


kite: fix  not starting brokerKite

kite: fix  not starting brokerKite

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -52,7 +52,7 @@
   runNeo4jFeeder: yes
   runGoBroker   : yes
   runGoBrokerKite: yes
-  runKontrol    : no
+  runKontrol    : yes
   runRerouting  : yes
   runUserPresence: yes
   runPersistence: no


Proper RabbitMQ user name in vagrant config.

--- config/main.vagrant.coffee
+++ config/main.vagrant.coffee
@@ -102,7 +102,7 @@
   mq            :
     host        : 'rabbitmq.local'
     login       : 'PROD-k5it50s4676pO9O'
-    componentUser: "prod-<component>"
+    componentUser: "PROD-k5it50s4676pO9O"
     password    : 'djfjfhgh4455__5'
     heartbeat   : 10
     vhost       : '/'


Image cropping Crop the SVG when we zoom in to a rectangle. Check the page zoom button while zoomed in.

--- overrides.coffee
+++ overrides.coffee
@@ -46,6 +46,10 @@
 help = document.querySelector('input[name=help]')

 classify_page.fieldGuideContainer.attr 'aria-hidden', !help.checked
+
+classify_page.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task}})->
+  task.reset 'yes' if task.key is 'illustrations'
+  page_zoom.checked = false

 # moving back and forward through the array of marked SVG rectangles
 classify_page.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task}})->
@@ -66,13 +70,17 @@
     subjectViewer.rescale()
     ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight
     current_tool?.el.classList.remove 'selected'
+    page_zoom.checked = false

   if task.key in ['details', 'parts'] and rectangles.length > 0
     page_zoom.disabled = false
+    page_zoom.checked = true
     current_tool?.el.classList.remove 'selected'
     current_tool = rectangles[rect_index]
     w = current_tool?.mark.width + MARGIN * 2
     h = current_tool?.mark.height + MARGIN * 2
+    ms.svg.attr 'width', w
+    ms.svg.attr 'height', h
     ms.rescale current_tool?.mark.left - MARGIN, current_tool?.mark.top - MARGIN, w, h
     current_tool?.el.classList.add 'selected'

@@ -86,10 +94,6 @@
       task.next = 'review'
     else
       task.next = 'parts'
-
-classify_page.el.on decisionTree.LOAD_TASK, ({originalEvent: detail: {task}})->
-  task.reset 'yes' if task.key is 'illustrations'
-  page_zoom.checked = false

 classify_page.el.on decisionTree.CHANGE, ({originalEvent: {detail}})->
   {key, value} = detail
@@ -121,11 +125,15 @@
 page_zoom.addEventListener 'change', (e) ->
   return unless current_tool?
   if @.checked
-    ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight
-  else
     w = current_tool.mark.width + MARGIN * 2
     h = current_tool.mark.height + MARGIN * 2
+    ms.svg.attr 'width', w
+    ms.svg.attr 'height', h
     ms.rescale current_tool.mark.left - MARGIN, current_tool.mark.top - MARGIN, w, h
+  else
+    subjectViewer.rescale()
+    ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight
+

 help.addEventListener 'change', (e) ->
   classify_page.fieldGuideContainer.attr 'aria-hidden', !@.checked


sort list of completions

sort list of completions

http://mantis.dyalog.com/view.php?id=11928

--- client/autocompletion.coffee
+++ client/autocompletion.coffee
@@ -12,16 +12,17 @@
     )
     return
   (skip, options) ->
-    c = cm.getCursor(); from = line: c.line, ch: c.ch - skip
-    cm.showHint
-      completeOnSingleClick: true
-      completeSingle: false
-      extraKeys:
-        Enter: ->
-        Right: (cm, m) -> m.pick(); return
-        Tab: (cm, m) -> m.moveFocus 1; return
-        'Shift-Tab': (cm, m) -> m.moveFocus -1; return
-      hint: ->
-        to = cm.getCursor(); u = cm.getLine(from.line)[from.ch...to.ch].toLowerCase() # u: completion prefix
-        {from, to, list: options.filter (o) -> o[...u.length].toLowerCase() == u}
+    if options.length
+      c = cm.getCursor(); from = line: c.line, ch: c.ch - skip
+      cm.showHint
+        completeOnSingleClick: true
+        completeSingle: false
+        extraKeys:
+          Enter: ->
+          Right: (cm, m) -> m.pick(); return
+          Tab: (cm, m) -> m.moveFocus 1; return
+          'Shift-Tab': (cm, m) -> m.moveFocus -1; return
+        hint: ->
+          to = cm.getCursor(); u = cm.getLine(from.line)[from.ch...to.ch].toLowerCase() # u: completion prefix
+          {from, to, list: options.filter((o) -> o[...u.length].toLowerCase() == u).sort()}
     return


increased accuracy of longer time contexts

--- clock.coffee
+++ clock.coffee
@@ -81,13 +81,16 @@

   setTime:->
     d = (new Date())
+    d2 = new Date(d.getFullYear(), 0, 1)
     @minute = d.getSeconds() + (parseInt(d.getTime() / 10) % parseInt(d.getTime() / 1000)) / 100
-    @hour = d.getMinutes() + (@minute/60)
-    @day = d.getHours() + (@hour/60)
-    @week = (d.getDay() / 7) * 60
-    c = new Date(d.getFullYear(), 0, 1) # http://javascript.about.com/library/bldayyear.htm
-    @year = (Math.ceil((d - c) / 86400000)) / 365 * 60 # http://javascript.about.com/library/bldayyear.htm
-    @decade = (d.getYear() % 10) / 10  * 60
-    @life = (clock.config.age / clock.config.expectancy) * 60
+    @hour = d.getMinutes() + (@minute / 60)
+    @day = d.getHours() + (@hour / 60)
+    @week = ((d.getDay() + d.getHours() / 24) / 7) * 60
+    @year = (Math.ceil((d - d2) / 86400000) + @day / 60) / 365 * 60
+    @decade = (d.getYear() % 10  + ((Math.ceil((d - d2) / 86400000)) / 365) + @hour / 60 / 365) / 10  * 60
+    @life = ((clock.config.age*365*24 + (Math.ceil((d - d2) / 86400000)) + @hour / 60)  / (clock.config.expectancy*365*24)) * 60
+    if @life == 60
+      @config.age = 1
+      $(".age").val(1)
     @drawClock()


Link to the official PHP-CS-Fixer repository

--- src/beautifiers/php-cs-fixer.coffee
+++ src/beautifiers/php-cs-fixer.coffee
@@ -9,7 +9,7 @@
 module.exports = class PHPCSFixer extends Beautifier

   name: 'PHP-CS-Fixer'
-  link: "http://php.net/manual/en/install.php"
+  link: "https://github.com/FriendsOfPHP/PHP-CS-Fixer"

   options:
     PHP: true
@@ -44,7 +44,7 @@
             ], {
               ignoreReturnCode: true
               help: {
-                link: "http://php.net/manual/en/install.php"
+                link: "https://github.com/FriendsOfPHP/PHP-CS-Fixer"
               }
             })
             .then(=>


Include css grammar in fenced block

Include css grammar in fenced block

--- src/packages/gfm/grammars/gfm.cson
+++ src/packages/gfm/grammars/gfm.cson
@@ -70,6 +70,16 @@
     'patterns': ['include': 'source.js']
   }
   {
+    'begin': '^```css$'
+    'beginCaptures':
+      '0': 'name': 'support.gfm'
+    'end': '^```$'
+    'endCaptures':
+      '0': 'name': 'support.gfm'
+    'name': 'markup.code.css.gfm'
+    'patterns': ['include': 'source.css']
+  }
+  {
     'begin': '^```.*$'
     'beginCaptures':
       '0': 'name': 'support.gfm'


Add OS X dock menu with 'New Window' option

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -82,6 +82,7 @@
     @listenForArgumentsFromNewProcess()
     @setupJavaScriptArguments()
     @handleEvents()
+    @setupDockMenu()
     @storageFolder = new StorageFolder(process.env.ATOM_HOME)

     if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
@@ -279,6 +280,13 @@

     ipc.on 'write-to-stderr', (event, output) ->
       process.stderr.write(output)
+
+  setupDockMenu: ->
+    if process.platform is 'darwin'
+      dockMenu = Menu.buildFromTemplate [
+        {label: 'New Window',  click: => @emit('application:new-window')}
+      ]
+      app.dock.setMenu dockMenu

   # Public: Executes the given command.
   #


Tests: Removed done from signup widget code.

--- src/tests/widgets/test_signup.coffee
+++ src/tests/widgets/test_signup.coffee
@@ -60,7 +60,7 @@
     expect(anchors.length).toBe 2

-  it 'should execute callback for sign up button', (done) ->
+  it 'should execute callback for sign up button', ->
     flag   = no
     widget = new spark.widgets.SignUp callback: -> flag = yes
     button = widget.getForm().getButtons()[0].getElement()


client: disable sourcemaps from uglifyjs

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/webpack.config.coffee
+++ client/webpack.config.coffee
@@ -224,6 +224,7 @@
     new webpack.optimize.OccurrenceOrderPlugin()
     new webpack.optimize.DedupePlugin()
     new webpack.optimize.UglifyJsPlugin
+      sourceMap: no
       mangle:
         keep_fnames: yes
       compress:
@@ -241,4 +242,3 @@

 module.exports = webpackConfig
-


Finish implementation

Finish implementation


map_to_screen should always return new data

--- static/coffee/renderers/glyph.coffee
+++ static/coffee/renderers/glyph.coffee
@@ -82,17 +82,14 @@
     return (spt1[i] - spt0[i] for i in [0..spt0.length-1])

   map_to_screen : (x, x_units, y, y_units) ->
-    sx = new Array(x.length)
-    sy = new Array(y.length)
-
     if x_units == 'screen'
-      sx = x
+      sx = x[..]
     else
       sx = @xmapper.v_map_screen(x)
       sx = @plot_view.viewstate.v_xpos(sx)

     if y_units == 'screen'
-      sy = y
+      sy = y[..]
     else
       sy = @ymapper.v_map_screen(y)
       sy = @plot_view.viewstate.v_ypos(sy)


correcao referencia totalizers

correcao referencia totalizers

--- src/coffee/portal-totalizers.coffee
+++ src/coffee/portal-totalizers.coffee
@@ -3,7 +3,7 @@
   pluginName = 'vtexTotalizers'
   defaults = {
     # scriptId: '#vtex-totalizers'
-    scriptId: '#container'
+    scriptId: '#vtex-totalizers'
   }

   class vtexTotalizers


Server init log message

Server init log message

--- server/serversystem/manager.coffee
+++ server/serversystem/manager.coffee
@@ -336,7 +336,6 @@
           serverObj.portRangeStart = parseInt prange[0]
           serverObj.portRangeEnd = parseInt prange[1]
           if installStr is "|"
-            console.log "server init "+serverObj.ip
             log.info "[SERVER] #{serverObj.ip} Initialized"
             if !serverObj._id?
               ourID = servers.insert serverObj


First draft of tutorial steps, from Trish

--- tutorial-steps.coffee
+++ tutorial-steps.coffee
@@ -1,7 +1,46 @@
 module.exports = [{
-  image: ''
-  alt: 'a tutorial image'
   content: '''
-    This will be a brief tutorial showing how to mark illustrations.
+
If the page contains an illustration that falls into any one of the following categories: painting, drawing, diagram, chart, table, map, or photograph   Please mark as “Yes”

+
If the page is blank or if the page contains an illustration that falls into one of the following categories:  title page, bookplate, book cover, index, table of content, decorative letters,  advertisements, table of content   Please mark as “No”

+
   '''
-}]
+  }, {
+    content: '''
+
If it is a full page illustration click OK to advance to the next screen, otherwise click Mark the Illustration and draw boundaries around the illustration.  Include any identifying information, such as a figure number or caption, within the bounding box.

+
Click OK

+                 '''
+
+  }, {
+    content: '''
+
First Identify the type of illustration:

+

+
Drawing/painting/diagram

+
Chart/table

+
Photograph

+
Map

+

+
For an example of each type see the Help screen

+
Then add general keywords or phrases about the primary species or subjects depicted in the  illustration Separate terms by semi-colons e.g. bird; landscape; crab; forest; man; woman; apple; pottery; cemetery; skull; fossil; microscopic view; meteor, meteorological observations.

+
If it is a chart or table, indicate the topic that the data represents e.g. Montana Wheat; Trout length; Livestock inventory; mirror

+
If is it a map, add any current or historic geographic locations indicated by the map or caption e.g. Three Lakes; Southern China; Canada; Uriconium; Yorkshire

+
+                 '''
+
+  }, {
+    content: '''
+
Species

+
If illustration displays or has distribution information about a particular plant or animal, indicate both the common and scientific name for the species if known

+
e.g. Common Name=Ostrich
+                                  Scientific Name=Struthio camelus

+
Inscription

+
Some paintings or drawings will have hand-written text such as a signature, date, or location at the bottom.  If it is legible, record the inscription exactly as it is written.  Separate each piece of data by semi-colons

+
e.g. Near Alata Sidaillo; Dec 15- 1926-; Bycanistes cristatus

+
+
Contributors

+
Prints will often indicate the name of an individual or company involved with the creation of the illustration such as the original painter the print was based on, the engraver, or printing company.
+                                  Record any contributor names you find and their role if known

+
e.g. Name=Werner Klinkhardt   Role=Printer
+                                  Name=Sharp & Son    Role=Illustrator

+                 '''
+
+  }]


Date comparison test ignores timezone to be cross machine compatible

--- server/test/units/indicator.coffee
+++ server/test/units/indicator.coffee
@@ -98,7 +98,7 @@
       assert.typeOf date, 'string'
       assert.typeOf value, 'string'

-      assert.strictEqual date, "Mon Jan 01 2001 00:00:00 GMT+0000 (GMT)"
+      assert.match date, /Mon Jan 01 2001 00:00:00 GMT\+0000/
       assert.strictEqual value, "4"

       done()


My fun little minification breaks font names with spaces...

People should just use clean-css if they want really good minification.

--- src/components/TypographyStyle.cjsx
+++ src/components/TypographyStyle.cjsx
@@ -3,24 +3,13 @@
 createStyles = require '../utils/createStyles'

 # TODO
-# responsive modular scales
 # adding or not adding normalize should be an prop

 module.exports = (vr, options) ->
   React.createClass
     displayName: "TypographyStyle"

-    PropTypes:
-      minify: React.PropTypes.bool
-
-    getDefaultProps: ->
-      minify: true
-
     render: ->
       styles = createStyles(vr, options)

-      # Minify styles
-      if @props.minify
-        styles = styles.replace(/\s/g, '')
-
       <style id="react-typography">{styles}</style>


use isObjectLike

use isObjectLike

--- shared/src/api/interceptors.coffee
+++ shared/src/api/interceptors.coffee
@@ -8,10 +8,10 @@
 some      = require 'lodash/some'
 set       = require 'lodash/set'
 every     = require 'lodash/every'
-isObject  = require 'lodash/isObject'
 isError   = require 'lodash/isError'
 isEmpty   = require 'lodash/isEmpty'
 propertyOf  = require 'lodash/propertyOf'
+isObjectLike = require 'lodash/isObjectLike'

 makeLocalRequest = (requestConfig) ->
   {url} = requestConfig
@@ -125,7 +125,7 @@
     Promise.reject(error) if isError(error)

   handleErrorMessage: (error) =>
-    {statusText, data} = error.response if isObject(error.response)
+    {statusText, data} = error.response if isObjectLike(error.response)

     try
       msg = JSON.parse(statusText)
@@ -134,7 +134,7 @@

     error.response.statusMessage = msg

-    unless isObject(data)
+    unless isObjectLike(data)
       try
         error.response.data = JSON.parse(data)
       catch e
@@ -143,7 +143,7 @@

   filterErrors: (error) =>
     {response, config} = error
-    {data} = response if isObject(response)
+    {data} = response if isObjectLike(response)
     return Promise.reject(error) if isEmpty(config)
     if isEmpty(config.handledErrors) or
       not areAllErrorsHandled(config.handledErrors, data?.errors, @_apiHandler.getOptions().errorNameProperty)


v0.1.4 - d3 Tilted working

v0.1.4 - d3 Tilted working

--- src/scripts/services/semver.coffee
+++ src/scripts/services/semver.coffee
@@ -1,3 +1,3 @@
 angular.module('app').factory 'semver', () ->
-	"0.1.3"
+	"0.1.4"


Add header to hodor script

Add header to hodor script

--- scripts/hodor.coffee
+++ scripts/hodor.coffee
@@ -1,3 +1,18 @@
+# Description:
+#   In which Hubot imitates hodor
+#
+# Dependencies:
+#   None
+#
+# Configuration:
+#   None
+#
+# Commands:
+#   None
+#
+# Author:
+#   corrigat
+
 module.exports = (robot) ->
   robot.hear /(hodor)/i, (msg) ->
     msg.send "Hodor"


Add Google Analytics

Add Google Analytics

--- src/core/javascripts/main.js.coffee
+++ src/core/javascripts/main.js.coffee
@@ -24,6 +24,7 @@
   'angular.filter',
   'ngCookies',
   'angulartics',
+  'angulartics.google.analytics',
   'pascalprecht.translate'
 ]);


Add warning label instead of warning panel when disabled

--- src/app/package-config-view.coffee
+++ src/app/package-config-view.coffee
@@ -14,6 +14,7 @@
         @span outlet: 'name'
         @span outlet: 'version', class: 'label'
         @span outlet: 'update', class: 'label label-info', 'Update Available'
+        @span outlet: 'disabedLabel', class: 'label label-warning', 'Disabled'
         @div class: 'btn-group pull-right', =>
           @button outlet: 'defaultAction', class: 'btn btn-small btn-primary'
           @button outlet: 'dropdownButton', class: 'btn btn-small btn-primary dropdown-toggle', 'data-toggle': 'dropdown', =>
@@ -149,10 +150,10 @@
     enableLink = @enableToggle.find('a')
     if @disabled
       enableLink.text('Enable')
-      @addClass('panel-warning')
+      @disabedLabel.show()
     else
       enableLink.text('Disable')
-      @removeClass('panel-warning')
+      @disabedLabel.hide()

     @enableToggle.hide() unless @installed


fix misunderstanding

fix misunderstanding

--- src/smc-webapp/course/store.coffee
+++ src/smc-webapp/course/store.coffee
@@ -274,7 +274,7 @@
         return x.get('time')

     has_grade: (assignment, student_id) =>
-        return !!assignment.get("grades")?.get(student_id)
+        return !!@get_assignment(assignment)?.get("grades")?.get(student_id)

     get_assignment_status: (assignment) =>
         #


Set cursor to original end position

Set cursor to original end position

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -146,6 +146,7 @@
     isBlockAttribute = Trix.attributes[attributeName]?.block

     if isBlockAttribute
+      endPosition = @document.rangeFromLocationRange(range)[1]
       range = @document.expandedLocationRangeForBlockTransformation(range)
       @setLocationRange(range)

@@ -157,9 +158,7 @@
       delete @currentAttributes[attributeName]

     if isBlockAttribute
-      index = range.index + 1
-      length = @document.getBlockAtIndex(index).getLength()
-      @setLocationRange({index, offset: 0}, {index, offset: length})
+      @setPosition(endPosition)

     @notifyDelegateOfCurrentAttributesChange()


Optimize, simplify media-grid layout, timeout logic.

--- src/js/jquery.hlf.media-grid.coffee
+++ src/js/jquery.hlf.media-grid.coffee
@@ -68,7 +68,6 @@
           @toggleExpandedItemFocus $(e.currentTarget), off
           return
         expand: (e, expanded) =>
-          clearTimeout @_dimTimeout if @_dimTimeout? and expanded
           @toggleItemFocus $(e.currentTarget), expanded, @expandDuration
           return
       , ".#{@classNames.item}"
@@ -88,7 +87,11 @@

       $(window).resize _.debounce( =>
         @_updateMetrics off
-        @_reLayoutItems()
+        if @$expandedItem?
+          @toggleItemExpansion @$expandedItem, off
+          @_reLayoutItems @expandDuration
+        else
+          @_reLayoutItems()
         return
       , @resizeDelay)
       return
@@ -121,6 +124,7 @@

     toggleItemFocus: ($item, focused, delay) ->
       $item.toggleClass @classNames.focused, focused
+      clearTimeout @_dimTimeout if @_dimTimeout?
       @_dimTimeout = setTimeout =>
         return unless focused is $item.hasClass(@classNames.focused)
         @$el.toggleClass @classNames.dimmed, focused
@@ -152,7 +156,7 @@
     _isRightEdgeItem: (i) -> (i + 1) % @metrics.rowSize is 0

     _layoutItems: ->
-      $(@$items.toArray().reverse()).each (i, item) =>
+      @$items.get().reverse().forEach (item, i) =>
         $item = $ item
         offset = $item.position()

@@ -164,21 +168,16 @@
       @$el.css width: @metrics.wrapWidth, height: @metrics.wrapHeight
       return

-    _reLayoutItems: ->
-      @$items.each (i, item) =>
-        @toggleItemExpansion $(item), off
-        return
-
-      _.delay =>
-        @$items.each (i, item) =>
-          $item = $ item
-          $item.css
-            top: 'auto', left: 'auto', bottom: 'auto', right: 'auto'
-            position: $item.data @attr('original-position')
+    _reLayoutItems: (delay=0) ->
+      clearTimeout @_layoutTimeout if @_layoutTimeout?
+      @_layoutTimeout = setTimeout =>
+        key = @attr 'original-position'
+        @$items.css
+          top: 'auto', left: 'auto', bottom: 'auto', right: 'auto'
+          position: -> $(@).data key

         @_layoutItems()
-        return
-      , @expandDuration
+      , delay
       return

     _updateMetrics: (hard=on) ->


Update default layer size

Update default layer size

--- framer/Defaults.coffee
+++ framer/Defaults.coffee
@@ -10,8 +10,8 @@
 		color: "white"
 		shadowColor: "rgba(123,123,123,0.5)"
 		borderColor: "rgba(123,123,123,0.5)"
-		width: 300
-		height: 300
+		width: 250
+		height: 250
 	Animation:
 		# curve: "spring(400,40,0)" # Or, the Cemre-Curve
 		curve: "ease"


Hotfix for FL bug outside proxy

Hotfix for FL bug outside proxy

--- js-library/app/js/loader/loader_common.coffee
+++ js-library/app/js/loader/loader_common.coffee
@@ -65,8 +65,8 @@
 this_env = JSON.parse(factlink_config).env

 extra_config =
-  if this_env != 'production' && FactlinkConfig_override_uri
-    'window.FactlinkConfig.base_uri = ' + JSON.stringify(FactlinkConfig_override_uri) + '; '
+  if this_env != 'production' && window.FactlinkConfig_override_uri
+    'window.FactlinkConfig.base_uri = ' + JSON.stringify(window.FactlinkConfig_override_uri) + '; '
   else
     ''


set title element to buttons

set title element to buttons

--- js-library/app/js/util/factlink_button.coffee
+++ js-library/app/js/util/factlink_button.coffee
@@ -1,10 +1,18 @@
 Factlink.initializeFactlinkButton = ->
-  $(document).on "click", ".js-factlink-create-button", (e) ->
+  buttonSel = ".js-factlink-create-button"
+
+  $(document).on "click", buttonSel, (e) ->
     e.preventDefault()
     e.stopPropagation()
     Factlink.triggerClick()

-  $(document).on "mousedown", ".js-factlink-create-button", (e) ->
+  $(document).on "mousedown", buttonSel, (e) ->
     # prevent mousedown from deselecting the text that
     # the button is intended to use:
     e.preventDefault()
+
+  setTitle = ->
+    $(buttonSel)
+      .attr('title', 'Select any statement in this article and click this button to add your opinion.')
+      .length > 0
+  setTitle() || $ setTitle


fix(iframe): return if the iframe doc hasn't loaded yet

--- src/components/evented-iframe.cjsx
+++ src/components/evented-iframe.cjsx
@@ -76,6 +76,7 @@
     return unless @props.searchable
     node = ReactDOM.findDOMNode(@)
     doc = node.contentDocument?.body ? node.contentDocument
+    return unless doc
     searchIndex = SearchableComponentStore.getCurrentRegionIndex(@_regionId)
     {searchTerm} = SearchableComponentStore.getCurrentSearchData()
     if @lastSearchIndex isnt searchIndex or @lastSearchTerm isnt searchTerm


#1104 - waiting circle should show while form is saving

--- jsapp/xlform_model_view/view.surveyApp.coffee
+++ jsapp/xlform_model_view/view.surveyApp.coffee
@@ -599,6 +599,9 @@
         evt.target.href = "data:text/csv;charset=utf-8,#{encodeURIComponent(@survey.toCSV())}"
     saveButtonClick: (evt)->
       # Save = store CSV in local storage.
+      icon = $(evt.target).find('i')
+      icon.addClass 'fa-spinner fa-spin blue'
+      icon.removeClass 'fa-check-circle green'
       @onSave.apply(@, arguments)
     publishButtonClick: (evt)->
       # Publish = trigger publish action (ie. post to formhub)


Allow to optionally load widgets in node.js package

--- bokehjs/src/coffee/node_main.coffee
+++ bokehjs/src/coffee/node_main.coffee
@@ -35,4 +35,9 @@
 APIs = require './api'
 _.extend(Bokeh, _.omit(APIs, "models"))

+Bokeh.require_widgets = () ->
+  Widgets = require './models/widgets/main'
+  _.extend(Bokeh, _.omit(Widgets, "models"))
+  Bokeh.Models.register_locations(Widgets.models)
+
 module.exports = Bokeh


post_info: send Content-Length header

post_info: send Content-Length header

--- node-http-client.coffee
+++ node-http-client.coffee
@@ -11,8 +11,12 @@

 post_info = (expected_code, url, info, c) ->
-  req_body = JSON.stringify info, null, 2
-  headers = {'Content-Type': 'application/json'}
+  json = JSON.stringify info, null, 2
+  req_body = new Buffer json, 'utf8'
+  headers = {
+    'Content-Type': 'application/json'
+    'Content-Length': req_body.length
+  }
   _request 'POST', expected_code, url, req_body, headers, _resParsing(c)


add notes about deploying rethinkdb.

add notes about deploying rethinkdb.

--- salvus/rethink.coffee
+++ salvus/rethink.coffee
@@ -42,12 +42,35 @@
     network    : false

 ###
-# Schema
-#   keys are the table names
-#   values describe the indexes, except:
-#        options - specifies table creation options as given at
-#           http://rethinkdb.com/api/javascript/table_create/
-#
+NOTES:
+
+# Sharding
+
+To automate sharding/replication of all the tables (depends on deployment).  E.g.,
+if there are 3 nodes, do this to reconfigure *all* tables:
+
+    db = require('rethink').rethinkdb(hosts:['db0'])
+	db.db.reconfigure(shards:3, replicas:3).run(console.log)
+
+# Configure Ubuntu 15.04 to run rethinkdb:
+
+	sudo su
+	apt-get update && apt-get upgrade && apt-get install libprotobuf9
+
+    # See https://github.com/rethinkdb/rethinkdb/releases for downloads
+	cd /tmp; wget http://download.rethinkdb.com/dev/2.1.0-0BETA2/rethinkdb_2.1.0%2b0BETA2~0vivid_amd64.deb && dpkg -i rethinkdb_2.1.0+0BETA2~0vivid_amd64.deb
+
+    # Configure rethinkdb
+    cp /etc/rethinkdb/default.conf.sample /etc/rethinkdb/instances.d/default.conf
+    echo "bind=all" >> /etc/rethinkdb/instances.d/default.conf
+    echo "server-name=`hostname`" >> /etc/rethinkdb/instances.d/default.conf
+    echo "join=db0" >> /etc/rethinkdb/instances.d/default.conf
+    service rethinkdb restart
+
+On client nodes, install this driver:
+
+    npm install http://download.rethinkdb.com/dev/2.1.0-0BETA1/rethinkdb-2.1.0-BETA1.nodejs.tgz
+
 ###

 SCHEMA = require('rethink_shared').SCHEMA