Esc then tab just bring back suggestion // Users can go back to their previous states after tabbing through suggestions. Related to #388

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -376,23 +376,41 @@
                 else if event.which is 9
                     event.preventDefault()
                     if event.type is 'keydown'
-                        # Switch throught the suggestions
-                        if event.shiftKey
-                            @current_highlighted_suggestion--
-                            if @current_highlighted_suggestion < 0
-                                @current_highlighted_suggestion = @current_suggestions.length-1
+                        if @$('.suggestion_name_list').css('display') is 'none'
+                            @show_suggestion()
+                            return true
                         else
-                            @current_highlighted_suggestion++
-                            if @current_highlighted_suggestion >= @current_suggestions.length
-                                @current_highlighted_suggestion = 0
-
-                        if @current_suggestions[@current_highlighted_suggestion]?
-                            @show_suggestion_without_moving()
-                            @highlight_suggestion @current_highlighted_suggestion # Highlight the current suggestion
-                            @write_suggestion
-                                suggestion_to_write: @current_suggestions[@current_highlighted_suggestion] # Auto complete with the highlighted suggestion
-                            @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
-                            return true
+                            # Switch throught the suggestions
+                            if event.shiftKey
+                                @current_highlighted_suggestion--
+                                if @current_highlighted_suggestion < -1
+                                    @current_highlighted_suggestion = @current_suggestions.length-1
+                                else if @current_highlighted_suggestion < 0
+                                    @show_suggestion_without_moving()
+                                    @remove_highlight_suggestion()
+                                    @write_suggestion
+                                        suggestion_to_write: @current_element
+                                    @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
+                                    return true
+                            else
+                                @current_highlighted_suggestion++
+                                if @current_highlighted_suggestion >= @current_suggestions.length
+                                    @show_suggestion_without_moving()
+                                    @remove_highlight_suggestion()
+                                    @write_suggestion
+                                        suggestion_to_write: @current_element
+                                    @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
+                                    @current_highlighted_suggestion = -1
+                                    return true
+
+
+                            if @current_suggestions[@current_highlighted_suggestion]?
+                                @show_suggestion_without_moving()
+                                @highlight_suggestion @current_highlighted_suggestion # Highlight the current suggestion
+                                @write_suggestion
+                                    suggestion_to_write: @current_suggestions[@current_highlighted_suggestion] # Auto complete with the highlighted suggestion
+                                @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
+                                return true
                 # If the user hit enter and (Ctrl or Shift)
                 else if event.which is 13 and (event.shiftKey or event.ctrlKey or event.metaKey)
                     @hide_suggestion_and_description()
@@ -1101,6 +1119,7 @@
                                 result.suggestions_regex = @create_safe_regex element.name # That means we are going to give all the suggestions that match element.name and that are in the good group (not yet defined)
                                 result.description = null
                                 @query_first_part = query.slice 0, element.position+1
+                                @current_element = element.name
                                 @cursor_for_auto_completion.ch -= element.name.length
                                 @current_query
                                 if i isnt 0
@@ -1283,11 +1302,14 @@

         #Highlight suggestion. Method called when the user hit tab or mouseover
         highlight_suggestion: (id) =>
-            @.$('.suggestion_name_li').removeClass 'suggestion_name_li_hl'
+            @remove_highlight_suggestion()
             @.$('.suggestion_name_li').eq(id).addClass 'suggestion_name_li_hl'
             @.$('.suggestion_description').html @description_template @extend_description @current_suggestions[id]

             @.$('.suggestion_description').show()
+
+        remove_highlight_suggestion: =>
+            @.$('.suggestion_name_li').removeClass 'suggestion_name_li_hl'

         # Write the suggestion in the code mirror
         write_suggestion: (args) =>


Fix suggestions' position in case of full view

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -334,7 +334,7 @@
         # Change the num_char_per_line value when we switch from normal view to full view and vice versa
         set_char_per_line: =>
             if @displaying_full_view is true
-                @num_char_per_line = Math.floor (@.$('Codemirror-scroll').width()-37)/8
+                @num_char_per_line = Math.floor (@.$('.CodeMirror-scroll').width()-37)/8
             else
                 @num_char_per_line = @default_num_char_per_line

@@ -355,11 +355,14 @@

             if margin_left < 200
                 @.$('.suggestion_full_container').css 'left', '0px'
-            else if margin_left > 468
-                @.$('.suggestion_full_container').css 'left', '440px'
             else
-                margin_left = Math.min 468, Math.floor(margin_left/100)*100
-                @.$('.suggestion_full_container').css 'left', (margin_left-100)+'px'
+                 max_margin = @.$('.CodeMirror-scroll').width()-418
+
+                if margin_left > max_margin
+                    @.$('.suggestion_full_container').css 'left', (max_margin-28)+'px'
+                else
+                    margin_left = Math.min max_margin, Math.floor(margin_left/100)*100
+                    @.$('.suggestion_full_container').css 'left', (margin_left-100)+'px'

         #TODO refactor show_suggestion, show_suggestion_description, add_description
         show_suggestion: =>


Update a comment for clean_query

Update a comment for clean_query

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -2564,7 +2564,7 @@
             "use strict"
             return eval(query)

-        # In a string \n becomes \\\\n, outside a string we just remove \n, so
+        # In a string \n becomes \\n, outside a string we just remove \n, so
         #   r
         #   .expr('hello
         #   world')


[gulp] package-json task now no raise error on first gulp execute

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -117,15 +117,19 @@
 # package.json copy Task
 #
 g.task "package-json", (cb) ->
-    string = fs.readFileSync "./package.json", {encoding: "utf8"}
-    json = JSON.parse(string)
-
-    delete json.devDependencies
-    newString = JSON.stringify json, null, "  "
-
-    fs.writeFileSync path.join(gulpOption.sourceDir, "package.json"), newString, {encoding: "utf8"}
-    fs.writeFileSync path.join(gulpOption.buildDir, "package.json"), newString, {encoding: "utf8"}
+    try
+        string = fs.readFileSync "./package.json", {encoding: "utf8"}
+        json = JSON.parse(string)
+
+        delete json.devDependencies
+        newString = JSON.stringify json, null, "  "
+
+        fs.mkdirSync(gulpOption.buildDir)
+        fs.writeFileSync path.join(gulpOption.sourceDir, "package.json"), newString, {encoding: "utf8"}
+        fs.writeFileSync path.join(gulpOption.buildDir, "package.json"), newString, {encoding: "utf8"}
+
     cb()
+    return

 #
 # File watch Task


Revert "Independent Atom instances (per $ATOM_HOME)"

--- src/main-process/atom-application.coffee
+++ src/main-process/atom-application.coffee
@@ -8,7 +8,6 @@
 ipcHelpers = require '../ipc-helpers'
 {BrowserWindow, Menu, app, dialog, ipcMain, shell, screen} = require 'electron'
 {CompositeDisposable, Disposable} = require 'event-kit'
-crypto = require 'crypto'
 fs = require 'fs-plus'
 path = require 'path'
 os = require 'os'
@@ -34,16 +33,11 @@
   # Public: The entry point into the Atom application.
   @open: (options) ->
     unless options.socketPath?
-      username = if process.platform is 'win32' then process.env.USERNAME else process.env.USER
-      # Lowercasing the ATOM_HOME to make sure that we don't get multiple sockets
-      # on case-insensitive filesystems due to arbitrary case differences in paths.
-      atomHomeUnique = path.resolve(process.env.ATOM_HOME).toLowerCase()
-      hash = crypto.createHash('sha1').update(username).update('|').update(atomHomeUnique)
-      atomInstanceDigest = hash.digest('hex').substring(0, 32)
       if process.platform is 'win32'
-        options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-#{process.arch}-#{atomInstanceDigest}-sock"
+        userNameSafe = new Buffer(process.env.USERNAME).toString('base64')
+        options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-#{userNameSafe}-#{process.arch}-sock"
       else
-        options.socketPath = path.join(os.tmpdir(), "atom-#{options.version}-#{process.arch}-#{atomInstanceDigest}.sock")
+        options.socketPath = path.join(os.tmpdir(), "atom-#{options.version}-#{process.env.USER}.sock")

     # FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
     # take a few seconds to trigger 'error' event, it could be a bug of node


Allow Atom on Windows to run multiple instances e.g. on Windows server/Citrix etc

--- src/main-process/atom-application.coffee
+++ src/main-process/atom-application.coffee
@@ -33,7 +33,8 @@
   @open: (options) ->
     unless options.socketPath?
       if process.platform is 'win32'
-        options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-sock"
+        userNameSafe = new Buffer(process.env.USERNAME).toString('base64')
+        options.socketPath = "\\\\.\\pipe\\atom-#{options.version}-#{userNameSafe}-sock"
       else
         options.socketPath = path.join(os.tmpdir(), "atom-#{options.version}-#{process.env.USER}.sock")


nginx: solve host header is not set problem

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -43,6 +43,7 @@
         proxy_pass            #{proxyPass};
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header      X-Host          $host; # for customisation
         proxy_set_header      Host            $host;
         proxy_next_upstream   error timeout   invalid_header http_500;
         proxy_connect_timeout 1;
@@ -66,6 +67,7 @@
         proxy_set_header      Connection      $connection_upgrade;

         proxy_set_header      Host            $host;
+        proxy_set_header      X-Host          $host; # for customisation
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_redirect        off;
@@ -142,6 +144,8 @@

   return """
       location ~*(^(\/(Pricing|About|Legal|Features|Blog|Docs))) {
+          proxy_set_header      Host            $host;
+          proxy_set_header      X-Host          $host; # for customisation
           proxy_set_header      X-Real-IP       $remote_addr;
           proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
           proxy_next_upstream   error timeout   invalid_header http_500;
@@ -279,6 +283,8 @@
       # special case for ELB here, for now
       location /-/healthCheck {
         proxy_pass            http://webserver;
+        proxy_set_header      Host            $host;
+        proxy_set_header      X-Host          $host; # for customisation
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_next_upstream   error timeout   invalid_header http_500;
@@ -289,6 +295,8 @@
       # // kd/klient installers
       location ~^/d/(.*)$ {
         proxy_pass            "https://s3.amazonaws.com/koding-dl/$1";
+        proxy_set_header      Host            $host;
+        proxy_set_header      X-Host          $host; # for customisation
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_connect_timeout 1;
@@ -299,6 +307,8 @@
       # Hackathon2014 is the old hackathon page and served via webserver
       # todo(cihangir) remove after hubspot integration
       location = /Hackathon2014 {
+        proxy_set_header      Host            $host;
+        proxy_set_header      X-Host          $host; # for customisation
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;


Social: created new proxy for socialapi

Social: created new proxy for socialapi

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -250,6 +250,14 @@
         proxy_connect_timeout 1;
       }

+      location ~ /api/social/channel/(.*)/history {
+        proxy_pass            http://socialapi/channel/$1/history$is_args$args;
+        proxy_set_header      X-Real-IP       $remote_addr;
+        proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_next_upstream   error timeout   invalid_header http_500;
+        proxy_connect_timeout 1;
+      }
+
       # special case for ELB here, for now
       location /-/healthCheck {
         proxy_pass            http://webserver;


Deployment: remove reduntant log

Deployment: remove reduntant log

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -180,7 +180,6 @@
       }"""

 module.exports.create = (KONFIG, environment)->
-  console.log "environment", environment
   workers = KONFIG.workers

   event_mechanism = switch process.platform


Nginx: set exact URL

Nginx: set exact URL

--- deployment/nginx.coffee
+++ deployment/nginx.coffee
@@ -314,7 +314,7 @@
         resolver 8.8.8.8;
       }

