Workspace: Update for join session key modal. Getting title and modal content from options now.

--- client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
+++ client/landing/app/CommonViews/workspace/collaborativeworkspace.coffee
@@ -186,28 +186,26 @@
             appManager.quit appManager.frontApp

   showJoinModal: (callback = noop) ->
-    modal                 = new KDModalView
-      title               : "Join New Session"
-      content             : @getOptions().joinModalContent or ""
-      overlay             : yes
-      cssClass            : "workspace-modal join-modal"
-      width               : 500
-      buttons             :
-        Join              :
-          title           : "Join Session"
-          cssClass        : "modal-clean-green"
-          callback        : =>
-            @handleJoinASessionFromModal sessionKeyInput.getValue(), modal
-        Close             :
-          title           : "Close"
-          cssClass        : "modal-cancel"
-          callback        : -> modal.destroy()
+    modal          = new KDModalView
+      title        : @getOptions().joinModalTitle   or "Join New Session"
+      content      : @getOptions().joinModalContent or "This is your session key, you can share this key with your friends to work together."
+      overlay      : yes
+      cssClass     : "workspace-modal join-modal"
+      width        : 500
+      buttons      :
+        Join       :
+          title    : "Join Session"
+          cssClass : "modal-clean-green"
+          callback : => @handleJoinASessionFromModal sessionKeyInput.getValue(), modal
+        Close      :
+          title    : "Close"
+          cssClass : "modal-cancel"
+          callback : -> modal.destroy()

     modal.addSubView sessionKeyInput = new KDHitEnterInputView
-      type        : "text"
-      placeholder : "Paste new session key and hit enter to join"
-      callback    : =>
-        @handleJoinASessionFromModal sessionKeyInput.getValue(), modal
+      type         : "text"
+      placeholder  : "Paste new session key and hit enter to join"
+      callback     : => @handleJoinASessionFromModal sessionKeyInput.getValue(), modal

     callback modal


Teams: Don't add 2 points

Teams: Don't add 2 points

--- client/landing/site.landing/coffee/team/forms/teamdomaintabform.coffee
+++ client/landing/site.landing/coffee/team/forms/teamdomaintabform.coffee
@@ -27,7 +27,7 @@
     @input.on 'input', (event) ->
       { length }  = @getValue()
       length      = 8  unless length
-      @setAttribute 'size', length + 2
+      @setAttribute 'size', length

     @suffix = new KDView
       tagName      : 'span'


Effects: Avoid bloom bleeding a different way.

--- engine/effects/bloom.coffee
+++ engine/effects/bloom.coffee
@@ -35,16 +35,19 @@
         # horizontal+downscale from @buffer to @buffer2
         # vertical from @buffer2 to @buffer
         vector = @blur.get_material().inputs.vector.value
-        drect = [0, 0, @width, @height]
+        # extend a couple of pixels outwards to avoid bleeding
+        # (TODO: benchmark against clearing whole buffer)
+        bleed = 1
+        drect = [0, 0, @width+bleed, @height+bleed]
         for i in [0...@steps] by 1
-            drect[2]>>=1
-            drect[3]>>=1
+            drect[2]=((drect[2]-bleed)>>1)+bleed
+            drect[3]=((drect[3]-bleed)>>1)+bleed
             # this is downscaling by a factor of 2, so
             # the blur vector needs to be 2 pixels relative to the source
             vec2.set vector, 2/@buffer.size_x, 0
-            @blur.apply @buffer, @buffer2, drect, {}, clear: true
+            @blur.apply @buffer, @buffer2, drect#, {}, clear: true
             vec2.set vector, 0, 1/@buffer2.size_y
-            @blur.apply @buffer2, @buffer, drect, {}, clear: true
+            @blur.apply @buffer2, @buffer, drect#, {}, clear: true
         # Step 3: Mix source and @buffer with screen mix.
         # The @buffer inputs are already set in previous functions
         destination = temporary


Fix example

Fix example

--- example/basic1_2/client/basic.coffee
+++ example/basic1_2/client/basic.coffee
@@ -17,14 +17,14 @@
 				Images.insert res

 	"click button.delete": ->
-		Cloudinary.delete @response.public_id, (err,res) ->
+		Cloudinary.delete @public_id, (err,res) ->
 			console.log "Upload Error:"
 			console.log err
 			console.log "Upload Result:"
 			console.log res

 	"click button.delete_private": ->
-		Cloudinary.delete @response.public_id, "private", (err,res) ->
+		Cloudinary.delete @public_id, "private", (err,res) ->
 			console.log "Upload Error:"
 			console.log err
 			console.log "Upload Result:"


Fix static backend path route

Fix static backend path route

--- src/routes/backend-static.coffee
+++ src/routes/backend-static.coffee
@@ -1,11 +1,16 @@
 CONFIG = require '../config'
 Request = require 'superagent'
+
+log = require('../log')(module)
+
 module.exports = (app, done) ->

 	# Swagger interface
-	app.get '/backend/:id*?', (req, res, next) ->
+	app.get '/backend/*', (req, res, next) ->
+		uri = "#{CONFIG.backendURI}/#{CONFIG.backendStaticPath}/#{req.params[0]}"
+		log.debug "Retrieving static backend asset '#{uri}'"
 		Request
-			.get("#{CONFIG.backendURI}/#{CONFIG.backendStaticPath}/#{req.params.id}")
+			.get(uri)
 			.end (err, backendResp) ->
 				if backendResp.headers['content-type']
 					res.header 'content-type', backendResp.headers['content-type']


Rename getPane to getPaneView

Rename getPane to getPaneView

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -27,10 +27,10 @@
     @subscribe this, 'core:move-down', => @scrollDown()
     @subscribe @buffer, 'saved reloaded', =>
       @renderMarkdown()
-      pane = @getPane()
-      pane.showItem(this) if pane? and pane isnt atom.workspaceView.getActivePane()
+      paneView = @getPaneView()
+      paneView.showItem(this) if paneView? and paneView isnt atom.workspaceView.getActivePaneView()

-  getPane: ->
+  getPaneView: ->
     @parents('.pane').view()

   serialize: ->


Use .comment

Use .comment

The Doc object strips out the \u0091 characters from between the `#`
chars. Those things seem to be there in the first place to not
unintentionally end a block comment with comment contents. They need
to be stripped when we get the comments back out, though.

--- src/metadata.coffee
+++ src/metadata.coffee
@@ -242,7 +242,7 @@
                   # find the matching method from the parsed files
                   func = _.find @methods, (method) ->
                     method.entity.getClassName() == className and method.name == value.name
-                  value.doc = if func? then func.doc.node.comment else null
+                  value.doc = if func? then func.doc.comment else null
           true

     # find the matching class from the parsed files
@@ -253,7 +253,7 @@
     bindingType: @bindingTypes[className] unless _.isUndefined @bindingTypes[className]
     classProperties: classProperties
     prototypeProperties: prototypeProperties
-    doc: if clazz? then clazz.doc.node.comment else null
+    doc: if clazz? then clazz.doc.comment else null
     range: [ [exp.locationData.first_line, exp.locationData.first_column], [exp.locationData.last_line, exp.locationData.last_column ] ]

   evalCode: (exp) ->


add some more logging

add some more logging

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -234,10 +234,14 @@
                     cb()
                     return
                 f = (host, cb) =>
-                    require('dns').lookup host.split(':')[0], {all:true}, (err, ips) =>
+                    hostname = host.split(':')[0]
+                    winston.debug("Looking up ip addresses of #{hostname}")
+                    require('dns').lookup hostname, {all:true}, (err, ips) =>
                         if err
+                            winston.debug("Got #{hostname} --> err=#{err}")
                             cb(err)
                         else
+                            winston.debug("Got #{hostname} --> #{JSON.stringify(ips)}")
                             # In kubernetes the stateful set service just has
                             # lots of ip address.  We connect to *all* of them,
                             # and spread queries across them equally.


add way to tell if queries are taking too long

--- src/smc-hub/postgres-base.coffee
+++ src/smc-hub/postgres-base.coffee
@@ -4,6 +4,8 @@
 COPYRIGHT : (c) 2017 SageMath, Inc.
 LICENSE   : AGPLv3
 ###
+
+QUERY_ALERT_THRESH_MS=1500

 EventEmitter = require('events')

@@ -233,8 +235,7 @@
             @__do_query(opts)

     __do_query: (opts) =>
-        dbg = @_dbg("_query('#{opts.query}') (concurrent=#{@_concurrent_queries})")
-        dbg()
+        dbg = @_dbg("_query('#{opts.query}')")
         if not @_client?
             # TODO: should also check that client is connected.
             opts.cb?("client not yet initialized")
@@ -442,16 +443,20 @@

         @_concurrent_queries += 1
         try
+            start = new Date()
             @_client.query opts.query, opts.params, (err, result) =>
+                query_time_ms = new Date() - start
                 @_concurrent_queries -= 1
                 if err
-                    dbg("done (concurrent=#{@_concurrent_queries}) -- error: #{err}")
+                    dbg("done (concurrent=#{@_concurrent_queries}), (query_time_ms=#{query_time_ms}) -- error: #{err}")
                     err = 'postgresql ' + err
                 else
-                    dbg("done (concurrent=#{@_concurrent_queries}) -- success")
+                    dbg("done (concurrent=#{@_concurrent_queries}) (query_time_ms=#{query_time_ms}) -- success")
                 if opts.cache and @_query_cache?
                     @_query_cache.set(full_query_string, [err, result])
                 opts.cb?(err, result)
+                if query_time_ms >= QUERY_ALERT_THRESH_MS
+                    dbg("QUERY_ALERT_THRESH: query_time_ms=#{query_time_ms}\nQUERY_ALERT_THRESH: query='#{opts.query}'\nQUERY_ALERT_THRESH: params='#{misc.to_json(opts.params)}'")
         catch e
             # this should never ever happen
             dbg("EXCEPTION in @_client.query: #{e}")


cocalc: more landing page tuning

cocalc: more landing page tuning

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -26,7 +26,6 @@
 {ErrorDisplay, Icon, Loading, ImmutablePureRenderMixin, Footer, UNIT, COLORS} = require('./r_misc')
 {HelpEmailLink, SiteName, SiteDescription, TermsOfService, AccountCreationEmailInstructions} = require('./customize')
 {HelpPageUsageSection} = require('./r_help')
-#DESC_FONT = "'Roboto Mono','monospace'"
 DESC_FONT = 'sans-serif'

 misc = require('smc-util/misc')
@@ -39,7 +38,6 @@
     require('sagepreview/03-latex.png'),
     require('sagepreview/05-sky_is_the_limit.png'),
 ]
-# 'static/sagepreview/04-files.png'

 $.get window.app_base_url + "/registration", (obj, status) ->
     if status == 'success'
@@ -138,7 +136,11 @@
             </FormGroup>

     render: ->
-        <Well style={marginTop:'10px'}>
+        well_style =
+            marginTop      : '10px'
+            borderWidth    : 5
+            'border-color' : COLORS.LANDING.TOP_BAR_BG
+        <Well style=well_style>
             {@display_token_input()}
             {@display_error("token")}
             {@display_error("account_creation_failed")}   {# a generic error}
@@ -605,7 +607,7 @@
                         forgot_password_success={@props.forgot_password_success}
                     /> if @props.show_forgot_password}
                 <Row style={fontSize: UNIT,\
-                            backgroundColor: COLORS.BLUE_BG,\
+                            backgroundColor: COLORS.LANDING.TOP_BAR_BG,\
                             padding: 5, margin: 0, borderRadius:4}
                      className="visible-xs">
                         <SignIn


Fix free/internet-less project warning

Fix free/internet-less project warning

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -137,6 +137,7 @@

     reduxProps :
         projects :
+            project_map : rtypes.immutable.Map
             get_total_project_quotas : rtypes.func
         "#{name}" :
             free_warning_extra_shown : rtypes.bool


Fix serialization issue

Fix serialization issue

--- app.coffee
+++ app.coffee
@@ -25,7 +25,7 @@
     done null, user || false

 passport.serializeUser (user, done) ->
