separate test for signing channel

separate test for signing channel

--- spec/coffeescripts/danthes_spec.js.coffee
+++ spec/coffeescripts/danthes_spec.js.coffee
@@ -81,6 +81,13 @@
     expect(faye.subscribe).toHaveBeenCalled()
     expect(pub.server).toEqual("server")
     expect(pub.subscriptions.somechannel.opts).toEqual(timestamp: options['timestamp'], signature: options['signature'])
+
+  it "adds a signed channel to subscribe later", ->
+    pub.fayeClient = 'string'
+    [faye, options] = signToChannel('somechannel')
+    expect(pub.subscriptions.somechannel).toBeDefined()
+    expect(pub.subscriptions.somechannel.opts.signature).toEqual('1234567890')
+    expect(pub.subscriptions.somechannel.opts.timestamp).toEqual(1234567890)

   it "connects to faye server, adds extension, and executes callbacks", ->
     callback = jasmine.createSpy()


[UPD] reactivated download functionality

--- client/scripts/main.coffee
+++ client/scripts/main.coffee
@@ -16,6 +16,6 @@
 		document.getElementById("output").classList.add('active')
 		blob = new Blob([htmlString], {type: "text/html;charset=utf-8"})

-		# saveAs(blob, docTitle)
+		saveAs(blob, docTitle)

 	document.getElementById("saveAs").addEventListener 'click', generate


Fix bug in speaker page for non-logged in users

--- client/view/speaker.coffee
+++ client/view/speaker.coffee
@@ -62,7 +62,7 @@

 Template.speaker.canSeeTrackRecord = ->
   cur = User.current()
-  @speaker.me() or (cur and cur.moderator() or cur.admin())
+  @speaker.me() or (cur and (cur.moderator() or cur.admin()))

 Template.speaker.hasProposals = ->
   @speaker.proposals().length > 0


Less info on printing base object

Less info on printing base object


Padding object implementation

Padding object implementation

--- framer/TextLayer.coffee
+++ framer/TextLayer.coffee
@@ -13,6 +13,9 @@
 			color: "#888"

 		super options
+
+		# Set padding
+		@_padding = options.padding or Utils.rectZero()

 		# Set default width
 		if not options.width
@@ -89,11 +92,22 @@
 			@html = value
 			@emit("change:text", value)

+	# @define "padding",
+	# 	get: -> @style.padding
+	# 	set: (value) ->
+	# 		@style.padding = "#{value}px"
+	# 		@emit("change:padding", value)
+
 	@define "padding",
-		get: -> @style.padding
-		set: (value) ->
-			@style.padding = "#{value}px"
-			@emit("change:padding", value)
+		get: ->
+			_.clone(@_padding)
+
+		set: (padding) ->
+			@_padding = Utils.rectZero(Utils.parseRect(padding))
+
+			# Top, Right, Bottom, Left
+			@style.padding =
+				"#{@_padding.top}px #{@_padding.right}px #{@_padding.bottom}px #{@_padding.left}px"

 	@define "fontFamily",
 		get: -> @style.fontFamily
@@ -149,7 +163,7 @@
 				@style.textAlign = "right"
 			else
 				@style.textAlign = value
-
+
 			@emit("change:textAlign", value)

 	@define "textTransform",


fix this bug: The new +New button ignores the directory when uploading files...

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -1291,7 +1291,7 @@
                 else
                     create_file('sagews')
             else
-                @load_target('new')
+                @display_tab("project-new-file")

         @container.find(".smc-mini-new-file-type-list").find("a[href=#new-file]").click (evt) ->
             mini_set_input()


project directory listing refactor; also delete a lot of old code not used anymore; fix bug where link to path doesn't work when refreshed; make cleaner and more efficient.

--- salvus/page/project.coffee
+++ salvus/page/project.coffee
@@ -24,9 +24,6 @@
 template_project_file          = templates.find(".project-file-link")
 template_home_icon             = templates.find(".project-home-icon")
 template_segment_sep           = templates.find(".project-segment-sep")
-template_project_commits       = templates.find(".project-commits")
-template_project_commit_single = templates.find(".project-commit-single")
-template_project_branch_single = templates.find(".project-branch-single")
 template_project_collab        = templates.find(".project-collab")
 template_project_linked        = templates.find(".project-linked")

@@ -48,86 +45,6 @@
 FROM_WEB_TIMEOUT_S = 45

-##################################################
-# Initialize the modal project management dialogs
-##################################################
-delete_path_dialog = $("#project-delete-path-dialog")
-move_path_dialog   = $("#project-move-path-dialog")
-
-class Dialog
-    constructor: (opts) ->
-        opts = defaults opts,
-            dialog      : required
-            submit      : required
-            before_show : undefined
-            after_show  : undefined
-
-        @opts = opts
-
-        submit = () =>
-            try
-                opts.dialog.modal('hide')
-                opts.submit(opts.dialog, @project)
-            catch e
-                console.log("Exception submitting modal: ", e)
-            return false
-
-        opts.dialog.submit submit
-        opts.dialog.find("form").submit submit
-        opts.dialog.find(".btn-submit").click submit
-        opts.dialog.find(".btn-close").click(() -> opts.dialog.modal('hide'); return false)
-
-    show: (project) =>
-        @project = project
-        @opts.before_show(@opts.dialog, project)
-        @opts.dialog.modal()
-        @opts.after_show(@opts.dialog, project)
-        return false
-
-delete_path_dialog = new Dialog
-    dialog      : $("#project-delete-path-dialog")
-    submit      : (dialog, project) ->
-        path = project.current_path.join('/')
-        commit_mesg = dialog.find("input[type=text]").val()
-        if commit_mesg == ""
-            commit_mesg = "deleted #{path}"
-        project.path_action
-            action      : 'delete'
-            branch      : project.meta.display_branch
-            path        : path
-            commit_mesg : commit_mesg
-
-    before_show : (dialog, project) ->
-        dialog.find(".project-delete-path-dialog-filename").text(project.current_pathname())
-        dialog.find("input[type=text]").val("")
-    after_show  : (dialog) ->
-        dialog.find("input[type=text]").focus()
-
-move_path_dialog = new Dialog
-    dialog      : $("#project-move-path-dialog")
-    submit      : (dialog, project) ->
-        src      = project.current_pathname()
-        dest     = dialog.find("input[name=new-filename]").val()
-        if src == dest
-            # nothing to do
-            return
-        why      = dialog.find("input[name=why]").val()
-        if why == ""
-            why = "move #{src} to #{dest}"
-        project.path_action
-            action      : 'move'
-            branch      : project.meta.display_branch
-            path        : src
-            commit_mesg : why
-            extra_options : {dest:dest}
-    before_show : (dialog, project) ->
-        dialog.find(".project-move-path-dialog-filename").text(project.current_pathname())
-        dialog.find("input[name=new-filename]").val(project.current_pathname())
-        dialog.find("input[name=why]").val("")
-    after_show  : (dialog) ->
-        dialog.find("input[name=new-filename]").focus()
-
-

 ##################################################
 # Define the project page class
@@ -135,6 +52,7 @@

 class ProjectPage
     constructor: (@project) ->
+        #window.p = @

         # whether or not we have full access to the project or only very limited
         # public access (since project is public)
@@ -168,8 +86,8 @@
         # current_path is a possibly empty list of directories, where
         # each one is contained in the one before it.
         @current_path = []
-
         @init_tabs()
+
         @update_topbar()
         @init_admin()

@@ -279,25 +197,6 @@
         #        @command_line_tab_complete()
         #        return false

-
-        # Make it so typing something into the "create a new branch..." box
-        # makes a new branch.
-        #@container.find(".project-branches").find('form').submit () ->
-        #    that.branch_op(branch:$(@).find("input").val(), op:'create')
-        #    return false
-
-        file_tools = @container.find(".project-file-tools")
-
-        file_tools.find("a[href=#delete]").click () ->
-            if not $(@).hasClass("disabled")
-                delete_path_dialog.show(that)
-            return false
-
-        file_tools.find("a[href=#move]").click () ->
-            if not $(@).hasClass("disabled")
-                move_path_dialog.show(that)
-            return false
-
         @init_file_sessions()

     init_new_tab_in_navbar: () =>
@@ -348,7 +247,7 @@
         @_file_list_is_sortable = false

     push_state: (url) =>
-        #console.log("push_state: ", url)
+        # console.log("push_state: ", url)
         if not url?
             url = @_last_history_state
         if not url?
@@ -821,8 +720,6 @@
                 that.display_tab(link.data("target"))
                 return false

-            that.update_file_list_tab()
-
             if name == "project-file-listing"
                 tab.onshow = () ->
                     that.editor?.hide_editor_content()
@@ -887,7 +784,6 @@
                 that.display_tab(link.data("target"))
                 return false

-            that.update_file_list_tab()
         @display_tab("project-file-listing")

     create_editor: (initial_files) =>   # initial_files (optional)
@@ -1407,12 +1303,8 @@

     # Update the listing of files in the current_path, or display of the current file.
     update_file_list_tab: (no_focus) =>
-        if @_updating_file_list_tab_LOCK
-            return # already updating it
-        @_updating_file_list_tab_LOCK = true
         @_update_file_list_tab no_focus, () =>
             @_show_all_files = false
-            setTimeout( (() => @_updating_file_list_tab_LOCK = false), 500 )

     init_listing_show_all: () =>
         @container.find(".project-file-listing-show_all").click () =>
@@ -1421,317 +1313,317 @@
             return false

     _update_file_list_tab: (no_focus, cb) =>
-        spinner = @container.find(".project-file-listing-spinner")
-        timer = setTimeout( (() -> spinner.show().spin()), 1000 )
-
-        # TODO: ** must change this -- do *not* set @current_path until we get back the correct listing!!!!

         path = @current_path.join('/')