-      location /Hackathon {
+      location = /Hackathon {
         proxy_set_header      X-Real-IP       $remote_addr;
         proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;


Few validation tweaks

Few validation tweaks

--- app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
+++ app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
@@ -286,7 +286,7 @@
     $('.effective-menu').effectiveMenuEditor() # Initialize the EffectiveMenus

     CKEDITOR.dialog.add 'effectiveMenusDialog', (editor) ->
-      definition = {
+      {
         title: 'Effective Menu Item'
         minWidth: 350,
         minHeight: 200,
@@ -391,15 +391,16 @@
                 setup: (element) ->
                   this.setValue(element.children('.menu-item').children("input[name$='[menuable_id]']").val())
                 commit: (element) ->
-                  if this.getElement().isVisible()
+                  if this.getDialog().getValueOf('item', 'source') == 'Page'
                     element.children('.menu-item').children("input[name$='[menuable_id]']").val(this.getValue())
                     element.children('.menu-item').children("input[name$='[menuable_type]']").val('Effective::Page')
                   else
                     element.children('.menu-item').children("input[name$='[menuable_id]']").val('')
                     element.children('.menu-item').children("input[name$='[menuable_type]']").val('')
                 validate: ->
-                  if this.getElement().isVisible() && (this.getValue() || '').length == 0
-                    CKEDITOR.dialog.validate.notEmpty('please select a page').apply(this)
+                  if this.getDialog().getValueOf('item', 'source') == 'Page' && (this.getValue() || '').length == 0
+                    if this.getDialog().effective_menu_item.hasClass('dropdown') == false
+                      CKEDITOR.dialog.validate.notEmpty('please select a page').apply(this)
               },
               {
                 id: 'url',
@@ -408,12 +409,12 @@
                 setup: (element) ->
                   this.setValue(element.children('.menu-item').children("input[name$='[url]']").val())
                 commit: (element) ->
-                  if this.getElement().isVisible() then value = this.getValue() else value = ''
+                  if this.getDialog().getValueOf('item', 'source') == 'URL' then value = this.getValue() else value = ''

                   element.children('.menu-item').children("input[name$='[url]']").val(value)
                   element.children('a').attr('href', value || '#')
                 validate: ->
-                  if this.getElement().isVisible() && (this.getValue() || '').length == 0
+                  if this.getDialog().getValueOf('item', 'source') == 'URL' && (this.getValue() || '').length == 0
                     CKEDITOR.dialog.validate.notEmpty('please enter a URL').apply(this)
               },
               {
@@ -426,7 +427,7 @@
                   if this.getDialog().getValueOf('item', 'source') == 'Divider'
                     element.children('.menu-item').children("input[name$='[special]']").val('divider')
                     element.children('a').html('')
-                  else if this.getElement().isVisible()
+                  else if this.getDialog().getValueOf('item', 'source') == 'Route'
                     element.children('.menu-item').children("input[name$='[special]']").val(this.getValue())
                   else
                     element.children('.menu-item').children("input[name$='[special]']").val('')
@@ -435,7 +436,7 @@
                   if this.getDialog().getValueOf('item', 'source') == 'Divider'
                     if this.getDialog().effective_menu_item.hasClass('dropdown')
                       CKEDITOR.dialog.validate.notEmpty('cannot convert existing dropdown menu to a Divider').apply(this)
-                  else if this.getElement().isVisible()
+                  else if this.getDialog().getValueOf('item', 'source') == 'Route'
                     if (this.getValue() || '').length == 0
                       CKEDITOR.dialog.validate.notEmpty('please enter a route').apply(this)
               }
@@ -559,26 +560,11 @@
         onShow: -> this.setupContent(this.effective_menu_item) if this.effective_menu_item

         onOk: ->
-          this.commitContent(this.effective_menu_item)
-          this.effective_menu_item.removeClass('new-item') if this.effective_menu_item.hasClass('new-item')
-          this.effective_menu_item = undefined
+          if this.effective_menu_item
+            this.commitContent(this.effective_menu_item)
+            this.effective_menu_item.removeClass('new-item') if this.effective_menu_item.hasClass('new-item')

         onCancel: ->
-          this.effective_menu_item.remove() if this.effective_menu_item.hasClass('new-item')
-          this.effective_menu_item = undefined
+          if this.effective_menu_item && this.effective_menu_item.hasClass('new-item')
+            this.effective_menu_item.remove()
       }
-
-      # console.log definition
-      # definition
-
-      # # So we've built the definition above, now we're going to add one checkbox each for all roles
-      # elements = definition['contents'][1]['elements']
-
-      # for effective_role in CKEDITOR.config['effective_regions']['roles']
-      #   description = effective_role[0]
-      #   role = effective_role[1]
-      #   disabled = effective_role[2]
-
-
-
-      # definition


assigning left and right on save

assigning left and right on save

--- app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
+++ app/assets/javascripts/ckeditor/plugins/effective_menus/plugin.js.coffee
@@ -84,6 +84,27 @@
       @menu.on 'click', 'a', (event) -> event.preventDefault()

     serialize: (retval) ->
+
+      # console.log "============ BEFORE =============="
+      # @menu.find('li').each (index, item) =>
+      #   item = $(item)
+      #   label = item.children('a').first().text()
+      #   left = item.children('.menu-item').children("input[name$='[lft]']").val()
+      #   right = item.children('.menu-item').children("input[name$='[rgt]']").val()
+      #   console.log "[#{label}] #{left}, #{right}"
+      # console.log "=================================="
+
+      @assignLftRgt(@menu, 1)
+
+      # console.log "============ AFTER =============="
+      # @menu.find('li').each (index, item) =>
+      #   item = $(item)
+      #   label = item.children('a').first().text()
+      #   left = item.children('.menu-item').children("input[name$='[lft]']").val()
+      #   right = item.children('.menu-item').children("input[name$='[rgt]']").val()
+      #   console.log "[#{label}] #{left}, #{right}"
+      # console.log "=================================="
+
       items = {}

       $.each @menu.find('input').serializeArray(), ->
@@ -97,13 +118,19 @@

       retval[@menu.data('effective-menu-id')] = items

-    assignLftRgt: ->
-      stack = []
-      console.log @menu.find('li')
-
-      @menu.find('li').each (item) ->
-        console.log 'item'
-
+    assignLftRgt: (parent, lft) ->
+      rgt = lft + 1
+
+      parent.children('.dropdown-menu').children('li').each (_, child) =>
+        rgt = @assignLftRgt($(child), rgt)
+
+      parent.children('li').each (_, child) =>
+        rgt = @assignLftRgt($(child), rgt)
+
+      parent.children('.menu-item').children("input[name$='[lft]']").val(lft)
+      parent.children('.menu-item').children("input[name$='[rgt]']").val(rgt)
+
+      rgt + 1

   $.fn.extend effectiveMenuEditor: (option, args...) ->
     @each ->
@@ -241,5 +268,9 @@
         ], # /contents

         onShow: -> this.setupContent(this.effective_menu_item)
-        onOk: -> this.commitContent(this.effective_menu_item)
+        onOk: ->
+          this.commitContent(this.effective_menu_item)
+          this.effective_menu_item = undefined
+        onCancel: ->
+          this.effective_menu_item = undefined
       }


fix for region selection on expanded proposal causes height changes

--- @client/histogram-canvas.coffee
+++ @client/histogram-canvas.coffee
@@ -589,11 +589,17 @@
           some_region_selected &&
            (!@local.touched || inRange(@local.mouse_opinion_value, min, max)))

+        # page resizes because of changing pros/cons shown unless we fix the height of the reasons region
+        reasons_region_el = ReactDOM.findDOMNode(@).closest('.OpinionBlock').querySelector('.reasons_region')
+
         if is_deselection
           clear_histogram_managed_opinion_views opinion_views
           if ev.type == 'touchstart'
             @local.mouse_opinion_value = null
+          reasons_region_el.style.minHeight = null
         else if @enable_range_selection
+
+          reasons_region_el.style.minHeight = "#{$$.height(reasons_region_el)}px"
           clear_histogram_managed_opinion_views opinion_views, 'single_opinion_selected'

           @users_in_region = @getUsersInRegion()
@@ -676,15 +682,12 @@
   onMouseLeave: (ev) ->
     return if fetch(namespaced_key('slider', @props.proposal)).is_moving

-    opinion_views = fetch 'opinion_views'
-    active = opinion_views.active_views
-    originating_histogram = (active.single_opinion_selected or active.region_selected)?.created_by == @props.histo_key
-
     @local.mouse_opinion_value = null
     save @local

-    # return if originating_histogram
-
+    opinion_views = fetch 'opinion_views'
+    if opinion_views.active_views.region_selected
+      clear_histogram_managed_opinion_views opinion_views

@@ -1005,27 +1008,22 @@

     histocache = @getAvatarPositions()?.positions or {}

-    @skip_frame = false
+    @skip_frame = !!SKIP_FRAMES
     if SKIP_FRAMES
-      skippable = 0
-      tot = 0
-      opacity_updated = false
       for key, __ of histocache
         sprite = @sprites[key]
+        continue if !sprite
         updating_opacity = sprite.target_opacity != sprite.from_opacity || sprite.opacity != 1

         if updating_opacity
-          opacity_updated = true
+          @skip_frame = false
           break

-        if (sprite.last_render_x && Math.abs(sprite.x - sprite.last_render_x) < SKIP_FRAMES) &&
-           (sprite.last_render_y && Math.abs(sprite.y - sprite.last_render_y) < SKIP_FRAMES) &&
-           (sprite.last_render_size && Math.abs(sprite.width - sprite.last_render_size) < SKIP_FRAMES)
-          skippable += 1
-        tot += 1
-
-      @skip_frame = !opacity_updated && skippable / tot > .5
-
+        if (!sprite.last_render_x?    || Math.abs(sprite.x - sprite.last_render_x) > SKIP_FRAMES) ||
+           (!sprite.last_render_y?    || Math.abs(sprite.y - sprite.last_render_y) > SKIP_FRAMES) ||
+           (!sprite.last_render_size? || Math.abs(sprite.width - sprite.last_render_size) > SKIP_FRAMES)
+          @skip_frame = false
+          break

     if !@skip_frame

@@ -1036,6 +1034,7 @@

       for key, __ of histocache
         sprite = @sprites[key]
+        continue if !sprite
         updating_opacity = sprite.target_opacity != sprite.from_opacity || sprite.opacity != 1
         if updating_opacity
           @buff_ctx.save()


应用标识不能修改

应用标识不能修改

--- lib/configure/application/app-view.coffee
+++ lib/configure/application/app-view.coffee
@@ -14,7 +14,7 @@
       @div class: "col-xs-12", =>
         @label class: 'col-sm-3 col-md-3', "应用标识"
         @div class: 'col-sm-9 col-md-9', =>
-          @subview 'appId', new TextEditorView(mini: true,placeholderText: 'appId...')
+          @div outlet: 'appId', class: 'textEditStyle'
       @div class: "col-xs-12 ", =>
         @label class: 'col-sm-3 col-md-3', "应用名称"
         @div class: 'col-sm-9 col-md-9', =>
@@ -52,7 +52,7 @@
   saveInput: ->
     project_path = if @configPath? then @configPath else PathM.join $('.entry.selected span').attr('data-path'),'appConfig.json'
     mod =
-      identifier: @appId.getText().trim()
+      identifier: @appId.html().trim()
       name: @appName.getText().trim()
       version: @appVersion.getText().trim()
       mainModule: @appStartModule.getText().trim()
@@ -98,7 +98,7 @@
       console.log '读取文件失败'

   setConfig:(config) ->
-    @appId.setText(config['identifier'])
+    @appId.html(config['identifier'])
     @appName.setText(config['name'])
     @appVersion.setText(config['version'])
     @appStartModule.setText(config['mainModule'])


use path from config file

use path from config file

--- gulp/watch.coffee
+++ gulp/watch.coffee
@@ -18,10 +18,10 @@
         'webpack:watch'
     )

-    gulp.watch './src/stylus/**/*.styl', [ 'build-stylus' ]
-    gulp.watch './src/css/**/*.css'    , [ 'build-css' ]
-    gulp.watch './src/jade/**/*.jade'  , [ 'build-jade' ]
-    gulp.watch './src/image/**/*'      , [ 'build-image' ]
+    gulp.watch config.stylus_selector , [ 'build-stylus' ]
+    gulp.watch config.css_selector    , [ 'build-css'    ]
+    gulp.watch config.jade_selector   , [ 'build-jade'   ]
+    gulp.watch config.image_selector  , [ 'build-image'  ]

 gulp.task 'webpack:watch', (cb) ->


:new: Add awesome autocompletion for functions

--- lib/autocomplete-hack.coffee
+++ lib/autocomplete-hack.coffee
@@ -8,6 +8,7 @@
   provide:->
     Path = require('path')
     Hack.init()
+    ArgumentsRegex = /function[ \w]*\((.*?)\)/
     Provider =
       inclusionPriority: 100
       excludeLowerPriority: true # To suppress the html autocompleter