-  done null, user.username
+  done null, user.data.username

 passport.deserializeUser (user, done) ->
   users.getUser user, (user) ->


glob not used in app.coffee

glob not used in app.coffee

--- app.coffee
+++ app.coffee
@@ -1,7 +1,6 @@
 _ = require 'underscore'
 conf = require './conf'
 express = require 'express'
-glob = require 'glob'
 path = require 'path'
 passport = require 'passport'
 spawn = require('child_process').spawn


Correction du calcul du centre

Correction du calcul du centre

--- js/game/core/Entity.coffee
+++ js/game/core/Entity.coffee
@@ -20,8 +20,8 @@
             @width = @height = 0

             # Centre absolu de l'objet, en fonction de sa taille (read only)
-            @get 'centerX', => @x + Math.cos(@angle) * 10#@width / 2
-            @get 'centerY', => @y + Math.sin(@angle + Math.PI*0) * 10#@height / 2
+            @get 'centerX', => @x + @width / 2
+            @get 'centerY', => @y + @height / 2
             @set 'centerX', =>
             @set 'centerY', =>


Do not expect object, expect buffer

Do not expect object, expect buffer

--- spec/GetMetadata.coffee
+++ spec/GetMetadata.coffee
@@ -70,7 +70,6 @@
       outs.orientation.on 'data', (data) ->
         chai.expect(data).to.be.equal expected.orientation
       outs.exif.on 'data', (data) ->
-        chai.expect(data).to.be.an 'object'
         chai.expect(data).to.be.instanceof Buffer
       outs.icc.on 'data', (data) ->
         chai.expect(data).to.be.equal expected.icc


Un-eff the prototype test

Un-eff the prototype test

--- spec/parser_spec.coffee
+++ spec/parser_spec.coffee
@@ -142,7 +142,7 @@
     it 'understands preprocessor flagging for visibility', ->
       constructDelta("spec/templates/methods/preprocessor_flagging.coffee")

-    fit 'understands prototypical methods', ->
+    it 'understands prototypical methods', ->
       constructDelta("spec/templates/methods/prototypical_methods.coffee")

     it 'understands return values', ->


Add timeout time

Add timeout time

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -53,7 +53,7 @@
   _.isEqual(a, b)

 if process.env.CI
-  jasmine.getEnv().defaultTimeoutInterval = 60000
+  jasmine.getEnv().defaultTimeoutInterval = 120000
 else
   jasmine.getEnv().defaultTimeoutInterval = 5000


Fixes for the spec helper

Fixes for the spec helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -1,4 +1,10 @@
 _ = require 'underscore'
+
+unorderedEqual = (one, two) ->
+  return false if one.length != two.length
+  for val in one
+    return false unless _.contains(two, val)
+  true

 beforeEach ->
   @addMatchers
@@ -36,7 +42,7 @@

             when "Array"
               if actual.length != expected.length
-                addFailure(path, "has length #{actual.length}", "has length #{expected.length}")
+                addFailure(path, "has length #{actual.length} #{JSON.stringify(actual)}", "has length #{expected.length} #{JSON.stringify(expected)}")
               else
                 for value, i in actual
                   compare(appendToPath(path, i), actual[i], expected[i])
@@ -44,8 +50,8 @@
             when "Object"
               actualKeys = _.keys(actual)
               expectedKeys = _.keys(expected)
-              unless _.isEqual(actualKeys, expectedKeys)
-                addFailure(path, "has keys #{JSON.stringify(actualKeys)}", "has keys #{JSON.stringify(expectedKeys)}")
+              unless unorderedEqual(actualKeys, expectedKeys)
+                addFailure(path, "has keys #{JSON.stringify(actualKeys.sort())}", "has keys #{JSON.stringify(expectedKeys.sort())}")
               else
                 for key, value of actual
                   continue unless actual.hasOwnProperty(key)


added test for window.navigator.javaEnabled() [#35]

--- spec/window-spec.coffee
+++ spec/window-spec.coffee
@@ -108,4 +108,9 @@
         assert.match browser.document.title, /colorDepth=24/
         assert.match browser.document.title, /pixelDepth=24/

+  ".navigator":
+    zombie.wants "http://localhost:3003/static"
+      "should exist": (browser)-> assert.isNotNull browser.window.navigator
+      ".javaEnabled should be false": (browser)-> assert.equal browser.window.navigator.javaEnabled(), false
+
 ).export(module)


chore(valign): Fix deprecated selector.

chore(valign): Fix deprecated selector.

--- keymaps/valign.cson
+++ keymaps/valign.cson
@@ -7,5 +7,5 @@

 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
-'.editor':
+'.atom-text-editor':
   'ctrl-\\': 'valign:align'


comment

comment

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -648,13 +648,15 @@
   # 'p': 'vim-mode-plus:swap-with-register'
   # 'P': 'vim-mode-plus:swap-with-register'

-# visual-blockwise
+# visual-characterwise
 # -------------------------
 'atom-text-editor.vim-mode-plus.visual-mode.characterwise':
   'D': 'vim-mode-plus:delete-line'
   'C': 'vim-mode-plus:change-line'
   'Y': 'vim-mode-plus:yank-line'

+# visual-blockwise
+# -------------------------
 'atom-text-editor.vim-mode-plus.visual-mode.blockwise':
   'o': 'vim-mode-plus:blockwise-other-end'
   'O': 'vim-mode-plus:reverse-selections'


user table

user table

--- src/shared/component/users.coffee
+++ src/shared/component/users.coffee
@@ -24,12 +24,27 @@
       users = that.state.users
       reactKup (k) ->
         k.div {className: 'container ComponentUsers'}, ->
-          k.h1 "Users"
           unless users?
-            k.p "loading"
+            k.p "loading users"
           else if users.length is 0
             k.p "no users"
           else
-            k.ul ->
-              that.state.users.forEach (user) ->
-                k.li -> k.a {href: urlUser.stringify(id: user.id)}, user.name
+            k.table {className: 'table table-striped table-hover'}, ->
+              k.caption "Users"
+              k.thead ->
+                k.tr ->
+                  k.th "#"
+                  k.th "name"
+                  k.th "email"
+                  k.th "created at"
+              k.tbody ->
+                that.state.users.forEach (user) ->
+                  url = urlUser.stringify(id: user.id)
+                  k.tr {
+                    # TODO we might navigate to the user here
+                    # onClick:
+                  }, ->
+                    k.td -> k.a {href: url}, user.id
+                    k.td -> k.a {href: url}, user.name
+                    k.td -> k.a {href: url}, user.email
+                    k.td -> k.a {href: url}, user.created_at


Build: fixed sprite paths

Build: fixed sprite paths

--- client/landing/gulpfile.coffee
+++ client/landing/gulpfile.coffee
@@ -23,7 +23,7 @@
 INDEX_PATH  = ['./app/index.html']
 SERVER_FILE = './server/server.coffee'
 SERVER_PATH = ['./server/**/*.coffee']
-BUILD_PATH  = argv.outputDir ? 'static'
+BUILD_PATH  = argv.outputDir ? './static/a/out'

 log = (color, message) -> gutil.log gutil.colors[color] message

@@ -81,12 +81,19 @@
     .pipe spritesmith
       imgName   : 'sprite@1x.png'
       cssName   : 'sprite@1x.styl'
-      imgPath   : '/images/sprite@1x.png'
+      imgPath   : "/a/out/images/sprite@1x.png"
       algorithm : 'binary-tree'
       padding   : 5
       cssFormat : 'stylus'
       cssVarMap : nameStylusVars.bind spriteStream, ''
-    .pipe gulp.dest 'static/images/'
+
+  spriteStream.css
+    .pipe gulp.dest './app/styl/'
+
+  spriteStream.img
+    .pipe gulp.dest "#{BUILD_PATH}/images/"
+
+  return spriteStream

 gulp.task 'sprites@2x', ['sprites@1x'], ->
@@ -95,15 +102,22 @@
     .pipe spritesmith
       imgName   : 'sprite@2x.png'
       cssName   : 'sprite@2x.styl'
-      imgPath   : '/images/sprite@2x.png'
+      imgPath   : "/a/out/images/sprite@2x.png"
       algorithm : 'binary-tree'
       padding   : 10
       cssFormat : 'stylus'
       cssVarMap : nameStylusVars.bind spriteStream, '__2x'
-    .pipe gulp.dest 'static/images/'
+
+  spriteStream.css
+    .pipe gulp.dest './app/styl/'
+
+  spriteStream.img
+    .pipe gulp.dest "#{BUILD_PATH}/images/"
+
+  return spriteStream

-gulp.task 'watch-styles', -> watchLogger 'cyan', gulp.watch STYLES_PATH, ['styles']
+gulp.task 'watch-styles', -> watchLogger 'cyan', gulp.watch STYLES_PATH, ['styles-only']

 gulp.task 'coffee', ->


fixed the missing include

fixed the missing include

--- client/landing/includes.coffee
+++ client/landing/includes.coffee
@@ -714,6 +714,7 @@
   "app/Applications/Account.kdapplication/account/accSettingsDevRepos.coffee",
   "app/Applications/Account.kdapplication/account/accSettingsDevSshKeys.coffee",
   "app/Applications/Account.kdapplication/account/accSettingsDevKodingKeys.coffee",
+  "app/Applications/Account.kdapplication/account/accSettingsPersReferralSystem.coffee",

   "app/Applications/Account.kdapplication/account/accSettingsSubscriptions.coffee",
   "app/Applications/Account.kdapplication/account/accSettingsPaymentHistory.coffee",


formBindings and subviews

formBindings and subviews

--- client/lanes/views/Base.coffee
+++ client/lanes/views/Base.coffee
@@ -2,7 +2,7 @@
 delegateEventSplitter = /^(\S+)\s*(.*)$/
 class ViewBase

-    extendedProperties: ['ui']
+    extendedProperties: ['ui','subviews']

     # Custom datatypes for views
     dataTypes:
@@ -81,7 +81,7 @@
         if !this.pubSub? # if it's unset, not true/false; default to parent or true
             this.pubSub = if this.parent?.pubSub? then this.parent.pubSub else true

-        if @formBindings || @parent?.formBindings
+        if @formBindings || @useFormBindings || @parent?.formBindings
             @formBindings = new Lanes.Views.FormBindings(this, @formBindings)

         if @keyBindings
@@ -217,7 +217,7 @@

         # bind each of the selectors
         for key, selector of bindings
-            this.ui[key] = this.$(selector)
+            this.ui[key] = this.$el.filter(selector).add(this.$el.find(selector))

     # Sets callbacks, where `this.domEvents` is a hash of
@@ -310,16 +310,20 @@
         self = this;
         opts = {
             selector: subview.container || "[data-hook='#{subview.hook||name}']"
-            waitFor: subview.waitFor || '',
+            waitFor: subview.waitFor || subview.model || subview.collection || ''
             prepareView: subview.prepareView || (el,options)->
                 klass = self._subviewClass(subview)
                 if subview.collection
                     new Lanes.Vendor.Ampersand.CollectionView({
                         el: el, parent: self, viewOptions: options, view: klass,
-                        collection: _.getPath(this,subview.collection)
+                        collection: _.getPath(this, subview.collection)
                     })
                 else
-                    new klass(_.extend(options,{ el: el, parent: self }))
+                    options = _.extend(options,{ el: el, parent: self })
+                    for attr in ['model','data']
+                        if subview[attr]
+                            options[attr] = _.getPath(this, subview[attr])
+                    new klass(options)

         }
         action = ->
@@ -387,10 +391,6 @@

     initializeKeyBindings: Lanes.emptyFn

-    # initializeFormBindings: ->
-    #     @_form_bindings = {}
-    #     for data, selector of @form_bindings
-    #         @_form_bindings[data] =

     setFieldsFromBindings: ->
         binding.setAllFields() for name, binding of @_form_bindings


Order has a different id now. So let's get the cart id.