-
-        url_path = path
-        if url_path.length > 0 and url_path[path.length-1] != '/'
-            url_path += '/'
-        @push_state('files/' + url_path)
-
-        that = @
-        click_file = (e) ->
-            obj = $(e.delegateTarget).closest(".project-path-link").data('obj')
-            target = $(e.target)
-            if target.hasClass("salvus-file-action") or target.parent().hasClass('salvus-file-action')
-                that.file_action_dialog(obj)
-            else
-                if obj.isdir
-                    that.set_current_path(obj.fullname)
-                    that.update_file_list_tab()
-                else
-                    that.open_file
-                        path       : obj.fullname
-                        foreground : not(e.which==2 or (e.ctrlKey or e.metaKey))
-            e.preventDefault()
-
-        @update_snapshot_link()
-
-        tm = misc.walltime()
-        #console.log("calling project_directory_listing with path=#{path}")
+        if path == @_requested_path
+            # already requested
+            return
+
+        if not @_requested_path?
+            spinner = @container.find(".project-file-listing-spinner")
+            @_file_list_tab_spinner_timer = setTimeout( (() -> spinner.show().spin()), 1000 )
+
         if @public_access
             f = salvus_client.public_project_directory_listing
         else
             f = salvus_client.project_directory_listing
+
+        @_requested_path = path
+
         f
             project_id : @project.project_id
             path       : path
             time       : @_sort_by_time
             hidden     : @container.find("a[href=#hide-hidden]").is(":visible")
+            timeout    : 20
             cb         : (err, listing) =>
-                #console.log("got back listing=",listing)
-                clearTimeout(timer)
-                spinner.spin(false).hide()
-
-                tm = misc.walltime()
-
-                @set_current_path(path)
-
-                # Update the display of the path above the listing or file preview
-                @update_current_path()
-
-                if err
-                    #console.log("update_file_list_tab: error -- ", err)
-                    if @_last_path_without_error? and @_last_path_without_error != path
-                        @set_current_path(@_last_path_without_error)
-                        @_last_path_without_error = undefined # avoid any chance of infinite loop
-                        @update_file_list_tab(no_focus)
-                    else
-                        @set_current_path('')
-                        @_last_path_without_error = undefined # avoid any chance of infinite loop
-                        @update_file_list_tab(no_focus)
+                if path != @_requested_path
+                    # requested another path after this one, so ignore
+                    # this now useless listing
                     cb?()
                     return
-
-                # remember for later
-                @_last_path_without_error = path
-
-                if not listing?
-                    cb?()
-                    return
-
-                # If the files haven't changed -- a VERY common case -- don't rebuild the whole listing.
-                files = misc.to_json(listing)  # use json to deep compare -- e.g., file size matters!
-                if @_update_file_list_tab_last_path == path and @_update_file_list_tab_last_path_files == files and @_update_file_sort_by_time == @_sort_by_time and @_last_show_all_files == @_show_all_files
-                    cb?()
-                    return
+                delete @_requested_path
+
+                clearTimeout(@_file_list_tab_spinner_timer)
+                @container.find(".project-file-listing-spinner").spin(false).hide()
+
+                if err
+                    alert_message(type:"error", message:"unable to show listing for #{path} -- #{err}")
+                    cb?(err)
                 else
-                    @_update_file_list_tab_last_path = path
-                    @_update_file_list_tab_last_path_files = files
-                    @_update_file_sort_by_time = @_sort_by_time
-                    @_last_show_all_files = @_show_all_files
-
-                @_last_listing = listing
-
-                if @current_path[0] == '.trash'
-                    @container.find("a[href=#empty-trash]").show()
-                    @container.find("a[href=#trash]").hide()
+                    @render_file_listing
+                        path     : path
+                        listing  : listing
+                        no_focus : no_focus
+                        cb       : cb
+                    @update_snapshot_link()
+
+    render_file_listing: (opts) =>
+        {path, listing, no_focus, cb} = defaults opts,
+            path     : required     # directory we are rendering the listing for
+            listing  : required     # the listing data
+            no_focus : false
+            cb       : undefined
+
+        url_path = path
+        if url_path.length > 0 and url_path[path.length-1] != '/'
+            url_path += '/'
+
+        if @current_tab.name == "project-file-listing"
+            @push_state('files/' + url_path)
+
+        # Update the display of the path above the listing or file preview
+        @set_current_path(path)
+        @update_current_path()
+
+        # If the files haven't changed -- a VERY common case -- don't rebuild the whole listing.
+        files = misc.to_json(listing)  # use json to deep compare -- e.g., file size matters!
+        if @_update_file_list_tab_last_path == path and @_update_file_list_tab_last_path_files == files and @_update_file_sort_by_time == @_sort_by_time and @_last_show_all_files == @_show_all_files
+            cb?()
+            return
+        else
+            @_update_file_list_tab_last_path       = path
+            @_update_file_list_tab_last_path_files = files
+            @_update_file_sort_by_time             = @_sort_by_time
+            @_last_show_all_files                  = @_show_all_files
+
+        @_last_listing = listing
+
+        if @current_path[0] == '.trash'
+            @container.find("a[href=#empty-trash]").show()
+            @container.find("a[href=#trash]").hide()
+        else
+            @container.find("a[href=#empty-trash]").hide()
+            @container.find("a[href=#trash]").show()
+
+        # Now rendering the listing or file preview
+        file_or_listing = @container.find(".project-file-listing-file-list")
+
+        # TODO: for long listings this file_or_listing.empty() dominates.
+        # We should just change data/displayed names of entries or something and hide others -- be way more clever. For LATER.
+        file_or_listing.empty()
+
+        directory_is_empty = true
+
+        @container.find(".project-file-tools a").removeClass("disabled")
+
+        # Hide the edit button
+        @container.find(".project-file-tools a[href=#edit]").addClass("disabled")
+
+        # Hide the move and delete buttons if and only if this is the top level path
+        if path == ""
+            @container.find(".project-file-tools a[href=#move]").addClass("disabled")
+            @container.find(".project-file-tools a[href=#delete]").addClass("disabled")
+
+        click_file = (e) =>
+            obj = $(e.delegateTarget).closest(".project-path-link").data('obj')
+            target = $(e.target)
+            if target.hasClass("salvus-file-action") or target.parent().hasClass('salvus-file-action')
+                @file_action_dialog(obj)
+            else
+                if obj.isdir
+                    @set_current_path(obj.fullname)
+                    @update_file_list_tab()
                 else