@@ -59,14 +60,30 @@
               Text = Entry[0]
               Label = Entry.slice(1).join(' ')
               {leftLabel, Type} = Provider.getType(Text, Label)
-              return {
+              EntryToReturn = {
                 type: Type
-                text: Text
                 leftLabel: leftLabel
                 description: Label
                 replacementPrefix: prefix
                 score: prefix.length > 0 and Text.score(prefix)
               }
+              if Type is 'function'
+                Arguments = ArgumentsRegex.exec Label
+                if Arguments and Arguments[1] and Arguments[1].length
+                  Snippet = []
+                  ExtractionRegexp = /(\$\w+)/g
+                  Num = 0
+                  while (Result = ExtractionRegexp.exec(Arguments[1])) isnt null
+                    ++Num
+                    Result = Result[1]
+                    Snippet.push "${#{Num}:#{Result}}"
+                  Snippet = Text + "(" + Snippet.join(", ") + ")"
+                  EntryToReturn.snippet = Snippet
+                else
+                  EntryToReturn.text = Text
+              else
+                EntryToReturn.text = Text
+              return EntryToReturn
             )
             ToReturn.sort (a,b)=>
               b.score - a.score


update users docs

update users docs

--- lib/argumenta/users.coffee
+++ lib/argumenta/users.coffee
@@ -3,7 +3,7 @@
 User = require '../argumenta/user'

 #
-# Users models user accounts.
+# Users models user accounts.
 # It integrates the Storage, Auth, and User modules.
 #
 class Users extends Base
@@ -19,6 +19,7 @@

   # Inits a Users instance.
   #
+  # @api public
   # @param [Argumenta] argumenta An argumenta instance.
   # @param [Storage] storage A storage instance.
   constructor: (@argumenta, @storage) ->
@@ -32,12 +33,13 @@
   #     users.create username, password, email, (err, user) ->
   #       console.log "created account for #{user.username}!" unless err
   #
+  # @api public
   # @param [String] username The user's username.
   # @param [String] password The user's password.
   # @param [String] email The user's email.
   # @param [Function] callback(err, user) Called on error or success.
   # @param [Error] err Any error.
-  # @param [User] user The new user.
+  # @param [User] user The new user instance.
   create: (username, password, email, callback) ->

     try User.validatePassword password


Coding style

Coding style

--- app/assets/javascripts/events.js.coffee
+++ app/assets/javascripts/events.js.coffee
@@ -56,17 +56,17 @@
       authenticity_token: FullCalendar.Form.authenticity_token()

     move: (event, delta, revertFunc) ->
-      url = event.path + "/move"
+      url = event.path + '/move'
       data = FullCalendar.Events.prepare_data(event, delta)
       FullCalendar.Events.send_request(url, data, revertFunc)

     resize: (event, delta, revertFunc) ->
-      url = event.path + "/resize"
+      url = event.path + '/resize'
       data = FullCalendar.Events.prepare_data(event, delta)
       FullCalendar.Events.send_request(url, data, revertFunc)

     show: (event) ->
-      url = event.path + "/edit"
+      url = event.path + '/edit'
       $.get url, (data) ->
         $('#modal-holder').html(data).find('.modal').modal()

@@ -95,7 +95,7 @@
       $('#modal-holder').find('.modal').modal()

     authenticity_token: ->
-      return($('meta[name="csrf-token"]').attr("content"))
+      $('meta[name="csrf-token"]').attr('content')

     set_form_options: (options) ->
       format    = options['datetimeFormat'] || 'DD/MM/YYYY HH:mm'


fixed conflict

fixed conflict

--- app/assets/javascripts/scroll.js.coffee
+++ app/assets/javascripts/scroll.js.coffee
@@ -4,7 +4,6 @@
     height = Math.min($('.scroll-col').eq(0).height(),
                       $('.scroll-col').eq(1).height(),
                       $('.scroll-col').eq(2).height())
-    console.log(height)
     if not ($('#loader').hasClass('loading') || $("#main-board").hasClass("no-load"))  and $(window).scrollTop() > height - $(window).height() - 50
       $('#loader').addClass('loading')
       $.getScript('/items')


removing unnecessary buttons from monthly graphs

--- app/assets/javascripts/wallet.js.coffee
+++ app/assets/javascripts/wallet.js.coffee
@@ -26,22 +26,7 @@
           type: 'month'
           count: 1
           text: '1m'