--- src/coffee/orderexport.coffee
+++ src/coffee/orderexport.coffee
@@ -101,7 +101,11 @@
   _processXmlOrder: (order) ->
     # TODO: what if customObject is not found?
     # TODO: why not doing it also for CSV export?
-    @client.customObjects.byId("#{CONTAINER_PAYMENT}/#{order.id}").fetch()
+    customObjectId = if order.cart
+      order.cart.id
+    else
+      order.id
+    @client.customObjects.byId("#{CONTAINER_PAYMENT}/#{customObjectId}").fetch()
     .then (result) =>
       paymentInfo = result.body
       if order.customerId?


fixes lookup map for inline case (forgot to detect the column indexes). Still weird: Why is _init() called twice?

--- src/coffee/transformer.coffee
+++ src/coffee/transformer.coffee
@@ -238,28 +238,30 @@
         @_headers = values.headers
         @_values = values.data

-        @_keyIdx = if _.isString @_keyCol then @_headers.indexOf(@_keyCol) else @_keyCol
-        @_valueIdx = if _.isString @_valueCol then @_headers.indexOf(@_valueCol) else @_valueCol
-
-        if @_keyIdx < 0 or @_valueIdx < 0
-          throw new Error("Something is wrong in lookup config: key '#{@_keyCol}' or value '#{@_valueCol}' column not found by name!.")
-
-        @_condenseLookup()
+        @_setColumnIdx()
+        @_buildLookupMap()
+        # discard raw file content to save the memory (for large files):
+        @_values = @_headers = values = null
         this
     else
-      @_condenseLookup()
+      @_setColumnIdx()
+      @_buildLookupMap()
       Q(this)

-  _condenseLookup: ->
-    # create a "map" with first occurrence of key (first to stay compatible with the previous _.find based implementation
+  _setColumnIdx: ->
+    @_keyIdx = if _.isString @_keyCol then @_headers.indexOf(@_keyCol) else @_keyCol
+    @_valueIdx = if _.isString @_valueCol then @_headers.indexOf(@_valueCol) else @_valueCol
+
+    if @_keyIdx < 0 or @_valueIdx < 0
+      throw new Error("Something is wrong in lookup config: key '#{@_keyCol}' or value '#{@_valueCol}' column not found by name!.")
+
+  _buildLookupMap: ->
+    # create a property map with first occurrence of key (first to stay compatible with the previous _.find based implementation)
     @_lookupMap = {}
     @_values.forEach(
       (row) ->  @_lookupMap[row[@_keyIdx]] = row[@_valueIdx] unless @_lookupMap[row[@_keyIdx]]?
       this
     )
-    # discard raw data to save memory
-    @_headers = null
-    @_values = null

   _parseCsv: (csvText) ->
     d = Q.defer()


Show first 7 characters of hash

Show first 7 characters of hash

Since the actual value of the hash isn't useful after some amount of bits, I
looked up how many characters GitHub uses in their UI. It's 7. If that's good
enough for them I think it's good enough for us too.

Test Plan:
 * Edited a line
 * Reloaded window,
 * Saw only 7 characters in hash
 * Saw only 7 hyphens in my edited line

--- lib/views/blame-line-view.coffee
+++ lib/views/blame-line-view.coffee
@@ -4,17 +4,21 @@
 module.exports =
 class BlameLineView extends View

+  @HASH_LENGTH: 7  # github uses this length
+
   @content: (params) ->
     if params.noCommit
       @div class: "blame-line no-commit text-subtle", =>
-        @span class: 'hash', '--------'
+        @span class: 'hash', '-'.repeat(@HASH_LENGTH)
         @span class: 'date', params.date
         @span class: 'committer', 'Nobody'
     else
       @div class: 'blame-line ' + params.backgroundClass, =>
-        @a 'data-hash': params.hash, class: 'hash', click: 'hashClicked', params.hash.substring(0,8)
+        @a 'data-hash': params.hash, class: 'hash', click: 'hashClicked',
+           params.hash.substring(0, @HASH_LENGTH)
         @span class: 'date', params.date
-        @span class: 'committer text-highlight', params.committer.split(' ').slice(-1)[0]
+        @span class: 'committer text-highlight',
+              params.committer.split(' ').slice(-1)[0]

   hashClicked: (event, element) ->


Use absoluteUrl instead of location.origin so ROOT_URL is taken into account

--- packages/rocketchat-lib/client/lib/settings.coffee
+++ packages/rocketchat-lib/client/lib/settings.coffee
@@ -41,12 +41,11 @@
 			if RocketChat.authz.hasRole(Meteor.userId(), 'admin') is false or Meteor.settings.public.sandstorm
 				return c.stop()

-			siteUrl = siteUrl.replace /\/$/, ''
-			if siteUrl isnt location.origin
+			if siteUrl isnt Meteor.absoluteUrl()
 				swal
 					type: 'warning'
 					title: t('Warning')
-					text: t("The_setting_s_is_configured_to_s_and_you_are_accessing_from_s", t('Site_Url'), siteUrl, location.origin) + '

' + t("Do_you_want_to_change_to_s_question", location.origin)
+					text: t("The_setting_s_is_configured_to_s_and_you_are_accessing_from_s", t('Site_Url'), siteUrl, Meteor.absoluteUrl()) + '

' + t("Do_you_want_to_change_to_s_question", Meteor.absoluteUrl())
 					showCancelButton: true
 					confirmButtonText: t('Yes')
 					cancelButtonText: t('Cancel')


Avoid capturing this when handling dragging

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -563,7 +563,7 @@
     @sampleBackgroundColors()
     @remeasureCharacterWidths()

-  handleDragUntilMouseUp: (dragHandler) =>
+  handleDragUntilMouseUp: (dragHandler) ->
     dragging = false
     lastMousePosition = {}
     animationLoop = =>


hotfix

hotfix

--- kit/cake_builder.coffee
+++ kit/cake_builder.coffee
@@ -72,10 +72,12 @@

             Promise.resolve(expand(
                 join('**', '*.coffee'),
+                join('!lib', '**', '*.coffee'),
                 join('!node_modules', '**', '*.coffee'),
                 join('!bower_components', '**', '*.coffee')
             )).then (file_list) ->
-                Promise.map file_list, lint
+                console.log file_list
+                #Promise.map file_list, lint
     ]
 ]


ADD: item handle selector option

ADD: item handle selector option

--- frontend/javascripts/sortable.coffee
+++ frontend/javascripts/sortable.coffee
@@ -10,12 +10,19 @@

   @attribute 'itemSelector', default: '[draggable="true"]'

+  @attribute 'itemHandleSelector'
+
   @attribute 'axis', default: 'y'

   _connected: ->
-    @on 'dragstart', @itemSelector, (e) =>
+    handleSelector = if @itemHandleSelector
+      "#{@itemSelector} #{@itemHandleSelector}"
+    else
+      @itemSelector
+
+    @on 'dragstart', handleSelector, (e) =>
       e.stopPropagation()
-      $item = $ e.currentTarget
+      $item = $(e.currentTarget).closest(@itemSelector)
       itemRect = $item.get(0).getBoundingClientRect()
       @_startDragging $item


improve cell handling

improve cell handling

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -19,6 +19,13 @@

   graphite.load_docs()

+  forwards = +1
+  backwards = -1
+
+  is_input = (cell) -> cell.type is 'input'
+  is_output = (cell) -> cell.type is 'output'
+  visible = (cell) -> cell.visible
+
   init_codemirror = ->
     CodeMirror.keyMap.lead = ed.key_map
     $.extend CodeMirror.commands, ed.commands
@@ -47,64 +54,98 @@
     notebook.$document.empty()
     notebook.cells.length = 0

+  cell_index = (cell) ->
+    cell.notebook.cells.indexOf cell
+
+  seek = (start_cell, direction, predicate) ->
+    notebook = start_cell.notebook
+    index = cell_index(start_cell) + direction
+    loop
+      cell = notebook.cells[index]
+      return unless cell?
+      return cell if predicate cell
+      index += direction
+
   input_cell_at_offset = (cell, offset) ->
-    index = cell.notebook.cells.indexOf cell
-    cell.notebook.cells[index + offset]
-
-  get_available_input_cell = (notebook) ->
-    last = notebook.cells[notebook.cells.length - 1]
-    if last?.is_clean()
-      return last
-    else
-      return null
+    seek cell, offset, is_input
+
+  get_input_cell_by_number = (notebook, number) ->
+    for cell in notebook
+      return cell if cell.input_number == number

   remove_cell = (cell) ->
-    index = cell.notebook.cells.indexOf cell
+    index = cell_index cell
     cell.$el.remove()
     cell.notebook.cells.splice index, 1

+  hide_cell = (cell) ->
+    cell.visible = false
+    cell.$el.hide()
+
+  insert_cell = (cell, position={}) ->
+    if position.before?
+      offset = 0
+      current_cell = position.before
+      current_cell.$el.before cell.$el
+    else if position.after?
+      offset = 1
+      current_cell = position.after
+      current_cell.$el.after cell.$el
+    else
+      cell.notebook.$document.append cell.$el
+      cell.notebook.cells.push cell
+      cell.rendered()
+      return
+
+    index = cell_index current_cell
+    current_cell.notebook.cells.splice index + offset, 0, cell
+
+    cell.rendered()
+
   add_context = (notebook, code='') ->
-    cell = get_available_input_cell notebook
-    if cell?
-      cell.editor.setValue code
-    else
-      cell = create_input_cell notebook, code
-      notebook.$document.append cell.$el
-      cell.rendered()
-      notebook.cells.push cell
+    cell = create_input_cell notebook
+    set_cell_value cell, code
+    insert_cell cell

     {editor} = cell
     editor.focus()
-    editor.setCursor(line: editor.lineCount() - 1)
+    cell
+
+  add_input_cell = (notebook, opts={}) ->
+    cell = create_input_cell notebook
+    set_cell_value cell, opts.code if opts.code?
+    insert_cell cell, opts
     cell

   run_in_available_context = (notebook, code) ->
     add_context(notebook, code).run()
-    add_context notebook

   # Add an input cell above the last input cell
   run_in_info_context = (current_cell, code) ->
-    cell = create_input_cell current_cell.notebook, code
-    current_cell.$el.before cell.$el
-    index = notebook.cells.indexOf cell
-    cell.rendered()
-    current_cell.notebook.cells.splice index, 0, cell
-    cell.run code
-
-  create_input_cell = (notebook, code) ->
+    cell = add_input_cell current_cell.notebook, code: code, before: current_cell
+    cell.run()
+
+  run_after = (current_cell, code) ->
+    cell = add_input_cell current_cell.notebook, code: code, after: current_cell
+    cell.run()
+
+  create_input_cell = (notebook) ->
     $el = $ '
'
     $code = $ '
'
     $el.append $code

     editor = CodeMirror $code.get(0),
-      value: code
+      value: ''
       mode: 'coffeescript'
       keyMap: 'lead'
       tabSize: 2
       viewportMargin: Infinity
       gutters: ['error']

-    context =
+    editor.setCursor(line: editor.lineCount() - 1)
+
+    cell =
+      type: 'input'
       notebook: notebook
       used: false
       editor: editor
@@ -113,13 +154,13 @@
       hide: -> $el.hide()
       is_clean: -> editor.getValue() is '' and not @.used
       run: ->
-        context.used = true
-        context.output_cell?.$el.remove()
-        context.output_cell = run context, editor.getValue()
-        context.input_number = notebook.input_number++
-        context.$el.attr 'data-cell-number', context.input_number
-
-    editor.lead_cell = context
+        cell.used = true
+        cell.output_cell?.$el.remove()
+        cell.output_cell = run cell, editor.getValue()
+        cell.input_number = notebook.input_number++
+        cell.$el.attr 'data-cell-number', cell.input_number
+
+    editor.lead_cell = cell

     error_marks = []

@@ -152,11 +193,10 @@
       clearTimeout compile_timeout
       compile_timeout = setTimeout (-> compile editor), 200