-                    @container.find("a[href=#empty-trash]").hide()
-                    @container.find("a[href=#trash]").show()
-
-
-                # Now rendering the listing or file preview
-                file_or_listing = @container.find(".project-file-listing-file-list")
-
-                # TODO: for long listings this file_or_listing.empty() dominates.
-                # We should just change data/displayed names of entries or something and hide others -- be way more clever. For LATER.
-                file_or_listing.empty()
-
-                directory_is_empty = true
-
-                # The path we are viewing.
-                path = @current_pathname()
-
-                @container.find(".project-file-tools a").removeClass("disabled")
-
-                # Show the command prompt
-                # @container.find("span.project-command-line").show().find("pre").hide()
-
-                # Hide the edit button
-                @container.find(".project-file-tools a[href=#edit]").addClass("disabled")
-
-                # Hide the move and delete buttons if and only if this is the top level path
-                if path == ""
-                    @container.find(".project-file-tools a[href=#move]").addClass("disabled")
-                    @container.find(".project-file-tools a[href=#delete]").addClass("disabled")
-
-                that = @
-
-                # TODO: not used
-                file_dropped_on_directory = (event, ui) ->
-                    src = ui.draggable.data('name')
-                    if not src?
-                        return
-                    dest = $(@).data('name')
-                    that.move_file
-                        src  : src
-                        dest : dest
-                        cb   : (err) =>
-                            if not err
-                                that.update_file_list_tab(true)
-
-                if that.current_path.length > 0
-                    # Create special link to the parent directory
-                    t = template_project_file.clone()
-                    t.find("a[href=#file-action]").hide()
-                    parent = that.current_path.slice(0, that.current_path.length-1).join('/')
-                    if parent == ""
-                        parent = "."
-                    t.data('name', parent)
-                    t.find(".project-file-name").html("Parent Directory")
-                    t.find(".project-file-icon").removeClass("fa-file").addClass('fa-reply')
-                    t.find("input").hide()  # hide checkbox, etc.
-                    # Clicking to open the directory
-                    t.click () ->
-                        that.current_path.pop()
-                        that.update_file_list_tab()
-                        return false
-                    t.droppable(drop:file_dropped_on_directory, scope:'files')
-                    t.find("a").tooltip
-                        trigger : 'hover'
-                        delay   : { show: 500, hide: 100 }
-                    t.find(".fa-arrows").tooltip
-                        trigger : 'hover'
-                        delay   : { show: 500, hide: 100 }
-
-                    file_or_listing.append(t)
-
-
-
-                #console.log("done updating misc stuff", misc.walltime(tm))
-
-                # Show the files
-                #console.log("building listing for #{path}...")
-
-                tm = misc.walltime()
-
-                masked_file_exts_bad  = (key for key of masked_file_exts)
-                masked_file_exts_good = (value for key, value of masked_file_exts)
-                masked_file_bad_index = []
-                masked_file_good_name = []
-                n = 0
-                that.container.find(".project-file-listing-show_all").hide().find('span').text('')
-                search = that._file_search_box.val()
-                for obj, i in listing.files
-                    if not search and (not that._show_all_files and n >= MAX_FILE_LISTING_SIZE)
-                        that.container.find(".project-file-listing-show_all").show().find('span').text(listing.files.length - n)
-                        break
-                    n += 1
-                    t = template_project_file.clone()
-                    t.data(obj:obj)
-                    if obj.isdir
-                        t.find(".project-file-name").text(obj.name)
-                        date = undefined
-                        if path == ".snapshots/master" and obj.name.length == '2014-04-04-061502'.length
-                            date = misc.parse_bup_timestamp(obj.name)
-                            t.find(".project-file-name").text(date)
-                        else if obj.mtime
-                            date = new Date(obj.mtime*1000)
-                        if date?
-                            t.find(".project-file-last-mod-date").attr('title', date.toISOString()).timeago()
-                        name = obj.name
-                        t.find(".project-file-icon").removeClass("fa-file").addClass("fa-folder-open-o").css('font-size':'21pt')
+                    @open_file
+                        path       : obj.fullname
+                        foreground : not(e.which==2 or (e.ctrlKey or e.metaKey))
+            e.preventDefault()
+
+        # TODO: not used
+        ###
+        that = @
+        file_dropped_on_directory = (event, ui) ->
+            src = ui.draggable.data('name')
+            if not src?
+                return
+            dest = $(@).data('name')
+            that.move_file
+                src  : src
+                dest : dest
+                cb   : (err) =>
+                    if not err
+                        that.update_file_list_tab(true)
+        ###
+
+        if @current_path.length > 0
+            # Create special link to the parent directory
+            t = template_project_file.clone()
+            t.find("a[href=#file-action]").hide()
+            parent = @current_path.slice(0, @current_path.length-1).join('/')
+            if parent == ""
+                parent = "."
+            t.data('name', parent)
+            t.find(".project-file-name").html("Parent Directory")
+            t.find(".project-file-icon").removeClass("fa-file").addClass('fa-reply')
+            t.find("input").hide()  # hide checkbox, etc.
+            # Clicking to open the directory
+            t.click () =>
+                @current_path.pop()
+                @update_file_list_tab()
+                return false
+            #t.droppable(drop:file_dropped_on_directory, scope:'files')
+            t.find("a").tooltip
+                trigger : 'hover'
+                delay   : { show: 500, hide: 100 }
+            t.find(".fa-arrows").tooltip
+                trigger : 'hover'
+                delay   : { show: 500, hide: 100 }
+
+            file_or_listing.append(t)
+
+
+
+        #console.log("done updating misc stuff", misc.walltime(tm))
+
+        # Show the files
+        #console.log("building listing for #{path}...")
+
+        tm = misc.walltime()
+
+        masked_file_exts_bad  = (key for key of masked_file_exts)
+        masked_file_exts_good = (value for key, value of masked_file_exts)
+        masked_file_bad_index = []
+        masked_file_good_name = []
+        n = 0
+        @container.find(".project-file-listing-show_all").hide().find('span').text('')
+        search = @_file_search_box.val()
+        for obj, i in listing.files
+            if not search and (not @_show_all_files and n >= MAX_FILE_LISTING_SIZE)
+                @container.find(".project-file-listing-show_all").show().find('span').text(listing.files.length - n)
+                break
+            n += 1
+            t = template_project_file.clone()
+            t.data(obj:obj)
+            if obj.isdir
+                t.find(".project-file-name").text(obj.name)
+                date = undefined
+                if path == ".snapshots/master" and obj.name.length == '2014-04-04-061502'.length
+                    date = misc.parse_bup_timestamp(obj.name)
+                    t.find(".project-file-name").text(date)
+                else if obj.mtime
+                    date = new Date(obj.mtime*1000)
+                if date?
+                    t.find(".project-file-last-mod-date").attr('title', date.toISOString()).timeago()
+                name = obj.name
+                t.find(".project-file-icon").removeClass("fa-file").addClass("fa-folder-open-o").css('font-size':'21pt')
+            else
+                if obj.name.indexOf('.') != -1
+                    ext = filename_extension(obj.name)
+                    name = obj.name.slice(0,obj.name.length - ext.length - 1)
+                else
+                    ext = ''
+                    name = obj.name
+                t.find(".project-file-name").text(name)
+                if ext != ''
+                    if ext in masked_file_exts_bad
+                        masked_file_bad_index.push(i)
+                    if ext in masked_file_exts_good
+                        masked_file_good_name.push(obj.name)
+                    t.find(".project-file-name-extension").text('.' + ext)
+                    if file_associations[ext]? and file_associations[ext].icon?
+                        t.find(".project-file-icon").removeClass("fa-file").addClass(file_associations[ext].icon)
+                if obj.mtime?
+                    date = (new Date(obj.mtime*1000)).toISOString()
+                    t.find(".project-file-last-mod-date").attr('title', date).timeago()
+                if obj.size?
+                    t.find(".project-file-size").text(human_readable_size(obj.size))
+                if obj.commit?.date?
+                    date = (new Date(obj.commit.date*1000)).toISOString()
+                    t.find(".project-file-last-commit-date").attr('title', date).timeago()
+                else
+                    t.find(".project-file-last-commit-date-container").hide()
+                if obj.commit?.message?
+                    t.find(".project-file-last-commit-message").text(trunc(obj.commit.message, 70))
+            #end if
+
+            obj.fullname = if path != "" then path + '/' + obj.name else obj.name
+            directory_is_empty = false
+            # Add our new listing entry to the list:
+            file_or_listing.append(t)
+            t.click(click_file)
+
+            continue
+            ###
+            # Define file actions using a closure
+            @_init_listing_actions(t, path, obj.name, obj.fullname, obj.isdir? and obj.isdir, obj.snapshot?)
+
+            # Drag handle for moving files via drag and drop.
+            handle = t.find(".project-file-drag-handle")
+            handle.click () =>
+                # do not want clicking on the handle to open the file.
+                return false
+            t.draggable
+                handle         : handle
+                zIndex         : 100
+                opacity        : 0.75
+                revertDuration : 200
+                revert         : "invalid"
+                axis           : 'y'
+                scope          : 'files'
+
+            t.find("a").tooltip
+                trigger : 'hover'
+                delay   : { show: 500, hide: 100 }
+            t.find(".fa-arrows").tooltip
+                trigger : 'hover'
+                delay   : { show: 500, hide: 100 }
+            ###
+
+        # Masks (greys out) files that the user probably doesn't want to open
+        if account.account_settings.settings.other_settings.mask_files
+            # mask compiled files corresponding to source files
+            for index in masked_file_bad_index
+                filename = listing.files[index].name
+                ext = filename_extension(filename)
+                name = filename.slice(0, filename.length - ext.length - 1)
+                compare_name = name
+
+                # TODO: other unusual cases may need to be added here
+                if ext == 'gz'
+                    second_extension = 'synctex'
+                    if filename_extension(name) == second_extension
+                        compare_name = name.slice(0, name.length - second_extension.length - 1)
                     else