-        }, {
-          type: 'month'
-          count: 3
-          text: '3m'
-        }, {
-          type: 'month'
-          count: 6
-          text: '6m'
-        }, {
-          type: 'year'
-          count: 1
-          text: '1y'
-        }, {
-          type: 'all'
-          text: 'All'
-        }]
+      }]
       title:
         text: "Monthly Performance"
       series: [{
@@ -70,22 +55,7 @@
           type: 'month'
           count: 1
           text: '1m'
-        }, {
-          type: 'month'
-          count: 3
-          text: '3m'
-        }, {
-          type: 'month'
-          count: 6
-          text: '6m'
-        }, {
-          type: 'year'
-          count: 1
-          text: '1y'
-        }, {
-          type: 'all'
-          text: 'All'
-        }]
+      }]
       title:
         text: "BTC/1MH Performance"
       series: [{


make @ and backslash work in find panel

make @ and backslash work in find panel

better solution ... lets try. (should wait a little longer before the commit next time ...)

--- lib/compatible-view.coffee
+++ lib/compatible-view.coffee
@@ -18,16 +18,15 @@
     @detach()

   at: (event) ->
-    debugger
     view = event.targetView()
     if view.editor
         view.editor.insertText('@')
-    else if view
-        view.setText(view.getText()+'@')
+    else if view.model
+        view.model.selections[0]?.editor.insertText("@")

   backslash: (event) ->
     view = event.targetView()
     if view.editor
         view.editor.insertText('\\')
     else if view
-        view.setText(view.getText()+'\\')
+        view.model.selections[0]?.editor.insertText("\\")


remove cruft

remove cruft

--- lib/console/console.coffee
+++ lib/console/console.coffee
@@ -197,7 +197,6 @@
   keyDown: (e, ed) ->
     if ed == @view.getInputEd()
       curs = ed.getCursorsOrderedByBufferPosition()
-      console.log [curs[0].getBufferRow()+1, ed.getLineCount()]
       if curs.length is 1 and curs[0].getBufferRow()+1 == ed.getLineCount()
         e.preventDefault()
         @next()


removing hovered highlight after 2 seconds

--- app/front/js/directives/scopebar.coffee
+++ app/front/js/directives/scopebar.coffee
@@ -8,9 +8,14 @@
       $scope.Editor.Sidebar.current_tab = 'scopes'
       $scope.Editor.ScopeHunter.hovered_rule = rule
       scroll_finished = ->
-        $timeout((-> $scope.Editor.ScopeHunter.hovered_rule = null), 2000)
+        $timeout(->
+          $scope.$apply -> $scope.Editor.ScopeHunter.hovered_rule = {}
+          return
+        , 2000)
       scroll_into_view = ->
-        $('.hovered').scrollintoview(duration: 400, direction: 'vertical', complete: scroll_finished)
+        $('.hovered').scrollintoview(duration: 400, direction: 'vertical')
+        scroll_finished()
+        return
       $timeout scroll_into_view
       return


Add new v39.0 Spring 17 objects

Add new v39.0 Spring 17 objects

--- src/salesforce-standard-objects.coffee
+++ src/salesforce-standard-objects.coffee
@@ -29,6 +29,7 @@
   "asset"
   "assetfeed"
   "assethistory"
+  "assetshare"
   "assignmentrule"
   "asyncapexjob"
   "attachedcontentdocument"
@@ -115,6 +116,7 @@
   "dashboardcomponentfeed"
   "dashboardfeed"
   "datatype"
+  "datacloudaddress"
   "datacloudcompany"
   "datacloudcontact"
   "dataclouddandbcompany"
@@ -227,6 +229,7 @@
   "orgwideemailaddress"
   "organization"
   "ownedcontentdocument"
+  "ownerchangeoptioninfo"
   "packagelicense"
   "partner"
   "partnerrole"
@@ -256,6 +259,7 @@
   "publisher"
   "pushtopic"
   "queuesobject"
+  "quotetemplaterichtextdata"
   "recentlyviewed"
   "recordtype"
   "recordtypelocalization"
@@ -280,6 +284,7 @@
   "site"
   "sitefeed"
   "sitehistory"
+  "skill"
   "solution"
   "solutionfeed"
   "solutionhistory"
@@ -328,4 +333,13 @@
   "vote"
   "weblink"
   "weblinklocalization"
+  "workaccess"
+  "workaccessshare"
+  "workbadge"
+  "workbadgedefinition"
+  "workbadgedefinitionfeed"
+  "workbadgedefinitionhistory"
+  "workbadgedefinitionshare"
+  "workthanks"
+  "workthanksshare"
 ]


Slightly more useful error messages for debugging

--- src/scripts/code-name-generator.coffee
+++ src/scripts/code-name-generator.coffee
@@ -30,4 +30,4 @@
           - #{msg.random wordlist} #{msg.random wordlist}
           """
         else
-          msg.send "Error: Couldn't access #{api_url}."
+          msg.send "Error: Couldn't access #{api_url}. Error: #{err}. Status Code: #{res.statusCode}"


add playlist.remove method

add playlist.remove method

--- src/scripts/directives/playlist.coffee
+++ src/scripts/directives/playlist.coffee
@@ -6,7 +6,16 @@
     return {
         link: (scope, element, attrs) ->
             scope.playlist = playlist = []
-            scope.player =
+            scope.playlist.remove = (index, e) ->
+                if player.current.index is index
+                    player.stop()
+                else if player.current.index > index
+                    player.current.index -= 1
+
+                @splice index, 1
+                e.stopPropagation() if e
+
+            scope.player = player =
                 current: {}
                 play: ->
                     if @current.sound?.paused


flip the approx. angle calculation

flip the approx. angle calculation

--- web/js/views/map_location_marker.coffee
+++ web/js/views/map_location_marker.coffee
@@ -82,9 +82,9 @@
     _computeAngle: (availableLatLng) ->
       DEGREE_PER_RADIAN = 57.2957795
       RADIAN_PER_DEGREE = 0.017453
-      dlat = availableLatLng.lat() - @location.lat()
-      dlng = availableLatLng.lng() - @location.lng()
-      yaw = Math.atan2(dlng * Math.cos(availableLatLng.lat() * RADIAN_PER_DEGREE), dlat) * DEGREE_PER_RADIAN
+      dlat = @location.lat() - availableLatLng.lat()
+      dlng = @location.lng() - availableLatLng.lng()
+      yaw = Math.atan2(dlng * Math.cos(@location.lat() * RADIAN_PER_DEGREE), dlat) * DEGREE_PER_RADIAN
       @_wrapAngle(yaw)

     _wrapAngle: (angle) ->


Add support for new pending item API

Add support for new pending item API

This adds backward-compatible checks for the new pending item APIs, and
falls back to using `Item::terminatePendingState` if they're not
supported in the current Atom environment.

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -213,7 +213,12 @@
           entry.toggleExpansion(isRecursive)
       when 2
         if entry instanceof FileView
-          @openedItem.then((item) -> item.terminatePendingState?())
+          @openedItem.then (item) ->
+            activePane = atom.workspace.getActivePane()
+            if activePane?.getPendingItem?
+              activePane.clearPendingItem() if activePane.getPendingItem() is item
+            else if item.terminatePendingState?
+              item.terminatePendingState()
           unless entry.getPath() is atom.workspace.getActivePaneItem()?.getPath?()
             @unfocus()
         else if entry instanceof DirectoryView
@@ -393,9 +398,13 @@
         selectedEntry.toggleExpansion()
     else if selectedEntry instanceof FileView
       uri = selectedEntry.getPath()
-      item = atom.workspace.getActivePane()?.itemForURI(uri)
+      activePane = atom.workspace.getActivePane()
+      item = activePane?.itemForURI(uri)
       if item? and not options.pending
-        item.terminatePendingState?()
+        if activePane?.getPendingItem?
+          activePane.clearPendingItem() if activePane.getPendingItem() is item
+        else if item.terminatePendingState?
+          item.terminatePendingState()
       atom.workspace.open(uri, options)

   openSelectedEntrySplit: (orientation, side) ->


Use workspace for managing focus

Use workspace for managing focus

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -156,13 +156,13 @@

     @panel.destroy()
     @panel = null
-    atom.workspaceView.focus()
+    @unfocus()

   focus: ->
     @list.focus()

   unfocus: ->
-    atom.workspaceView.focus()
+    atom.workspace.getActivePane().activate()

   hasFocus: ->
     @list.is(':focus') or document.activeElement is @list[0]
@@ -183,7 +183,7 @@
         entry.toggleExpansion(isRecursive) if entry instanceof DirectoryView
       when 2
         if entry instanceof FileView
-          atom.workspaceView.getActiveView()?.focus()
+          @unfocus()
         else if DirectoryView
           entry.toggleExpansion(isRecursive)


:art: Remove `returns noop` documentation

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -775,16 +775,12 @@

   # Public: Toggle full-menu class on the main list element to display the full context
   #         menu.
-  #
-  # Returns noop
   showFullMenu: ->
     @list[0].classList.remove('multi-select')
     @list[0].classList.add('full-menu')

   # Public: Toggle multi-select class on the main list element to display the the
   #         menu with only items that make sense for multi select functionality
-  #
-  # Returns noop
   showMultiSelectMenu: ->
     @list[0].classList.remove('full-menu')
     @list[0].classList.add('multi-select')
@@ -806,8 +802,6 @@
     e.currentTarget.parentNode.classList.remove('selected')

   # Public: Handle entry name object dragstart event
-  #
-  # returns noop
   onDragStart: (e) ->
     e.stopPropagation()

@@ -817,15 +811,11 @@
     e.originalEvent.dataTransfer.setData("initialPath", initialPath)

   # Public: Handle entry dragover event; reset default dragover actions
-  #
-  # returns noop
   onDragOver: (e) ->
     e.preventDefault()
     e.stopPropagation()

   # Public: Handle entry drop event
-  #
-  # returns noop
   onDrop: (e) ->
     e.preventDefault()
     e.stopPropagation()


Update uniformer.coffee

Update uniformer.coffee

--- lib/uniformer.coffee
+++ lib/uniformer.coffee
@@ -15,7 +15,7 @@

 _typeProc = (val) ->
   if typeof val == 'number' || !isNaN(val)
-    return new Number(val);
+    return new Number(val).valueOf();
   else if val.toLowerCase() == 'false'
     return false;
   else if val.toLowerCase() == 'true'
@@ -91,4 +91,4 @@
   resultant = extend true,resultant,argvProc

-module.exports = uniformer+module.exports = uniformer


add modeCheck fund for oeprator-pending

add modeCheck fund for oeprator-pending

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -609,6 +609,7 @@
   isVisualMode: -> @mode is 'visual'
   isNormalMode: -> @mode is 'normal'
   isInsertMode: -> @mode is 'insert'
+  isOperatorPendingMode: -> @mode is 'operator-pending'

   updateStatusBar: ->
     @statusBarManager.update(@mode, @submode)


Filter current file in doCheck

Filter current file in doCheck

--- lib/editor-controller.coffee
+++ lib/editor-controller.coffee
@@ -121,7 +121,8 @@

   doCheck: ->
     @clearError()
-    @process.doCheck @getText(), @showError
+    @process.doCheck @getText(), (point,message,file,path) =>
+      @showError point,message if file==path

   getText: ->
     @editor.getText()


Toggle with multiple edges between the same node

--- app/scripts/alchemy/core/filters.coffee
+++ app/scripts/alchemy/core/filters.coffee
@@ -149,8 +149,8 @@
           .attr {"id":"toggle-edges","class":"list-group-item active-label toggle"}
           .html "Toggle Edges"
           .on "click", ->
-              _.each _.values(alchemy._edges), (e)->
-                  e.toggleHidden()
+              _.each _.values(alchemy._edges), (edges)->
+                  _.each edges, (e)-> e.toggleHidden()

     #create nodes toggle
     nodesToggle: () ->


Replace CompletionClosure by ledger.defer

--- app/src/wallet/sweep_private_key.coffee
+++ app/src/wallet/sweep_private_key.coffee
@@ -4,14 +4,14 @@

   sweepPrivateKey: ({privateKey, account, txFee}, callback) ->
     txFee ?= 10000
-    completion = new CompletionClosure(callback)
+    d = ledger.defer(callback)
     recipientAddress = account.getHDAccount().getCurrentPublicAddress()
     ecKey = new window.bitcoin.ECKey.fromWIF(privateKey)
     publicKey = ecKey.pub.getAddress().toString()
     addresses = [publicKey]

     ledger.api.UnspentOutputsRestClient.instance.getUnspentOutputsFromAddresses addresses , (outputs, error) ->
-      return completion.failure(error) if error?
+      return d.reject(error) if error?

       txBuilder = new window.bitcoin.TransactionBuilder()
       amountToSend = new ledger.wallet.Value()
@@ -22,7 +22,7 @@

         if hasNext is false and amountToSend.lte(txFee)
           # Not enough available funds
-          completion.failure(new ledger.StandardError(ledger.errors.NotEnoughFunds))
+          d.rejectWithError(ledger.errors.NotEnoughFunds)
         else if hasNext is false
           amountToSend = amountToSend.subtract(10000)
           txBuilder.addOutput(recipientAddress, amountToSend.toNumber())
@@ -30,8 +30,8 @@
           txBuilder.sign(index, ecKey) for input, index in txBuilder.tx.ins
           txHex = txBuilder.build().toHex()
           ledger.api.TransactionsRestClient.instance.postTransactionHex txHex, (txHash, error) =>
-            completion.complete(txHash, error)
+            d.resolve(txHash, error)
         else
           # Continue to collect funds
           do done
-    completion.readonly()+    d.promise


refactored statds api class

refactored statds api class

--- src/api/statsd.coffee
+++ src/api/statsd.coffee
@@ -10,6 +10,7 @@
 moment = require "moment"
 metrics = require "../metrics"
 logger = require "winston"
+co = require "co"

 # Overall Metrics

@@ -79,10 +80,11 @@
   if (type == 'status') {
     for (i = 0; i < status_array.length; i++) {
       path = render_url + ".statuses." + status_array[i] + ".count,'1week'))&format=json";
-      results[status_array[i]] = yield exports.fetchData(path);
-      promises.push(results[status_array[i]] )
+      results[status_array[i]] = co(function* () {
+        yield exports.fetchData(path)
+      });
     }
-  Q.all(promises).then (function(){
+
     var failed = 'data' in results.Failed ? results.Failed.data[0][0] : 0,
       processing = 'data'  in results.Processing ? results.Processing.data[0][0]  : 0,
       completed = 'data' in results.Completed ? results.Completed.data[0][0]  : 0,
@@ -96,7 +98,7 @@
       completed: completed,
       completedWErrors: completedWErrors
     });
-  })
+

   } else {


Pass test using generic HTML child

Pass test using generic HTML child

--- test/spec/test_reactive.coffee
+++ test/spec/test_reactive.coffee
@@ -83,7 +83,7 @@
         x: 10
         y: 20
       }, bind -> [
-        rxt.tags.button ['click me']
+        rxt.tags.animatetransform ['click me']
       ]

     it 'should have the right tag', ->


Create area_chart.coffee

Create area_chart.coffee

--- src/area_chart.coffee
+++ src/area_chart.coffee
@@ -1,3 +1,3 @@
 require('chart')

-class area_charts extends chart
+class area_chart extends chart


module install

module install

--- lib/modules/blog/Blog.coffee
+++ lib/modules/blog/Blog.coffee
@@ -52,7 +52,7 @@
       _category_names   : {writeable: false, value: {}}

-  initialise: () ->
+  initialize: () ->

     @controller.load.dbforge() unless @controller.dbforge?
     @install(@controller.dbforge)
@@ -65,22 +65,22 @@
   #
   # @return [Void]
   #
-  install: ($t) ->
+  install: ($db) ->

     #
     # Create the category table
     #
     @controller.queue ($next) ->

-      $t.createTable 'category', $next, do ($t) ->
-        $t.addKey 'id', true
-        $t.addField
+      $db.createTable 'category', $next, do ->
+        $db.addKey 'id', true
+        $db.addField
           id:
             type: 'INT', constraint: 5, unsigned: true, auto_increment: true
           name:
             type: 'VARCHAR', constraint: 255

-        $t.addData id: 1, name: "Article"
+        $db.addData id: 1, name: "Article"

     #
@@ -88,9 +88,9 @@
     #
     @controller.queue ($next) ->

-      $t.createTable 'blog', $next, do ($t) ->
-        $t.addKey 'id', true
-        $t.addField
+      $db.createTable 'blog', $next, do ->
+        $db.addKey 'id', true
+        $db.addField
           id:
             type: 'INT', constraint: 5, unsigned: true, auto_increment: true
           author_id:
@@ -110,7 +110,7 @@
           body:
             type: 'TEXT'

-        $t.addData
+        $db.addData
           id: 1,
           author_id: 2,
           category_id: 1,
@@ -120,21 +120,6 @@
           title: "About",
           body: "
Dark Overlord of Data is:

a web page

created using e x s p r e s s o

bruce davidson

a software developer who lives in seattle with his wife and daughter, two cats, one dog, and an electric guitar

"

-
-
-  installz: () ->
-
-    # Migrate the blog categories table
-    @controller.queue ($next) =>
-      InstallCategory = require(MODPATH+'blog/install/InstallCategory.coffee')
-      $table = new InstallCategory(@controller)
-      $table.install $next
-
-    # Migrate the blog document table
-    @controller.queue ($next) =>
-      InstallBlog = require(MODPATH+'blog/install/InstallBlog.coffee')
-      $table = new InstallBlog(@controller)
-      $table.install $next


调整for循环代码

调整for循环代码

--- lib/pushservices/apns.coffee
+++ lib/pushservices/apns.coffee
@@ -51,12 +51,12 @@
                 difference = note_bytesize - 265
             alert_bytesize =  oldAlert_bytesize - difference - 3
             alertNew = ''
-            for(i = 0; i < b.length;i++){
+            for(i = 0; i < b.length;i++)
                 if alertNew.replace(/[^\x00-\xff]/gi, "--").length + b.charAt(i).replace(/[^\x00-\xff]/gi, "--").length <= alert_bytesize
                     alertNew = alertNew + b.charAt(i)
                 else
                     alertNew = alertNew + '...'
-            }
+

             note.alert = alertNew


Update mpns handler to use the new API version. Also fixes an issue of logging directly to console.

--- lib/pushservices/mpns.coffee
+++ lib/pushservices/mpns.coffee
@@ -13,10 +13,11 @@

     push: (subscriber, subOptions, payload) ->
         subscriber.get (info) =>
+            note = {}
             if subOptions?.ignore_message isnt true
                 switch @conf.type
                     when "toast"
-                        note = new mpns.toast()
+                        sender = mpns.sendToast
                         note.text1 = payload.localizedTitle(info.lang)
                         note.text2 = payload.localizedMessage(info.lang)
                         if @conf.paramTemplate and info.version >= 7.5
@@ -30,10 +31,10 @@
                         map = @conf.tileMapping
                         properties = ["id", "title", "count", "backgroundImage", "backBackgroundImage", "backTitle", "backContent"]
                         if info.version >= 8.0
-                            note = new mpns.flipTile()
+                            sender = mpns.sendFlipTile
                             properties.push(["smallBackgroundImage", "wideBackgroundImage", "wideBackContent", "wideBackBackgroundImage"]...)
                         else
-                            note = new mpns.liveTile()
+                            sender = mpns.sendTile
                         for property in properties
                             if map[property]
                                 try
@@ -45,19 +46,19 @@
                         @logger?.error("Unsupported MPNS notification type: #{@conf.type}")

             else
-                note = new mpns.raw()
+                sender = mpns.sendRaw
                 note[key] = value for key, value of payload.data

-            if note
-                note.send info.token, (error, result) =>
-                    console.log [error, result]
+            if sender
+                sender info.token, note, (error, result) =>
                     if error
                         if error.shouldDeleteChannel
                             @logger?.warn("MPNS Automatic unregistration for subscriber #{subscriber.id}")
                             subscriber.delete()
                         else
                             @logger?.error("MPNS Error: (#{error.statusCode}) #{error.innerError}")
-
+                    else
+                        @logger?.verbose("MPNS result: #{JSON.stringify result}")

 exports.PushServiceMPNS = PushServiceMPNS


#81 :bug: tailRange measurement should use screenPosition/Range.

Former implementation was not correct in case screenPosition and
bufferPosition is not equal(softWrapped).

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -67,16 +67,16 @@
     if (@isSingleRow() and @isLinewise())
       @getBufferRangeForTailRow()
     else
-      tail = @selection.getTailBufferPosition()
-      {row, column} = tail
-      if @selection.isReversed()
-        column -= 1
-        clip = 'backward'
+      {editor} = @selection
+      [columnDelta, clip] = if @selection.isReversed()
+        [-1, 'backward']
       else
-        column += 1
-        clip = 'forward'
-      range = new Range(tail, [row, column])
-      @selection.editor.clipScreenRange(range, {clip})
+        [+1, 'forward']
+
+      tail = @selection.getTailScreenPosition()
+      point = tail.translate([0, columnDelta])
+      screenRange = editor.clipScreenRange(new Range(tail, point), {clip})
+      editor.bufferRangeForScreenRange(screenRange)

   preserveCharacterwise: ->
     prop = @detectCharacterwiseProperties()


Remove useless variable

Remove useless variable

--- lib/skeleton/template.coffee
+++ lib/skeleton/template.coffee
@@ -361,18 +361,17 @@
     files

   printTemplateEngine: ->
-    engine = @opts.renderer
     module = ''
     spaces = '      '

     # Jade doesn’t need express-partials
-    if engine != 'jade'
+    if @opts.renderer != 'jade'
       module = """
         , "express-partials": ">= 0.0.5"\n#{spaces}
       """

     module += """
-      , "#{engine}": "*"
+      , "#{@opts.renderer}": "*"
     """

     # Returns the module(s) string


comparing checkout flows

comparing checkout flows

--- resources/assets/coffee/_classes/store-checkout.coffee
+++ resources/assets/coffee/_classes/store-checkout.coffee
@@ -33,6 +33,10 @@
       access_token: token,
       sandbox: true

+    StoreCheckout.initButton(options)
+    # StoreCheckout.initIframe(options)
+
+  @initButton: (options) ->
     s = document.createElement('script')
     s.type = "text/javascript"
     s.async = true
@@ -42,13 +46,33 @@
       paybar = document.querySelector('#paybar')
       console.log(paybar)

-      button = document.createElement('button')
-      button.setAttribute('data-xpaystation-widget-open', '')
-      paybar.appendChild(button)
+      element = StoreCheckout.createPaymentButton()
+      paybar.appendChild(element)
       XPayStationWidget.init(options)
     , false
     head = document.getElementsByTagName('head')[0]
     head.appendChild(s)

+  @initIframe: (options) ->
+    paybar = document.querySelector('#paybar')
+    element = StoreCheckout.createPaymentIframe(options)
+    paybar.appendChild(element)
+
+  @createPaymentButton: ->
+    element = document.createElement('button')
+    element.setAttribute('data-xpaystation-widget-open', '')
+
+    element
+
+  @createPaymentIframe: (options) ->
+    element = document.createElement('iframe')
+    element.frameborder = '0'
+    element.style.height = '100vh'
+    element.style.width = '100vw'
+    element.style.border = 'none'
+    element.src = "https://sandbox-secure.xsolla.com/paystation3/?access_token=#{options.access_token}"
+
+    element
+
   @getXsollaToken: ->
     $.get laroute.route('store.payments.xsolla-token')


Handle spawned process errors

Handle spawned process errors

--- index.coffee
+++ index.coffee
@@ -6,6 +6,10 @@
 temp.track()

 module.exports = (grunt) ->
+  spawn = (options, callback) ->
+    spawnedProcess = grunt.util.spawn(options, callback)
+    spawnedProcess.on('error', callback)
+
   grunt.registerTask 'create-windows-installer', 'Create the Windows installer', ->
     @requiresConfig("#{@name}.appDirectory")

@@ -39,7 +43,7 @@
       outputDirectory
     ]

-    grunt.util.spawn {cmd, args}, (error, result, code) ->
+    spawn {cmd, args}, (error, result, code) ->
       return done(error) if error?

       nupkgPath = path.join(outputDirectory, "#{metadata.name}.#{metadata.version}.nupkg")
@@ -53,7 +57,8 @@
         '--loadingGif'
         loadingGif
       ]
-      grunt.util.spawn {cmd, args}, (error, result, code) ->
+
+      spawn {cmd, args}, (error, result, code) ->
         grunt.log.writeln result
         grunt.log.writeln code
         done(error)


Test loading voxel-engine via voxel-cs

Test loading voxel-engine via voxel-cs

--- index.coffee
+++ index.coffee
@@ -52,7 +52,7 @@
   plugins = createPlugins null, {require: require}

   configuration =
-    'voxel-engine':
+    'voxel-cs': {commonOpts:
       appendDocument: true
       exposeGlobal: true  # for debugging

@@ -102,8 +102,7 @@
         'T': 'console'
         '/': 'console2'
         '.': 'console3'
-
-    'voxel-cs': {}
+    }

     'voxel-registry': {}
     'craftingrecipes': {}


virtualization controller: close modal and refresh view after provisioning

--- client/landing/app/MainApp/VirtualizationController.coffee
+++ client/landing/app/MainApp/VirtualizationController.coffee
@@ -370,12 +370,15 @@
       overlay : yes

     workflow
-      .on('DataCollected', @bound 'provisionVm')
+      .on 'DataCollected', (data) =>
+        @provisionVm data
+        modal.destroy()
+
       .on('Cancel', modal.bound 'destroy')
+
       .enter()

   provisionVm: ({ subscription, paymentMethod, productData })->
-
     { JVM } = KD.remote.api

     { plan, pack } = productData
@@ -387,13 +390,14 @@

         @provisionVm { subscription, productData }

-    subscription.debit pack, (err, nonce) ->
+    subscription.debit pack, (err, nonce) =>
       return  if KD.showError err

-      JVM.createVmByNonce nonce, (err, vm) ->
+      JVM.createVmByNonce nonce, (err, vm) =>
         return  if KD.showError err

-        debugger
+        @emit 'VMListChanged'
+        @showVMDetails vm

   askForApprove:(command, callback)->


tiny fixes

tiny fixes

--- asterisk.coffee
+++ asterisk.coffee
@@ -30,13 +30,17 @@

 read_config = ->
     try
-        text = fs.readFileSync(process.env.HOME + "/.asterisk.json", 'utf8')
+        text = fs.readFileSync(".asterisk.json", 'utf8')
     catch e
-        print "could not read ~/.asterisk.json"
+        print "could not read .asterisk.json"
+        try
+            text = fs.readFileSync(process.env.HOME + "/.asterisk.json", 'utf8')
+        catch e
+            print "could not read ~/.asterisk.json"
     try
         return JSON.parse(text)
     catch e
-        print "could not parse ~/.asterisk.json"
+        print "could not parse any .asterisk.json"
     process.exit()
 config = read_config()

@@ -89,6 +93,7 @@
         buffer = fs.readFileSync("client/asterisk.html")
         buffer = buffer.toString().replace("$rand", gen_iden())
         res.end(buffer)
+console.log("Starting sever on http://locahost:" + HTTP_PORT)
 server.listen(HTTP_PORT, "localhost")


Fixed selector

Fixed selector

--- client/test/lib/environments/vmactions_professional.coffee
+++ client/test/lib/environments/vmactions_professional.coffee
@@ -2,7 +2,7 @@
 helpers = require '../helpers/helpers.js'
 assert  = require 'assert'

-professionalPlanSelector = '.single-plan.hobbyist.professional.current'
+professionalPlanSelector = '.single-plan.professional.current'
 secondVmSelector         = '.activity-sidebar .machines-wrapper .koding-vm-1'
 url                      = helpers.getUrl()


fix spelling in source provider template

--- SourceProviderTemplate.coffee
+++ SourceProviderTemplate.coffee
@@ -14,7 +14,7 @@
     ###############################
     #~~~~~~~~~ CONSTANTS ~~~~~~~~~#
     ###############################
-    # Constants shoudl be defined on the class instance in the constructor.
+    # Constants should be defined on the class instance in the constructor.

     # The name of your source provider, in kebab-case.
     @NAME = 'your_internal_source_provider_name'


Ensure the tooltip is wide enough in all cases

--- resources/assets/coffee/_classes/line-chart.coffee
+++ resources/assets/coffee/_classes/line-chart.coffee
@@ -237,7 +237,7 @@
     unless @tooltipContainer.attr('data-width-set') == '1'
       @tooltipContainer
         .attr 'data-width-set', '1'
-        .style 'width', "#{@tooltipContainer.node().getBoundingClientRect().width}px"
+        .style 'width', "#{@tooltipContainer.node().getBoundingClientRect().width * 1.2}px"

   lookupX: (x) =>


unnecessary event; loading is already false

--- resources/assets/coffee/react/beatmaps/main.coffee
+++ resources/assets/coffee/react/beatmaps/main.coffee
@@ -85,7 +85,6 @@
   componentDidMount: =>
     $(document).on 'beatmap:load_more.beatmaps', @loadMore
     $(document).on 'beatmap:search:start.beatmaps', @search
-    $(document).on 'beatmap:search:done.beatmaps', @hideLoader
     $(document).on 'beatmap:search:filtered.beatmaps', @updateFilters
     $(document).on 'turbolinks:before-visit.beatmaps', @recordUrl
     $(document).on 'turbolinks:before-cache.beatmaps', @saveState
@@ -170,10 +169,6 @@
     e.preventDefault()

     @setState isExpanded: !@state.isExpanded
-
-
-  hideLoader: =>
-    @setState loading: false

   isSupporterMissing: =>
@@ -239,8 +234,7 @@
           more: data.length > 0
         loading: false

-      @setState newState, ->
-        $(document).trigger 'beatmap:search:done'
+      @setState newState

   sorting: =>


Fix more esoteric deprecation warning

Fix more esoteric deprecation warning

--- lib/haskell-ghc-mod.coffee
+++ lib/haskell-ghc-mod.coffee
@@ -28,9 +28,10 @@
     @process=null
     @numInstances=0

-    atom.views.addViewProvider
-      modelConstructor: HaskellGhcModMessage
-      viewConstructor: HaskellGhcModMessageElement
+    atom.views.addViewProvider HaskellGhcModMessage, (message)->
+      el=new HaskellGhcModMessageElement
+      el.setModel(message)
+      el

     @subscriptions.add atom.commands.add 'atom-text-editor',
       'haskell-ghc-mod:type': ->


Amend: remove problematic ampersand

Amend: remove problematic ampersand

--- services/filestore/app/coffee/BucketController.coffee
+++ services/filestore/app/coffee/BucketController.coffee
@@ -10,7 +10,7 @@
 		{bucket} = req.params
 		key = req.params[0]
 		{format, style} = req.query
-		credentials = settings.filestore.s3&[bucket]
+		credentials = settings.filestore.s3[bucket]
 		options = {
 			key: key,
 			bucket: bucket,


added more logging

added more logging

--- services/filestore/app/coffee/FileConverter.coffee
+++ services/filestore/app/coffee/FileConverter.coffee
@@ -21,6 +21,10 @@
 			timeout: twoMinsInMs
 		exec args, opts, (err, stdout, stderr)->
 			timer.done()
+			if err?
+				logger.err err:err, stderr:stderr, sourcePath:sourcePath, requestedFormat:requestedFormat, "something went wrong converting file"
+			else
+				logger.log  sourcePath:sourcePath, requestedFormat:requestedFormat, "finished converting file"
 			callback(err, destPath)

 	thumbnail: (sourcePath, callback)->
@@ -36,6 +40,10 @@
 		opts =
 			timeout: twoMinsInMs
 		exec args, opts,(err, stdout, stderr)->
+			if err?
+				logger.err err:err, stderr:stderr, sourcePath:sourcePath, "something went wrong converting file to preview"
+			else
+				logger.log  sourcePath:sourcePath, "finished thumbnailing file"
 			callback(err, destPath)

 	preview: (sourcePath, callback)->
@@ -51,4 +59,8 @@
 		opts =
 			timeout: twoMinsInMs
 		exec args, opts,(err, stdout, stderr)->
+			if err?
+				logger.err err:err, stderr:stderr, sourcePath:sourcePath, "something went wrong converting file to preview"
+			else
+				logger.log  sourcePath:sourcePath, "finished converting file to preview"
 			callback(err, destPath)


Adapt to Paw JS API version 0.2.0

Adapt to Paw JS API version 0.2.0

--- RubyNetHTTPCodeGenerator.coffee
+++ RubyNetHTTPCodeGenerator.coffee
@@ -1,4 +1,10 @@
-require "mustache.js"
+# in API v0.2.0 and below (Paw 2.2.2 and below), require had no return value
+((root) ->
+  if root.bundle?.minApiVersion('0.2.0')
+    root.Mustache = require("./mustache")
+  else
+    require("mustache.js")
+)(this)

 addslashes = (str) ->
     ("#{str}").replace(/[\\"]/g, '\\$&')


header - fix mobile search trigger

header - fix mobile search trigger

--- _assets/javascripts/shared/shared.coffee
+++ _assets/javascripts/shared/shared.coffee
@@ -113,6 +113,7 @@

   mobileWatchSearchToggle: ->
     $(".search_holder .trigger .search").click (e) ->
+      console.log 'yo'
       e.preventDefault()

       $search_form = $(".search_holder")
@@ -132,7 +133,7 @@
       left = $search_form.next().offset().left
       width = ($(window).width() - left - 30)
       $search_form.find('span.form').css('left',left).width(width)
-    else
+    else if $(window).width() > 719 && $search_form.hasClass('expanded')
       $search_form.removeClass("expanded")
       $search_form.find('span.form').removeAttr('style') # reset it in case previously fired


Updated the gruntfile to include the app module

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -7,6 +7,7 @@
                 options:
                     bare: true
                 files: {
+                    'dev/public/assets/dist/app.js': 'dev/public/assets/app/app.coffee'
                     'dev/public/assets/dist/modules/config.js': 'dev/public/assets/app/modules/config.coffee'
                 }
             tests:
@@ -31,6 +32,7 @@
                 dest: 'public/assets/lib/lib.js'
             app:
                 src: [
+                    'dev/public/assets/dist/app.js'
                     'dev/public/assets/dist/modules/config.js'
                 ]
                 dest: 'public/assets/lib/app.js'
@@ -49,6 +51,7 @@
                             reload: true
                     }, {
                         files: [
+                            'dev/public/assets/app/*.coffee',
                             'dev/public/assets/app/modules/*.coffee',
                             'dev/var/test-public/modules/*.coffee'
                         ]
@@ -57,9 +60,10 @@
                             reload: true
                     }, {
                         files: [
+                            'dev/public/assets/dist/**/*.js',
                             'dev/public/assets/lib/**/*.js'
                         ]
-                        tasks: ['coffee:*']
+                        tasks: ['concat:*']
                         callback:
                             reload: true
                     }]


fixed copy task

fixed copy task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -10,7 +10,7 @@

   grunt.initConfig

-    copy:
+    # copy:

     exec:
       bower:
@@ -34,7 +34,7 @@

   grunt.registerTask "build", [
     "exec:bower"
-    "copy"
+    # "copy"
   ]

   grunt.registerTask "serve", [


Grunt: Add dump.rdb to ignorefile.

Grunt: Add dump.rdb to ignorefile.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -94,6 +94,7 @@
             'config/*'
             'Gemfile'
             'Gemfile.lock'
+            'dump.rdb'
           ]

   grunt.loadNpmTasks('grunt-contrib-jade')


Fixing sdk dependency regex to actually replace APPSDK_SRC_VERSION

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -261,7 +261,7 @@
             js_dependencies = grunt.file.readJSON 'js_dependencies.json'
             sdk_dependency = _.filter(js_dependencies, (dep) -> dep.id.indexOf('com.rallydev.js:appsdk-src') is 0)[0]
             if sdk_dependency and process.env.APPSDK_SRC_VERSION
-              sdk_dependency.id = sdk_dependency.id.replace(/:[\w]+?$/, ":#{process.env.APPSDK_SRC_VERSION}")
+              sdk_dependency.id = sdk_dependency.id.replace(/:[^:]+$/, ":#{process.env.APPSDK_SRC_VERSION}")
             js_dependencies
           )()
       push:


Add concat to watch

Add concat to watch

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -7,11 +7,11 @@
     watch:
       src:
         files: 'src/*'
-        tasks: ["coffee:dist"]
+        tasks: ['coffee:dist', 'concat']

       spec:
         files: 'spec/*'
-        tasks: ["coffee:spec"]
+        tasks: ['coffee:spec']

     coffee:
       dist:


Follow updated Grunt pattern for compiling a set of files and directing output to a separate folder.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -12,8 +12,14 @@

     coffee:
       quickui:
-        files:
-          "build/*.js": "src/*.coffee"
+        files: [
+          # "build/*.js": "src/*.coffee"
+          expand: true
+          cwd: "src/"
+          src: "*.coffee"
+          dest: "build/"
+          ext: ".js"
+        ]
       test:
         files:
           "test/unittests.js": [
@@ -59,7 +65,7 @@

     min:
       dist:
-        src: ["quickui.js"]
+        src: [ "quickui.js" ]
         dest: "quickui.min.js"

   # Default task.


Revert "grunt: build jekyll with --draft"

This reverts commit ebcb44ab782f922e262837a5607397129b05cf65.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -56,7 +56,7 @@
       install:
         cmd: "bundle install"
       jekyll:
-        cmd: "bundle exec jekyll build --trace --draft"
+        cmd: "bundle exec jekyll build --trace"

     bower:
       install: {}


Rename uglify Grunt sub task.

Rename uglify Grunt sub task.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -19,7 +19,7 @@
     uglify:
       options:
         report: 'min'
-      noflo:
+      'gss-compiler':
         files:
           './browser/gss-compiler.min.js': ['./browser/gss-compiler.js']


Update gruntfile to remove the various DEPRECATED bits.

This can't be merged until the chrome+firefox extensions have been
updated by users, and that might take a few weeks.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -37,12 +37,6 @@
           'build/js/jail_iframe/wrap/last.js'
         ]
         dest: 'build/jail_iframe.js'
-      loader_DEPRECATED:
-        src: [
-          'build/js/loader/loader_common.js'
-          'build/js/loader/loader_basic.js'
-        ]
-        dest: 'build/factlink.js'
       loader_basic:
         src: [
           'build/js/loader/loader_common.js'
@@ -66,9 +60,6 @@
         src: 'build/css/basic.min.css'
         dest: 'build/jail_iframe.js' # See jail_iframe/initializers/style.coffee

-      jail_iframe_loader_DEPRECATED:
-        src: 'build/jail_iframe.js'
-        dest: 'build/factlink.js'
       jail_iframe_loader_basic:
         src: 'build/jail_iframe.js'
         dest: 'build/factlink_loader_basic.js'
@@ -79,9 +70,6 @@
         src: 'build/jail_iframe.js'
         dest: 'build/factlink_loader_bookmarklet.js'

-      jail_iframe_loader_DEPRECATED_min:
-        src: 'build/jail_iframe.min.js'
-        dest: 'build/factlink.min.js'
       jail_iframe_loader_basic_min:
         src: 'build/jail_iframe.min.js'
         dest: 'build/factlink_loader_basic.min.js'
@@ -121,7 +109,6 @@
           'build/factlink.stop_annotating.min.js':    ['build/factlink.stop_annotating.js']
           'build/factlink.start_highlighting.min.js': ['build/factlink.start_highlighting.js']
           'build/factlink.stop_highlighting.min.js':  ['build/factlink.stop_highlighting.js']
-          'build/factlink.min.js':                    ['build/factlink.js']
           'build/factlink_loader_basic.min.js':       ['build/factlink_loader_basic.js']
           'build/factlink_loader_publishers.min.js':  ['build/factlink_loader_publishers.js']
           'build/factlink_loader_bookmarklet.min.js': ['build/factlink_loader_bookmarklet.js']


tidy gruntfile

tidy gruntfile

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -155,20 +155,26 @@
   grunt.loadNpmTasks 'grunt-express'
   grunt.loadNpmTasks 'grunt-env'

-  grunt.registerTask 'build'
-  , ['clean', 'coffeeLint', 'coffee', 'ngTemplateCache','copy:libs'
-  , 'requirejs', 'copy:dev', 'clean:temp']
-
-  grunt.registerTask 'default'
-  , ['build', 'mochaTest:unit', 'cucumberjs:unit', 'karma:unit', 'cucumberjs:integration' ]
-
-  grunt.registerTask 'ci', [
-    'default'
+  grunt.registerTask 'build', [
+    'clean'
+    'coffeeLint'
+    'coffee'
+    'ngTemplateCache'
+    'copy:libs'
+    'requirejs'
+    'copy:dev'
+    'clean:temp'
   ]

   grunt.registerTask 'unit', [
-    'build'
     'mochaTest:unit'
+    'mochaTest:unit'
+    'cucumberjs:unit'
+    'karma:unit'
+  ]
+
+  grunt.registerTask 'integration', [
+    'cucumberjs:integration'
   ]

   grunt.registerTask 'e2e', [
@@ -178,15 +184,18 @@
   ]

   grunt.registerTask 'test', [
+    'build'
+    'unit'
+    'integration'
+    'e2e'
+  ]
+
+  grunt.registerTask 'default', [
+    'build'
+    'unit'
+    'integration'
+  ]
+
+  grunt.registerTask 'ci', [
     'default'
-    'e2e'
-  ]
-
-  grunt.registerTask 'coverage'
-  , ['build', 'karma:coverage', 'clean:bin']
-
-  grunt.registerTask 'ciserver'
-  , ['build', 'karma:unit']
-
-  grunt.registerTask 'run'
-  , [ 'build', 'watch']+  ]


Ignore slides beginning by _

Ignore slides beginning by _

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -192,7 +192,7 @@
                         elementPath = path.join options.cwd, f
                         fileStats = fs.lstatSync elementPath
                         if fileStats.isDirectory()
-                            insideFiles = glob.sync f + '/*.{md,html}', options
+                            insideFiles = glob.sync f + '/[^_]*.{md,html}', options
                             return insideFiles
                         return f


Fix fs setting

Fix fs setting

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -45,7 +45,7 @@
           resolve:
             extensions: ["", ".coffee", ".js"]
           node:
-            fs: "mock"
+            fs: "empty"
         ignores: [
           /bin\/coffee/
         ]


Also delete count as it's added by Lang.choice

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -351,6 +351,8 @@

     if !isFallbackLocale && !translated?
       delete replacements.count_delimited
+      # added by Lang.choice
+      delete replacements.count

       return osu.transChoice(key, count, replacements, fallbackLocale)


Use subscribe instead of on

Use subscribe instead of on

--- lib/file-view.coffee
+++ lib/file-view.coffee
@@ -29,9 +29,9 @@
       @fileName.addClass('text-icon')

     if git?
-      git.on 'status-changed', (path, status) =>
+      @subscribe git, 'status-changed', (path, status) =>
         @updateStatus() if path is @getPath()
-      git.on 'statuses-changed', =>
+      @subscribe git, 'statuses-changed', =>
         @updateStatus()

     @updateStatus()


Comments on library index

Comments on library index

--- library/index.coffee
+++ library/index.coffee
@@ -1,33 +1,33 @@
 miniLockLib = module.exports = {}

-# Make a set of keys.
+# Construct and start a [key pair operation](MakeKeyPairOperation.html).
 miniLockLib.makeKeyPair = (secretPhrase, emailAddress, callback) ->
   operation = new miniLockLib.MakeKeyPairOperation {secretPhrase, emailAddress}
   operation.start(callback)

-# Encrypt a file.
+# Construct and start an [encrypt operation](EncryptOperation.html).
 miniLockLib.encrypt = (params, callback) ->
   operation = new miniLockLib.EncryptOperation params
   operation.start(callback)

-# Decrypt a file.
+# Construct and start a [decrypt operation](DecryptOperation.html).
 miniLockLib.decrypt = (params, callback) ->
   operation = new miniLockLib.DecryptOperation params
   operation.start(callback)

-# Acceptance tests for key pair inputs.
+# Supply [secret phrase](SecretPhrase.html) and [email address](EmailAddress.html) acceptance tests.
 miniLockLib.SecretPhrase = require "./SecretPhrase"
 miniLockLib.EmailAddress = require "./EmailAddress"

-# Encode, decode and test identification.
+# Supply [identification](ID.html) function to encode, decode and test miniLock IDs.
 miniLockLib.ID = require "./ID"

-# miniLock crypto operations.
+# Supply operation constructors.
 miniLockLib.MakeKeyPairOperation = require "./MakeKeyPairOperation"
 miniLockLib.EncryptOperation = require "./EncryptOperation"
 miniLockLib.DecryptOperation = require "./DecryptOperation"

-# Export special extras.
+# Supply special extras.
 miniLockLib.Base58  = require "./Base58"
 miniLockLib.BLAKE2s = require "./BLAKE2s"
 miniLockLib.NaCl    = require "./NaCl"


FIX: redraw the canvas on new seqs

FIX: redraw the canvas on new seqs

Former-commit-id: f37d36542a07bfcc0ca2e8f42fe3e7ef2194adcf [formerly e6a51aa56cd2dcf977b4fa6a7dfe32f0d18ae0ca]
Former-commit-id: 7b13074734b79f70aa8e416f4978aae9a38b9acf

--- src/views/canvas/CanvasSeqBlock.coffee
+++ src/views/canvas/CanvasSeqBlock.coffee
@@ -22,6 +22,7 @@
     @listenTo @g.zoomer,"change:alignmentWidth change:alignmentHeight", @render
     @listenTo @g.colorscheme, "change", @render
     @listenTo @g.selcol, "reset add", @render
+    @listenTo @model, "reset add", @render

     # el props
     @el.style.display = "inline-block"


Tweak mobile controller

Tweak mobile controller

--- src/public/scripts/app.controller.coffee
+++ src/public/scripts/app.controller.coffee
@@ -61,15 +61,6 @@
 				# Send the device orientation 10 times a second
 				if window.DeviceOrientationEvent
 					setTimeout( @sendDeviceOrientation, 100)
-
-				@boost.bind("touchstart", () => @sendBoostEvent(true))
-				@boost.bind("touchend", () => @sendBoostEvent(false))
-				@boost.bind("touchcancel", () => @sendBoostEvent(false))
-
-				@shoot.bind("touchstart", () => @sendShootEvent(true))
-				@shoot.bind("touchend", () => @sendShootEvent(false))
-				@shoot.bind("touchcancel", () => @sendShootEvent(false))
-

 			)

@@ -89,21 +80,21 @@

 			if width > height

+				@shoot = @canvas.display.rectangle({
+					x: width / 4,
+					y: height / 4,
+					origin: { x: "center", y: "center" },
+					width: width / 2,
+					height: height / 2,
+					fill: "#0ef"
+				})
 				@boost = @canvas.display.rectangle({
 					x: width / 4,
-					y: height / 4,
+					y: height *3 / 4,
 					origin: { x: "center", y: "center" },
 					width: width / 2,
 					height: height / 2,
 					fill: "#0aa"
-				})
-				@shoot = @canvas.display.rectangle({
-					x: width / 4,
-					y: height * 3 / 4,
-					origin: { x: "center", y: "center" },
-					width: width / 2,
-					height: height / 2,
-					fill: "#0ef"
 				})
 				@fire = @canvas.display.rectangle({
 					x: width * 3 / 4,
@@ -116,21 +107,21 @@

 			else

-				@boost = @canvas.display.rectangle({
+				@shoot = @canvas.display.rectangle({
 					x: width / 4,
 					y: height / 4,
 					origin: { x: "center", y: "center" },
 					width: width / 2,
 					height: height / 2,
-					fill: "#0aa"
-				})
-				@shoot = @canvas.display.rectangle({
+					fill: "#0ef"
+				})
+				@boost = @canvas.display.rectangle({
 					x: width * 3 / 4,
 					y: height / 4,
 					origin: { x: "center", y: "center" },
 					width: width / 2,
 					height: height / 2,
-					fill: "#0ef"
+					fill: "#0aa"
 				})
 				@fire = @canvas.display.rectangle({
 					x: width / 2,
@@ -140,6 +131,15 @@
 					height: height / 2,
 					fill: "#f21"
 				})
+
+			shootText = @canvas.display.text({
+				x: 0,
+				y: 0,
+				origin: { x: "center", y: "top" },
+				font: "bold 25px/1.5 sans-serif",
+				text: "Fire",
+				fill: "#000"
+			})

 			boostText = @canvas.display.text({
 				x: 0,
@@ -159,30 +159,43 @@
 				fill: "#000"
 			})

+			@canvas.addChild(@shoot)
+			@shoot.addChild(shootText)
+
 			@canvas.addChild(@boost)
 			@boost.addChild(boostText)

 			@canvas.addChild(@fire)
 			@fire.addChild(fireText)

-			@canvas.addChild(@shoot)
-
-			#@fire.bind("tap click",  , false )
+			window.requestAnimationFrame(@bindEvents())
+
+		bindEvents: () =>
+
+			@boost.bind("touchstart", () => @sendBoostEvent(true))
+			@boost.bind("touchend", () => @sendBoostEvent(false))
+			@boost.bind("touchcancel", () => @sendBoostEvent(false))
+
+			@shoot.bind("touchstart", () => @sendShootEvent(true))
+			@shoot.bind("touchend", () => @sendShootEvent(false))
+			@shoot.bind("touchcancel", () => @sendShootEvent(false))
+
 			@fire.bind("touchenter", @initiatePoke, false )
 			@fire.bind("touchmove", @handlePoke, false )
 			@fire.bind("touchend", @stopPoke, false )
 			#@fire.bind("touchcancel", @stopPoke, false )

+			#window.requestAnimationFrame(@bindEvents())
+
 		initiatePoke: (event) =>
 			console.log "start: ", event.x, event.y
 			@poke.x = event.x
 			@poke.y = event.y
-			#@sendFireEvent()

 		handlePoke: (event) =>
 			sendPoke = {}
-			sendPoke.x = event.x - @poke.x
-			sendPoke.y = event.y - @poke.y
+			sendPoke.x = Math.round((event.x - @poke.x) / 4)
+			sendPoke.y = Math.round((event.y - @poke.y) / 4)
 			console.log sendPoke
 			@node.getPeers()[0].emit('controller.cannon', sendPoke)

@@ -230,6 +243,7 @@
 		#
 		sendBoostEvent: ( boost ) ->
 			if boost isnt @_lastBoost
+				console.log "boost ", boost
 				@_lastBoost = boost
 				@node.getPeers()[0].emit('controller.boost', boost)


replace url id

replace url id

--- app/assets/javascripts/epiclogger.coffee
+++ app/assets/javascripts/epiclogger.coffee
@@ -85,6 +85,7 @@
       }
     }
     $helpfulLink = $('.helpful-link')
+    $('i.fa.fa-user').parent().attr('href').replace('1', $.auth.user.id)
     $helpfulLink.attr('data-helpful-email', $.auth.user.email)
     $helpfulLink.attr('data-helpful-name', $.auth.user.name)
     $('.user-sidebar').render $.auth.user, userDirectives


added code for extracting the payer information from a patient.

--- app/assets/javascripts/patient.js.coffee
+++ app/assets/javascripts/patient.js.coffee
@@ -113,6 +113,16 @@
       return hQuery.createCodedValue code: @json['race'], code_system: 'CDC Race'

   ###*
+  @returns {CodedValue} of the payer of the patient
+  SOP codes: https://www.health.ny.gov/statistics/sparcs/sysdoc/appp.htm
+  ###
+  payer: ->
+    if @json['insurance_providers']?.length
+      ip = @json['insurance_providers'][0]
+      if ip.codes && ip.codes.SOP?.length
+        return hQuery.createCodedValue code: ip.codes.SOP[0], code_system: 'SOP'
+
+  ###*
   @returns {CodedValue} of the ethnicity of the patient
   CDC codes:  http://phinvads.cdc.gov/vads/ViewCodeSystemConcept.action?oid=2.16.840.1.113883.6.238&code=1000-9
   ###


Adapt suggest script for translations

Adapt suggest script for translations

Proposal, Debate, Budget Investment  and Comment will be translatable
models soon. This commit prepares suggestions script to work well with
translatable forms elements too.

--- app/assets/javascripts/suggest.js.coffee
+++ app/assets/javascripts/suggest.js.coffee
@@ -9,11 +9,15 @@
       callback = ->
         $.ajax
           url: $this.data("js-url")
-          data: { search: $this.val() },
-          type: "GET",
+          data:
+            search: $this.val()
+          type: "GET"
           dataType: "html"
           success: (stHtml) ->
             js_suggest_selector = $this.data("js-suggest")
+            if js_suggest_selector.startsWith(".")
+              locale = $this.closest(".translatable-fields").data("locale")
+              js_suggest_selector += "[data-locale=#{locale}]"
             $(js_suggest_selector).html(stHtml)

       timer = null


For @janpaul123: avoid return; =>

For @janpaul123: avoid return; =>

--- core/app/backbone/views/facts/top_fact_heading_view.coffee
+++ core/app/backbone/views/facts/top_fact_heading_view.coffee
@@ -4,14 +4,12 @@

   ui: favicon: '.js-favicon'

-  verifyLoaded:  =>
+  verifyLoaded:  ->
     @ui.favicon.removeClass('favicon-missing') if @ui.favicon[0].naturalWidth
-    return

   onRender: ->
-    @ui.favicon.on 'load', @verifyLoaded
+    @ui.favicon.on 'load', (=> @verifyLoaded(); return)
     @verifyLoaded()
-    return

 class window.TopFactHeadingUserView extends Backbone.Marionette.ItemView
   className: 'top-fact-heading'


paint the current playing track

paint the current playing track

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -1,4 +1,6 @@
 console.log "Hay there, you can find me @aberigle"
+
+window.isArray = (object) -> Object::toString.call(object) is "[object Array]"

 class API

@@ -51,7 +53,7 @@

     @_get url, (response) ->
       response = JSON.parse(response.response).recenttracks
-      if Object::toString.call(response.track) is "[object Array]"
+      if response.track? and isArray response.track
         callback? response.track[0]
       else callback?()

@@ -62,15 +64,20 @@

 window.onload = ->
   drawTrack = (track) ->
-    alert track.name
+    link = copyright.firstChild
+    link.text = track.name + " - " + track.artist["#text"]
+    link.href = track.url
+    console.log track
+
+  drawImage = (response) ->
+    document.body.style.backgroundImage = "url(#{response.imageUrl})"
+    link = copyright.firstChild
+    link.href = response.copyrightLink
+    link.text = response.copyright
+
+    copyright.onmouseenter = -> document.body.classList.add "clean"
+    copyright.onmouseleave = -> document.body.classList.remove "clean"

   aberigle.api.lastfm.getNowPlaying "jayle23", (track) ->
     if track? then drawTrack track
-    else aberigle.api.image.getImage (response) ->
-      document.body.style.backgroundImage = "url(#{response.imageUrl})"
-      link = copyright.firstChild
-      link.href = response.copyrightLink
-      link.text = response.copyright
-
-      copyright.onmouseenter = -> document.body.classList.add "clean"
-      copyright.onmouseleave = -> document.body.classList.remove "clean"
+    else aberigle.api.image.getImage drawImage


Do not look for 'Makefile' when invoking via require('coffeemake').run; better detection of real command line arguments

--- src/coffeemake.coffee
+++ src/coffeemake.coffee
@@ -365,7 +365,7 @@
     { process } = @
     # Extract correct argument list
     { argv } = process
-    args = if /([/]|^)coffee([.]exe)?$/i.test argv[0] then argv.slice(1) else argv.slice(0)
+    args = if /([/]|^)(node(js)?|coffee)([.]exe)?$/i.test argv[0] then argv.slice(1) else argv.slice(0)
     progname = args.shift()
     cb.call @, args, { progname }
     @
@@ -378,16 +378,10 @@
       @runWithArgs(args, setup)

   runWithArgs: (args)->
+    @processOptions(args).performAction()
+
+  runStandalone: ({ process })->
     @defaultMakefile = 'Makefile'
-    @processOptions(args).performAction()
-
-  runStandalone: ({ process })->
     @process = process if process?
-    @withProcessArgs @runWithArgs
-
-  runIfStandalone: ->
-    # Run stand-alone, if the process was started on this file only
-    @withProcessArgs (args, { progname })->
-      if (/([/]|^)coffeemake([.].*)?$/).test(progname)
-        @runWithArgs args
-    @
+    @withProcessArgs (args)->
+      @runWithArgs args


fix #318 text-object can be invoked directly from any mode.

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -27,7 +27,7 @@
         if (mode is 'visual') and operation.isRequireTarget()
           operation = operation.setTarget(new CurrentSelection(@vimState))
       when operation.isTextObject()
-        if mode in ['visual', 'normal']
+        unless mode is 'operator-pending'
           operation = @select.setTarget(operation)
       when operation.isMotion()
         if (mode is 'visual')


:lipstick: ? ugly but I like

:lipstick: ? ugly but I like

--- lib/operation-stack.coffee
+++ lib/operation-stack.coffee
@@ -77,13 +77,13 @@

     @inspect()
     debug "-> @pop()"
-    operation = @pop()
-    debug "  - popped = <#{operation.getKind()}>"
+    op = @pop()
+    debug "  - popped = <#{op.getKind()}>"
     debug "  - newTop = <#{@peekTop()?.getKind()}>"
     unless @isEmpty()
       try
-        debug "-> <#{@peekTop().getKind()}>.compose(<#{operation.getKind()}>)"
-        @peekTop().compose(operation)
+        debug "-> <#{@peekTop().getKind()}>.compose(<#{op.getKind()}>)"
+        @peekTop().compose(op)
         debug "-> @process(): recursive"
         @process()
       catch error
@@ -92,12 +92,12 @@
         else
           throw error
     else
-      @vimState.history.unshift(operation) if operation.isRecordable()
-      if operation.isPure()
+      @vimState.history.unshift(op) if op.isRecordable()
+      if op.isPure()
         null # Something new way of execution.
       else
-        debug " -> <#{operation.getKind()}>.execute()"
-        operation.execute()
+        debug " -> <#{op.getKind()}>.execute()"
+        op.execute()
         @vimState.counter.reset()
         debug "#=== Finish at #{new Date().toISOString()}\n"

@@ -119,10 +119,10 @@
   isEmpty: ->
     @stack.length is 0

-  isSameOperatorPending: (operation) ->
-    constructor = operation.constructor
-    _.detect @stack, (operation) ->
-      operation instanceof constructor
+  isSameOperatorPending: (op) ->
+    constructor = op.constructor
+    _.detect @stack, (op) ->
+      op instanceof constructor

   isProcessing: ->
     @processing


Bug Fix - Get targetValue of BasicAnchor was looking at  populated value instead of current and had conditional reversed.

--- src/scripts/ui/dialogs/customlink.coffee
+++ src/scripts/ui/dialogs/customlink.coffee
@@ -397,7 +397,7 @@

 	populate: (v) ->

-		@_url = v
+		@_domInput.value = v

 	_addDOMEventListeners: () ->
 		super()
@@ -421,9 +421,9 @@
 	targetValue: () ->
 		#targetCSSClass = @_domTargetSection.getAttribute('class')
 		#return (targetCSSClass.indexOf('ct-section--applied') > -1 ? '_blank' : null)
-		if @_url.indexOf('//') is -1
-			return '_blank'
-		return null
+		if @_domInput.value.indexOf('//') is -1
+			return null
+		return '_blank'

 class MailToUI extends ContentTools.AnchoredComponentUI


Adding talky support

Adding talky support

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -340,6 +340,8 @@
         videoConferenceUrl: (conferenceSystem, slug) ->
             if conferenceSystem == "appear-in"
                 return "https://appear.in/#{$rootScope.site.headers['x-site-host']}-#{slug}"
+            else if conferenceSystem == "talky"
+                return "https://talky.io/#{$rootScope.site.headers['x-site-host']}-#{slug}"
             return ""

     $rootScope.momentFormat = (input, format) ->


fix source component to handle changing props

--- app/components.coffee
+++ app/components.coffee
@@ -32,8 +32,10 @@

 SourceComponent = React.createClass
   displayName: 'SourceComponent'
+  renderCode: -> format_code @props.value, @props.language, @getDOMNode()
   render: -> React.DOM.pre()
-  componentDidMount: -> format_code @props.value, @props.language, @getDOMNode()
+  componentDidMount: -> @renderCode()
+  componentDidUpdate: -> @renderCode()

 TreeNodeComponent = React.createClass


navItem defaulted to an object for app manifests to avoid throwing when none specified

--- core/KD.coffee
+++ core/KD.coffee
@@ -141,6 +141,7 @@
     options.behavior     or= ""           # a String "application", "hideTabs", or ""
     options.thirdParty    ?= no           # a Boolean
     options.menu         or= null         # <Array<Object{title: string, eventName: string, shortcut: string}>>
+    options.navItem      or= {}           # <Object{title: string, eventName: string, shortcut: string}>

     options.route        or= {}           # <string> or <Object{slug: string, handler: function}>


add support for only_changes changefeeds (not necessarily going to be used for anything, but may be nice to have)

--- src/smc-hub/postgres-user-queries.coffee
+++ src/smc-hub/postgres-user-queries.coffee
@@ -304,6 +304,8 @@
         for x in options
             for name, value of x
                 switch name
+                    when 'only_changes'
+                        r.only_changes = !!value
                     when 'limit'
                         r.limit = value
                     when 'slice'
@@ -1106,10 +1108,12 @@
             options = options.concat(schema_options)

         # Parse option part of the query
-        {limit, order_by, slice, err} = @_query_parse_options(options)
+        {limit, order_by, slice, only_changes, err} = @_query_parse_options(options)

         if err
             return {err: err}
+        if only_changes
+            r.only_changes = true
         if limit?
             r.limit = limit
         else if not multi
@@ -1427,13 +1431,18 @@
                 else
                     cb()
             (cb) =>
-                dbg("finally doing query")
-                @_user_get_query_do_query _query_opts, client_query, opts.query, opts.multi, json_fields, (err, result) =>
-                    if err
-                        cb(err)
-                        return
-                    locals.result = result
+                if _query_opts.only_changes
+                    dbg("skipping query")
+                    locals.result = undefined
                     cb()
+                else
+                    dbg("finally doing query")
+                    @_user_get_query_do_query _query_opts, client_query, opts.query, opts.multi, json_fields, (err, result) =>
+                        if err
+                            cb(err)
+                            return
+                        locals.result = result
+                        cb()
         ], (err) =>
             if err
                 dbg("series failed -- err=#{err}")


Fix edge case.

Fix edge case.

--- core.coffee
+++ core.coffee
@@ -154,8 +154,9 @@
 			if date isnt ""
 				items.shift()
 			if items.length > 0
+				newDate = ""
 				async.eachLimit items, 100, (item, callback) ->
-					date = item.commit.author.date
+					newDate = item.commit.author.date
 					return callback() unless item.author?
 					getUserOrCreateUser item.author.login, (err, user) ->
 						return callback() unless user?
@@ -166,7 +167,11 @@
 							timestamp: item.commit.author.date
 						fetchCommit repo, commit, callback
 				, ->
-					fetchRepoCommits commits, repo, date, callback
+					if newDate isnt date
+						fetchRepoCommits commits, repo, newDate, callback
+					else
+						winston.info "Saved commits for repo #{repo.fullName} since #{logDate}"
+						callback()
 			else
 				winston.info "Saved commits for repo #{repo.fullName} since #{logDate}"
 				callback()


Add path to cookie

Add path to cookie

--- app/assets/javascripts/base.js.coffee
+++ app/assets/javascripts/base.js.coffee
@@ -10,7 +10,7 @@
     button.addClass('active')
     selectedTime = button.attr('id')
     $('body').addClass selectedTime
-    document.cookie = "time-toggle=#{selectedTime}"
+    document.cookie = "time-toggle=#{selectedTime}; path=/"

   cookieValue  = document.cookie.match(/time-toggle=([\w-]+)/)
   selectedTime = if cookieValue then cookieValue[1] else 'wall-time'


legend label exp2

legend label exp2

--- app/assets/javascripts/zvg/zvg.coffee
+++ app/assets/javascripts/zvg/zvg.coffee
@@ -290,7 +290,7 @@
       # ({ key: x, text: (@legend_labels()[x] or "_value_#{x}")} for x in @series_3_domain().slice(0).reverse())

       # This one uses all legend labels, whether they are represented by the data or not.
-      ({ key: key, text: text }) for key, text of @legend_labels()
+      ({ key: key, text: text }) for key, text of @legend_labels().reverse()
     catch e
       console.info(e)
       console.log(@series_3_domain())


use anchor for parsing origin url for IE

--- csrf.coffee
+++ csrf.coffee
@@ -60,8 +60,11 @@

   return

+origin = document.createElement 'a'
+origin.href = location.href
+
 # Check if url is within the same origin policy.
 isSameOrigin = (url) ->
   a = document.createElement 'a'
   a.href = url
-  "#{location.protocol}//#{location.host}" == "#{a.protocol}//#{a.host}"
+  "#{origin.protocol}//#{origin.host}" == "#{a.protocol}//#{a.host}"


Fix "next" button disable

Fix "next" button disable

--- app/controllers/subject_viewer.coffee
+++ app/controllers/subject_viewer.coffee
@@ -117,8 +117,8 @@
     @classification.updateAttribute 'favorite', false

   onClickNext: ->
+    @nextButton.attr disabled: true
     @classification.send()
-    @nextButton.attr disabled: true

   play: ->
     # Flip the images back and forth a couple times.


finish multiline support for REPL rewrite

--- repl.coffee
+++ repl.coffee
@@ -5,54 +5,67 @@

 replDefaults =
   prompt: 'coffee> ',
-  eval: (code, context, file, cb) ->
+  eval: (input, context, filename, cb) ->
     try
-      return cb(null) if /^\(\s+\)$/.test code # Empty command
-      code = code.replace /子/mg, '\n' # Temporary hack, see TODO below
-      code = CoffeeScript.compile(code, {filename: file, bare: true})
-      cb(null, vm.runInContext(code, context, file))
+      return cb null if /^\(\s+\)$/.test input # Empty command
+      # TODO: pass in-scope vars and avoid accidentally shadowing them by omitting those declarations
+      js = CoffeeScript.compile input, {filename, bare: yes}
+      cb null, vm.runInContext js, context, filename
     catch err
-      cb(err)
+      cb err

 # TODO: how to test?
 addMultilineHandler = (repl) ->
   {rli, inputStream, outputStream} = repl

-  multiline =
+  multiline =
     enabled: off
-    prompt: new Array(repl.prompt.length).join('.') + ' '
+    initialPrompt: '------> '
+    prompt: '......> '
     buffer: ''

   # Proxy node's line listener
   nodeLineListener = rli.listeners('line')[0]
   rli.removeListener 'line', nodeLineListener
   rli.on 'line', (cmd) ->
-    if multiline.enabled is on
+    if multiline.enabled
       multiline.buffer += "#{cmd}\n"
+      rli.setPrompt multiline.prompt
       rli.prompt true
     else
       nodeLineListener(cmd)
+    return

   # Handle Ctrl-v
   inputStream.on 'keypress', (char, key) ->
     return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
-    multiline.enabled = !multiline.enabled
-    if multiline.enabled is off
+    if multiline.enabled
+      # allow arbitrarily switching between modes any time before multiple lines are entered
       unless multiline.buffer.match /\n/
+        multiline.enabled = not multiline.enabled
         rli.setPrompt repl.prompt
         rli.prompt true
         return
-      # TODO: how to encode line breaks so the node repl will pass the complete multiline to our eval?
-      multiline.buffer = multiline.buffer.replace /\n/mg, '子'
+      # no-op unless the current line is empty
+      return unless rli.line.match /^\s*$/
+      # eval, print, loop
+      multiline.enabled = not multiline.enabled
+      rli.line = ''
+      rli.cursor = 0
+      rli.output.cursorTo 0
+      rli.output.clearLine 1
       rli.emit 'line', multiline.buffer
       multiline.buffer = ''
     else
-      rli.setPrompt multiline.prompt
+      multiline.enabled = not multiline.enabled
+      rli.setPrompt multiline.initialPrompt
       rli.prompt true
+    return

 module.exports =
   start: (opts = {}) ->
-    opts = merge(replDefaults, opts)
+    opts = merge replDefaults, opts
     repl = nodeREPL.start opts
-    addMultilineHandler(repl)
-    repl+    repl.on 'exit', -> repl.outputStream.write '\n'
+    addMultilineHandler repl
+    repl