-    context
-
-  get_input_cell_by_number = (notebook, number) ->
-    for cell in notebook
-      return cell if cell.input_number == number
+    cell
+
+  set_cell_value = (cell, value) ->
+    cell.editor.setValue value

   bind_cli = (run_context) ->
     bind_op = (op) ->
@@ -223,7 +263,9 @@
       failure: ->
         scroll_to_result $top
         ignore
-      set_code: (code) -> add_context notebook, code
+      set_code: (code) ->
+        cell = add_input_cell notebook, code: code, after: input_cell
+        add_context notebook, code
       run: (code) -> run_in_available_context notebook, code
       clear_output: -> clear_notebook notebook
       previously_run: -> input_cell_at_offset(input_cell, -1).editor.getValue()
@@ -379,14 +421,15 @@
       fragment = uri.fragment()
       if fragment.length > 0 and fragment[0] == '/'
         id = fragment[1..]
-        run_in_available_context notebook, "gist #{JSON.stringify id}, run: true; quiet"
+        program = "gist #{JSON.stringify id}, run: true; quiet"
       else
         program = if location.search isnt ''
           atob decodeURIComponent location.search[1..]
         else
           'intro'

-        run_in_available_context notebook, program
+      run_in_available_context notebook, program
+      add_input_cell notebook

     run: (cell) ->
       cell.run()


Fix line width for borders and pings

Fix line width for borders and pings

--- site/public/js/planetaryjs.coffee
+++ site/public/js/planetaryjs.coffee
@@ -73,7 +73,7 @@
       context.stroke()

       context.strokeStyle = '#fff'
-      context.lineWidth = 2
+      context.lineWidth = 1
       currentTime = Date.now()
       for ping in pings
         difference = (currentTime - ping.time) # milliseconds


Update metrics.coffee

Update metrics.coffee

--- libraries/metrics/metrics.coffee
+++ libraries/metrics/metrics.coffee
@@ -21,7 +21,7 @@
 module.exports = Metrics =
 	initialize: (_name) ->
 		name = _name
-		collectDefaultMetrics({ timeout: 5000, prefix: name })
+		collectDefaultMetrics({ timeout: 5000, prefix: name + "_" })

 	registerDestructor: (func) ->
 		destructors.push func


Enable successive invocations

Enable successive invocations

--- library/membrane/describe.coffee
+++ library/membrane/describe.coffee
@@ -99,7 +99,10 @@
         assert _.isFunction(method), noMethod
         assert _.isFunction(descriptor), noDescriptor
         method.document ?= new Document
-        method.document.descriptor = descriptor
+        previous = method.document.descriptor
+        method.document.descriptor = ->
+            previous?.apply this, arguments
+            descriptor.apply this, arguments
         method.document.blankSlate = ->
             document = new Document
             document.descriptor = @descriptor


Minor change in the phrasing

Minor change in the phrasing

--- library/membrane/validate.coffee
+++ library/membrane/validate.coffee
@@ -87,7 +87,7 @@
             assert not error, "internval valdation error"
             errors = _.any _.values(results), _.isObject
             hasRender = _.isFunction @renderValidation
-            assert hasRender, "no renderValidation method"
+            assert hasRender, "no method to render validation"
             params = [results, request, response, continuation]
             return @renderValidation params... if errors
             return continuation.bind(this)()


one less fs call

one less fs call

--- lib/connection/process.coffee
+++ lib/connection/process.coffee
@@ -58,27 +58,27 @@
   checkPath: (p) ->
     new Promise (resolve, reject) =>
       # check whether path exists
-      fs.exists p, (exists) =>
-        if exists
-          # check whether the path actually is a file
-          fs.stat p, (err, stats) =>
-            if stats.isFile()
-              resolve()
-            else
-              # if it isn't, look for a file called `julia(.exe)`
-              fs.readdir p, (err, files) =>
-                if files.indexOf(@executable()) > 0
-                  newpath = path.join p, @executable()
-                  # check whether that file is no directory
-                  fs.stat newpath, (err, stats) =>
-                    if stats.isFile()
-                      # change the `Julia Path` setting to that new path and carry on
-                      atom.config.set 'julia-client.juliaPath', newpath
-                      resolve()
-                    else
-                      reject()
-                else
-                  reject()
+      fs.stat p, (err, stats) =>
+        console.log err
+        if not err
+          if stats.isFile()
+            resolve()
+          else
+            # if it isn't, look for a file called `julia(.exe)`
+            fs.readdir p, (err, files) =>
+              if err then reject()
+              if files.indexOf(@executable()) > 0
+                newpath = path.join p, @executable()
+                # check whether that file is no directory
+                fs.stat newpath, (err, stats) =>
+                  if stats.isFile()
+                    # change the `Julia Path` setting to that new path and carry on
+                    atom.config.set 'julia-client.juliaPath', newpath
+                    resolve()
+                  else
+                    reject()
+              else
+                reject()
         # fallback to calling `which` or `where` on the path
         else
           which = if process.platform is 'win32' then 'where' else 'which'


Fix Status and Examples

Fix Status and Examples

--- src/nodes/doc.coffee
+++ src/nodes/doc.coffee
@@ -290,7 +290,7 @@
     info_block     = @parse_description(sections.shift())

     text     = info_block.description
-    status   = info_block.status
+    @status   = info_block.status

     if _.first(sections) && /^\w+\s+\-/.test(_.first(sections))
       @params or= []
@@ -300,7 +300,9 @@

     while current
       if /^Examples/.test(current)
-        @parse_examples(current, sections)
+        @examples or= []
+
+        @examples = @parse_examples(current, sections)
       else if /^Returns/.test(current)
         @returnValue or= []

@@ -343,12 +345,11 @@
     section = _.str.strip(section.replace(/Examples/, ''))

     examples.push(section) unless _.isEmpty(section)
-
     while _.first(sections) && !/^\S/.test(_.first(sections))
-      lines = sections.shift.split("\n")
+      lines = sections.shift().split("\n")
       examples.push(@deindent(lines).join("\n"))

-    @examples = examples
+    examples

   # Parse returns section.
   #
@@ -400,7 +401,7 @@
     # remove indention
     spaces = _.map lines, (line) ->
       return line if _.isEmpty(_.str.strip(line))
-      md = /^(\s*)/.match(line)
+      md = line.match(/^(\s*)/)
       if md then md[1].length else null

     spaces = _.compact(spaces)
@@ -435,6 +436,7 @@
         copyright: @copyright
         comment: @comment
         summary: @summary
+        status: @status
         params: @params
         options: @paramsOptions
         see: @see


Try/catch in xhr.send

Try/catch in xhr.send

--- src/quo.ajax.coffee
+++ src/quo.ajax.coffee
@@ -1,5 +1,5 @@
 ###
-  QuoJS 2.1.3
+  QuoJS 2.2.0
   (c) 2011, 2012 Javi Jiménez Villar (@soyjavi)
   http://quojs.tapquo.com
 ###
@@ -53,16 +53,11 @@
                 _xhrTimeout xhr, settings
             , settings.timeout)

-        xhr.send settings.data
-
-        # if settings.data
-        #     parameters = $$.serializeParameters(settings.data)
-        #     settings.data = parameters.substr(1, parameters.length)
-        # try
-        #     xhr.send settings.data
-        # catch error
-        #     xhr = error
-        #     _xhrError "Resource not found", xhr, settings
+        try
+            xhr.send settings.data
+        catch error
+            xhr = error
+            _xhrError "Resource not found", xhr, settings

         (if (settings.async) then xhr else _parseResponse(xhr, settings))


save belongsTo relationships when necessary

--- src/relation.coffee
+++ src/relation.coffee
@@ -65,8 +65,11 @@
     @record[@fkey] and @model.exists(@record[@fkey])

   update: (value) ->
+    unless value instanceof @model
+      value = new @model(value)
+    value.save() if value.isNew()
     @record[@fkey] = value and value.id
-
+
 class Singleton extends Spine.Module
   constructor: (options = {}) ->
     for key, value of options
@@ -118,7 +121,7 @@
         name: name, model: model,
         record: record, fkey: fkey
       )
-
+
     @::[name] = (value) ->
       association(@).update(value) if value?
       association(@).exists()


Fix a bug with array results

Fix a bug with array results

--- src/response.coffee
+++ src/response.coffee
@@ -78,7 +78,7 @@
     runAdapterSend = (_, done) =>
       result = context.strings
       result.push(callback) if callback?
-      @robot.adapter[methodName](@envelope, result)
+      @robot.adapter[methodName](@envelope, result...)
       done()
     @robot.middleware.response.execute context,
                                        runAdapterSend,


toString

toString

--- src/template.coffee
+++ src/template.coffee
@@ -100,6 +100,9 @@
             else
                 @end(template)

+    toString: ->
+        "[object Template]"
+
     register: =>
         @xml.register arguments...


fix hugo generator bin name for win 32

fix hugo generator bin name for win 32

--- lib/generators/hugo.coffee
+++ lib/generators/hugo.coffee
@@ -17,6 +17,8 @@
       command = command + "-x64"
     else
       command = command + "-x32"