-                        if obj.name.indexOf('.') != -1
-                            ext = filename_extension(obj.name)
-                            name = obj.name.slice(0,obj.name.length - ext.length - 1)
-                        else
-                            ext = ''
-                            name = obj.name
-                        t.find(".project-file-name").text(name)
-                        if ext != ''
-                            if ext in masked_file_exts_bad
-                                masked_file_bad_index.push(i)
-                            if ext in masked_file_exts_good
-                                masked_file_good_name.push(obj.name)
-                            t.find(".project-file-name-extension").text('.' + ext)
-                            if file_associations[ext]? and file_associations[ext].icon?
-                                t.find(".project-file-icon").removeClass("fa-file").addClass(file_associations[ext].icon)
-                        if obj.mtime?
-                            date = (new Date(obj.mtime*1000)).toISOString()
-                            t.find(".project-file-last-mod-date").attr('title', date).timeago()
-                        if obj.size?
-                            t.find(".project-file-size").text(human_readable_size(obj.size))
-                        if obj.commit?.date?
-                            date = (new Date(obj.commit.date*1000)).toISOString()
-                            t.find(".project-file-last-commit-date").attr('title', date).timeago()
-                        else
-                            t.find(".project-file-last-commit-date-container").hide()
-                        if obj.commit?.message?
-                            t.find(".project-file-last-commit-message").text(trunc(obj.commit.message, 70))
-                    #end if
-
-                    obj.fullname = if path != "" then path + '/' + obj.name else obj.name
-                    directory_is_empty = false
-                    # Add our new listing entry to the list:
-                    file_or_listing.append(t)
-                    t.click(click_file)
-
-                    continue
-
-                    # Define file actions using a closure
-                    @_init_listing_actions(t, path, obj.name, obj.fullname, obj.isdir? and obj.isdir, obj.snapshot?)
-
-                    # Drag handle for moving files via drag and drop.
-                    handle = t.find(".project-file-drag-handle")
-                    handle.click () =>
-                        # do not want clicking on the handle to open the file.
-                        return false
-                    t.draggable
-                        handle         : handle
-                        zIndex         : 100
-                        opacity        : 0.75
-                        revertDuration : 200
-                        revert         : "invalid"
-                        axis           : 'y'
-                        scope          : 'files'
-
-                    t.find("a").tooltip
-                        trigger : 'hover'
-                        delay   : { show: 500, hide: 100 }
-                    t.find(".fa-arrows").tooltip
-                        trigger : 'hover'
-                        delay   : { show: 500, hide: 100 }
-
-                # Masks (greys out) files that the user probably doesn't want to open
-                if account.account_settings.settings.other_settings.mask_files
-                    # mask compiled files corresponding to source files
-                    for index in masked_file_bad_index
-                        filename = listing.files[index].name
-                        ext = filename_extension(filename)
-                        name = filename.slice(0, filename.length - ext.length - 1)
-                        compare_name = name
-
-                        # TODO: other unusual cases may need to be added here
-                        if ext == 'gz'
-                            second_extension = 'synctex'
-                            if filename_extension(name) == second_extension
-                                compare_name = name.slice(0, name.length - second_extension.length - 1)
-                            else
-                                continue
-
-                        good_name_index = masked_file_good_name.indexOf(compare_name + "." + masked_file_exts[ext])
-                        if good_name_index != -1
-                            # mask the matched file
-                            i = if path == "" then index else index + 1 # skip over 'Parent Directory' link
-                            $(@container.find(".project-file-listing-file-list").children()[i]).addClass("project-file-listing-masked-file")
-
-                    # mask files starting with a '.'
-                    for file, index in listing.files
-                        if file.name.indexOf('.') == 0
-                            i = if path == "" then index else index + 1 # skip over 'Parent Directory' link
-                            $(@container.find(".project-file-listing-file-list").children()[i]).addClass("project-file-listing-masked-file")
-
-                #@clear_file_search()
-                #console.log("done building listing in #{misc.walltime(tm)}")
-                tm = misc.walltime()
-                @update_file_search()
-                #console.log("done building file search #{misc.walltime(tm)}")
-                tm = misc.walltime()
-
-                # No files
-                if directory_is_empty and path != ".trash" and path.slice(0,10) != ".snapshots"
-                    @container.find(".project-file-listing-no-files").show()
-                else
-                    @container.find(".project-file-listing-no-files").hide()
-
-                if path.slice(0,10) == '.snapshots'
-                    @container.find(".project-file-listing-snapshot-warning").show()
-                else
-                    @container.find(".project-file-listing-snapshot-warning").hide()
-
-                if no_focus? and no_focus
-                    cb?(); return
-
-                @focus_file_search()
-                #console.log("done with everything #{misc.walltime(tm)}")
-
-                cb?()
+                        continue
+
+                good_name_index = masked_file_good_name.indexOf(compare_name + "." + masked_file_exts[ext])
+                if good_name_index != -1
+                    # mask the matched file
+                    i = if path == "" then index else index + 1 # skip over 'Parent Directory' link
+                    $(@container.find(".project-file-listing-file-list").children()[i]).addClass("project-file-listing-masked-file")
+
+            # mask files starting with a '.'
+            for file, index in listing.files
+                if file.name.indexOf('.') == 0
+                    i = if path == "" then index else index + 1 # skip over 'Parent Directory' link
+                    $(@container.find(".project-file-listing-file-list").children()[i]).addClass("project-file-listing-masked-file")
+
+        #@clear_file_search()
+        #console.log("done building listing in #{misc.walltime(tm)}")
+        tm = misc.walltime()
+        @update_file_search()
+        #console.log("done building file search #{misc.walltime(tm)}")
+        tm = misc.walltime()
+
+        # No files
+        if directory_is_empty and path != ".trash" and path.slice(0,10) != ".snapshots"
+            @container.find(".project-file-listing-no-files").show()
+        else
+            @container.find(".project-file-listing-no-files").hide()
+
+        if path.slice(0,10) == '.snapshots'
+            @container.find(".project-file-listing-snapshot-warning").show()
+        else
+            @container.find(".project-file-listing-snapshot-warning").hide()
+
+        if no_focus? and no_focus
+            cb?(); return
+
+        @focus_file_search()
+        #console.log("done with everything #{misc.walltime(tm)}")
+
+        cb?()

     _init_listing_actions: (t, path, name, fullname, isdir, is_snapshot) =>
         if not fullname?
@@ -3287,149 +3179,6 @@
                     @display_tab("project-editor")
                 @editor.display_tab(path:opened_path, foreground:opts.foreground)

-    switch_displayed_branch: (new_branch) =>
-        if new_branch != @meta.display_branch
-            @meta.display_branch = new_branch
-            @update_file_list_tab()
-            @update_commits_tab()
-
-    update_commits_tab: () =>
-        {commit_list, commits} = @meta.logs[@meta.display_branch]
-
-        # Set the selector that allows one to choose the current branch.
-        select = @container.find(".project-commits-branch")
-        select.empty()
-        for branch in @meta.branches
-            select.append($("<option>").text(branch).attr("value",branch))
-        select.val(@meta.display_branch)
-        that = @
-        select.change  () ->
-            that.switch_displayed_branch($(@).val())
-            return false
-
-        # Set the list of commits for the current branch.
-        list = @container.find(".project-commits-list")
-        list.empty()
-        for id in commit_list
-            entry = commits[id]
-            t = template_project_commit_single.clone()
-            t.find(".project-commit-single-message").text(trunc(entry.message, 80))
-            t.find(".project-commit-single-author").text(entry.author)
-            t.find(".project-commit-single-date").attr('title', entry.date).timeago()
-            t.find(".project-commit-single-sha").text(id.slice(0,10))
-            list.append(t)
-
-    # Display all the branches, along with information about each one.
-    update_branches_tab: () =>
-        list = @container.find(".project-branches-list")
-        list.empty()
-
-        current_branch = @meta.current_branch
-        @container.find(".project-branch").text(current_branch)
-        that = @
-
-        for branch in @meta.branches
-            t = template_project_branch_single.clone()
-            t.find(".project-branch-single-name").text(branch)
-            if branch == current_branch
-                t.addClass("project-branch-single-current")
-                t.find("a[href=#checkout]").hide()
-                #t.find("a[href=#compare]").hide()
-                t.find("a[href=#merge]").hide()
-            t.data('branch', branch)
-
-            # TODO -- combine following three into a single loop
-
-            # Make it so clicking on the "Checkout" button checks out a given branch.
-            t.find("a[href=#checkout]").data("branch", branch).click (evt) ->
-                branch = $(@).data('branch')
-                that.branch_op(branch:branch, op:'checkout')
-                return false
-
-            t.find("a[href=#delete]").data("branch",branch).click (evt) ->
-                branch = $(@).data('branch')
-                # TODO -- stern warnings
-                that.branch_op(branch:branch, op:'delete')
-                return false
-
-            t.find("a[href=#merge]").data("branch",branch).click (evt) ->
-                branch = $(@).data('branch')
-                # TODO -- stern warnings
-                that.branch_op(branch:branch, op:'merge')
-                return false
-
-            list.append(t)
-
-        @container.find(".project-branches").find("input").attr('placeholder',"Create a new branch from '#{current_branch}'...")
-
-    #########################################
-    # Operations on files in a path and branch.
-    #########################################
-
-    path_action: (opts) =>
-        opts = defaults opts,
-            action  : required     # 'delete', 'move'
-            branch  : undefined    # defaults to displayed branch
-            path    : undefined    # defaults to displayed current_path
-            commit_mesg : required
-            extra_options : undefined  # needed for some actions
-
-        spin_timer = undefined
-
-        async.series([
-            # Display the file/listing spinner
-            (cb) =>
-                spinner = @container.find(".project-file-listing-spinner")
-                spin_timer = setTimeout((()->spinner.show().spin()), 500)
-                cb()
-            # Switch to different branch if necessary
-            (cb) =>
-                if opts.branch != @meta.current_branch
-                    @branch_op(branch:opts.branch, op:'checkout', cb:cb)
-                else
-                    cb()
-
-            # Carry out the action
-            (cb) =>
-                switch opts.action
-                    when 'delete'
-                        salvus_client.remove_file_from_project
-                            project_id : @project.project_id
-                            path       : opts.path
-                            cb         : (err, mesg) =>
-                                if err
-                                    cb(err)
-                                else if mesg.event == "error"
-                                    cb(mesg.error)
-                                else
-                                    @current_path.pop()
-                                    cb()
-                    when 'move'
-                        salvus_client.move_file_in_project
-                            project_id : @project.project_id
-                            src        : opts.path
-                            dest       : opts.extra_options.dest
-                            cb         : (err, mesg) =>
-                                if err
-                                    cb(err)
-                                else if mesg.event == "error"
-                                    cb(mesg.error)
-                                else
-                                    @current_path = opts.extra_options.dest.split('/')
-                                    cb()
-                    else
-                        cb("unknown path action #{opts.action}")
-
-            # Reload the files/branches/etc to take into account new commit, file deletions, etc.
-            (cb) =>
-                clearTimeout(spin_timer)
-                @update_file_list_tab()
-                cb()
-
-        ], (err) ->
-            if err
-                alert_message(type:"error", message:err)
-        )

 project_pages = {}


syncdoc -- automatically @connect (hence sync) on reconnection; this should make the user experience much, much better.

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -190,10 +190,12 @@
     _add_listeners: () =>
         salvus_client.on 'codemirror_diffsync_ready', @__diffsync_ready
         salvus_client.on 'codemirror_bcast', @__receive_broadcast
+        salvus_client.on 'signed_in', @__reconnect

     _remove_listeners: () =>
         salvus_client.removeListener 'codemirror_diffsync_ready', @__diffsync_ready
         salvus_client.removeListener 'codemirror_bcast', @__receive_broadcast
+        salvus_client.removeListener 'signed_in', @__reconnect

     __diffsync_ready: (mesg) =>
         if mesg.session_uuid == @session_uuid
@@ -202,6 +204,12 @@
     __receive_broadcast: (mesg) =>
         if mesg.session_uuid != @session_uuid
             @_receive_broadcast?(mesg)   # define in derived class
+
+    __reconnect: () =>
+        # The main websocket to the remote server died then came back, so we
+        # setup a new syncdoc session with the remote hub.  This will work fine,
+        # even if we connect to a different hub.
+        @connect (err) =>

     _apply_patch_to_live: (patch) =>
         @dsync_client._apply_edits_to_live(patch)