+    if process.platform is "win32"
+      command = command + ".exe"
     args = [
       '-s', projectPath ,
       "--theme=#{atom.config.get 'docapp.theme'}" ,


:bug: Fix error raised when right clicking an editor without a shadow root

Fixes #44

--- lib/color-buffer.coffee
+++ lib/color-buffer.coffee
@@ -283,7 +283,8 @@
     {clientX, clientY} = event

     editorElement = atom.views.getView(@editor)
-    {top, left} = editorElement.querySelector('::shadow .lines').getBoundingClientRect()
+    rootElement = editorElement.shadowRoot ? editorElement
+    {top, left} = rootElement.querySelector('.lines').getBoundingClientRect()
     top = clientY - top + @editor.getScrollTop()
     left = clientX - left + @editor.getScrollLeft()
     {top, left}


fixing deleted reaction

fixing deleted reaction

--- src/reactions.coffee
+++ src/reactions.coffee
@@ -63,6 +63,9 @@
     robot.respond /react (facepalm|fp|oh no)/i, (msg) ->
         msg.send msg.random facepalm

+    robot.respond /react (bye|later|see ya)/i, (msg) ->
+        msg.send msg.random bye
+
     robot.respond /react (smash|hulk smash)/i, (msg) ->
         msg.send msg.random smash


Directly change the cursor's position

Directly change the cursor's position

--- lib/editor-proxy.coffee
+++ lib/editor-proxy.coffee
@@ -98,7 +98,6 @@
     value = tabstopData.text.replace(/\t/g, @editorView.editor.getTabText())
     firstTabStop = tabstopData.tabstops[0]

-    debugger
     if firstTabStop
       firstTabStop.start += start
       firstTabStop.end += start
@@ -107,7 +106,6 @@
         start: value.length + start
         end: value.length + start

-    range = @editor.getSelection().getBufferRange()
     changeRange = [
       Point.fromObject(@editor.getBuffer().positionForCharacterIndex(start))
       Point.fromObject(@editor.getBuffer().positionForCharacterIndex(end))
@@ -115,12 +113,14 @@

     @editor.getBuffer().change(changeRange, value)

-    range.start = Point.fromObject(@editor.getBuffer().positionForCharacterIndex(firstTabStop.start))
-    range.end = Point.fromObject(@editor.getBuffer().positionForCharacterIndex(firstTabStop.end))
+    # handles where to place the cursor after the replacement
+    cursorRange = {}
+    cursorRange.start = Point.fromObject(@editor.getBuffer().positionForCharacterIndex(firstTabStop.start))
+    cursorRange.end = Point.fromObject(@editor.getBuffer().positionForCharacterIndex(firstTabStop.end))

     # passes the cursor along when tabbing normally
     unless value == @editor.getTabText()
-      @editor.getSelection().setBufferRange(range)
+      @editor.getSelection().setBufferRange(cursorRange)

   # Returns the editor content.
   getContent: ->


fixed marking invitations accepted. in case of another (e.g. multi use) invitation used, we'll look for an individual koding invitation and mark it as accepted (otherwise it will popup in pending group invitations). also fixed other variations such as using invitation code of a group, but that also koding gets marked approved etc.

--- workers/social/lib/social/models/user.coffee
+++ workers/social/lib/social/models/user.coffee
@@ -308,24 +308,29 @@
           data = JSON.parse data.substr(1, data.length - 2)
           if data.error then callback yes else callback null

-  @addToGroup = (account, slug, isInvited, callback)->
-    [callback, isInvited] = [isInvited, callback]  unless callback
-    isInvited ?= no
+  @addToGroup = (account, slug, email, invite, callback)->
     JGroup.one {slug}, (err, group)->
       if err or not group then callback err
-      else if not isInvited and group.privacy is 'private' and group.slug isnt 'koding'
+      else if invite?.group isnt slug and group.privacy is 'private' and group.slug isnt 'koding'
         group.requestAccessFor account, callback
-      else group.approveMember account, callback
-
-  @addToGroups = (account, invite, entryPoint, callback)->
-    @addToGroup account, 'koding', (err)=>
+      else
+        group.approveMember account, (err)->
+          return callback err  if err
+          cb = (invite)-> invite.markAccepted connection:delegate:account, callback
+          if invite?.group is slug then cb invite
+          else
+            selector = {group: slug, inviteeEmail: email, status: 'sent'}
+            (require './invitation').one selector, (err, invite)->
+              if invite and not err then cb invite
+              else callback err
+
+  @addToGroups = (account, invite, entryPoint, email, callback)->
+    @addToGroup account, 'koding', email, invite, (err)=>
       if err then callback err
-      else if invite.group or entryPoint
-        if invite.group is 'koding'
-          invite.markAccepted connection:delegate:account, callback
-        else
-          @addToGroup account, invite.group or entryPoint, invite.group?, callback
-      else callback null
+      else if (slug = invite.group or entryPoint) and slug isnt 'koding'
+        @addToGroup account, slug, email, invite, callback
+      else
+        callback null

   @createUser = ({ username, email, password, firstName, lastName }, callback)->
     slug =
@@ -430,7 +435,7 @@
                       return callback err  if err
                       @removeUnsubscription userData, (err)=>
                         return callback err  if err
-                        @addToGroups account, invite, entryPoint, (err) ->
+                        @addToGroups account, invite, entryPoint, email, (err) ->
                           if err then callback err
                           else if silence
                             JUser.grantInitialInvitations user.username


change model of pixelPositionForBufferPosition from TextEditor to TextEditorView

--- lib/input-method.coffee
+++ lib/input-method.coffee
@@ -209,9 +209,9 @@
             @resize range

     resize: (range) ->
-
-        topLeft   = @core.editor.pixelPositionForBufferPosition range.start
-        downRight = @core.editor.pixelPositionForBufferPosition range.end
+        editorElement = atom.views.getView @core.editor
+        topLeft   = editorElement.pixelPositionForBufferPosition range.start
+        downRight = editorElement.pixelPositionForBufferPosition range.end
         length = range.end.column - range.start.column

         @css


Fix trailing spaces

Fix trailing spaces

--- lib/ltserver-helper.coffee
+++ lib/ltserver-helper.coffee
@@ -60,7 +60,7 @@
             atom.notifications.addError("""The public languagetool server is
               not responding. The linter will be disabled.""",
               {detail: err.message})
-            reject(err)
+            reject(err)
           )
       )
     )


:art: Uppercase tabs indexes

:art: Uppercase tabs indexes

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -13,17 +13,17 @@
     @statusTiles = []

     @tabs = {} # Object has methods that we need to perform certain operations, map won't be a good fit
-    @tabs['line'] = new BottomTab()
-    @tabs['file'] = new BottomTab()
-    @tabs['project'] = new BottomTab()
+    @tabs['Line'] = new BottomTab()
+    @tabs['File'] = new BottomTab()
+    @tabs['Project'] = new BottomTab()

     @panel = document.createElement 'div'
     @bubble = null
     @bottomStatus = new BottomStatus()

-    @tabs['line'].initialize 'Line', => @changeTab('line')
-    @tabs['file'].initialize 'File', => @changeTab('file')
-    @tabs['project'].initialize 'Project', => @changeTab('project')
+    @tabs['Line'].initialize 'Line', => @changeTab('line')
+    @tabs['File'].initialize 'File', => @changeTab('file')
+    @tabs['Project'].initialize 'Project', => @changeTab('project')

     @bottomStatus.initialize()
     @bottomStatus.addEventListener 'click', ->
@@ -86,16 +86,15 @@
     @updateLineMessages()

     @renderPanel()
-    @tabs['file'].count = counts.file
-    @tabs['project'].count = counts.project
+    @tabs['File'].count = counts.file
+    @tabs['Project'].count = counts.project
     @bottomStatus.count = counts.project

   updateTabs: ->
-    visible = @getVisibleTabs()
     for key, tab of @tabs # for...of (key, value)
       tab.classList.remove('first')
       tab.classList.remove('last')
-      tab.visibility = visible.indexOf(key) isnt -1
+      tab.visibility = atom.config.get("linter.showErrorTab#{key}")

   # consumed in editor-linter, _renderPanel
   updateBubble: (point) ->
@@ -135,18 +134,18 @@
       @messages.forEach (message) =>
         if message.currentFile and message.range?.intersectsRow @currentLine
           @lineMessages.push message
-      @tabs['line'].count = @lineMessages.length
+      @tabs['Line'].count = @lineMessages.length

   # This method is called when we get the status-bar service
   attachBottom: (statusBar) ->
     @statusTiles.push statusBar.addLeftTile
-      item: @tabs['line'],
+      item: @tabs['Line'],
       priority: -1002
     @statusTiles.push statusBar.addLeftTile
-      item: @tabs['file'],
+      item: @tabs['File'],
       priority: -1001
     @statusTiles.push statusBar.addLeftTile
-      item: @tabs['project'],
+      item: @tabs['Project'],
       priority: -1000
     statusIconPosition = atom.config.get('linter.statusIconPosition')
     @statusTiles.push statusBar["add#{statusIconPosition}Tile"]
@@ -172,13 +171,6 @@
       @tabs.forEach (tab, key) -> tab.active = Tab is key
       @renderPanel()
     @setShowPanel @showPanel
-
-  getVisibleTabs: ->
-    toReturn = []
-    toReturn.push 'line' if atom.config.get('linter.showErrorTabLine')
-    toReturn.push 'file' if atom.config.get('linter.showErrorTabFile')
-    toReturn.push 'project' if atom.config.get('linter.showErrorTabProject')
-    toReturn

   removeBubble: ->
     return unless @bubble


:new: Add a default @lineMessages value

:new: Add a default @lineMessages value

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -10,6 +10,7 @@

     @messages = new Set
     @markers = []
+    @lineMessages = []
     @statusTiles = []

     @tabs = {} # Object has methods that we need to perform certain operations, map won't be a good fit
@@ -102,7 +103,7 @@
     first.classList.add('first') if first
     last.classList.add('last') if last

-  # consumed in editor-linter, _renderPanel
+  # consumed in editor-linter, renderPanel
   updateBubble: (point) ->
     @removeBubble()
     return unless @showBubble
@@ -203,7 +204,6 @@
       @panel.appendChild Element
     @updateBubble()

-
   removeMarkers: ->
     return unless @markers.length
     for marker in @markers


:new: Add LinterViews::notifyEditor

:new: Add LinterViews::notifyEditor

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -48,6 +48,12 @@
       return unless message.filePath and message.range
       unless editorLinter = @linter.getEditorLinterByPath(message.filePath)
         return
+      editorLinter.addMessage(message)
+
+  notifyEditor: (editorLinter) ->
+    editorPath = editorLinter.editor.getPath()
+    @messages.forEach (message) ->
+      return unless message.filePath and message.range and message.filePath is editorPath
       editorLinter.addMessage(message)

   renderLineMessages: (render = false) ->


Add screen capture support

Add screen capture support

--- lib/mocha-phantomjs.coffee
+++ lib/mocha-phantomjs.coffee
@@ -75,6 +75,11 @@
         @output.write data['Mocha.process.stdout.write']
       else if data?.hasOwnProperty 'mochaPhantomJS.run'
         @waitForRunMocha() if @injectJS()
+      else if data?.hasOwnProperty('screenshot')
+        name=data['screenshot']
+        if !name
+          name = "#{new Date().getTime()}"
+        @page.render("phantom-screenshot-#{name}.png")
       true

   onLoadFailed: ->


Add a background-message element

Add a background-message element