@@ -420,10 +428,19 @@
     _add_listeners: () =>
         salvus_client.on 'codemirror_diffsync_ready', @_diffsync_ready
         salvus_client.on 'codemirror_bcast', @_receive_broadcast
+        salvus_client.on 'signed_in', @__reconnect
+

     _remove_listeners: () =>
         salvus_client.removeListener 'codemirror_diffsync_ready', @_diffsync_ready
         salvus_client.removeListener 'codemirror_bcast', @_receive_broadcast
+        salvus_client.removeListener 'signed_in', @__reconnect
+
+    __reconnect: () =>
+        # The main websocket to the remote server died then came back, so we
+        # setup a new syncdoc session with the remote hub.  This will work fine,
+        # even if we connect to a different hub.
+        @connect (err) =>

     disconnect_from_session: (cb) =>
         @_remove_listeners()


PlotList was still using PlotContext in many places

--- static/coffee/common/plot_context.coffee
+++ static/coffee/common/plot_context.coffee
@@ -188,14 +188,14 @@
     children: []
     render_loop: true

+class PlotList extends PlotContext
+  type: 'PlotList'

 class PlotContexts extends Backbone.Collection
   model: PlotContext

-class PlotList extends PlotContext
-  type: 'PlotList'
-
 class PlotLists extends PlotContexts
+  model: PlotList

 exports.PlotContext = PlotContext
 exports.PlotContexts = PlotContexts


fix message getting

fix message getting

--- scripts/chat-logger.coffee
+++ scripts/chat-logger.coffee
@@ -114,7 +114,7 @@
       nick: msg.envelope.user.name
       type: t
       date: Date.now()
-    msg_data['text'] = msg.text if msg.text?
+    msg_data['text'] = msg.message.text if msg.message.text?

     t = new Date msg_data.date
     data = chat_data()[room] ||= {}


Refactor closure compilation

Refactor closure compilation

* Break up `Closure` and merge `Closure.wrap` into `Base.compileClosure`
* Construct class closure directly in `Class.compileNode`
* Reuse `isLiteralArguments` in `Range.compileArray`
* Move all helpers to bottom of file
* Add test for #3063

--- nodes.coffee
+++ nodes.coffee
@@ -79,7 +79,17 @@
     if jumpNode = @jumps()
       jumpNode.error 'cannot use a pure statement in an expression'
     o.sharedScope = yes
-    Closure.wrap(this).compileNode o
+    func = new Code [], Block.wrap [this]
+    args = []
+    if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
+      args = [new Literal 'this']
+      if argumentsNode
+        meth = 'apply'
+        args.push new Literal 'arguments'
+      else
+        meth = 'call'
+      func = new Value func, [new Access new Literal meth]
+    (new Call func, args).compileNode o

   # If the code generation wishes to use the result of a complex expression
   # in multiple places, ensure that the expression is only ever evaluated once,
@@ -848,7 +858,7 @@
       cond    = "#{@fromVar} <= #{@toVar}"
       body    = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
     post   = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
-    hasArgs = (node) -> node?.contains (n) -> n instanceof Literal and n.value is 'arguments' and not n.asKey
+    hasArgs = (node) -> node?.contains isLiteralArguments
     args   = ', arguments' if hasArgs(@from) or hasArgs(@to)
     [@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]

@@ -1078,9 +1088,16 @@
   # equivalent syntax tree and compile that, in pieces. You can see the
   # constructor, property assignments, and inheritance getting built out below.
   compileNode: (o) ->
+    if jumpNode = @body.jumps()
+      jumpNode.error 'Class bodies cannot contain pure statements'
+    if argumentsNode = @body.contains isLiteralArguments
+      argumentsNode.error "Class bodies shouldn't reference arguments"
+
     name  = @determineName() or '_Class'
-    name = "_#{name}" if name.reserved
+    name  = "_#{name}" if name.reserved
     lname = new Literal name
+    func  = new Code [], Block.wrap [@body]
+    args  = []

     @hoistDirectivePrologue()
     @setContext name
@@ -1091,18 +1108,15 @@
     @body.expressions.unshift @ctor unless @ctor instanceof Code
     @body.expressions.push lname

-    call  = Closure.wrap @body
-
-    if @parent and call.args
-      @superClass = new Literal o.scope.freeVariable 'super', no
-      @body.expressions.unshift new Extends lname, @superClass
-      call.args.push @parent
-      params = call.variable.params or call.variable.base.params
-      params.push new Param @superClass
+    if @parent
+      superClass = new Literal o.scope.freeVariable 'super', no
+      @body.expressions.unshift new Extends lname, superClass
+      func.params.push new Param superClass
+      args.push @parent

     @body.expressions.unshift @directives...

-    klass = new Parens call, yes
+    klass = new Parens new Call func, args
     klass = new Assign @variable, klass if @variable
     klass.compileToFragments o

@@ -2080,50 +2094,6 @@
   unfoldSoak: ->
     @soak and this

-# Faux-Nodes
-# ----------
-# Faux-nodes are never created by the grammar, but are used during code
-# generation to generate other combinations of nodes.
-
-#### Closure
-
-# A faux-node used to wrap an expressions body in a closure.
-Closure =
-
-  # Wrap the expressions body, unless it contains a pure statement,
-  # in which case, no dice. If the body mentions `this` or `arguments`,
-  # then make sure that the closure wrapper preserves the original values.
-  wrap: (expressions, statement, noReturn) ->
-    return expressions if expressions.jumps()
-    func = new Code [], Block.wrap [expressions]
-    args = []
-    argumentsNode = expressions.contains @isLiteralArguments
-    if argumentsNode and expressions.classBody
-      argumentsNode.error "Class bodies shouldn't reference arguments"
-    if argumentsNode or expressions.contains @isLiteralThis
-      meth = new Literal if argumentsNode then 'apply' else 'call'
-      args = [new Literal 'this']
-      args.push new Literal 'arguments' if argumentsNode
-      func = new Value func, [new Access meth]
-    func.noReturn = noReturn
-    call = new Call func, args
-    if statement then Block.wrap [call] else call
-
-  isLiteralArguments: (node) ->
-    node instanceof Literal and node.value is 'arguments' and not node.asKey
-
-  isLiteralThis: (node) ->
-    (node instanceof Literal and node.value is 'this' and not node.asKey) or
-      (node instanceof Code and node.bound) or
-      (node instanceof Call and node.isSuper)
-
-# Unfold a node's child if soak, then tuck the node under created `If`
-unfoldSoak = (o, parent, name) ->
-  return unless ifn = parent[name].unfoldSoak o
-  parent[name] = ifn.body
-  ifn.body = new Value parent
-  ifn
-
 # Constants
 # ---------

@@ -2190,8 +2160,8 @@
 IS_STRING = /^['"]/
 IS_REGEX = /^\//

-# Utility Functions
-# -----------------
+# Helper Functions
+# ----------------

 # Helper for ensuring that utility functions are assigned at the top level.
 utility = (name) ->
@@ -2212,3 +2182,18 @@
     parseInt x, 16
   else
     parseFloat x
+
+isLiteralArguments = (node) ->
+  node instanceof Literal and node.value is 'arguments' and not node.asKey
+
+isLiteralThis = (node) ->
+  (node instanceof Literal and node.value is 'this' and not node.asKey) or
+    (node instanceof Code and node.bound) or
+    (node instanceof Call and node.isSuper)
+
+# Unfold a node's child if soak, then tuck the node under created `If`
+unfoldSoak = (o, parent, name) ->
+  return unless ifn = parent[name].unfoldSoak o
+  parent[name] = ifn.body
+  ifn.body = new Value parent
+  ifn


Adding back in the closed-comprehensions tests.

--- nodes.coffee
+++ nodes.coffee
@@ -1293,7 +1293,7 @@
     hasCode = @body.contains (node) -> node instanceof Code
     name    = not @pattern and @name?.compile o
     index   = @index?.compile o
-    ivar    = if not index or hasCode then scope.freeVariable 'i' else index
+    ivar    = if not index then scope.freeVariable 'i' else index
     varPart = guardPart = defPart = retPart = ''
     idt     = @idt 1
     unless hasCode


_this to __this, for consistency. (StanAngeloff)

--- nodes.coffee
+++ nodes.coffee
@@ -185,11 +185,11 @@
     @expressions[idx]: last.makeReturn()
     this

-  # A bound function uses a local `_this` variable instead of the real `this`.
+  # A bound function uses a local `__this` variable instead of the real `this`.
   rewriteThis: ->
     @traverseChildren false, (child) ->
       if child instanceof ValueNode and child.base.value is 'this'
-        child.base: literal '_this'
+        child.base: literal '__this'

   # An **Expressions** is the only node that can serve as the root.
   compile: (o) ->
@@ -892,7 +892,7 @@
     func: "function(${ params.join(', ') }) {$code${ code and @idt(if @bound then 1 else 0) }}"
     func: "($func)" if top and not @bound
     return func unless @bound
-    "(function(_this) {\n${@idt(1)}return $func;\n$@tab})(this)"
+    "(function(__this) {\n${@idt(1)}return $func;\n$@tab})(this)"

   topSensitive: ->
     true


Changes remove hotkey from 'r' to 'd'.

Changes remove hotkey from 'r' to 'd'.

--- keymaps/quick-file-actions.cson
+++ keymaps/quick-file-actions.cson
@@ -9,5 +9,5 @@
 # https://atom.io/docs/latest/behind-atom-keymaps-in-depth
 'atom-workspace':
   'ctrl-alt-m': 'quick-file-actions:move'
-  'ctrl-alt-r': 'quick-file-actions:remove'
+  'ctrl-alt-d': 'quick-file-actions:remove'
   'ctrl-alt-c': 'quick-file-actions:copy'


should check SHARELATEX_REDIS_HOST not SHARELATEX_REDIS_URL


Achievements: Fix uploading achievements to server

--- server/achievements.coffee
+++ server/achievements.coffee
@@ -3,6 +3,7 @@
 # on in the future.

 {_} = require 'underscore'
+async = require 'async'
 config = require './config'
 redis = require './redis'
 ratings = require './ratings'
@@ -107,6 +108,47 @@
     conditionFn: streak(50)
   }
 ]