--- lib/styleguide-view.coffee
+++ lib/styleguide-view.coffee
@@ -596,6 +596,40 @@
             @li 'Another info line'
         '''

+      @exampleSection 'background-messages', 'Background Messages', ->
+        @p => @raw '''
+          Subtle background messages for panes. Use for cases when there are no results.
+        '''
+
+        @exampleCode '''
+          @ul class: 'background-message', =>
+            @li 'No Results'
+        '''
+
+        @p => @raw '''
+          Centered background messages will center horizontally and vertically.
+          Your container for this element must have position set with relative or
+          absolute.
+        '''
+
+        @exampleCode '''
+          @ul class: 'background-message centered', =>
+            @li 'No Results'
+        '''
+
+      @exampleSection 'error-messages', 'Messages', ->
+        @p => @raw '''
+          Use to convey info to the user when something happens. See find-and-replace
+          for an example.
+        '''
+
+        @h2 'Error messages'
+        @exampleCode '''
+          @ul class: 'error-messages block', =>
+            @li 'This is an error!'
+            @li 'And another'
+        '''
+
       @exampleSection 'progress', 'Loading/Progress', ->

         @h2 'Progress Bars'


Only prevent default when the drop is an Atom tab event

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -345,9 +345,9 @@
     @removePlaceholder()

   onDrop: (event) ->
+    return unless @isAtomTabEvent(event)
+
     event.preventDefault()
-
-    return unless @isAtomTabEvent(event)

     fromWindowId  = parseInt(event.dataTransfer.getData('from-window-id'))
     fromPaneId    = parseInt(event.dataTransfer.getData('from-pane-id'))


created a link for the bahn connector. thx to edmund from http://www.myway.de/e.lauterbach/auskunft/

--- src/connectors/bahn.de.coffee
+++ src/connectors/bahn.de.coffee
@@ -99,8 +99,8 @@
           d: dep_date.format 'DDMMYY'
           t: dep_date.format 'HHmm'
         }
-        link     = "http://reiseauskunft.bahn.de/bin/query2.exe/dn?#{params}"
-
+				#        link     = "http://reiseauskunft.bahn.de/bin/query2.exe/dn?#{params}"
+        link = "http://reiseauskunft.bahn.de/bin/query.exe/dn?REQ0JourneyDate=#{dep_date.format('DDMMYY')}&REQ0JourneyTime=#{dep_date.format('HH')}%3A#{dep_date.format('mm')}&REQ0HafasSearchForw=1&REQ1JourneyDate=&REQ1JourneyTime=&REQ1HafasSearchForw=1&REQ0JourneyStopsSA=1&REQ0JourneyStopsSG=#{orig.cityName()}&REQ0JourneyStopsZA=1&REQ0JourneyStopsZG=#{dest.cityName()}&REQ0JourneyStops1A=1&REQ0JourneyStops1G=&REQ0JourneyStopover1=&REQ0JourneyStops2A=1&REQ0JourneyStops2G=&REQ0JourneyStopover2=&existReverseVias=yes&REQ0JourneyRevia=on&REQ0JourneyProduct_prod_section_0_list=1%3A11111111110000&REQ0JourneyProduct_opt_section_0_list=0%3A0000&REQ0JourneyProduct_prod_section_1_list=1%3A11111111110000&REQ0JourneyProduct_opt_section_1_list=0%3A0000&REQ0JourneyProduct_prod_section_2_list=1%3A11111111110000&REQ0JourneyProduct_opt_section_2_list=0%3A0000&existProductAutoReturn=yes&REQ0JourneyDep__enable=Foot&REQ0JourneyDest__enable=Foot&REQ0HafasChangeTime=0&REQ0Tariff_Class=2&REQ0Tariff_TravellerType.1=E&REQ0Tariff_TravellerReductionClass.1=0&REQ0Tariff_TravellerAge.1=&REQ0Tariff_TravellerType.2=NULL&REQ0Tariff_TravellerReductionClass.2=0&REQ0Tariff_TravellerAge.2=&REQ0Tariff_TravellerType.3=NULL&REQ0Tariff_TravellerReductionClass.3=0&REQ0Tariff_TravellerAge.3=&REQ0Tariff_TravellerType.4=NULL&REQ0Tariff_TravellerReductionClass.4=0&REQ0Tariff_TravellerAge.4=&REQ0Tariff_TravellerType.5=NULL&REQ0Tariff_TravellerReductionClass.5=0&REQ0Tariff_TravellerAge.5=&REQ0HafasOptimize1=1%3A2&existOptimizePrice=yes&start=+los+geht%27s+"
         rides.push
           dep: dep_date.unix()
           arr: arr_date.unix()


Fix intendation bug

Fix intendation bug

--- client/app.coffee
+++ client/app.coffee
@@ -59,19 +59,18 @@
     zoom: zoom
   });
   rectangle_editor.addTo(map)
-  map.fitBounds(rectangle_editor.getMarkerBounds())
+  map.fitBounds(rectangle_editor.getMarkerBounds()) if rectangle

   syncTerrainWithSelector = ->
     terrain.show(
       crs.project(rectangle_editor.getMarkerBounds()[0].getNorthWest()),
       crs.project(rectangle_editor.getMarkerBounds()[0].getSouthEast())
     )
-
-  rectangle_editor.on 'change', (event) ->
-    syncTerrainWithSelector()
-    if localStorage
-      localStorage.setItem('rectangle', JSON.stringify(event.bounds))
-      localStorage.setItem('zoom', map.getZoom())
+    rectangle_editor.on 'change', (event) ->
+      syncTerrainWithSelector()
+      if localStorage
+        localStorage.setItem('rectangle', JSON.stringify(event.bounds))
+        localStorage.setItem('zoom', map.getZoom())

   canvas = $('canvas#terrain')[0]
   if false && canvas?


Fixed argument order in test callbacks

Fixed argument order in test callbacks

--- test/client.coffee
+++ test/client.coffee
@@ -186,7 +186,7 @@
 					test.expect 4
 					test.done()

-				@model.applyOp @name, {v:0, op:[{i:'hi', p:0}]}, (data, error) ->
+				@model.applyOp @name, {v:0, op:[{i:'hi', p:0}]}, (version, error) ->
 					test.ifError error

 		'get a nonexistent document passes null to the callback': (test) ->
@@ -231,7 +231,7 @@
 					test.deepEqual op, serverTransformed
 					onOpApplied()

-				@model.applyOp @name, {v:0, op:serverOp}, (data, error) ->
+				@model.applyOp @name, {v:0, op:serverOp}, (version, error) ->
 					test.ifError error

 		'doc fires both remoteop and change messages when remote ops are received': (test) ->
@@ -245,7 +245,7 @@
 					test.deepEqual op, sentOp
 					passPart()

-				@model.applyOp @name, {v:0, op:sentOp}, (data, error) ->
+				@model.applyOp @name, {v:0, op:sentOp}, (version, error) ->
 					test.ifError error

 		'doc only fires change ops from locally sent ops': (test) ->
@@ -267,7 +267,7 @@
 					throw new Error 'Should not have received op when the doc was unfollowed'

 				doc.close =>
-					@model.applyOp @name, {v:0, op:[{i:'asdf', p:0}]}, (data, error) =>
+					@model.applyOp @name, {v:0, op:[{i:'asdf', p:0}]}, (version, error) =>
 						test.done()

 		'created locally is set on new docs': (test) ->
@@ -441,7 +441,7 @@
 		'Submitting an op and closing straight after works': (test) ->
 			# This catches a real bug.
 			client.open @name, 'text', "http://localhost:#{@port}/sjs", (doc, error) =>
-				doc.insert 'hi', 0
+				doc.insert 0, 'hi'
 				doc.close ->
 					test.done()


Make intercom's 'Report' reliable

Make intercom's 'Report' reliable

--- js/inner.coffee
+++ js/inner.coffee
@@ -451,10 +451,14 @@
       eval code

   raiseIntercomDialog: (message=null) =>
-    $("#IntercomDefaultWidget").click()
+    jq = intercomJQuery
+    jq("#IntercomDefaultWidget").click()
+    unless jq('#IntercomNewMessageContainer').is(':visible')
+      jq('.new_message').click()
+    jq('#newMessageBody').focus()
     if message
-      $('#newMessageBody').text(message)
-      $('#newMessageBody').focus()
+      jq('#newMessageBody').text(message)
+

   logout: (cx) =>
     # TODO: add CSRF protection


Handle non-GitNotFound errors with a notification

--- lib/view/error-view.coffee
+++ lib/view/error-view.coffee
@@ -43,5 +43,6 @@
     if err instanceof GitNotFoundError
       atom.workspace.addTopPanel item: new GitNotFoundErrorView(err)
     else
-      console.error err
+      atom.notifications.addError err.message
+      console.error err.message, err.trace
     true


cleanup myself

cleanup myself

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -28,6 +28,7 @@
       disposableByEditor.set @editor, @editor.onDidDestroy =>
         checkPointByEditor.delete(@editor)
         stateByEditor.delete(@editor)
+        disposableByEditor.delete(@editor)

     isSequential = stateByEditor.get(@editor) is @getSelectedTexts()
     unless isSequential


add test user

add test user

--- lib/app.auth.coffee
+++ lib/app.auth.coffee
@@ -1,13 +1,16 @@
 getAuthenticationCookie = ->
   JSON.parse(decodeURIComponent($.macaroon("userSession")))

-exports.getAuthToken = ->
-  getAuthenticationCookie()['token']
+# exports.getAuthToken = ->
+#   getAuthenticationCookie()['token']

+TEST_USER = {user: {provider: 'test', id:'test'}, token: 'test'}
 exports.getCurrentUser = ->
   try
     {user, token} = getAuthenticationCookie()
     user.token = token
     user
   catch err
-    console?.exception?(err)
+    console?.log?("no user found, using test user")
+    console?.exception?(err) or console?.error?(err)
+    TEST_USER


Fix missing drop indicator on Electron >= 1.14

The security model of new Chromium versions forbids all event listeners,
except for `onDragStart` and `onDrop`, to inspect the data values that
have been previously set on the event's `dataTransfer` object.

To circumvent this problem we can exploit the fact that all we need to
do is checking whether a boolean has been set or not. To do so, we will
simply list all the items contained in the `dataTransfer` object and
read their `kind` property (that we use as a dictionary key), verifying
that at least one of them contains the key we are searching for.

--- lib/root-drag-and-drop.coffee
+++ lib/root-drag-and-drop.coffee
@@ -71,7 +71,7 @@
   onDragOver: (e) =>
     return unless @treeView.list.contains(e.target)

-    unless e.dataTransfer.getData('atom-tree-view-event') is 'true'
+    unless @isAtomTreeViewEvent(e)
       return

     e.preventDefault()
@@ -121,7 +121,7 @@
     {dataTransfer} = e

     # TODO: support dragging folders from the filesystem -- electron needs to add support first
-    return unless dataTransfer.getData('atom-tree-view-event') is 'true'
+    return unless @isAtomTreeViewEvent(e)

     fromWindowId = parseInt(dataTransfer.getData('from-window-id'))
     fromRootPath  = dataTransfer.getData('from-root-path')
@@ -171,7 +171,18 @@
     e.target.closest('.project-root-header')

   isDragging: (e) ->
-    Boolean e.dataTransfer.getData 'from-root-path'
+    for item in e.dataTransfer.items
+      if item.type is 'from-root-path'
+        return true
+
+    return false
+
+  isAtomTreeViewEvent: (e) ->
+    for item in e.dataTransfer.items
+      if item.type is 'atom-tree-view-event'
+        return true
+
+    return false

   getPlaceholder: ->
     unless @placeholderEl


use indexOf instead of search

use indexOf instead of search

string.prototype.search casts to RegExp but this fails if basepath
contains backslashes

so instead of escaping basepath here just use indexOf in that case

--- src/exceptionFormatter.coffee
+++ src/exceptionFormatter.coffee
@@ -109,7 +109,8 @@
                     filename = parsed.filename
                     endOfHeader = true
                     type = SOURCE
-                    if filename.indexOf('node_modules') is -1 and filename.search(basepath) isnt -1
+                    if filename.indexOf('node_modules') is -1 and
+                      filename[if isString basepath then 'indexOf' else 'search'](basepath) isnt -1
                         type = OUR_SOURCE

             if !endOfHeader and !type


Allowed ++

Allowed ++

--- coffeelint.coffee
+++ coffeelint.coffee
@@ -18,7 +18,7 @@
 	"no_trailing_semicolons":
 		"level": "error"
 	"no_plusplus":
-		"level": "error"
+		"level": "ignore"
 	"no_throwing_strings":
 		"level": "error"
 	"cyclomatic_complexity":


Refactor attributes

Refactor attributes

 Declare @dir, @commitFilePath, and @currentPane attributes at
 top of the export. @commitFilePath is function returing string
and using activating currentPane after closing message pane
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is ahead of 'origin/master' by 5 commits.
#   (use "git push" to publish your local commits)
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# 	modified:   lib/git-plus.coffee
#
#

--- lib/git-plus.coffee
+++ lib/git-plus.coffee
@@ -4,9 +4,13 @@
 StatusView = require './status-view'

 module.exports =
+  currentPane: null
+  dir: ''
+  commitFilePath: -> @dir + '/.git/COMMIT_EDITMSG'
+
   activate: (state) ->
     @dir = atom.project.getRepo().getWorkingDirectory()
-    @commitFilePath = @dir + '/.git/COMMIT_EDITMSG'
+    @commitFilePath()
     atom.workspaceView.command "git-plus:commit", => @gitStatus()

   deactivate: ->
@@ -16,6 +20,7 @@
     # gitPlusViewState: @gitPlusView.serialize()

   gitStatus: ->
+    @currentPane = atom.workspace.getActivePane()
     process = new BufferedProcess({
       command: 'git'
       args: ['status']
@@ -36,7 +41,7 @@
     text = text.replace(/\n/g, "\n# ")
     # in order to make sure each line doesn't start with a space, the preceding
     #   line should end with a backslash
-    new File(@commitFilePath)
+    new File(@commitFilePath())
       .write " \n\
        # Please enter the commit message for your changes. Lines starting\n\
        # with '#' will be ignored, and an empty message aborts the commit.\n\
@@ -44,21 +49,21 @@
     @showFile()

   showFile: ->
-    atom.workspace.open @commitFilePath, split: 'right', activatePane: true
-    PathWatcher.watch @commitFilePath, (event) =>
+    atom.workspace.open @commitFilePath(), split: 'right', activatePane: true
+    PathWatcher.watch @commitFilePath(), (event) =>
       if event is 'change'
         @commit()

   commit: ->
     process = new BufferedProcess({
       command: 'git'
-      args: ['commit', "--file=#{@commitFilePath}"]
+      args: ['commit', "--file=#{@commitFilePath()}"]
       options:
         cwd: @dir
       stdout: (data) =>
         PathWatcher.closeAllWatchers()
         atom.workspace.destroyActivePane()
-        atom.workspace.activateNextPane()
+        @currentPane.activate()
       stderror: (data) =>
         alert data.toString()
     })


AppleScript support. closes rgbkrk/atom-script#125 :apple: :page_facing_up:

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -2,6 +2,14 @@
 # As well as any special setup for arguments.

 module.exports =
+  AppleScript:
+    'Selection Based':
+      command: 'osascript'
+      args: (code)  -> ['-e', code]
+    'File Based':
+      command: 'osascript'
+      args: (filename) -> [filename]
+
   'Behat Feature':
     "File Based":
       command: "behat"


Put cursor on empty line after return in braces

When return is pressed with the cursor inside a matching pair, add an
empty line between the braces, move the cursor there and autoindent.
Implements the behavior discussed in #7, but breaks some other specs.

--- lib/bracket-matcher.coffee
+++ lib/bracket-matcher.coffee
@@ -53,6 +53,19 @@
         @bracketMarkers.push editor.markBufferRange(range)
         false

+    _.adviseBefore editor, 'insertNewline', =>
+      return if editor.hasMultipleCursors()
+      return unless editor.getSelection().isEmpty()
+
+      cursorBufferPosition = editor.getCursorBufferPosition()
+      previousCharacter = editor.getTextInBufferRange([cursorBufferPosition.add([0, -1]), cursorBufferPosition])
+      nextCharacter = editor.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.add([0,1])])
+      if @pairedCharacters[previousCharacter] is nextCharacter
+        editor.insertText "\n\n"
+        editor.moveCursorUp()
+        editor.autoIndentSelectedRows()
+        false
+
     _.adviseBefore editor, 'backspace', =>
       return if editor.hasMultipleCursors()
       return unless editor.getSelection().isEmpty()


Remove created ifs that are not required.

--- lib/hostonly.coffee
+++ lib/hostonly.coffee
@@ -1,3 +1,4 @@
+async = require 'async'
 parse = require './parse.coffee'
 proto = require './proto.coffee'
 command = require './command.coffee'
@@ -58,9 +59,14 @@
 	* @param {function(?err)} callback
 ###
 exports.ensure_if = (netname, ip, netmask, callback) ->
+	snapshoted_ifs = null
+
 	exports.list (err, ifaces) ->
 		return callback err if err

+		if not snapshoted_ifs
+			snapshoted_ifs = ifaces.map (iface) -> iface.Name
+
 		iface = ifaces.narrow (previous, current) ->
 			return previous if previous and previous.Name == netname
 			return current if current and current.Name == netname
@@ -73,7 +79,22 @@

 				exports.list callee
 		else
+			created_ifs = ifaces
+				.map (iface) -> iface.Name
+				.filter (iface) -> iface not in snapshoted_ifs and iface != netname
+
+			if created_ifs.length > 0
+				wrapper = (next) ->
+					return async.each created_ifs, ((iface, callback) -> exports.remove_if iface, callback), next
+			else
+				wrapper = (next) ->
+					return do next if next
+
 			if iface.IP != ip or iface.NetworkMask != netmask
-				exports.configure_if netname, ip, netmask, callback
+				wrapper (err) ->
+					return callback err if err
+					return exports.configure_if netname, ip, netmask, callback
 			else
-				return do callback if callback
+				wrapper (err) ->
+					return callback err if err
+					return do callback if callback


Added up the version and codename echo

Added up the version and codename echo

--- library/gearing/kernel.coffee
+++ library/gearing/kernel.coffee
@@ -56,6 +56,8 @@
         arts = [@title, "larry3d"]
         asciify arts..., (error, art) =>
             util.puts art.toString().blue
+            identify = "Running kernel %s, codename %s"
+            logger.info(identify.grey, @version, @codename)
             initializer?.apply(@)

     # Create a new instance of the kernel, run all the prerequisites


add silencer

add silencer

--- library/nucleus/kernel.coffee
+++ library/nucleus/kernel.coffee
@@ -189,7 +189,7 @@
     # instance of the specified kind (class). If it succeeds then
     # it returns an instance to the invoker. If not, however, it
     # throws an assertion error about being unable to accquire.
-    accquire: (kinded) ->
+    accquire: (kinded, silent=no) ->
         assert ident = try kinded.identify() or null
         error = "could not find a %s in the registry"
         noKinded = "the supplied arg has to be class"
@@ -198,7 +198,8 @@
         assert _.isObject(kinded.__super__), noKinded
         look = (fxc) -> try fxc.objectOf kinded, yes
         spoted = _.find(registry, look) or undefined
-        assert _.isObject(spoted), formatted; spoted
+        assert _.isObject(spoted) or silent, formatted
+        try spotted.accuired? kinded, silent; spoted

     # This routines sets up the infrastructure necessary for kernel
     # to properly intercept and process errors and exceptions. This


close quote

close quote

--- libs/rules/close_quote.coffee
+++ libs/rules/close_quote.coffee
@@ -12,20 +12,19 @@
   apply:->
     # return if @config.on
     self = @
-    use = false
+
     # Правило
     re = /([a-zа-яё0-9]|\.|\&hellip\;|\!|\?|\>|\)|\:)(\"+)(\.|\&hellip\;|\;|\:|\?|\!|\,|\s|\)|\<\/|$)/i
+    m = @text.match re
+    if m
+      # Замена
+      @text = @text.replace re , (str)->
+        self.debug str
+        self.debug m

-    # Замена
-    @text = @text.replace re , (str)->
-      self.debug str
+        m[1] + self.Lib.QUOTE_FIRS_CLOSE + m[3]

-      m = str.match re
-      self.debug m
-      use = true
-      m[1] + self.Lib.QUOTE_FIRS_CLOSE + m[3]
-
-    use
+    !!m

 module.exports = CloseQuote


increase performance of fromXYZ() by using inverse matrix (less accurate)

--- luDecompositionSpec.coffee
+++ luDecompositionSpec.coffee
@@ -55,3 +55,14 @@
       C = (_ A).solve _B, _B
       expect(C).toBe _B # in place ?
       expect(A.mult(C)).toApprox B, 0.0000000000001 # correct solution?
+
+
+  describe 'getInverse()', ->
+
+    it 'returns the inverse of the source matrix', ->
+      _A = A.clone()
+      B = (_ _A).getInverse()
+      expect(_A).toEqual A # input untouched?
+      # correct solution?
+      expect(A.mult(B)).toApprox (M.I 3), 0.000000000000001
+      expect(B.mult(A)).toApprox (M.I 3), 0.00000000000001


Add 'edit' to command help.

Add 'edit' to command help.

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -157,6 +157,7 @@
 javac <source file>    -- Compile Java source.
 java <class> [args...] -- Run with command-line arguments.
 javap <class>          -- Display disassembly.
+edit <file>            -- Edit a file.
 ls                     -- List all files.
 rm <file>              -- Delete a file.
 clear_heap             -- Clear the heap.


tweak so that console keyboard shortcuts work

--- lib/console/console.coffee
+++ lib/console/console.coffee
@@ -65,7 +65,7 @@

   done: ->
     if @isInput
-      @view.element.focus() # Defocus input
+      @view.items.focus() # Defocus input
       @isInput = false

   @debounce: (t, f) ->


possible straights before the flop are okay

--- src/valueHand.coffee
+++ src/valueHand.coffee
@@ -54,9 +54,11 @@
   if hand.community.length == 0
     if hand.hole[0].suit == hand.hole[1].suit
       return 1
+    if detectStraight hand.all, 2
+      return 0.5

   if hand.community.length < 3 or value > 0
-    highCards = _.filter hand.hole, (card) -> card.rank > 9
+    highCards = _.filter hand.hole, (card) -> card.rank > 10
     if highCards.length > 0
       value = value + (highCards.length / hand.all.length - value) * 0.5
       if hand.community.length == 0


Don't check bracket-matcher state on each newline.

--- lib/did-insert-text.coffee
+++ lib/did-insert-text.coffee
@@ -39,13 +39,13 @@
   # if a newline is entered after the opening backtick
   # indent cursor and add a closing backtick
   insertNewlineAfterBacktick: () ->
-    return true unless @bracketMatcherBackticks()
     cursorBufferPosition = @editor.getCursorBufferPosition()
     return true unless cursorBufferPosition.column > 0
     betweenBackTicks = 'punctuation.definition.quasi.end.js' is @editor.scopeDescriptorForBufferPosition(cursorBufferPosition).getScopesArray().slice(-1).toString()
     cursorBufferPosition.column--
     return true unless 'punctuation.definition.quasi.begin.js' is @editor.scopeDescriptorForBufferPosition(cursorBufferPosition).getScopesArray().slice(-1).toString()
     indentLength = @editor.indentationForBufferRow(cursorBufferPosition.row)
+    return true unless @bracketMatcherBackticks()
     if (betweenBackTicks)
       @editor.insertText("\n\n")
       @editor.setIndentationForBufferRow cursorBufferPosition.row+1, indentLength+1, { preserveLeadingWhitespace: false }


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_flash.coffee
+++ src/camera_flash.coffee
@@ -27,6 +27,8 @@
       I.flashColor.a = 1 if I.flashColor.a > 0.9999

       I.flashCooldown = I.flashCooldown.approach(0, 1)
+
+

   ###*
   A convenient way to set the flash effect instance variables. Alternatively, you can modify them by hand, but


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_flash.coffee
+++ src/camera_flash.coffee
@@ -28,7 +28,7 @@

       I.flashCooldown = I.flashCooldown.approach(0, 1)

-  self.bind 'flash', fu(canvas)
+  self.bind 'flash', (canvas)
     canvas.fill(I.flashColor)

   ###*


:fire: Remove messages

:fire: Remove messages

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -1,10 +0,0 @@
-class LinterTrace
-  constructor: (@Message, @File, @Position) ->
-
-class LinterMessage then constructor: (@Message, @File, @Position, @Trace) ->
-
-class LinterError extends LinterMessage
-
-class LinterWarning extends LinterMessage
-
-module.exports = {LinterTrace, LinterMessage, LinterError, LinterWarning}


Message auto stops all editors after 1 hour of idle (fix #306)

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -1,5 +1,8 @@
 import { defaultFormat } from './settings.coffee'
 import { ShareJS } from 'meteor/edemaine:sharejs'
+
+idleUpdate = 1000      ## one second of idle time before edits update message
+idleStop = 60*60*1000  ## one hour of idle time before auto stop editing

 ## Thanks to https://github.com/aldeed/meteor-simple-schema/blob/4ead24bcc92e9963dd994c07d275eac144733c3e/simple-schema.js#L548-L551
 @idRegex = "[23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz]{17}"
@@ -512,8 +515,6 @@
   ## Need to be able to edit the child message and be able to post within
   ## the target parent.
   canEdit(child) and canPost group, parent
-
-idle = 1000   ## one second

 @message2group = (message) ->
   findMessage(message)?.group
@@ -901,6 +902,7 @@

 if Meteor.isServer
   editorTimers = {}
+  stopTimers = {}

   ## If we're using a persistent store for sharejs, we need to cleanup
   ## leftover documents from last time.  This should only be for local
@@ -921,11 +923,22 @@
       , editors, msg

   delayedEditor2messageUpdate = (id) ->
+    updateStopTimer id
     Meteor.clearTimeout editorTimers[id]
     editors = findMessage(id).editing
     editorTimers[id] = Meteor.setTimeout ->
       editor2messageUpdate id, editors
-    , idle
+    , idleUpdate
+
+  updateStopTimer = (id) ->
+    Meteor.clearTimeout stopTimers[id]
+    stopTimers[id] = Meteor.setTimeout ->
+      ## This code is like an extreme form of `messageEditStop` below:
+      editor2messageUpdate id, Messages.findOne(id).editing ? []
+      Messages.update id,
+        $set: editing: []
+      ShareJS.model.delete id
+    , idleStop

 Meteor.methods
   messageUpdate: (id, message) -> _messageUpdate id, message
@@ -1053,10 +1066,9 @@
       unless old.editing?.length
         ShareJS.model.delete id
         ShareJS.initializeDoc id, old.body ? ''
-        timer = null
-        listener = Meteor.bindEnvironment (opData) ->
+        ShareJS.model.listen id, Meteor.bindEnvironment (opData) ->
           delayedEditor2messageUpdate id
-        ShareJS.model.listen id, listener
+        updateStopTimer id
       ## We used to do the following update in client too, to do
       ## speculatively, but it seems problematic for now.
       Messages.update id,
@@ -1069,11 +1081,12 @@
       ## speculatively, but it seems problematic for now.
       Messages.update id,
         $pull: editing: Meteor.user().username
-      unless Messages.findOne(id).editing?.length
+      unless Messages.findOne(id).editing?.length  ## removed last editor
+        Meteor.clearTimeout stopTimers[id]
         editor2messageUpdate id, [Meteor.user().username]
         ShareJS.model.delete id
-    ## xxx should add to last MessagesDiff (possibly just made) that
-    ## Meteor.user().username just committed this version.
+      ## xxx should add to last MessagesDiff (possibly just made) that
+      ## Meteor.user().username just committed this version.

   messageImport: (group, parent, message, diffs) ->
     #check Meteor.userId(), String  ## should be done by 'canImport'


support jsonp (and some additions for custom root paths)

--- lib/mobwrite.coffee
+++ lib/mobwrite.coffee
@@ -62,17 +62,17 @@
   logger = options?.logger
   daemonPort = options?.port or DEFAULT_DAEMON_PORT
   daemonHost = options?.host or DEFAULT_DAEMON_HOST
+  root = options?.root or "__mobwrite__"
   serve({logger: logger, port: daemonPort, host: daemonHost})

+  # Make a pattern that we can match inbound requests for mobwrite middleware.
+  rootPattern = new RegExp("#{root}/([^\/]+)")
+
   # Create a connect middleware (e.g. for use in ExpressJS).
+  # TODO: verify that this works with plain http.createServer()
   bodyParser = connect.bodyParser()
   return (req, res, next) ->
-
-    # Parse the POST body and bail out if there was no data in there.
     bodyParser req, res, ->
-      if not req.body?
-        req.on "end", ->
-        res.send(500, "missing body in POST")

       # Respond to any requests to the sample q.py/q.php/q.jsp endpoints
       # by grabbing the "q" or "p" parameters and interacting with the Daemon.
@@ -80,9 +80,9 @@
       # /__mobwrite__/sync (POST)
       # /__mobwrite__/client.js (GET)
       # /__mobwrite__/forms.js (GET)
-      else if MOBWRITE_SAMPLE_ENDPOINT_PATTERN.test(req.url)
-        clientNeedsJsonp = req.body?.p?
-        daemonRequest = req.body.q or req.body.p or "\n"
+      if MOBWRITE_SAMPLE_ENDPOINT_PATTERN.test(req.url) or rootPattern.test(req.url)
+        clientNeedsJsonp = req.query.p?
+        daemonRequest = req.body?.q or req.query.p or "\n"
         daemonResponse = ""

         # Open a socket to the daemon.
@@ -100,61 +100,84 @@
         daemonSocket.on "end", ->
           logger?.log(">>> socket finished reading from daemon")
           if clientNeedsJsonp
-            daemonResponse = daemonResponse.replace("\\", "\\\\").replace("\"", "\\\"")
-            daemonResponse = daemonResponse.replace("\n", "\\n").replace("\r", "\\r")
+            daemonResponse = daemonResponse.replace(new RegExp("\\\\", "g"), "\\\\")
+            daemonResponse = daemonResponse.replace(new RegExp("\\\"", "g"), "\\\"")
+            daemonResponse = daemonResponse.replace(new RegExp("\\n", "g"), "\\n")
+            daemonResponse = daemonResponse.replace(new RegExp("\\r", "g"), "\\r")
             daemonResponse = "mobwrite.callback(\"#{daemonResponse}\");"
-          res.send(daemonResponse)
+          console.warn(">> sending to client\n---\n#{daemonResponse}\n---")
+          res.writeHead(200, {"Content-Type": "text/javascript"})
+          res.end(daemonResponse)

         daemonSocket.on "timeout", (err) ->
           logger?.log("!!! socket timeout: #{err}")
-          res.send(500, err.toString())
+          res.writeHead(500)
+          res.end(err.toString())

         daemonSocket.on "error", (err) ->
           logger?.log("!!! socket error: #{err}")
-          res.send(500, err.toString())
+          res.writeHead(500)
+          res.end(err.toString())

 module.exports = middleware

 if module is require.main
-  app = require("express")()
+  express = require("express")
+  app = express()
   app.get "/editor", (req, res) ->
     res.send("""
-
-
-      <TITLE>MobWrite as a Collaborative Editor</TITLE>
-
-
-
-
-
-

-
-

MobWrite as a Collaborative Editor

-
-
<INPUT TYPE="text" ID="demo_editor_title" STYLE="width: 50%">

-
-
<TEXTAREA ID="demo_editor_text" STYLE="width: 100%; height: 100%"></TEXTAREA>

-
-

-
-
-
+      </script>
+
+
+          <form id="mobwrite-form" action="" method="post" accept-charset="utf-8">
+

+

+
+
MobWrite as a Collaborative Editor

+
Calling remotely via JSON-P.

+
+

+

+
+                          <input type="text" id="editor-title" placeholder="Your name" style="width:50%;">
+
+

+

+
+                          <textarea id="editor-text" style="width:100%; height:100%;"></textarea>
+
+

+

+          </form>
+
+
+
       """)
+  app.use(express.logger({format: '[:date] [:response-time] [:status] [:method] [:url]'}))
   app.use(middleware({logger: console}))
   app.listen(8000)


Allow future timestamps.

Allow future timestamps.

--- app/assets/javascripts/_site.js.coffee
+++ app/assets/javascripts/_site.js.coffee
@@ -614,6 +614,7 @@
         wordSeparator: " "
         numbers: []
       jQuery.timeago.settings.strings = strings
+      jQuery.timeago.settings.allowFuture = true
       $("time.timeago").timeago()
       return


fixed turbolinks issue

fixed turbolinks issue

--- app/assets/javascripts/deposits.coffee
+++ app/assets/javascripts/deposits.coffee
@@ -6,8 +6,9 @@
     str = "files"
   returnStr = num + " " + str + " selected"

-
-ready = ->
+# when document is loaded - includes workaround for turbolinks
+# (http://stackoverflow.com/questions/20252530/coffeescript-jquery-on-click-only-working-when-page-is-refreshed)
+$(document).on 'turbolinks:load', ->
   # bind the "choose files" buttons to the actual file upload button elements (this is a workaround so that the file upload button can be styled)
   $('#file_upload_btn').click ->
     $('#real_file_btn').click()
@@ -19,7 +20,4 @@
   $('#real_dir_btn').change ->
     $('#dir_upload_status').html(plural($(@)[0].files.length))

-# call "ready" when document is loaded - includes workaround for turbolinks
-# (http://stackoverflow.com/questions/20252530/coffeescript-jquery-on-click-only-working-when-page-is-refreshed)
-$(document).ready(ready)
-$(document).on('turbolinks:load', ready);
+


Dynamically set the interactive URL.

Dynamically set the interactive URL.

--- src/code/wrapper.coffee
+++ src/code/wrapper.coffee
@@ -14,7 +14,10 @@
     @updateRuntimeDataSchedule = false
     @updateInterval = 500 # 0.5s
     @loadConfiguration()
-    @registerPhones(id)
+    $(id).attr('src', @interactiveUrl)
+    $(id).load () =>
+      l.info "loaded interactive #{@interactiveUrl}"
+      @registerPhones(id)

   loadConfiguration: () ->
     @datasetName = getParameterByName "datasetName", "prediction-dataset"
@@ -23,7 +26,18 @@
     @globalStateKey = getParameterByName "globalStateKey", "gstate-prediction-dataset"
     l.info "Global key #{@globalStateKey}"

+    @interactiveUrl = getParameterByName "interactive", "http://lab.concord.org/embeddable.html#interactives/itsi/sensor/prediction-prediction.json"
+    l.info "Interactive #{@interactiveUrl}"
+
   registerPhones: (id) ->
+    if @interactivePhone
+      @interactivePhone.hangup()
+      @interactivePhone = null
+
+    if @runtimePhone
+      @runtimePhone.hangup()
+      @runtimePhone = null
+
     @runtimePhone = new iframePhone.getIFrameEndpoint()
     @registerHandlers(@runtimePhone, @runtimeHandlers())
     @interactivePhone = new iframePhone.ParentEndpoint $(id)[0], =>
@@ -79,8 +93,8 @@
       reg = (evt) =>
         l.info("wiring a request for #{evt}")
         @interactivePhone.post "listenForDatasetEvent",
-        eventName: evt
-        datasetName: "prediction-dataset"
+          eventName: evt
+          datasetName: "prediction-dataset"
       events = "sampleAdded dataReset sampleRemoved".split /\s+/
       reg(evnt) for evnt in events


:bug: fix #58

:bug: fix #58

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -308,7 +308,7 @@

 class SurroundWord extends Surround
   @extend()
-  target: 'Word'
+  target: 'InnerWord'

 class DeleteSurround extends Surround
   @extend()


Fix source maps for CoffeeScript on Windows.

The sourceURL needs to be a URL, not a file path.

--- src/coffee-cache.coffee
+++ src/coffee-cache.coffee
@@ -18,11 +18,17 @@
     try
       fs.readFileSync(cachePath, 'utf8')

+convertFilePath = (filePath) ->
+  if process.platform is 'win32'
+    encodeURI '/' + path.resolve(filePath).replace(/\\/g, '/')
+  else
+    encodeURI filePath
+
 compileCoffeeScript = (coffee, filePath, cachePath) ->
   {js, v3SourceMap} = CoffeeScript.compile(coffee, filename: filePath, sourceMap: true)
   # Include source map in the web page environment.
   if btoa? and JSON? and unescape? and encodeURIComponent?
-    js = "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=#{filePath}"
+    js = "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=#{convertFilePath(filePath)}"
   try
     fs.writeFileSync(cachePath, js)
   js


Use axis variable

Use axis variable

--- src/coffee/glide.coffee
+++ src/coffee/glide.coffee
@@ -122,24 +122,25 @@
   slide: (targetPage, currentPage) ->
     targetPage.style.display = "-webkit-box"
     screenWidth = window.innerWidth + 'px'
+    axis = "X"

     if @back
-      @translate currentPage, "X", "0%"
-      @translate targetPage, "X", "-" + screenWidth, "0ms"
-
-      setTimeout =>
-        @translate currentPage, "X", "100%"
-      , 0
-    else
-      @translate currentPage, "X", "0%"
-      @translate targetPage,"X", screenWidth, "0ms"
-
-      setTimeout =>
-        @translate currentPage, "X", "-100%"
+      @translate currentPage, axis, "0%"
+      @translate targetPage, axis, "-" + screenWidth, "0ms"
+
+      setTimeout =>
+        @translate currentPage, axis, "100%"
+      , 0
+    else
+      @translate currentPage, axis, "0%"
+      @translate targetPage, axis, screenWidth, "0ms"
+
+      setTimeout =>
+        @translate currentPage, axis, "-100%"
       , 0

     setTimeout =>
-      @translate targetPage, "X", "0%"
+      @translate targetPage, axis, "0%"
       @back = false
     , 0

@@ -149,16 +150,17 @@
   slideUp: (targetPage, currentPage) ->
     targetPage.style.display = "-webkit-box"
     screenHeight = window.innerHeight + 'px'
+    axis = "Y"

     if @back
       setTimeout =>
-        @translate currentPage, "Y", screenHeight
+        @translate currentPage, axis, screenHeight
       , 0
     else
       targetPage.style.zIndex = "1000"
-      @translate targetPage, "Y", screenHeight,"0ms"
-      setTimeout =>
-        @translate targetPage, "Y", "0%"
+      @translate targetPage, axis, screenHeight,"0ms"
+      setTimeout =>
+        @translate targetPage, axis, "0%"
       , 0

     @back = false


sprite go

sprite go

--- src/layout/test_layout.coffee
+++ src/layout/test_layout.coffee
@@ -63,7 +63,11 @@
     t.equal sprite.get('fill'), 'blue', 'fill'
     t.end()

-  test "invoke sprite", (t) ->
+  test "command sprite", (t) ->
+    test_position t, [1,1]
+    sprite.call('go', 1)
+    test_position t, [2,1]
+
     t.notOk sprite.call('apply', {target: grid}), "only apply to self"
     forward = sprite.get('language')['forward']
     message = {target: sprite, action: forward}