+
+# Checks what achievements a player is eligible for
+# The achievements are then awarded to the player
+@checkAndAwardAchievements = (server, player, next = ->) ->
+  checkAchievements player.ratingKey, (err, achievements) ->
+    return next(err) if err
+    return next() if achievements.length == 0
+
+    filterEarned player.id, achievements, (err, achievements) ->
+      return next(err) if err
+      return next() if achievements.length == 0
+
+      notifyServer player.name, achievements, (err, byStatus) ->
+        return next(err) if err
+
+        # TODO: Handle errors, probably add them to some queue to retry
+        # Currently its fine, as playing another battle will do a re-evalation
+        # but in the future this may not be the case!
+
+        # Flag all of the achievements that have been earned
+        achievementsToFlag = _.union(byStatus.success, byStatus.duplicate)
+        flagEarned(player.id, achievementsToFlag)  if achievementsToFlag.length > 0
+
+        # for each new achievement, notify the user if said user is online
+        if byStatus.success.length > 0
+          user = server.getUser(player.id)
+          if user
+            user.send('achievementsEarned', byStatus.success)
+
+        next()
+
+# Registers a battle with the achievement system
+# If the battle is not eligible for achievements, it is ignored
+@registerBattle = (server, battle) ->
+  return  if battle.format != 'xy1000'
+  return  unless Conditions.RATED_BATTLE in battle.conditions
+
+  battle.once 'ratingsUpdated', =>
+    for player in battle.players
+      @checkAndAwardAchievements(server, player)
+

 # Returns the achievements a player is eligible for, including already earned ones
 # id is the rating key used to identify the player
@@ -134,43 +176,29 @@
   redis.hmset("#{ACHIEVEMENT_KEY}:#{playerId}", hash)

 # Notifies the server about achievements to add to the user
-# All achievements that have been successfully get passed to next
-notifyServer = (playerId, achievements, next) ->
+# All achievements are separated by server result and passed to next
+notifyServer = (playerName, achievements, next) ->
+  achievementsByStatus =
+    success: []
+    duplicate: []
+    error: []
+
   if config.IS_LOCAL
-    return next(null, achievements)
-
-  request.post {
-    url: "http://pokebattle.com/api/v1/achievements/"
-    json: achievements.map((a) -> a.id)
-  }, (err, res, duplicates) ->
-    return next(err)  if err
-
-    # Remove duplicate achievements then return
-    filtered = achievements.filter((a) -> a.id not in duplicates)
-    next(null, achievements)
-
-@registerBattle = (server, battle) ->
-  return  if battle.format != 'xy1000'
-  return  unless Conditions.RATED_BATTLE in battle.conditions
-
-  battle.once 'ratingsUpdated', ->
-    for player in battle.players
-      do (player) ->
-        checkAchievements player.ratingKey, (err, achievements) ->
-          return  if err
-          return  if achievements.length == 0
-
-          filterEarned player.id, achievements, (err, achievements) ->
-            return  if err
-            return  if achievements.length == 0
-
-            notifyServer player.id, achievements, (err, achievements) ->
-              return  if err
-              return  if achievements.length == 0
-
-              flagEarned(player.id, achievements)
-
-              # for each new achievement, notify the user if said user is online
-              user = server.getUser(player.id)
-              if user
-                user.send('achievementsEarned', achievements)+    achievementsByStatus.success = achievements
+    return next(null, achievementsByStatus)
+
+  calls = for achievement in achievements
+    do (achievement) -> (callback) ->
+      request.post {
+        url: "https://www.pokebattle.com/api/v1/achievements/"
+        json: { user: playerName, achievement: achievement.name }
+      }, (err, res, data) ->
+        status = "success"
+        status = "error"  if err
+        status = "duplicate" if data.awarded
+        achievementsByStatus[status].push(achievement)
+        callback()
+
+  async.parallel calls, (err, results) ->
+    next(null, achievementsByStatus)
+


Fix typo

Fix typo

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -16,7 +16,7 @@

 import logger from '../log'

-import updateResults {updateAllResults} from '../calculations/updateResults'
+import updateResults, {updateAllResults} from '../calculations/updateResults'
 import downloadMaterials from '../cron/downloadMaterials'
 import * as downloadContests from '../cron/downloadContests'
 import * as downloadSubmits from "../cron/downloadSubmits"


moved responsibility for tracking collections into setup_collections

also, made the collection lists all caps

--- server/publications.coffee
+++ server/publications.coffee
@@ -1,8 +1,7 @@
 root = exports ? this
-hidden_collections = {'users': Meteor.users, 'meteor_accounts_loginServiceConfiguration': undefined}
+HIDDEN_COLLECTIONS = {'users': Meteor.users, 'meteor_accounts_loginServiceConfiguration': undefined}
+ADDED_COLLECTIONS = {}
 # TODO: describe what this is, exactly, and how it differs from Houston._collections.
-collections = {}
-

 Dummy = new Meteor.Collection("system.dummy")  # hack.

@@ -10,7 +9,8 @@
   Meteor.publish Houston._houstonize(name), func

 Houston._setup_collection = (collection) ->
-  return if collection._name in collections
+  return if collection._name of ADDED_COLLECTIONS
+
   name = collection._name
   methods = {}
   methods[Houston._houstonize "#{name}_insert"] = (doc) ->
@@ -55,6 +55,7 @@
     Houston._collections.collections.update c._id, {$set: count: collection.find().count(), fields: fields}
   else
     Houston._collections.collections.insert {name, count: collection.find().count(), fields: fields}
+  ADDED_COLLECTIONS[name] = collection

 sync_collections = ->
   Dummy.findOne()  # hack. TODO: verify this is still necessary
@@ -65,16 +66,17 @@
            (col.collectionName.indexOf "houston_") isnt 0)

     collection_names.forEach (name) ->
-      unless name of collections or name of hidden_collections
+      unless name of ADDED_COLLECTIONS or name of HIDDEN_COLLECTIONS
+        new_collection = null
         try
-          collections[name] = new Meteor.Collection(name)
+          new_collection = new Meteor.Collection(name)
         catch e
           for key, value of root
             if name == value._name # TODO here - typecheck also?
-              collections[name] = value
+              new_collection = value

-        if name of collections  # found it!
-          Houston._setup_collection(collections[name])
+        if new_collection?  # found it!
+          Houston._setup_collection(new_collection)
         else
           console.log """
 Houston: couldn't find access to the #{name} collection.


check image size

check image size

--- lib/client/autoform-file.coffee
+++ lib/client/autoform-file.coffee
@@ -57,6 +57,30 @@
   $(t.find('.js-value')).keyup()
   $(t.find('.js-value')).trigger('change')

+makeThatImage = (f, t, e) ->
+  console.log "doing the thing!"
+  console.log file, t
+
+  file = new FS.File f
+
+  if Meteor.userId
+    file.createdBy = Meteor.userId()
+
+  collection = getCollection t.data
+
+  Session.set('uploaderProgress', 0)
+
+  collection.insert file, (err, fileObj) ->
+    if err then return console.log err
+    setValue(fileObj._id, e, t);
+    progress = setInterval(->
+      pct = fileObj.uploadProgress()
+      if (pct >= 100)
+        clearInterval(progress)
+        update(t)
+      Session.set('uploaderProgress', pct)
+    , 250)
+
 Template.afFileUpload.events
   'click .js-select-file': (e, t) ->
     e.preventDefault()
@@ -69,25 +93,27 @@
     return false

   'change .js-file': (e, t) ->
-    file = new FS.File e.target.files[0]
-    if Meteor.userId
-      file.createdBy = Meteor.userId()
+    theTemplate = t
+    theEvent = e

-    collection = getCollection t.data
-    #console.log([name, 'change .js-file', e.target, t, file, collection]);
+    if e.target.files[0].type = "image/png" || e.target.files[0].type = "image/jpg"
+      img = new Image
+      img.onload = () ->
+        if img.width < 1920 && img.height < 1080
+          makeThatImage(e.target.files[0], theTemplate, theEvent)
+        else
+          BootstrapModalPrompt.prompt
+            title: "Woah there!"
+            content: "That image is WAY too big. Try a smaller image, or resize it before uploading."
+          , (result) ->
+            if result
+              #
+            else
+              #
+      img.src = URL.createObjectURL(e.target.files[0])
+    else
+      makeThatImage(e.target.files[0], theTemplate, theEvent)

-    Session.set('uploaderProgress', 0)
-
-    collection.insert file, (err, fileObj) ->
-      if err then return console.log err
-      setValue(fileObj._id , e ,t);
-      progress = setInterval(->
-        pct = fileObj.uploadProgress()
-        if (pct >= 100)
-          clearInterval(progress)
-          update(t)
-        Session.set('uploaderProgress', pct)
-      , 250)

 Template.afFileUploadThumbIcon.helpers
   icon: ->


:racehorse: Prevent drawing tokens past the canvas width

--- lib/mixins/canvas-drawer.coffee
+++ lib/mixins/canvas-drawer.coffee
@@ -180,6 +180,8 @@
           x = @drawToken(context, value, color, x, y0, charWidth, charHeight)
         else
           x += w * charWidth
+
+        break if x > canvasWidth

       # Finally the highlight over decorations are drawn.
       highlightDecorations = @minimap.decorationsByTypesForRow(firstRow + row, 'highlight', 'highlight-over', decorations)


Fix. VData repeaded name fix

Fix. VData repeaded name fix

--- snippets/vue_template.cson
+++ snippets/vue_template.cson
@@ -17,10 +17,10 @@
     rightLabelHTML: 'Vue data: Object | Function'
     body: 'data: {\n\treturn {\n\t\t$1\n\t}\n},'
   'Vue: props':
-    prefix: 'vData'
+    prefix: 'vProps'
     leftLabelHTML: 'Ⓢ'
     rightLabelHTML: 'Vue props: Array<string> | Object'
-    body: 'props: {\n\t$1\n},'
+    body: 'props: {\n\t${1:[\'$2\']}\n},'
   'Vue: propsData()':
     prefix: 'vPropsData'
     leftLabelHTML: 'Ⓢ'


Added oss.maxcdn.com for jquery

Added oss.maxcdn.com for jquery

--- src/configs/hash-config.coffee
+++ src/configs/hash-config.coffee
@@ -1,6 +1,9 @@
 module.exports =
 	'jquery':
 		versions: [
+			'2.1.4'
+			'2.1.3'
+			'2.1.2'
 			'2.1.1'
 			'2.1.0'
 			'2.0.3'
@@ -41,6 +44,7 @@
 			'ajax.aspnetcdn.com/ajax/jQuery/jquery-$version$.min.js'
 			'cdnjs.cloudflare.com/ajax/libs/jquery/$version$/jquery.min.js'
 			'code.jquery.com/jquery-$version$.min.js'
+			'oss.maxcdn.com/jquery/$version$/jquery.min.js'
 		]
 		file: '$name$/$version$/jquery.min.js'


Testing for cache in #firstPage()

Testing for cache in #firstPage()

--- test/connect-instagram-proxy-test.coffee
+++ test/connect-instagram-proxy-test.coffee
@@ -1,9 +1,12 @@

 should = require 'should'
 nock = require 'nock'
-proxy = require '../dist/connect-instagram-proxy'
+rewire = require 'rewire'
+proxy = rewire '../src/connect-instagram-proxy'
 firstPageMock = require './mocks/first-page-mock'
 secondPageMock = require './mocks/second-page-mock'
+
+mediaCache = proxy.__get__ 'mediaCache'

 nock('https://api.instagram.com')
   .persist()
@@ -26,6 +29,9 @@

     firstPage = proxy.firstPage

+    afterEach ->
+      mediaCache.flushAll()
+
     it 'should exist as a public function', (done) ->

       firstPage.should.be.a.Function
@@ -44,14 +50,29 @@
       middleware.should.be.a.Function
       done()

-    it 'should insert instagram json response in the request object', (done) ->
+    it 'should respond from cache if instagram response is cached', (done) ->
+
+      mediaCache.set 'firstPage', firstPageMock

       clientId = 1
       userId = 1

       middleware = firstPage clientId, userId
       middleware req, res, ->
+        cacheStats = mediaCache.getStats()
+        cacheStats.hits.should.equal 1
+        req.instagram.firstPage.should.be.an.Object
+        done()

+    it 'should consult instagram API and cache response if not cached', (done) ->
+
+      clientId = 1
+      userId = 1
+
+      middleware = firstPage clientId, userId
+      middleware req, res, ->
+        cacheStats = mediaCache.getStats()
+        cacheStats.hits.should.equal 0
         req.instagram.firstPage.should.be.an.Object
         done()


Fix mediator tests correct way

Fix mediator tests correct way

--- test/integration/mediatorAPITests.coffee
+++ test/integration/mediatorAPITests.coffee
@@ -1160,10 +1160,9 @@
               Channel.find {}, (err, channels) ->
                 return done err if err
                 channels.length.should.be.exactly 2
-                channelX = true if channels[0].name is 'Save Encounter 1' or 'Save Encounter 2'
-                channelY = true if channels[1].name is 'Save Encounter 1' or 'Save Encounter 2'
-                channelY.should.be true
-                channelX.should.be true
+                channelNames = channels.map (channel) -> channel.name
+                channelNames.should.containEql 'Save Encounter 1'
+                channelNames.should.containEql 'Save Encounter 2'
                 done()

       it 'should add selected channels in the defaultChannelConfig property if the body is set (save one)', (done) ->
@@ -1203,10 +1202,9 @@
                 Channel.find {}, (err, channels) ->
                   done err if err
                   channels.length.should.be.exactly 2
-                  channelX = true if channels[0].name is 'Save Encounter 1' or 'Save Encounter 2'
-                  channelY = true if channels[1].name is 'Save Encounter 1' or 'Save Encounter 2'
-                  channelY.should.be true
-                  channelX.should.be true
+                  channelNames = channels.map (channel) -> channel.name
+                  channelNames.should.containEql 'Save Encounter 1'
+                  channelNames.should.containEql 'Save Encounter 2'
                   done()

       it 'should return a 400 when a channel from the request body isn\'t found', (done) ->


Fix error handling bug in RailsStorage adapter

* Exception handler attempted to use `env`; a non-existent object.

--- src/extras/batman.rails.coffee
+++ src/extras/batman.rails.coffee
@@ -114,7 +114,7 @@
           try
             validationErrors = @_errorsFrom422Response(response)
           catch extractionError
-            env.error = extractionError
+            @error = extractionError
             return next()

           for key, errorsArray of validationErrors


whittle it down to rules structs

whittle it down to rules structs

--- src/fill-all-the-things.coffee
+++ src/fill-all-the-things.coffee
@@ -11,7 +11,7 @@

 ( ($,_) ->

-  f.actions = [
+  types = [
       test: (it) -> it.is(':checkbox,:radio')
       action: (it) -> it.attr('checked','checked')
     ,
@@ -19,35 +19,44 @@
       action: (it) -> it.find('option[value="1975"],option:last').val()
     ,
       test: -> true
-      action: (it, val) -> f.figureOutAValue(it, val)
+      action: (it, val) -> figureOutAValue(it, val)
+  ]
+
+  textFills = [
+      test: (it) -> it.is(':password')
+      action: -> "f1llTh!NG$?"
+    ,
+      test: (it) -> it.is('[type="email"]') or /email/i.test(it.attr('name'))
+      action: -> "fill@llthethings.org"
+    ,
+      test: (it) -> it.is('[type="tel"]')
+      action: -> "123-456-7890"
+    ,
+      test: (it) -> it.is('[type="url"]')
+      action: -> "http://www.w3.org"
+    ,
+      test: (it, val) -> !val
+      action: -> "Filling a Thing"
+    ,
+      test: -> true
+      action: (it, val) -> val
   ]

   f.fill = ->
     $inputs = $(':input:visible:enabled').val (i, val) ->
-      it = $(@)
-      newVal = val
-      _(f.actions).find (o) ->
-        if o.test(it, val)
-          newVal = o.action(it, val)
-          true
-      newVal
+      doFirst(types, [$(@), val])

-  f.figureOutAValue = (it, val) ->
-    if it.is(':password')
-      "f1llTh!NG$?"
-    else if it.is('[type="email"]') or /email/i.test(it.attr('name'))
-      "fill@llthethings.org"
-    else if it.is('[type="tel"]')
-      "123-456-7890"
-    else if it.is('[type="url"]')
-      "http://www.w3.org"
-    else if !val
-      "Filling a Thing"
-    else
-      val
+  figureOutAValue = (it, val) ->
+    doFirst(textFills,[it,val])

+  doFirst = (actions, args) ->
+    match = undefined
+    _(actions).find (o) ->
+      if o.test.apply(@, args)
+        match = o.action.apply(@, args)
+        true
+    match

-  $ ->
-    f.fill()
+  $ -> f.fill()
 )(f.$,f._)


Use helper methods instead of reading asyncStatus

--- src/flux/course-listing.coffee
+++ src/flux/course-listing.coffee
@@ -48,10 +48,10 @@
     # Loads the store if it's not already loaded or loading
     # Returns false if the store is already loaded, true otherwise
     ensureLoaded: ->
-      if @_asyncStatus is LOADED
+      if CourseListingStore.isLoaded()
         false
       else
-        CourseListingActions.load() unless @_asyncStatus is LOADING
+        CourseListingActions.load() unless CourseListingStore.isLoading()
         true

     allCourses: ->


clean up naming

clean up naming

--- tutor/src/flux/app.coffee
+++ tutor/src/flux/app.coffee
@@ -36,8 +36,8 @@
     _.last(@_cache[requestHashKey])

   _getPendingHashKey: (requestConfig) ->
-    sparseRequest = @_getRequestOpts(requestConfig)
-    getRequestHash(sparseRequest)
+    request = @_getRequestOpts(requestConfig)
+    getRequestHash(request)

   _getPendingInfo: (requestConfig) ->
     requestHashKey = @_getPendingHashKey(requestConfig)
@@ -100,7 +100,6 @@
     get: ->
       pending: @_pending
       statuses: @_statuses
-      current: @_cache

     isPending: (requestConfig) ->
       @_getPendingInfo(requestConfig)?


Show queries waiting to execute correctly

--- lib/pg-hoff-results-view.coffee
+++ lib/pg-hoff-results-view.coffee
@@ -73,29 +73,38 @@
     createTable: (x, resultsetIndex) ->
         container = document.createElement('div')
         container.classList.add('table')
+        container.classList.add('executing')

         if x.executing
             pre = container.appendChild(document.createElement('pre'))
             pre.textContent = 'Executing for ' + x.runtime_seconds + ' seconds...'
             container.classList.add('executing')
+
             return container
+
+        if not x.complete
+            pre = container.appendChild(document.createElement('pre'))
+            pre.textContent = 'Waiting to execute...'
+            container.classList.add('executing')

         table = container.appendChild(document.createElement('table'))

         # Header columns
-        col_tr = table.appendChild(document.createElement('tr'))
-        i = 0
-        for c in x.columns
-            col_tr.appendChild(@createTh(c.name, resultsetIndex, i))
-            i = i + 1
+        if x.columns?
+            col_tr = table.appendChild(document.createElement('tr'))
+            i = 0
+            for c in x.columns
+                col_tr.appendChild(@createTh(c.name, resultsetIndex, i))
+                i = i + 1

         # Rows
-        for r in x.rows
-            row_tr = table.appendChild(document.createElement('tr'))
-            i = 0
-            for c in r
-                row_tr.appendChild(@createTd(c, x.columns[i].type_code))
-                i = i + 1
+        if x.rows?
+            for r in x.rows
+                row_tr = table.appendChild(document.createElement('tr'))
+                i = 0
+                for c in r
+                    row_tr.appendChild(@createTd(c, x.columns[i].type_code))
+                    i = i + 1

         return container

@@ -160,8 +169,9 @@
         i = 0
         for resultset in resultsets
             if atom.config.get('pg-hoff.displayQueryExecutionTime') && !resultset.executing
-                time = @element.appendChild(document.createElement('div'))
-                time.textContent = resultset.runtime_seconds + ' seconds.'
+                if resultset.runtime_seconds?
+                    time = @element.appendChild(document.createElement('div'))
+                    time.textContent = resultset.runtime_seconds + ' seconds.'

             @element.appendChild(@createTable(resultset, i))
             i++


NEW: inspectedPath for more detailed path-based info over just inspectedName

--- source/Art/Engine/Core/ElementBase.coffee
+++ source/Art/Engine/Core/ElementBase.coffee
@@ -16,6 +16,7 @@
   compact
   isObject
   object
+  compactFlatten
   w
 } = require 'art-standard-lib'
 {Bitmap} = require 'art-canvas'
@@ -188,6 +189,10 @@
     shortNamespacePath: ->
       name = @namespacePath
       peek name.split '.'
+
+    inspectedPath: ->
+      compactFlatten [@parent?.inspectedPath, @inspectedName]
+      .join '/'

     inspectedName: ->
       "#{@shortNamespacePath}:#{@pendingKey || @instanceId}"


Update CoffeeScript per class name change, fixes #683

--- src/resources/forms/orbeon/builder/resources/section-grid-repeat/grid-repeat-editor.coffee
+++ src/resources/forms/orbeon/builder/resources/section-grid-repeat/grid-repeat-editor.coffee
@@ -125,7 +125,7 @@
         # Functions maintaining current row/col position
         resetPos = (pos) -> -> current[pos] = -1
         setPos = (pos) -> (rowCol) ->
-            selector = '.fr-grid-' + rowCol.el[0].nodeName.toLowerCase()
+            selector = '.fb-grid-' + rowCol.el[0].nodeName.toLowerCase()
             current[pos] = f$.length f$.prevAll selector, rowCol.el

         Builder.currentRowColChanged gridsCache,


Add CPU usage

Add CPU usage

--- workers/librato/lib/librato/index.coffee
+++ workers/librato/lib/librato/index.coffee
@@ -16,6 +16,9 @@

 os = require 'os'
 http = require 'http'
+
+# Interval
+interval = if librato?.interval? then librato.interval else 10000

 # Node ID
 node_id = os.hostname()
@@ -52,6 +55,14 @@
     name: 'load'
     source: '15:' + node_id
     value: loadavg[2]
+
+  # CPU usage
+  cpu_usage = get_cpu_usage()
+  print "CPU usage: " + cpu_usage
+  cpu_total =
+    name: 'cpu'
+    source: node_id
+    value: cpu_usage

   # Available memory
   mem_total = os.totalmem() / (1024 * 1024)
@@ -90,6 +101,8 @@
       load1,
       load5,
       load15,
+      # CPU usage
+      cpu_total,
       # Memory
       memory_used,
       # Users
@@ -102,6 +115,20 @@
     ]
   print ""
   stats
+
+get_cpu_usage = ->
+  cpus = os.cpus()
+  used = 0
+  total = 0
+
+  for i of cpus
+    cpu = cpus[i]
+    for type of cpu.times
+      total += cpu.times[type]
+      if type != 'idle'
+        used += cpu.times[type]
+
+  Math.floor 100 * used / total

 # Pushes stats to Librato
 push = (stats) ->
@@ -144,9 +171,9 @@
 # Collect data from Mongo in every <interval>/2 seconds
 setInterval ->
   collect_mongo()
-, (librato.interval / 2)
+, (interval / 2)

 # Post to Librato in every <interval> seconds
 setInterval ->
   post_to_librato()
-, librato.interval+, interval


better indentation handling for far-left heredocs and herecomments

--- nodes.coffee
+++ nodes.coffee
@@ -368,7 +368,7 @@

   compile_node: (o) ->
     if @type is 'herecomment'
-      sep: '\n' + @idt(1)
+      sep: '\n' + @tab
       "$@tab/*$sep${ @lines.join(sep) }\n$@tab*/"
     else
       "$@tab//" + @lines.join("\n$@tab//")


coco dc8945c: less ternaries, more returns-from-ifs. More readable.

--- nodes.coffee
+++ nodes.coffee
@@ -35,7 +35,7 @@
   # already been asked to return the result (because statements know how to
   # return results).
   compile: (o, lvl) ->
-    o        = if o then extend {}, o else {}
+    o        = extend {}, o
     o.level  = lvl if lvl?
     node     = @unfoldSoak(o) or this
     node.tab = o.indent
@@ -160,8 +160,6 @@

   children: ['expressions']

-  isStatement: YES
-
   constructor: (nodes) ->
     @expressions = compact flatten nodes or []

@@ -187,6 +185,11 @@
   # Is this an empty block of code?
   isEmpty: ->
     not @expressions.length
+
+  isStatement: (o) ->
+    for exp in @expressions when exp.isPureStatement() or exp.isStatement o
+      return yes
+    no

   # An Expressions node does not return its entire body, rather it
   # ensures that the final expression is returned.
@@ -200,9 +203,25 @@
   compile: (o = {}, level) ->
     if o.scope then super o, level else @compileRoot o

-  compileNode: (o) ->
-    @tab = o.indent
-    (@compileExpression node, o for node in @expressions).join '\n'
+  # Compile all expressions within the **Expressions** body. If we need to
+  # return the result, and it's an expression, simply return it. If it's a
+  # statement, ask the statement to do so.
+  compileNode: (o) ->
+    @tab  = o.indent
+    top   = o.level is LEVEL_TOP
+    codes = []
+    for node in @expressions
+      node = node.unwrapAll()
+      node = (node.unfoldSoak(o) or node)
+      if top
+        node.front = true
+        code = node.compile o
+        codes.push if node.isStatement o then code else @tab + code + ';'
+      else
+        codes.push node.compile o, LEVEL_LIST
+    return codes.join '\n' if top
+    code = codes.join(', ') or 'void 0'
+    if codes.length > 1 and o.level >= LEVEL_LIST then "(#{code})" else code

   # If we happen to be the top-level **Expressions**, wrap everything in
   # a safety closure, unless requested not to.
@@ -219,6 +238,7 @@
   # Compile the expressions body for the contents of a function, with
   # declarations of all inner variables pushed up to the top.
   compileWithDeclarations: (o) ->
+    o.level = LEVEL_TOP
     code    = @compileNode o
     {scope} = o
     if scope.hasAssignments this
@@ -227,17 +247,6 @@
       code = "#{@tab}var #{ scope.compiledDeclarations() };\n#{code}"
     code

-  # Compiles a single expression within the expressions body. If we need to
-  # return the result, and it's an expression, simply return it. If it's a
-  # statement, ask the statement to do so.
-  compileExpression: (node, o) ->
-    node = node.unwrapAll()
-    node = node.unfoldSoak(o) or node
-    node.front = yes
-    o.level = LEVEL_TOP
-    code    = node.compile o
-    if node.isStatement o then code else @tab + code + ';'
-
   # Wrap up the given nodes as an **Expressions**, unless it already happens
   # to be one.
   @wrap: (nodes) ->
@@ -254,7 +263,7 @@
   constructor: (@value) ->

   makeReturn: ->
-    if @isStatement() then this else super()
+    if @isPureStatement() then this else new Return this

   # Break and continue must be treated as pure statements -- they lose their
   # meaning when wrapped in a closure.
@@ -1467,12 +1476,9 @@
     if @isStatement o then @compileStatement o else @compileExpression o

   makeReturn: ->
-    if @isStatement()
-      @body     and= @ensureExpressions @body.makeReturn()
-      @elseBody and= @ensureExpressions @elseBody.makeReturn()
-      this
-    else
-      new Return this
+    @body     and= new Expressions [@body.makeReturn()]
+    @elseBody and= new Expressions [@elseBody.makeReturn()]
+    this

   ensureExpressions: (node) ->
     if node instanceof Expressions then node else new Expressions [node]
@@ -1489,7 +1495,9 @@
     ifPart   = @tab + ifPart unless child
     return ifPart unless @elseBody
     ifPart + ' else ' + if @isChain
-       @elseBodyNode().compile merge o, indent: @tab, chainChild: true
+      o.indent = @tab
+      o.chainChild = yes
+      @elseBody.unwrap().compile o, LEVEL_TOP
     else
       "{\n#{ @elseBody.compile o, LEVEL_TOP }\n#{@tab}}"