Forgot QUnit

Forgot QUnit

--- tests/batman/model/associations/has_many_test.coffee
+++ tests/batman/model/associations/has_many_test.coffee
@@ -69,7 +69,7 @@
   @Store.find 1, (err, store) ->
     store.get('products').on 'loaded', ->
       ok true, 'loaded fired'
-      start()
+      QUnit.start()

 asyncTest "hasMany associations are loaded using encoders", 1, ->
   @Product.encode 'name'


Handle premature node process exit during cleanup

--- lib/debugger.coffee
+++ lib/debugger.coffee
@@ -76,6 +76,10 @@
     self = this
     new Promise (resolve, reject) =>
       return resolve() if not @process?
+      if @process.exitCode
+        logger.info 'child_process', 'process already exited with code ' + @process.exitcode
+        @process = null
+        return resolve()

       onProcessEnd = R.once =>
         logger.info 'child_process', 'die'


use eachR

use eachR

--- lib/assets/javascripts/stark/view.js.coffee
+++ lib/assets/javascripts/stark/view.js.coffee
@@ -110,8 +110,8 @@
     @bind_link_events()
     @bind_models()
     @after_render()
-    _.each @after_render_hooks, (hook) ->
-      hook.call()
+    _.eachR @, @after_render_hooks, (hook) ->
+      hook.call(@)

     return @

@@ -176,13 +176,12 @@
       return ret

     if _.isArray(data)
-      _.each data, (key) ->
+      _.eachR @, data, (key) ->
         ret[key] = @[key]
-      , @

     else if _.isFunction(data)
       data = data.call(@, el)
-      _.each data, (val, key) ->
+      _.eachR @, data, (val, key) ->
         ret[key] = val

     return ret
@@ -284,9 +283,8 @@

   # Unsubscribe all @app level events (see #bind_app_events)
   unbind_app_events: ->
-    _.each @app_events, (cb, key) ->
+    _.eachR @, @app_events, (cb, key) ->
       @app.unsubscribe(key, cb, @)
-    , @

   # Cleanup any resources used by the view. Should remove all views and unbind any events
   dispose: ->


fixed merge conflicts from ios5 patch

fixed merge conflicts from ios5 patch

--- lib/assets/javascripts/turbolinks.js.coffee
+++ lib/assets/javascripts/turbolinks.js.coffee
@@ -2,6 +2,7 @@
 currentState = null
 initialized  = false
 referer = document.location.href
+historyState = null

 visit = (url) ->
   if browserSupportsPushState
@@ -61,20 +62,20 @@
   document.title = title
   document.documentElement.replaceChild body, document.body

-  currentState = window.history.state
+  currentState = historyState
   triggerEvent 'page:change'

 reflectNewUrl = (url) ->
   if url isnt document.location.href
     referer = document.location.href
-    window.history.pushState { turbolinks: true, position: currentState.position + 1 }, '', url
+    historyPushState { turbolinks: true, position: currentState.position + 1 }, '', url

 rememberCurrentUrl = ->
-  window.history.replaceState { turbolinks: true, position: window.history.length - 1 }, '', document.location.href
+  historyReplaceState { turbolinks: true, position: window.history.length - 1 }, '', document.location.href

 rememberCurrentState = ->
-  currentState = window.history.state
+  currentState = historyState

 rememberInitialPage = ->
   unless initialized
@@ -165,12 +166,20 @@
   nonHtmlLink(link)  or remoteLink(link)      or noTurbolink(link)  or
   newTabClick(event)

+historyReplaceState = (state, title, url) ->
+  window.history.replaceState state, title, url
+  historyState = state
+
+historyPushState = (state, title, url) ->
+  window.history.pushState state, title, url
+  historyState = state

 browserSupportsPushState =
   window.history and window.history.pushState and window.history.replaceState

 if browserSupportsPushState
   window.addEventListener 'popstate', (event) ->
+    historyState = event.state
     fetchHistory event.state if event.state?.turbolinks

   document.addEventListener 'click', handleClick,true


Update Project schema

Update Project schema

--- services/web/app/coffee/models/Project.coffee
+++ services/web/app/coffee/models/Project.coffee
@@ -35,7 +35,7 @@
 	track_changes     : { type: Object }
 	overleaf          :
 		id              : { type: Number }
-		imported_at_version : { type: Number }
+		imported_at_ver_id : { type: Number }

 ProjectSchema.statics.getProject = (project_or_id, fields, callback)->
 	if project_or_id._id?


limit project name in email to 40 chars

limit project name in email to 40 chars

--- services/web/app/coffee/Features/Email/EmailBuilder.coffee
+++ services/web/app/coffee/Features/Email/EmailBuilder.coffee
@@ -97,7 +97,7 @@

 templates.projectInvite =
-	subject: _.template "<%= project.name %> - shared by <%= owner.email %>"
+	subject: _.template "<%= project.name.slice(0, 40) %> - shared by <%= owner.email %>"
 	layout: BaseWithHeaderEmailLayout
 	type:"notification"
 	plainTextTemplate: _.template """
@@ -111,16 +111,16 @@
 """
 	compiledTemplate: (opts) ->
 		SingleCTAEmailBody({
-			title: "#{ opts.project.name } – shared by #{ opts.owner.email }"
+			title: "#{ opts.project.name.slice(0, 40) } – shared by #{ opts.owner.email }"
 			greeting: "Hi,"
-			message: "#{ opts.owner.email } wants to share “#{ opts.project.name }” with you."
+			message: "#{ opts.owner.email } wants to share “#{ opts.project.name.slice(0, 40) }” with you."
 			secondaryMessage: null
 			ctaText: "View project"
 			ctaURL: opts.inviteUrl
 			gmailGoToAction:
 				target: opts.inviteUrl
 				name: "View project"
-				description: "Join #{ opts.project.name } at ShareLaTeX"
+				description: "Join #{ opts.project.name.slice(0, 40) } at ShareLaTeX"
 		})

 templates.completeJoinGroupAccount =


Testing

Testing

--- widgets/cusi_progress_bars/cusi_progress_bars.coffee
+++ widgets/cusi_progress_bars/cusi_progress_bars.coffee
@@ -145,13 +145,13 @@
   # @element - element to be set
   # @value - the numeric value to set the element to. This can be a float.
   # /
-  setProgressBarValue = (element, barvalue, value) ->
+  setProgressBarValue = (element, value, barvalue) ->
     if (barvalue > 100)
       barvalue = 100
     else if (barvalue < 0)
       barvalue = 0
 #    element.textContent = Math.floor(value) + "%"
-    element.textContent = Math.floor(barvalue)
+    element.textContent = Math.floor(value)
     element.style.width = barvalue + "%"

   #***/


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- source/javascripts/_cornerstone/array_extensions.js.coffee
+++ source/javascripts/_cornerstone/array_extensions.js.coffee
@@ -430,7 +430,7 @@
     i = start
     result = []

-
+    while i < end
     result.push(this[i.mod(this.length)]) while i++ < end

     return result


validate stream pre-partitioning

validate stream pre-partitioning

--- lib/index.coffee
+++ lib/index.coffee
@@ -49,11 +49,11 @@
     ([stream, stream_type], cb_m) ->
       key = options.on[stream_type]
       file_name = path.join os.tmpdir(), "#{Date.now()}'-'#{Math.random().toString().split('.')[1]}.json"
-      [have_join_key, dont_have_join_key] = partition stream, (obj) -> obj[key]?
+      validated = _(stream).stream().assert(_.isObject, 'received non-object in stream').stream()
+      [have_join_key, dont_have_join_key] = partition validated, (obj) -> obj[key]?
       async.parallel [
         (cb_p) ->
           _(dont_have_join_key).stream()
-            .assert(_.isObject, 'received non-object in stream')
             # The other stream gets stringified and parsed since it goes to disk - do so here as
             # well for consistency
             .map((obj) -> JSON.stringify obj)
@@ -70,7 +70,6 @@
             .run (err) -> cb_p err
         (cb_p) ->
           _(have_join_key).stream()
-            .assert(_.isObject, 'received non-object in stream')
             .map((obj, cb) -> setImmediate delimmed_key_and_obj, key, options.delim, obj, cb)
             .writeFile(file_name)
             .run (err) -> cb_p err


add support for redis auth

add support for redis auth

This is needed if the redis server requires a password

--- lib/index.coffee
+++ lib/index.coffee
@@ -4,22 +4,25 @@
 async = require 'async'
 debug = require('debug') 'redis-reservation'

-create_redis_client = _.memoize (host, port, log) ->
+create_redis_client = _.memoize (host, port, password, log) ->
   _redis = redis.createClient port, host,
     retry_max_delay: 100
     connect_timeout: 500
     max_attempts: 10
     socket_keepalive: true
+
+  _redis.auth password if password
+
   # Client emits an error every time it tries to reconnect and fails. Only emit an error once.
   _redis.once 'error', (err) ->
     log "RESERVE: Error connecting to REDIS: #{host}:#{port}.", err
     throw err
   _redis

-, (host, port) -> "#{host}:#{port}"
+, (host, port, password) -> "#{host}:#{port}:#{password}"

 module.exports = class ReserveResource
-  constructor: (@by, @host, @port, @heartbeat_interval, @lock_ttl, @log=console.log) ->
+  constructor: (@by, @host, @port, @heartbeat_interval, @lock_ttl, @log=console.log, @password='') ->
     @_lost_reservation = false
     return

@@ -111,4 +114,4 @@
       return cb null, (state is 1)

   _init_redis: ->
-    @_redis = create_redis_client @host, @port, @log.bind(@) unless @_redis?
+    @_redis = create_redis_client @host, @port, @password, @log.bind(@) unless @_redis?


Renamed qopts -> ops consistantly

Renamed qopts -> ops consistantly

--- lib/index.coffee
+++ lib/index.coffee
@@ -474,7 +474,7 @@
             # way to speed this code up.
             modifies = undefined #snapshotDb.willOpMakeDocMatchQuery cachedData?, query, d.op

-            return if qopts.shouldPoll and !qopts.shouldPoll d.c, d.docName, d, index, query
+            return if opts.shouldPoll and !opts.shouldPoll d.c, d.docName, d, index, query

             # Not sure whether the changed document should be in the result set
             if modifies is undefined


enable non-simulate mode

enable non-simulate mode

--- lib/index.coffee
+++ lib/index.coffee
@@ -14,7 +14,7 @@
         unless @style in STYLE
             throw new Error 'style should either be "absolute" or "relative"'

-        @simulate = opt.simulate or true
+        @simulate = opt.simulate or false
         @cwd = process.cwd()
         debug "cwd is #{@cwd}"
         @localsMap = {}
@@ -63,8 +63,7 @@
                     simulateContainer[localName] ?= {}
                     simulateContainer[localName][script] = newContent
                 else
-                    console.log "//fs.writeFileSync..."
-                    #fs.writeFileSync filePath, newContent
+                    fs.writeFileSync filePath, newContent, 'utf8'

         return simulateContainer


Added rate limiting for queries. Hardcoded at 2 seconds for now

--- lib/index.coffee
+++ lib/index.coffee
@@ -6,6 +6,15 @@
 redisLib = require 'redis'
 arraydiff = require 'arraydiff'
 ot = require './ot'
+
+rateLimit = (time, fn) ->
+  timeout = null
+  ->
+    return if timeout
+    timeout = setTimeout ->
+      fn()
+      timeout = null
+    , time

 exports.client = (snapshotDb, redis = redisLib.createClient(), redisObserver, extraDbs = {}) ->
   # This is a set.
@@ -424,6 +433,30 @@
             d.c ||= index
             docIdx["#{d.c}.#{d.docName}"] = i

+          if poll then runQuery = rateLimit 2000, ->
+            # We need to do a full poll of the query, because the query uses limits or something.
+            db.query client, index, query, (err, newResultset) ->
+              return emitter.emit 'error', new Error err if err
+
+              if !Array.isArray newResultset
+                if newResultset.extra
+                  unless deepEquals extra, newResultset.extra
+                    emitter.emit 'extra', newResultset.extra
+                    emitter.extra = extra = newResultset.extra
+                newResults = newResultset.results
+              else
+                newResults = newResultset
+
+              r.c ||= index for r in newResults
+
+              diff = arraydiff results, newResults, (a, b) ->
+                return false unless a and b
+                a.docName is b.docName and a.c is b.c
+              if diff.length
+                emitter.data = results = newResults
+                data.type = data.type for data in diff
+                emitter.emit 'diff', diff
+
           do f = -> while d = stream.read() then do (d) ->
             # Collection name.
             d.c = d.channel
@@ -443,30 +476,7 @@
             # Not sure whether the changed document should be in the result set
             if modifies is undefined
               if poll
-                # We need to do a full poll of the query, because the query uses limits or something.
-                db.query client, index, query, (err, newResultset) ->
-                  return emitter.emit 'error', new Error err if err
-
-                  if !Array.isArray newResultset
-                    if newResultset.extra
-                      unless deepEquals extra, newResultset.extra
-                        emitter.emit 'extra', newResultset.extra
-                        emitter.extra = extra = newResultset.extra
-                    newResults = newResultset.results
-                  else
-                    newResults = newResultset
-
-                  r.c ||= index for r in newResults
-
-                  diff = arraydiff results, newResults, (a, b) ->
-                    return false unless a and b
-                    a.docName is b.docName and a.c is b.c
-                  if diff.length
-                    emitter.data = results = newResults
-                    d.type = d.type for d in diff
-                    emitter.emit 'diff', diff
-
-                  #docIdx["#{d.c}.#{d.docName}"] = i for d, i in results
+                runQuery()
               else
                 db.queryDoc client, index, d.c, d.docName, query, (err, result) ->
                   return emitter.emit 'error', new Error err if err


Cleaned up the level module

Cleaned up the level module

--- lib/level.coffee
+++ lib/level.coffee
@@ -1,34 +1,23 @@
-# ------------------------------------------------------------------------------
-
 module.exports =
 class Level
-
-  ## Construction --------------------------------------------------------------
-
   constructor: (@properties) ->

-  ## Getting level properties --------------------------------------------------
-
   getNumber: ->
-    @properties.number
+    return @properties.number

   getName: ->
-    @properties.name
+    return @properties.name

   getDescription: ->
-    @properties.description
+    return @properties.description

   getGrammar: ->
-    @properties.grammar
+    return @properties.grammar

   getOption: (option) ->
-    @properties.options[option]
-
-  ## Managing the associated language ------------------------------------------
+    return @properties.options[option]

   getLanguage: ->
-    @language
+    return @language

-  setLanguage: (@language) ->
-
-# ------------------------------------------------------------------------------
+  setLanguage: (@language) ->


only request refresh once

only request refresh once

--- src/storage.coffee
+++ src/storage.coffee
@@ -41,9 +41,11 @@

     bind_events: ->
         chrome.storage.onChanged.addListener (changes, namespace) =>
+            refresh_requested = false
             for own k, v of changes
                 do (k, v) ->
-                    if H.contains k, ['alt', 'kfs', 'mode', 'keymode', 'color']
+                    if H.contains k, ['alt', 'kfs', 'mode', 'keymode', 'color'] and not refresh_requested
+                        refresh_requested = true
                         chrome.runtime.sendMessage type: 'refresh_all'
                     else if k is 'auto_opac' and v.newValue
                         chrome.runtime.sendMessage type: 'update_opacity'


Fix messageEditStart crashing right after messageNew

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -1132,22 +1132,22 @@

   messageEditStart: (id) ->
     #check Meteor.userId(), String  ## should be done by 'canEdit'
+    return if @isSimulation
     unless canEdit id
       throw new Meteor.Error 'messageEditStart.unauthorized',
         "Insufficient permissions to edit message '#{id}'"
-    if Meteor.isServer
-      old = Messages.findOne id
-      return unless old?
-      unless old.editing?.length
-        ShareJS.model.delete id
-        ShareJS.initializeDoc id, old.body ? ''
-        ShareJS.model.listen id, Meteor.bindEnvironment (opData) ->
-          delayedEditor2messageUpdate id
-        updateStopTimer id
-      ## We used to do the following update in client too, to do
-      ## speculatively, but it seems problematic for now.
-      Messages.update id,
-        $addToSet: editing: Meteor.user().username
+    old = Messages.findOne id
+    return unless old?
+    unless old.editing?.length
+      ShareJS.model.delete id
+      ShareJS.initializeDoc id, old.body ? ''
+      ShareJS.model.listen id, Meteor.bindEnvironment (opData) ->
+        delayedEditor2messageUpdate id
+      updateStopTimer id
+    ## We used to do the following update in client too, to do
+    ## speculatively, but it seems problematic for now.
+    Messages.update id,
+      $addToSet: editing: Meteor.user().username

   messageEditStop: (id) ->
     ## When updating this method, you might also want to update the "extreme"


Fix ShareJS import for new Meteor (edit button!)

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -1,4 +1,5 @@
 import { defaultFormat } from './settings.coffee'
+import { ShareJS } from 'meteor/mizzao:sharejs'

 @untitledMessage = '(untitled)'

@@ -621,8 +622,6 @@

 if Meteor.isServer
   editorTimers = {}
-
-  `import {ShareJS} from 'meteor/mizzao:sharejs'`

   ## If we're using a persistent store for sharejs, we need to cleanup
   ## leftover documents from last time.  This should only be for local


Properly cleanup editor when last person stops editing.  Fix #19

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -199,6 +199,25 @@
       doc.updated = new Date
     MessagesParent.insert doc

+if Meteor.isServer
+  editorTimers = {}
+
+  editor2messageUpdate = (id) ->
+    Meteor.clearTimeout editorTimers[id]
+    doc = Meteor.wrapAsync(ShareJS.model.getSnapshot) id
+    #console.log id, 'changed to', doc.snapshot
+    msg = Messages.findOne id
+    unless msg.body == doc.snapshot
+      _messageUpdate id,
+        body: doc.snapshot
+      , msg.editing, msg
+
+  delayedEditor2messageUpdate = (id) ->
+    Meteor.clearTimeout editorTimers[id]
+    editorTimers[id] = Meteor.setTimeout ->
+      editor2messageUpdate id
+    , idle
+
 Meteor.methods
   messageUpdate: (id, message) -> _messageUpdate id, message

@@ -276,23 +295,7 @@
           ShareJS.initializeDoc id, old.body ? ''
           timer = null
           listener = Meteor.bindEnvironment (opData) ->
-            Meteor.clearTimeout timer
-            timer = Meteor.setTimeout ->
-              doc = Meteor.wrapAsync(ShareJS.model.getSnapshot) id
-              #console.log id, 'changed to', doc.snapshot
-              msg = Messages.findOne id
-              unless msg.body == doc.snapshot
-                _messageUpdate id,
-                  body: doc.snapshot
-                , msg.editing, msg
-              #Messages.update id,
-              #  $set: body: doc.snapshot
-              #MessagesDiff.insert
-              #  id: id
-              #  updators: msg.editing
-              #  updated: new Date
-              #  body: doc.snapshot
-            , idle
+            delayedEditor2messageUpdate id
           ShareJS.model.listen id, listener
       Messages.update id,
         $addToSet: editing: Meteor.user().username
@@ -309,6 +312,7 @@
     #if Meteor.isServer and (after.editing ? []).length == 0
     if Meteor.isServer
       unless Messages.findOne(id).editing?.length
+        editor2messageUpdate id
         ShareJS.model.delete id

   messageImport: (group, parent, message, diffs) ->


use @ instead of this; added the calledexactly assertion

--- test/spy-test.coffee
+++ test/spy-test.coffee
@@ -4,20 +4,21 @@

 describe "calculator", ->
   before ->
-    this.calculator = sinon.spy sample, 'calculator'
-    this.multiple = sinon.spy sample, 'multiple'
-    this.addition = sinon.spy sample, 'addition'
-    this.minus = sinon.spy sample, 'minus'
-    this.divide = sinon.spy sample, 'divide'
+    @calculator = sinon.spy sample, 'calculator'
+    @multiple = sinon.spy sample, 'multiple'
+    @addition = sinon.spy sample, 'addition'
+    @minus = sinon.spy sample, 'minus'
+    @divide = sinon.spy sample, 'divide'

   it "should throw an error if no method is matched", ->
     try
       sample.calculator('non-exist-method', 1, 2)
     catch e
-      sinon.assert.threw(this.calculator)
+      sinon.assert.threw(@calculator)

   it "should call the given method when parameter is valid", ->
     methods = ['multiple', 'addition', 'minus', 'divide']
     for method in methods
       sample.calculator(method, 1, 2)
-      sinon.assert.calledOnce(this[method])
+      sinon.assert.calledOnce(@[method])
+      sinon.assert.calledWithExactly(@[method], [1, 2])


minor cleanup

minor cleanup

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -89,10 +89,9 @@
     {@mutationManager, @occurrenceManager, @persistentSelection} = @vimState

     # If occurrence property is set at this timing and there is no preset-occurrence marker.
-    # treat `occurrence` is BOUNDED to operator itself so cleanup on each finish.
+    # treat `occurrence` is BOUND to operator itself so cleanup on each finish.
     if @occurrence and not @occurrenceManager.hasMarkers()
-      @onDidResetOperationStack =>
-        @occurrenceManager.resetPatterns()
+      @onDidResetOperationStack(@resetOccurrencePattern.bind(this))

     @initialize()
     @onDidSetOperatorModifier(@setModifier.bind(this))
@@ -125,8 +124,7 @@
         # Reset existing preset-occurrence. e.g. `c o p`, `d o f`
         @occurrenceManager.resetPatterns()
         @addOccurrencePattern()
-        @subscribe @onDidDeactivateMode ({mode}) =>
-          @occurrenceManager.resetPatterns() if mode is 'operator-pending'
+        @onDidResetOperationStack(@resetOccurrencePattern.bind(this))

   canSelectPersistentSelection: ->
     @acceptPersistentSelection and
@@ -150,6 +148,9 @@
       point = @getCursorBufferPosition()
       pattern = getWordPatternAtBufferPosition(@editor, point, singleNonWordChar: true)
     @occurrenceManager.addPattern(pattern)
+
+  resetOccurrencePattern: ->
+    @occurrenceManager.resetPatterns()

   # target is TextObject or Motion to operate on.
   setTarget: (@target) ->
@@ -184,7 +185,7 @@
       @addOccurrencePattern()

     # To repoeat(`.`) operation where multiple occurrence patterns was set.
-    # Here we save patterns which resresent unioned regex which @occurrenceManager knows.
+    # Here we save patterns which represent unioned regex which @occurrenceManager knows.
     @patternForOccurrence ?= @occurrenceManager.buildPattern()

     @occurrenceManager.select()


Add `=` support

Add `=` support

Fixes #2

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -44,3 +44,21 @@
       description: 'Scope string to match'
       type: 'string'
       default: 'constant\.hashkey'
+    '=-alignment':
+      title: 'Padding for :'
+      description: 'Pad left or right of the character'
+      type: 'string'
+      enum: ['left', 'right']
+      default: 'left'
+    '=-leftSpace':
+      title: 'Left space for :'
+      description: 'Add 1 whitespace to the left'
+      type: 'boolean'
+      default: true
+    '=-rightSpace':
+      title: 'Right space for :'
+      description: 'Add 1 whitespace to the right'
+      type: 'boolean'
+      default: true
+  privateConfig:
+    '=-scope': 'assignment'


Revert "work around vim-mode's settings being undefined"

These cause ~/.atom/config.cson to sometimes get corrupted.  :(

This reverts:
- commit 4668ee61bb3271f7bf74264d41fb63b4339e401b
- commit 495057c849f39dbc5d2bc81c607513e9ac697071

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -22,15 +22,6 @@
   settings[k] = ->
     atom.config.get('vim-mode.'+k)

-  # work around vim-mode's settings sometimes being undefined
-  # https://github.com/bronson/vim-mode-next/issues/2
-  atom.config.observe 'vim-mode-next.'+k, (val) ->
-    val = undefined if val is settings.config[k].default
-    atom.config.set 'vim-mode.'+k, val
-
-# ensure vim-mode has the proper schema too
-atom.config.setSchema 'vim-mode', {type: 'object', properties: settings.config}
-
 settings.defaultRegister = ->
   if settings.useClipboardAsDefaultRegister() then '*' else '"'


code cleaning: reduce repetition of tests on environment

--- lib/token.coffee
+++ lib/token.coffee
@@ -3,6 +3,9 @@

 permissions = {}
 tokens = {}
+
+productionOrTest = process.env.NODE_ENV is "production" or
+    process.env.NODE_ENV is "test"

 ## function checkToken (auth, tokens, callback)
@@ -33,7 +36,7 @@
 ## Check if application can manage docType
 module.exports.checkDocType = (auth, docType, callback) ->
     # Check if application is authenticated
-    if process.env.NODE_ENV is "production" or process.env.NODE_ENV is "test"
+    if productionOrTest
         checkToken auth, (err, isAuthenticated, name) =>
             if isAuthenticated
                 if docType?
@@ -60,7 +63,7 @@
 ## Check if application is proxy
 ## Useful for register and login requests
 module.exports.checkProxyHome = (auth, callback) ->
-    if process.env.NODE_ENV is "production" or process.env.NODE_ENV is "test"
+    if productionOrTest
         if auth isnt "undefined" and auth?
             # Recover username and password in field authorization
             auth = auth.substr(5, auth.length - 1)
@@ -89,7 +92,7 @@
 ## @callback {function} Continuation to pass control back to when complete.
 ## Update application permissions and token
 module.exports.updatePermissions = (body, callback) ->
-    if process.env.NODE_ENV is "production" or process.env.NODE_ENV is "test"
+    if productionOrTest
         if body.password?
             tokens[body.name] = body.password
         permissions[body.name] = {}
@@ -133,7 +136,7 @@
             for docType, description of appli.permissions
                 docType = docType.toLowerCase()
                 permissions[appli.name][docType] = description
-            callback null
+    callback null

 ## function init (callback)
@@ -141,7 +144,7 @@
 ## Initialize tokens which contains applications and their tokens
 module.exports.init = (callback) ->
     # Read shared token
-    if process.env.NODE_ENV is "production" or process.env.NODE_ENV is "test"
+    if productionOrTest
         initHomeProxy () ->
             # Add token and permissions for other started applications
             db.view 'application/all', (err, res) ->


ALL 1516 PASS

ALL 1516 PASS

--- test/tests/CaffeineScript/Parser/ControlStructures.coffee
+++ test/tests/CaffeineScript/Parser/ControlStructures.coffee
@@ -157,6 +157,16 @@
       if true
       else b
       """: "if (true) {undefined;} else {b;};"
+
+      # this one isn't easy
+      # """
+      # if true
+      # """: "if (true) {undefined;};"
+
+      """
+      if true
+      else
+      """: "if (true) {undefined;};"

       """
       a = if true


Only fire tap events if we don’t and a session of another sort with a touchend

--- framer/GestureInputRecognizer.coffee
+++ framer/GestureInputRecognizer.coffee
@@ -88,16 +88,24 @@

 		event = @_getGestureEvent(event)

+		# TODO: base cancel click on how iOS handles it:
+		# - session no longer than 0.75 seconds
+		# - no more movement more than 45 points
+		fireTapEvent = true
 		for eventName, value of @session.started
-			@["#{eventName}end"](event) if value
-
-		# We only want to fire a tap event if the original target is the same
-		# as the release target, so buttons work the way you expect if you
-		# release the mouse outside.
-		if not @session?.startEvent
-			@tap(event)
-		else if @session.startEvent.target is event.target
-			@tap(event)
+			if value
+				# Do not fire a tap event if we are ending another events session
+				fireTapEvent = false
+				@["#{eventName}end"](event)
+
+		if fireTapEvent
+			# We only want to fire a tap event if the original target is the same
+			# as the release target, so buttons work the way you expect if you
+			# release the mouse outside.
+			if not @session?.startEvent
+				@tap(event)
+			else if @session.startEvent.target is event.target
+				@tap(event)

 		@tapend(event)
 		@cancel()


move file opening logic to ink

move file opening logic to ink

--- lib/ui/views.coffee
+++ lib/ui/views.coffee
@@ -59,35 +59,17 @@
         e.stopPropagation()
     view

-  openEditorById: (id, line) ->
-    for pane in atom.workspace.getPanes()
-      for item in pane.getItems()
-        if item.constructor.name is "TextEditor" and item.getBuffer().id is id
-          pane.setActiveItem item
-          item.setCursorBufferPosition [line, 0]
-          item.scrollToCursorPosition()
-          return true
-    false
-
-  getUntitledId: (file) -> file.match(/untitled-([\d\w]+)$/)?[1]
-
   link: ({file, line, contents}) ->
     view = @render @tags.a {href: '#'}, contents
     # TODO: maybe need to dispose of the tooltip onclick and readd them, but
     # that doesn't seem to be necessary
-    if id = @getUntitledId file
+    if @ink.Opener.isUntitled(file)
       tt = atom.tooltips.add view, title: -> 'untitled'
-      view.onclick = (e) =>
-        @openEditorById id, line
-        e.stopPropagation()
     else
       tt = atom.tooltips.add view, title: -> file
-      view.onclick = (e) =>
-        @ink.util.focusEditorPane()
-        atom.workspace.open file,
-          initialLine: if line >= 0 then line
-          searchAllPanes: true
-        e.stopPropagation()
+    view.onclick = (e) =>
+      @ink.Opener.open(file, line)
+      e.stopPropagation()
     view.addEventListener 'DOMNodeRemovedFromDocument', =>
       tt.dispose()
     view


Reset unloader workers after unloaded event

--- lib/unloader.coffee
+++ lib/unloader.coffee
@@ -11,6 +11,7 @@
   unload: ->
     async.series @workers, =>
       @emit "unloaded"
+      @workers = []

 module.exports = ->
   new Unloader


Remove unnecessary undefined argument in test

--- test/api.spec.coffee
+++ test/api.spec.coffee
@@ -123,7 +123,6 @@
       fields:
         IP: '123.123.123.123'
         sha: '2fd4e1c67a2d28fced849ee1bb76e7391b93eb12'
-    , undefined

     expect(slack.request).toHaveBeenCalledWith
       username: 'Robot'


This worked

This worked

--- test/appveyor.coffee
+++ test/appveyor.coffee
@@ -21,6 +21,7 @@
   uri.headers =
     'Content-Type': 'application/json'
     'Content-Length': data.length
+  console.log 'POST', api, uri, data
   q = http.request uri, ->
   q.on 'error', ->
     console.error 'HTTP error!'


(feature) Log more informative error messages.

--- src/promised_callback.coffee
+++ src/promised_callback.coffee
@@ -8,6 +8,6 @@
     else
       res.status(204).end()
   promise.catch (error) ->
-    log.error error
+    log.error error.stack
     # TODO: perhaps we dont send errors in non dev mode?
     res.status(errStatus).send error


:bug: debug function was broken.

:bug: debug function was broken.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -11,14 +11,14 @@

 debug = (message) ->
   return unless settings.get('debug')
-  msg += "\n"
+  message += "\n"
   switch settings.get('debugOutput')
     when 'console'
       console.log message
     when 'file'
       filePath = fs.normalize settings.get('debugOutputFilePath')
       if fs.existsSync(filePath)
-        fs.appendFileSync filePath, msg
+        fs.appendFileSync filePath, message

 selectLines = (selection, rowRange=null) ->
   {editor} = selection


catch exceptions from rimraf to fix #20

catch exceptions from rimraf to fix #20

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -22,11 +22,15 @@
     window.VERSION = atom.packages.getLoadedPackage('wakatime').metadata.version

     if not isCLIInstalled()
-      installCLI()
+      installCLI(->
+        console.log 'Finished installing wakatime cli.'
+      )
     else
       isCLILatest((latest) ->
         if not latest
-          installCLI()
+          installCLI(->
+            console.log 'Finished installing wakatime cli.'
+          )
       )
     isPythonInstalled((installed) ->
       if not installed
@@ -200,17 +204,15 @@
 extractCLI = (zipFile, callback) ->
   console.log 'Extracting wakatime-master.zip file...'
   if fs.existsSync(__dirname + path.sep + 'wakatime-master')
-    rimraf(__dirname + path.sep + 'wakatime-master', ->
-      unzip(zipFile, __dirname, true)
-      console.log 'Finished installing wakatime cli.'
-      if callback?
-        callback()
-    )
+    try
+      rimraf(__dirname + path.sep + 'wakatime-master', ->
+        unzip(zipFile, __dirname, callback)
+      )
+    catch e
+      console.warn e
+      unzip(zipFile, __dirname, callback)
   else
-    unzip(zipFile, __dirname, true)
-    console.log 'Finished installing wakatime cli.'
-    if callback?
-      callback()
+    unzip(zipFile, __dirname, callback)

 downloadFile = (url, outputFile, callback) ->
   r = request(url)
@@ -223,7 +225,7 @@
     )
   )

-unzip = (file, outputDir, cleanup) ->
+unzip = (file, outputDir, callback) ->
   if fs.existsSync(file)
     zip = new AdmZip(file)
     try
@@ -231,8 +233,9 @@
     catch e
       console.warn e
     finally
-      if cleanup
-        fs.unlink(file)
+      fs.unlink(file)
+      if callback?
+        callback()

 sendHeartbeat = (file, lineno, isWrite) ->
   time = Date.now()


Add timeout

Add timeout

--- lib/zen.test.coffee
+++ lib/zen.test.coffee
@@ -15,6 +15,7 @@
     method  : method
     uri     : "http://#{ZENrequest.host}:#{ZENrequest.port}/#{url}"
     headers : headers
+    timeout : 30000
   if parameters? and (method is "GET" or method is "DELETE")
     options.uri += "?#{qs.stringify(parameters)}"
   else


<debug> Fat Arrow saving another day.

<debug> Fat Arrow saving another day.

--- tests/helpers.coffee
+++ tests/helpers.coffee
@@ -37,7 +37,7 @@
             host: 'localhost'
             port: TESTPORT
         start = require("#{prefix}server").start
-        start option, (app, server) ->
+        start option, (app, server) =>
             @server = server
             done null


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -108,6 +108,6 @@
   obj.cooldown 'health',
     target: 10

-
+  ob

 module()


fix rect render bug

fix rect render bug

--- src/renderer/Renderer.coffee
+++ src/renderer/Renderer.coffee
@@ -191,12 +191,14 @@
 						shape.size.height)
 			else
 				@context.beginPath()
-				pt1 = shape.position
-				pt3 = shape.rightBottomPoint
-				@context.dashedLine(pt1.x, pt1.y, pt3.x, pt1.y, shape.dashStyle, shape.dashDelta)
-				@context.dashedLine(pt3.x, pt1.y, pt3.x, pt3.y, shape.dashStyle, shape.dashDelta)
-				@context.dashedLine(pt3.x, pt3.y, pt1.x, pt3.y, shape.dashStyle, shape.dashDelta)
-				@context.dashedLine(pt1.x, pt3.y, pt1.x, pt1.y, shape.dashStyle, shape.dashDelta)
+				xL = shape.position.x
+				xR = shape.pointRB.x
+				yT = shape.position.y
+				yB = shape.pointRB.y
+				@context.dashedLine(xL, yT, xR, yT, shape.dashStyle, shape.dashDelta)
+				@context.dashedLine(xR, yT, xR, yB, shape.dashStyle, shape.dashDelta)
+				@context.dashedLine(xR, yB, xL, yB, shape.dashStyle, shape.dashDelta)
+				@context.dashedLine(xL, yB, xL, yT, shape.dashStyle, shape.dashDelta)
 				@context.stroke()
 		@drawVertexes(shape.points)


add a test with a server client listener

--- test/lib/test.coffee
+++ test/lib/test.coffee
@@ -58,7 +58,6 @@

         # use `cio` to create a server with a tranform (and an arbitrary port)
         server = cio.server
-          # retryDelay: 100
           onConnect: (connection) ->
             serverConnection = connection
             serverConnection.on 'data', (data) ->
@@ -99,6 +98,82 @@
       it 'should receive', -> assert.equal received, 'done'

       it 'should close', -> assert.equal closed, true
+
+
+    describe.only 'with server-client listener', ->
+
+      cio = buildCio()
+
+      # remember these for assertions
+      client = null
+      server = null
+      received = null
+      listening = false
+      connected = false
+      closed = false
+      serverClient = null
+      serverClientCio = null
+
+
+      before 'build server', ->
+
+        # use `cio` to create a server with a tranform (and an arbitrary port)
+        server = cio.server
+          onConnect: (connection) ->
+            serverConnection = connection
+            serverConnection.on 'data', (data) ->
+              received = data.toString 'utf8'
+            serverConnection.on 'end', ->
+              server.close()
+
+        server.on 'error', (error) -> console.log 'Server error:',error
+
+        # once the server is listening do the client stuffs
+        server.on 'listening', ->
+          listening = true
+
+          # create a client via `cio` with its transform and the same port as the server
+          client = cio.client
+            port     : server.address().port
+            host     : 'localhost'
+            onConnect: ->
+              connected = true
+              client.end 'done', 'utf8'
+
+          client.on 'error', (error) -> console.log 'client error:',error
+
+        server.on 'close', -> closed = true
+
+        cio.onServerClient ->
+          # remember what we received
+          serverClient = @serverClient
+          serverClientCio = @cio
+          return
+
+      before 'wait for server to listen', (done) ->
+
+        server.listen 1357, 'localhost', done
+
+      before 'wait for server to close', (done) ->
+
+        server.on 'close', done
+
+      it 'should listen', -> assert.equal listening, true
+
+      it 'should connect', -> assert.equal connected, true
+
+      it 'should receive', -> assert.equal received, 'done'
+
+      it 'should close', -> assert.equal closed, true
+
+      it 'should add server-client listener to chain', ->
+        assert cio._serverClientChain, 'should build the chain'
+        assert.equal cio._serverClientChain.array.length, 2, 'should have default listener and this new one (so 2)'
+
+      it 'should provide `serverClient`', -> assert serverClient
+
+      it 'should provide cio to server-client listener', ->
+        assert.strictEqual serverClientCio, cio

     describe 'with security certs', ->


OPCE3: Rte.insert and Rte.removeSel function now working + use underscorejs

--- lib/y-rte.coffee
+++ lib/y-rte.coffee
@@ -1,24 +1,9 @@
 _ = require 'underscore'

-splitWord = word ->
-  word.match(/(^ +|)\W+ +/g)
-
-
-buildWord = (word, prepend='', append='') ->
-  tmp = word.match /(^ +|)\W+ +/g
-  tmp2 = []
-
-  if tmp.length == 1
-    tmp2.push (new Word (prepend+tmp.pop()+append))
-  else
-    tmp2.push (new Word (prepend+tmp.pop(0)))
-    while tmp.length > 1
-      tmp2.push (new Word (tmp.pop(0) + ' '))
-
-    tmp2.push (new Word (tmp.pop() + append + ' '))
-
-  tmp2.reverse()
-
+# XRegExp = require('xregexp').XRegExp
+WordRegExp = /\S+\s*/g
+PreSpacesRegExp = /^\s+/
+PostSpacesRegExp = /\s+$/
 # Simple class that contains a word and links to the selections pointing
 # to it
 class Word
@@ -276,11 +261,16 @@
       throw new Error "Index out of bounds"
     @_rte.words[index].word = content

-  # Split the content around ' ' and append all the words created to the object
-  #
-  _push: (content) ->
-    for w in content.split(' ')
-      @_rte.words.push(w)
+  # Append new words at the end of the word list
+  # @param [String] content the string to append
+  push: (content) ->
+    preSpaces = content.match PreSpacesRegExp
+    if preSpaces isnt null
+      @_rte.words.push(new Word (preSpaces[0]))
+    words = content.match WordRegExp
+    if _.isArray(words)
+      for w in words
+        @_rte.words.push (new Word w)

   # Insert words at position
   #
@@ -289,20 +279,20 @@
   #
   insertWords: (position, words)->
     # TODO: update selections
-    if typeof position isnt "number"
-      throw new Error 'Expected a number as first parameter'
-    if words.constructor isnt Array
-      throw new Error 'Expected a string array as second parameter'
+    if not _.isNumber position
+      throw new Error "Expected a number as first parameter, got #{position}"
+    if not _.isArray words
+      throw new Error "Expected a string array as second parameter, got #{words}"
     if 0 <= position <= @_rte.words.length
-      after = [new Word ' '].concat(@getWords(position))
-      before = @getWords(0, Math.max(position-1, 0)).append(new Word ' ')
-      if position == 0
-        before = []
-      else
-        before = @_rte.words[..position-1]
-      after = @_rte.words[position..]
-      @_rte.words = before.concat(words).concat(after)
-      # @updateInsertWords(position, words.length)
+      wordsObj = (new Word w for w in words)
+      console.log "splcing!", wordsObj
+
+      left = @_rte.words.slice(0, position)
+      right = @_rte.words.slice(position)
+      console.log left, right
+      @_rte.words = left.concat(wordsObj).concat(right)
+
+      console.log @_rte.words
     else
       throw new Error 'Index #{position} out of bound in word list'

@@ -317,22 +307,22 @@
     # TODO

+  # @overload deleteWords (start, end)
   # Delete all the words between start and end
   #
   # @param [Integer] start position of first word to delete
   # @param [Integer] end position of last word to delete
+  #
+  # @overload deleteWords (position)
+  # Delete the word at position
+  #
+  # @param [Integer] position position the word to delete
   deleteWords: (start, end) ->
     if _.isUndefined(end)
       end = start+1

     if start <= end
-      if start - 1 < 0
-        before = []
-      else
-        before = @getWords(0, start-1)
-
-      @_rte.words = before.concat(@getWords(end))
-    @_rte.words
+      @_rte.words.splice(start, end-start)

   # Merge two words at position
   #
@@ -341,8 +331,8 @@
   merge: (n) ->
     if 0 <= n < @_rte.words.length
       w = @getWord(n).trimRight()
-      @deleteWords(n)
-      @insert({startPos: {word: n, pos:0}}, w)
+      @deleteWords n
+      @insert {startPos: {word: n, pos:0}}, w
     else
       throw new Error "Impossible to merge"

@@ -394,40 +384,31 @@
     index = sel.startPos.word   #position to work from
     pos = sel.startPos.pos

-    wordsToInsert = content.match /\w+ +/g
-    preSpaces = content.search /^ +/
+    preSpaces = content.search PreSpacesRegExp

     currWord = @getWord index

-    # Get spaces of next input and append them to last word to insert
-    if index < @_rte.words.length - 1 and pos == (currWord.length - 1)
-      nextWord = @getWord (index+1)
-      preSpaces = nextWord.search /^ +/
-      if preSpaces > -1         # add them to current word
-        @setWord (index+1) nextWord.substring(preSpaces)
-        last = _.last(wordsToInsert) + nextWord.substring(0, preSpaces)
-        wordsToInsert[wordsToInsert.length-1] = last
-    else if pos < (currWord.length - 1)
-      nextWord = currWord.substring(pos)
-      preSpaces = nextWord.search /^ +/
-      if preSpaces > -1
-        if not preSpaces == (nextWord.length-1)
-          @insertWord (index+1) nextWord.substring(preSpaces)
-        last = _.last(wordsToInsert) + nextWord.substring(0, preSpaces)
-        wordsToInsert[wordsToInsert.length-1] = last
-
-    # Add the spaces at beginning of input string to previous word
-    # if any, or create an empty one
+    # move the spaces to the previous word if a pos == 0
     if preSpaces > -1
       if pos == 0
-        if index == 0
-          emptyWord = new Word content.substring(0, preSpaces)
-          @insertWord 0, emptyWord
-        else
-          # update previous word
-          prevWord = @getWord (index-1)
-          prevWord += content.substring(0, preSpaces)
-          @setWord (index-1), prevWord
+        if index = 0
+          index += 1
+          @insertWord 0, (new Word '')
+        prevWord = @getWord (index-1)
+        prevWord += content.substring(0, preSpaces)
+        content = content.substring(preSpaces)
+
+    # insert the content at position
+    currWord = currWord.substring(0, pos) + content + currWord.substring(pos)
+
+    # cut the word
+    newWords = currWord.match WordRegExp
+    tmp = currWord.search PreSpacesRegExp
+    if (tmp) > - 1
+      newWords[0] = (currWord.substring 0, tmp) + newWords[0]
+    console.log newWords
+    @setWord index, newWords[0]
+    @insertWords index+1, newWords[1..]

   split: (n) ->
@@ -443,8 +424,7 @@
         wlist[wlist.length-2] += ' '
       @insertWords n, wlist
     else
-      console.log word
-      console.log "Error here"
+      ''

   # Relative jump from position
@@ -522,7 +502,7 @@
       else if delta.insert?
         end = position + delta.insert.length
         selection = new Selection position, end, @
-        @insert(selection, delta.insert)
+        @insert selection, delta.insert
         position = end
       else
         throw new Error "Unknown operation"


Add SDKError.throw.

Add SDKError.throw.

This allows for cleaner error exits.

--- compiler/SDKError.coffee
+++ compiler/SDKError.coffee
@@ -86,6 +86,10 @@
         url = DOCS[subject]
     return util.log("#{ _prefix }#{ colors.warn(message) } Docs: #{ colors.underline(colors.help(url)) }")

+SDKError.throw = (subject, message) ->
+    util.log("#{ _prefix }#{ colors.error(message) } Docs: #{ colors.underline(colors.help(url)) }")
+    process.exit()
+
 SDKError.formatProjectPath = (p, f=null) ->
     p_parent = path.dirname(p) + '/'
     if f


Add more tests

Add more tests

--- spec/indentation-indicator-spec.coffee
+++ spec/indentation-indicator-spec.coffee
@@ -29,6 +29,26 @@
         view = atom.workspaceView.find('.indentation-indicator')
         expect(view.text()).toBe 'Tabs:3'

+    it 'represents softTabs true as "Spaces"', ->
+      atom.config.set('editor', {softTabs: true, tabLength: 5})
+
+      waitsForPromise ->
+        atom.workspace.open('sample.js')
+
+      runs ->
+        view = atom.workspaceView.find('.indentation-indicator')
+        expect(view.text()).toBe 'Spaces:5'
+
+    it 'uses the defaults if no editor settings are available', ->
+      atom.config.set('editor', {})
+
+      waitsForPromise ->
+        atom.workspace.open('sample.js')
+
+      runs ->
+        view = atom.workspaceView.find('.indentation-indicator')
+        expect(view.text()).toBe 'Spaces:2'
+
   describe '.deactivate', ->
     it 'removes the indicator view', ->
       view = atom.workspaceView.find('.indentation-indicator')


Updated

Updated

--- _mdwriter.cson
+++ _mdwriter.cson
@@ -1,8 +1,5 @@
 # static engine of your blog ("general", "jekyll", "hexo", "html", "octopress")
 siteEngine: "jekyll"
-
-# absolute path to the root directory of your blog
-siteLocalDir: "/home/msurbey/workspace/github.com/ecwpz91/ecwpz91.github.io"

 # directory to drafts from the root of siteLocalDir
 siteDraftsDir: "_drafts/"


Added askForTrack function with header

Added askForTrack function with header

--- source/Downloader.coffee
+++ source/Downloader.coffee
@@ -36,3 +36,7 @@
   winston.info('Calculated destination.')
   download URI, destination, () ->
     winston.info('Bye.')
+
+# Ask the user to select a track from
+# the given selection.
+askForTrack = (titles, links) ->


More refine

More refine

--- src/scripts/hubot-zoi.coffee
+++ src/scripts/hubot-zoi.coffee
@@ -25,8 +25,8 @@
     if err
       cb.onError(err)
     else
-      zois = body.match(/image: ['"]?https?:\/\/.+.(jpg|png|gif).*["']?/mg).
-                  map((l) -> l.match("'(https.+\.jpg):large'")[1])
+      zois = body.match(/image: ['"]?https?:\/\/.+\.(jpg|png|gif).*["']?/mg).
+                  map((l) -> l.match(/(https?:\/\/.+\.(?:jpg|png|gif))/)[1])
       (cb.onZoi || cb.onSuccess)(zois)
   )


Attempting fix

Attempting fix

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -721,7 +721,7 @@
         if escalationPolicy?
           cb(escalation_policy: escalationPolicy.id, name: escalationPolicy.name)
         else
-          oneScheduleMatching msg, string, (schedule) ->
+          SchedulesMatching msg, string, (schedule) ->
             if schedule
               withCurrentOncallUser msg, schedule, (user, schedule) ->
                 cb(assigned_to_user: user.id,  name: user.name)


another try on fixing #502

another try on fixing #502

--- app/assets/javascripts/dashboard/models/logged_time_model.coffee
+++ app/assets/javascripts/dashboard/models/logged_time_model.coffee
@@ -34,5 +34,5 @@
         interval : interval.year + "-" + (if interval.month < 10 then "0" else "") + interval.month
         months: interval.year * 12 + interval.month
       }
-    ).sort( (a, b) -> a.months < b.months )
+    ).sort( (a, b) -> b.months - a.months )


New Renderer.Native item actions added

New Renderer.Native item actions added

--- actions.coffee
+++ actions.coffee
@@ -26,6 +26,8 @@
 	SCROLLABLE_CONTENT_X: i++
 	SCROLLABLE_CONTENT_Y: i++
 	TEXT_INPUT_TEXT: i++
+	NATIVE_ITEM_WIDTH: i++
+	NATIVE_ITEM_HEIGHT: i++

 exports.out = do (i=0) ->
 	# basic
@@ -86,6 +88,11 @@
 	SET_TEXT_INPUT_ALIGNMENT_HORIZONTAL: i++
 	SET_TEXT_INPUT_ALIGNMENT_VERTICAL: i++

+	CREATE_NATIVE_ITEM: i++
+	ON_NATIVE_ITEM_POINTER_PRESS: i++
+	ON_NATIVE_ITEM_POINTER_RELEASE: i++
+	ON_NATIVE_ITEM_POINTER_MOVE: i++
+
 	LOAD_FONT: i++

 	CREATE_RECTANGLE: i++


fix preview tab

fix preview tab

--- app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
+++ app/assets/javascripts/uploadcare/widget/tabs/preview-tab.coffee
@@ -46,7 +46,7 @@

       @file.done ifCur (info) =>
         state = if info.isImage then 'image' else 'regular'
-        if info.sourceInfo.source == 'camera'
+        if state != 'image' and info.sourceInfo.source == 'camera'
           state = 'video'
         if (state != 'image' and state != 'video') or state != @__state
           @__setState(state, {file: info})


form: fix get calls

form: fix get calls

--- app/assets/javascripts/store/gestpay/iframe.js.coffee
+++ app/assets/javascripts/store/gestpay/iframe.js.coffee
@@ -1,13 +1,13 @@
 class form
   cardNumber: ->
-    get 'card_number'
+    @get 'card_number'

   cardExpiration: ->
-    month: get 'card_month'
-    year:  get 'card_year'
+    month: @get 'card_month'
+    year:  @get 'card_year'

   cardSecureCode: ->
-    get 'card_code'
+    @get 'card_code'

   get: (id) ->
     $("##{id}").val()


paste the corrected number when its a valid one into the form

--- app/assets/javascripts/elfproef/elfproef.coffee
+++ app/assets/javascripts/elfproef/elfproef.coffee
@@ -2,6 +2,7 @@
     constructor: (nr) ->
       @nr = nr.toString().match(/[\d+$]/g)
       @nrs = [2..9].reverse()
+      @str = @nr.join('')

     elfproef: (a) ->
         @nrs.push(a)
@@ -35,13 +36,17 @@

 $ ->
   $(document).on 'blur', '.checkMyBsn',  ->
-    if new ElfProef(@value).validBsn()
+    bsn = new ElfProef(@value)
+    if bsn.validBsn()
+      $(".checkMyBsn").val(bsn.str)
       $(".bsn_message").text("ok")
     else
       $(".bsn_message").text("geen geldig bsn-nummer")

    $(document).on 'blur', '.checkMyRekening',  ->
-    if new ElfProef(@value).validRekening()
+    rek = new ElfProef(@value)
+    if rek.validRekening()
+      $(".checkMyRekening").val(rek.str)
       $(".rekening_message").text("ok")
     else
       $(".rekening_message").text("geen geldig rekening-nummer")


OX-320 start in volume mode

OX-320 start in volume mode

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -71,7 +71,7 @@
       @initKeyboard()

       @setMode(constants.MODE_PLANE_TRACING)
-      # @setMode(constants.MODE_VOLUME)
+      @setMode(constants.MODE_VOLUME)

       if constants.MODE_PLANE_TRACING not in @allowedModes
         if constants.MODE_ARBITRARY in @allowedModes


fixed annonymous time parsing

fixed annonymous time parsing

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -205,9 +205,8 @@
       )

     # parse hard time limit and convert from min to ms
-    hardLimitRe = /Limit: ([0-9]+)/
     expectedTime = @model.tracing.task.type.expectedTime
-    timeLimit = parseInt(expectedTime.match(hardLimitRe)[1]) * 60 * 1000 or 0
+    timeLimit = expectedTime.maxHard * 60 * 1000 or 0

     # setTimeout uses signed 32-bit integers, an overflow would cause immediate timeout execution
     if timeLimit >= Math.pow(2, 32) / 2


FF fix

FF fix

--- app/assets/javascripts/projects/tasks.js.coffee
+++ app/assets/javascripts/projects/tasks.js.coffee
@@ -13,7 +13,7 @@
   unsubscribe: (callback) ->
     @subscribers = @subscribers.filter (item) -> item isnt callback

-  notify: () ->
+  notify: ->
     subscriber() for subscriber in @subscribers

   loadTasks: ->
@@ -47,7 +47,7 @@
     task = @getTask(item.id)
     task.name = item.name
     task.description = item.description
-    task.tags = item.tags
+    task.tag_list = item.tag_list
     task.assignee_id = item.assignee_id
     task.original_estimate = item.original_estimate
     task.remaining_estimate = item.remaining_estimate
@@ -233,8 +233,8 @@
       afterChange: (changes, source) ->
         Tasks.updateSummary()

-        if source == 'loadData'
-          return
+        return if source != 'edit'
+
         for change in changes
           if change[1] != 'delta'
             instance = grid.handsontable('getInstance')
@@ -243,8 +243,9 @@
             else
               physicalIndex = change[0]
             item = instance.getDataAtRow(physicalIndex)
-            Api.update_task gon.project_id, item.id, item, (task) ->
-              # nothing to do here
+            if item?
+              Api.update_task gon.project_id, item.id, item, (task) ->
+                # nothing to do here

       cells: (row, col, prop) =>
         cellProperties = {}


assign the element to a variable to DRY things up

--- app/assets/javascripts/socializer/shares.coffee
+++ app/assets/javascripts/socializer/shares.coffee
@@ -4,10 +4,11 @@

 jQuery ->
   if $("body").data("controller") == "shares"
-    audience_path = $("#share_object_ids").data("source")
+    tokeninput = $("#share_object_ids")
+    audience_path = tokeninput.data("source")
     prepopulate = null

-    $("#share_object_ids").tokenInput audience_path,
+    tokeninput.tokenInput audience_path,
       minChars: 0
       preventDuplicates: true
       prePopulate: prepopulate


Added infinite-scroll to module deps. list

--- app/assets/javascripts/sprangular/module.coffee
+++ app/assets/javascripts/sprangular/module.coffee
@@ -1,5 +1,5 @@
 # Main Module
-window.Sprangular = angular.module "Sprangular", ['ui.bootstrap', 'ngRoute', 'ngResource', 'ngAnimate', 'underscore', 'ngSanitize', 'rawFilter', 'mgcrea.ngStrap']
+window.Sprangular = angular.module "Sprangular", ['ui.bootstrap', 'ngRoute', 'ngResource', 'ngAnimate', 'underscore', 'ngSanitize', 'rawFilter', 'mgcrea.ngStrap', 'infinite-scroll']
   .run ->
     if PAYMENT_METHODS.stripe
       Stripe.setPublishableKey PAYMENT_METHODS.stripe.publishable_key


Add callback to renderResults function

Add callback to renderResults function

--- app/assets/javascripts/widgets/direction.coffee
+++ app/assets/javascripts/widgets/direction.coffee
@@ -50,7 +50,7 @@
   markers = []
   overlays = []

-renderResults = (search_groups, search_results) ->
+renderResults = (search_groups, search_results, callback) ->
   do clearMarkers

   if gmap.getZoom() > 11
@@ -130,6 +130,8 @@
   $('a.point-relic').click ->
     google.maps.event.trigger(markers[$(this).data('id')], 'click')
     false
+
+  do callback if callback?

 # Serialize form to JSON
 $.fn.serializeObject = ->
@@ -313,10 +315,8 @@
         search_params.bounding_box = bounds.toString()

       performSearch search_params, (result) ->
-        renderResults(result.clusters, result.relics)
-
-        if printAction?
-          printRenderRelics result.relics
+        renderResults result.clusters, result.relics, ->
+          printRenderRelics result.relics if printAction?
   else
     performSearch search_params, (result) ->
       renderResults(result.clusters, [])


chat -- tiny fix to computation of when should show red

--- src/smc-webapp/file_use.cjsx
+++ src/smc-webapp/file_use.cjsx
@@ -201,7 +201,7 @@
         #   fields is older than another edit or chat field
         y.is_unseen = you_last_seen < other_newest_edit_or_chat
         # - unseen chat: means that you haven't seen the newest chat for this document.
-        y.is_unseenchat = you_last_chatseen < other_newest_edit_or_chat
+        y.is_unseenchat = you_last_chatseen < newest_chat

     get_notify_count: =>
         if not @_cache?


fix https://github.com/sagemathinc/smc/issues/1708 -- "sagews: exception in constructor of SynchronizedWorksheetCell"

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -2434,7 +2434,7 @@

         # Input
         x = @cm.getLine(start)
-        if x[0] == MARKERS.cell
+        if x?[0] == MARKERS.cell
             if misc.is_valid_uuid_string(x.slice(1,37)) and x[x.length-1] == MARKERS.cell
                 # valid input line
                 @_start_uuid = x.slice(1,37)
@@ -2450,7 +2450,7 @@

         # Output
         x = @cm.getLine(end)
-        if x[0] == MARKERS.output
+        if x?[0] == MARKERS.output
             if misc.is_valid_uuid_string(x.slice(1,37)) and x[37] == MARKERS.output
                 # valid output line
                 @_output_uuid = x.slice(1,37)


Special rename requests

Special rename requests

--- client/app/MainApp/fs/fshelper.coffee
+++ client/app/MainApp/fs/fshelper.coffee
@@ -87,7 +87,7 @@
       withArgs : {path}
     , callback

-  @getGlobList = (pattern, vmName, callback)->
+  @glob = (pattern, vmName, callback)->
     [vmName, callback] = [callback, vmName]  if typeof vmName is "function"
     KD.getSingleton('vmController').run
       method   : "fs.glob"


Do not add twice reply prefix in subject

When composing a message, it doesn't add twice the Re: prefix if it is already
there.

--- client/app/utils/message_utils.coffee
+++ client/app/utils/message_utils.coffee
@@ -90,9 +90,7 @@
                 message.to = @getReplyToAddress inReplyTo
                 message.cc = []
                 message.bcc = []
-                message.subject = """
-                    #{t 'compose reply prefix'}#{inReplyTo.get 'subject'}
-                    """
+                message.subject = @getReplySubject inReplyTo
                 message.text = separator + @generateReplyText(text) + "\n"
                 message.html = """

@@ -113,9 +111,7 @@
                     inReplyTo.get('cc')).filter (dest) ->
                     return dest? and toAddresses.indexOf(dest.address) is -1
                 message.bcc = []
-                message.subject = """
-                    #{t 'compose reply prefix'}#{inReplyTo.get 'subject'}
-                    """
+                message.subject = @getReplySubject inReplyTo
                 message.text = separator + @generateReplyText(text) + "\n"
                 message.html = """

#{separator} …

@@ -353,3 +349,13 @@
             #{html}
             """

+    # Add a reply prefix to the current subject. Do not add it again if it's
+    # already there.
+    getReplySubject: (inReplyTo) ->
+        subject =  """
+        #{inReplyTo.get 'subject'}
+        """
+        replyPrefix = t 'compose reply prefix'
+        if subject.indexOf(replyPrefix) isnt 0
+            subject = "#{replyPrefix}#{subject}"
+        subject


Properly set current submit where needed

--- client/components/ReviewResult.coffee
+++ client/components/ReviewResult.coffee
@@ -71,7 +71,7 @@
         @state =
             currentSubmit: if @props.submits then @props.submits[@props.submits.length - 1] else null
             currentDiff: [undefined, undefined]
-        props.setCurrentSubmit?(@state.currentSubmit)
+        @props.setCurrentSubmit?(@state.currentSubmit)
         @setCurrentSubmit = @setCurrentSubmit.bind this
         @setCurrentDiff = @setCurrentDiff.bind this

@@ -80,6 +80,7 @@
             @setState
                 currentSubmit: if @props.submits then @props.submits[@props.submits.length - 1] else null
                 currentDiff: [undefined, undefined]
+            @props.setCurrentSubmit?(@state.currentSubmit)
         else
             newState =
                 currentSubmit: null
@@ -89,6 +90,7 @@
                     newState.currentSubmit = submit
             if not deepEqual(newState, @state)
                 @setState(newState)
+            @props.setCurrentSubmit?(@state.currentSubmit)

     setCurrentSubmit: (submit) ->
         (e) =>


team: creation: team domain suggestion

team: creation: team domain suggestion

--- client/landing/site.landing/coffee/team/teamdomaintabform.coffee
+++ client/landing/site.landing/coffee/team/teamdomaintabform.coffee
@@ -11,17 +11,23 @@

     super options, data

+    team = KD.utils.getTeamData()
+
+    if name = team.signup?.companyName
+      teamName = KD.utils.slugify name
+
     @input = new KDInputView
-      placeholder : 'your-team'
+      placeholder  : 'your-team'
+      defaultValue : teamName  if teamName
+      name         : 'slug'
+      # change      : @bound 'verifySlug'
+      # keyUp       : KD.utils.debounce 300, @bound 'verifySlug'

     @button = new KDButtonView
-      title       : 'NEXT'
-      style       : 'SignupForm-button SignupForm-button--green'
-      attributes  : testpath  : 'domain-button'
-      loader      : yes
-      callback    : =>
-        console.log 'go to email:'
-        KD.singletons.router.handleRoute '/Team/alloweddomain'
+      title      : 'NEXT'
+      style      : 'SignupForm-button SignupForm-button--green'
+      attributes : testpath  : 'domain-button'
+      type       : 'submit'

   pistachio: ->


Main: boilerplate / route addition

Main: boilerplate / route addition

--- client/landing/app/coffee/main.coffee
+++ client/landing/app/coffee/main.coffee
@@ -1,3 +1,24 @@
+console.time 'Koding.com loaded'
+MainController = require './core/maincontrollerloggedout'
+
 do ->

-  new KDView partial : 'it works!'+  setConfig = ->
+
+    KD.config or= {}
+
+    KD.config.environment = if location.hostname is 'koding.com'\
+                            then 'production' else 'development'
+
+  registerRoutes = ->
+
+    require './core/routes.coffee'
+    require './login/routes.coffee'
+
+
+
+
+  setConfig()
+  registerRoutes()
+  # BIG BANG
+  new MainController


use increment/decrement

use increment/decrement

--- src/utilities/request.coffee
+++ src/utilities/request.coffee
@@ -68,5 +68,5 @@

   @set('pendingRequestCount', 0)
   @classAccessor 'requestIsPending', -> @get('pendingRequestCount') > 0
-  @::on 'loading', ->  Batman.Request.set('pendingRequestCount', Batman.Request.get('pendingRequestCount') + 1)
-  @::on 'loaded', -> Batman.Request.set('pendingRequestCount', Batman.Request.get('pendingRequestCount') - 1)
+  @::on 'loading', ->  Batman.Request.increment('pendingRequestCount')
+  @::on 'loaded', -> Batman.Request.decrement('pendingRequestCount')


Pass set name into categories analytics message

--- apps/personalize/client/views/categories.coffee
+++ apps/personalize/client/views/categories.coffee
@@ -44,17 +44,17 @@
   followCategory: (e) ->
     $(e.currentTarget).find('.follow-button').click()

-  setupFollowButton: (model, el) ->
+  setupFollowButton: (model, el, set) ->
     key = model.id
     @followButtonViews ?= {}
     @followButtonViews[key].remove() if @followButtonViews[key]?
     @followButtonViews[key] = new FollowButton
-      analyticsUnfollowMessage : 'Unfollowed gene from personalize gene suggestions'
-      analyticsFollowMessage   : 'Followed gene from personalize gene suggestions'
-      modelName                : 'Gene'
-      model                    : model
-      el                       : el
-      following                : @following
+      analyticsUnfollowMessage: "Unfollowed gene from #{set} personalize gene suggestions"
+      analyticsFollowMessage: "Followed gene from #{set} personalize gene suggestions"
+      modelName: 'Gene'
+      model: model
+      el: el
+      following: @following

   setupFollowButtons: ->
     _.each @categories, (categories, key) =>
@@ -62,7 +62,7 @@
       categories.each (category) =>
         model   = new Gene id: category.get 'gene_id'
         el      = @$(".follow-button[data-id=#{category.get('gene_id')}]")
-        button  = @setupFollowButton model, el
+        button  = @setupFollowButton model, el, key
         @listenTo button, 'click', @setSkipLabel, this

   setupCategories: (categories, key) ->


Teste publish azure

Teste publish azure

--- app/app.coffee
+++ app/app.coffee
@@ -17,7 +17,7 @@
 app.use express.static __dirname + '/public'
 app.use '/vendor', express.static __dirname + '/bower_components'

-#app.use serveFavicon 'path to favico'
+##app.use serveFavicon 'path to favico'
 app.use morgan 'dev'
 app.use bodyParser.json()
 app.use bodyParser.urlencoded extended: true


Fix header menu toggle

Fix header menu toggle

--- app/assets/javascripts/tenon/features/header_menu.js.coffee
+++ app/assets/javascripts/tenon/features/header_menu.js.coffee
@@ -11,4 +11,10 @@

   hideMenu: (e) =>
     # unless $(e.target).is('.header-menu ul *, .header-menu ul')
-    $('.header-menu').removeClass('active')
+    for headerMenu in $('.header-menu')
+      $hm = $(headerMenu)
+      $a = $hm.find('a:first')
+      $hm.removeClass('active')
+      $a.find('i')
+        .removeClass('fa-times')
+        .addClass('fa-' + $a.data('icon'))


--- app/assets/javascripts/application.coffee
+++ app/assets/javascripts/application.coffee
@@ -65,11 +65,12 @@

 # Mask the contributors if they are too many
 $("article.news .edited_by").each ->
-  nb = @find("a").length
+  field = $(@)
+  nb = field.find("a").length
   if nb > 3
-    was = @html()
-    @html "#{nb} contributeurs"
-    @one "click", => @html was
+    was = field.html()
+    field.html "#{nb} contributeurs"
+    field.one "click", -> field.html was

 # Toolbar preferences
 $("#account_visible_toolbar")


JS fix for removing old comment text

JS fix for removing old comment text

--- app/assets/javascripts/comments.js.coffee
+++ app/assets/javascripts/comments.js.coffee
@@ -7,3 +7,7 @@
 $ ->
   $("#new_comment").on "ajax:success", (e, data, status, xhr) ->
     $("#comment_message").val('')
+
+$(document).on 'page:load', ->
+  $("#new_comment").on "ajax:success", (e, data, status, xhr) ->
+    $("#comment_message").val('')


Extract loadCaptionForm function

Extract loadCaptionForm function

--- app/assets/javascripts/fancybox.js.coffee
+++ app/assets/javascripts/fancybox.js.coffee
@@ -1,3 +1,10 @@
+loadCaptionForm = (fancybox) ->
+  return unless window.admin
+  id = fancybox.element.data('photo-id')
+  caption = fancybox.title
+  authenticityToken = $('#authenticity_token').val()
+  fancybox.title = captionForm(id, caption, authenticityToken)
+
 captionForm = (id, caption, authenticityToken) ->
   "<form method='post' action='/admin/photos/#{id}' data-remote data-confirmation>
     <input type='hidden' name='_method' value='patch'></input>
@@ -13,8 +20,4 @@
     caption:
       type: 'inside'
     afterLoad: ->
-      return unless window.admin
-      id = @element.data('photo-id')
-      caption = @title
-      authenticityToken = $('#authenticity_token').val()
-      @title = captionForm(id, caption, authenticityToken)
+      loadCaptionForm(this)


fix sorting by source count for good

fix sorting by source count for good

--- app/assets/javascripts/gene_groups.coffee
+++ app/assets/javascripts/gene_groups.coffee
@@ -1,6 +1,7 @@
 $ ->
+  lastCol = $('#table-by-source th').size()-1
   $("#table-by-source").dataTable
-    aaSorting: [[7, "desc"], [0, "asc"]]
+    aaSorting: [[lastCol, "desc"], [0, "asc"]]
     sPaginationType: "bootstrap"
     iDisplayLength: "50"
     sDom: "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>"


Another small typo

Another small typo

--- app/assets/javascripts/projects.js.coffee
+++ app/assets/javascripts/projects.js.coffee
@@ -41,7 +41,7 @@
     placeholder: "Select a user",
     allowClear: true
     ajax:
-      url: 'http://api.fablabs.io/v0/users'
+      url: 'https://api.fablabs.io/v0/users'
       dataType: 'json'
       delay: 250
       data: (params) ->


Simplify form via form change event.

Simplify form via form change event.

In browser DOM, forms events does’t bubble, which sucks. In React
Virtual DOM, change events bubble.
Also removed type=“text” since text is default value according to
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Input

--- app/client/js/react/pages/editsong.coffee
+++ app/client/js/react/pages/editsong.coffee
@@ -17,34 +17,36 @@

       render: ->
         div className: 'new-song',
-          form onSubmit: @onFormSubmit, ref: 'form', role: 'form',
+          form
+            # PATTERN(steida): React forms events bubbles, which is nice.
+            # onFormChange handler immediately stores form state for us.
+            onChange: @onFormChange
+            onSubmit: @onFormSubmit
+            ref: 'form'
+            role: 'form'
+          ,
             div className: 'form-group',
               input
                 autoFocus: true
                 className: 'form-control'
-                name: 'name'
-                onChange: @onFieldChange
-                placeholder: EditSong.MSG_SONG_NAME
-                type: 'text'
                 # PATTERN(steida): Don't use props for app model. Use stores.
                 # Stores should be prefetched by app.Storage.
-                value: store.newSong.name
+                defaultValue: store.newSong.name
+                name: 'name'
+                placeholder: EditSong.MSG_SONG_NAME
             div className: 'form-group',
               input
                 className: 'form-control'
-                onChange: @onFieldChange
+                defaultValue: store.newSong.artist
                 name: 'artist'
                 placeholder: EditSong.MSG_SONG_ARTIST
-                type: 'text'
-                value: store.newSong.artist
             div className: 'form-group',
               textarea
                 className: 'form-control'
-                onChange: @onFieldChange
+                defaultValue: store.newSong.lyrics
                 name: 'lyrics'
                 placeholder: EditSong.MSG_WRITE_LYRICS_HERE
                 ref: 'lyrics'
-                value: store.newSong.lyrics
               a
                 href: 'http://linkesoft.com/songbook/chordproformat.html'
                 target: '_blank'
@@ -62,7 +64,7 @@
       componentWillUnmount: ->
         @chordproTextarea_.dispose()

-      onFieldChange: (e) ->
+      onFormChange: (e) ->
         # PATTERN(steida): All changes are immediatelly stored into stores.
         # Stores are asap synced with local/rest storages.
         store.setNewSong e.target.name, e.target.value


Remove unnecessary test

Remove unnecessary test

--- nodejs/palsu.coffee
+++ nodejs/palsu.coffee
@@ -39,11 +39,6 @@
             success: (eventCollection) ->
                 VIE.cleanup()
                 return response.send window.document.innerHTML
-
-server.get '/foo', (req, resp) ->
-    html = jQuery('html')
-    jQuery("
test passes
").appendTo("body", html)
-    resp.send jQuery(html).html()

 server.listen(8002)


Make skills editable, there is just one little bug.

--- client/landing/app/CommonViews/FormViews.coffee
+++ client/landing/app/CommonViews/FormViews.coffee
@@ -330,8 +330,32 @@
     @memberData.skillTags or= []

     @setCallback (formData)=>
-      tagIds = formData.skillTags.map((tag)-> tag.getId?() or $suggest: tag)
-      @memberData.addTags 'skillTags', tagIds, (err)-> debugger
+      if formData.skillTags?
+        newTags      = formData.skillTags?.filter((tag)-> tag.$suggest?)
+        oldTags      = formData.skillTags?.filter((tag)-> tag.id?)
+        plainNewTags = newTags.map((tag)-> tag.$suggest)
+        plainOldTags = oldTags.map((tag)-> tag.title)
+
+        joinedTags   = plainNewTags.concat plainOldTags
+
+        # log newTags, oldTags, joinedTags
+        @memberData.addTags 'skillTags', newTags, (err)=>
+          if err
+            log "An error occured:", err
+          else
+            changes = $set:
+              'skillTags' : joinedTags
+            @memberData.update changes, (err)=>
+              if err
+                log err
+                new KDNotificationView
+                  title : "There was an error updating your profile."
+              else
+                @memberData.emit "update"
+                new KDNotificationView
+                  title     : "Success!"
+                  duration  : 500
+                @unsetClass 'active'

   showForm:->
     unless @$().hasClass "active"
@@ -404,6 +428,7 @@
     super

   putDefaultValues:(stringTags)->
+    # log "Try to put them in list", stringTags
     bongo.api.JTag.some
       title     :
         $in     : stringTags
@@ -416,8 +441,6 @@
         else
           warn "there was a problem fetching default tags!", err, tags

-
-
 class SkillTagAutoCompletedItem extends KDAutoCompletedItem
   constructor:(options, data)->
     options.cssClass = "clearfix"


Improved socket io connection lost policy

--- lib/client_connector/socketio_connector.coffee
+++ lib/client_connector/socketio_connector.coffee
@@ -53,6 +53,10 @@
       io = require("socket.io").listen(server)
     else
       io = require("socket.io").listen(properties.port) #Creates the HTTP server
+
+    io.set "close timeout", 21
+    io.set "heartbeat timeout", 15
+    io.set  "heartbeat interval", 10

     logLevels =
       DEBUG: 3


Don't manually fade, instead use iframe control's fade

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -18,15 +18,11 @@
     FactlinkJailRoot.set_position_of_element @_top, @_left, window, @frame.$el
     #TODO:what's this line do?
     #FactlinkJailRoot.$factlinkCoreContainer.find('div.fl-button').removeClass('active')
-    @$el.addClass 'active'
-    @frame.$el.addClass 'factlink-control-visible'
+    @frame.fadeIn()

-  hide: =>
-    @$el.removeClass 'active'
-    @frame.$el.removeClass 'factlink-control-visible'
+  hide: => @frame.fadeOut()

-  destroy: =>
-    @frame.destroy()
+  destroy: => @frame.destroy()

 class FactlinkJailRoot.ShowButton extends Button
   content: """


Update position regularly and on document resize

--- app/js/jail_iframe/classes/buttons.coffee
+++ app/js/jail_iframe/classes/buttons.coffee
@@ -19,12 +19,13 @@
     @$el.removeClass classes
     @frame.sizeFrameToFitContent()

-  _showAtCoordinates: (top, left) =>
+  _fadeIn: ->
     return if @_visible

     @_visible = true
     @frame.fadeIn()

+  _showAtCoordinates: (top, left) =>
     containerOffset = FactlinkJailRoot.$factlinkCoreContainer.offset()
     left -= containerOffset.left
     top -= containerOffset.top
@@ -56,16 +57,24 @@
   constructor: ->
     super
     @$el.on 'mouseleave', => @_removeClass 'fl-button-state-hovered'
+    $(document).on 'mouseenter', => @_removeClass 'fl-button-state-hovered'

   _textContainer: ($el) ->
     for el in $el.parents()
       return el if $(el).css('display') == 'block'

   placeNearElement: (el) ->
-    $el = $(el)
-    top = $el.offset().top + $el.outerHeight()/2 - @frame.$el.outerHeight()/2
+    @$nearEl = $(el)
+    @_updatePosition()
+    @_fadeIn()

-    textContainer = @_textContainer($el)
+    $(window).on 'resize', => @_updatePosition()
+    setInterval (=> @_updatePosition()), 1000
+
+  _updatePosition: ->
+    top = @$nearEl.offset().top + @$nearEl.outerHeight()/2 - @frame.$el.outerHeight()/2
+
+    textContainer = @_textContainer(@$nearEl)
     range = document.createRange()
     range.setStartBefore textContainer
     range.setEndAfter textContainer
@@ -102,6 +111,8 @@
     FactlinkJailRoot.createFactFromSelection(current_user_opinion)

   placeNearSelection: (mouseX=null) ->
+    return if @_visible
+
     selectionBox = window.document.getSelection().getRangeAt(0).getBoundingClientRect()
     selectionLeft = selectionBox.left + $(window).scrollLeft()
     selectionTop = selectionBox.top + $(window).scrollTop()
@@ -117,3 +128,4 @@
     top = selectionTop-2-@frame.$el.outerHeight()

     @_showAtCoordinates top, left
+    @_fadeIn()


Remove pointer events on text elements in svg.

- Fixes the node select problem.

--- community/browser/lib/visualization/init.coffee
+++ community/browser/lib/visualization/init.coffee
@@ -29,6 +29,7 @@

       text.enter().append('text')
       .attr('text-anchor': 'middle')
+      .attr('pointer-events': 'none')

       text
       .text((line) -> line.text)
@@ -88,6 +89,7 @@

       texts.enter().append("text")
       .attr("text-anchor": "middle")
+      .attr('pointer-events': 'none')

       texts
       .attr('font-size', (rel) -> viz.style.forRelationship(rel).get('font-size'))


find body class once

find body class once

--- components/email/client/editorial_signup.coffee
+++ components/email/client/editorial_signup.coffee
@@ -87,13 +87,7 @@
   showEditorialCTA: ->
     @test = splitTest('editorial_cta_banner')
     @outcome = @test.outcome()
-    if @outcome is 'banner' and not $('body').hasClass('body-transparent-header')
-      @$('#main-layout-container').css('margin-top', '53px').before editorialCTABannerTemplate
-        mode: @outcome
-        email: sd.CURRENT_USER?.email or ''
-        image: sd.EDITORIAL_CTA_BANNER_IMG
-      setTimeout((=> @$('.articles-es-cta--banner').height(315).attr('data-state', 'in') ), 1000)
-    else if @outcome is 'modal' or $('body').hasClass('body-transparent-header')
+    if @outcome is 'modal' or $('body').hasClass('body-transparent-header')
       @outcome = 'modal'
       @$('#modal-container').append editorialCTABannerTemplate
         mode: @outcome
@@ -102,6 +96,12 @@
       @$('#articles-show').waypoint (direction) =>
         if direction is 'down'
           setTimeout((=> @$('.articles-es-cta--banner').attr('data-state', 'in').css('opacity', 1)), 2000)
+    else if @outcome is 'banner'
+      @$('#main-layout-container').css('margin-top', '53px').before editorialCTABannerTemplate
+        mode: @outcome
+        email: sd.CURRENT_USER?.email or ''
+        image: sd.EDITORIAL_CTA_BANNER_IMG
+      setTimeout((=> @$('.articles-es-cta--banner').height(315).attr('data-state', 'in') ), 1000)
     else if @outcome is 'old_modal'
       mediator.on 'auction-reminders:none', @setupCTAWaypoints
     analyticsHooks.trigger('view:editorial-signup', type: @outcome )


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -168,5 +168,10 @@
   equal noAlpha.toString(), "rgba(30, 40, 23, 1)"
   equal withAlpha.toString(), "rgba(29, 49, 50, 0.45)"

+test "#toHex", ->
+  color = Color2(23, 45, 100)
+
+  equal color.toHex(), "#172d64"
+
 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -212,9 +212,9 @@

   color2 = color.grayscale()

-  equal color2.r, 35.7
-  equal color2.g, 35.7
-  equal color2.b, 35.7
+  equal color2.r, 35
+  equal color2.g, 35
+  equal color2.b, 35
   equal color2.a, 1.0

 test "#saturate", ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -180,6 +180,6 @@

   engine.collides(Rectangle(0, 0, 10, 10), null)

-test "#se" ->
+test "#setState" ->

 module()


fix(specs): Empty parameter list

fix(specs): Empty parameter list

--- internal_packages/category-picker/spec/category-picker-spec.cjsx
+++ internal_packages/category-picker/spec/category-picker-spec.cjsx
@@ -40,8 +40,8 @@
     @userCategory = new Category(id: 'id-789', name: null, displayName: "MyCategory", accountId: TEST_ACCOUNT_ID)

     observable = NylasTestUtils.mockObservable([@inboxCategory, @archiveCategory, @userCategory])
-    observable.sort = () => observable
-
+    observable.sort = => observable
+
     spyOn(Categories, "forAccount").andReturn observable
     spyOn(CategoryStore, "getStandardCategory").andReturn @inboxCategory
     spyOn(AccountStore, "accountForItems").andReturn @account


added german translations (#4154)

added german translations (#4154)

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -1427,25 +1427,25 @@
     cs4_curriculum_desc: "Umfang und Reihenfolge, Unterrichtspläne und mehr für Kurs 4."
     cs1_pairprogramming: "Paar-Programmieraktivität"
     cs1_pairprogramming_desc: "Zeige deinen Schülern eine paarweise Programmierübung. So lernen sie, besser zuzuhören und zu kommunizieren."
-#    wd1: "Web Development 1"
-#    wd1_headlines: "Headlines & Headers Activity"
-#    wd1_headlines_example: "View sample solution"
+    wd1: "Web Entwicklung 1"
+    wd1_headlines: "Schlagzeilen & Kopfzeilen Aktivität"
+    wd1_headlines_example: "Beispielprobe ansehen"
 #    wd1_headlines_desc: "Why are paragraph and header tags important? Use this activity to show how well-chosen headers make web pages easier to read. There are many correct solutions to this!"
-#    wd1_html_syntax: "HTML Syntax Guide"
+    wd1_html_syntax: "HTML Syntax-Anleitung"
 #    wd1_html_syntax_desc: "One-page reference for the HTML style students will learn in Web Development 1."
-#    wd1_css_syntax: "CSS Syntax Guide"
+    wd1_css_syntax: "CSS Syntax-Anleitung"
 #    wd1_css_syntax_desc: "One-page reference for the CSS and Style syntax students will learn in Web Development 1."
-#    wd2: "Web Development 2"
-#    wd2_jquery_syntax: "jQuery Functions Syntax Guide"
+     wd2: "Web Entwicklung 2"
+     wd2_jquery_syntax: "jQuery Funktionen Syntax-Anleitung"
 #    wd2_jquery_syntax_desc: "One-page reference for the jQuery functions students will learn in in Web Development 2."
-#    wd2_quizlet_worksheet: "Quizlet Planning Worksheet"
-#    wd2_quizlet_worksheet_instructions: "View instructions & examples"
+    wd2_quizlet_worksheet: "Quizlet Planungs-Arbeitsblatt"
+    wd2_quizlet_worksheet_instructions: "Anleitung und Beispiele ansehen"
 #    wd2_quizlet_worksheet_desc: "Before your students build their personality quiz project at the end of Web Development 2, they should plan out their quiz questions, outcomes and responses using this worksheet. Teachers can distribute the instructions and examples for students to refer to."
     student_overview: "Übersicht"
-#    student_details: "Student Details"
-#    student_name: "Student Name"
-#    no_name: "No name provided."
-#    no_username: "No username provided."
+    student_details: "Studenten Details"
+    student_name: "Studenten Name"
+    no_name: "Kein Name angegeben."
+    no_username: "Kein Benutzername angegeben"
     no_email: "Der Schüler hat keine E-Mail-Adresse eingestellt."
     student_profile: "Schülerprofil"
     playtime_detail: "Spielzeit Details"


Update es-ES.coffee

Update es-ES.coffee

Traslate

--- app/locale/es-ES.coffee
+++ app/locale/es-ES.coffee
@@ -77,7 +77,7 @@
   nav:
     play: "Jugar" # The top nav bar entry where players choose which levels to play
     community: "Comunidad"
-#    courses: "Courses"
+    courses: "Cursos"
     editor: "Editor"
     blog: "Blog"
     forum: "Foro"
@@ -94,10 +94,10 @@
     twitter_follow: "Síguenos en Twitter"
     teachers: "Profesores"
     careers: "Carreras"
-#    facebook: "Facebook"
-#    twitter: "Twitter"
+    facebook: "Facebook"
+    twitter: "Twitter"
 #    create_a_class: "Create a Class"
-#    other: "Other"
+    other: "Otros"
 #    learn_to_code: "Learn to Code!"

   modal:


Update pt-PT.coffee

Update pt-PT.coffee

--- app/locale/pt-PT.coffee
+++ app/locale/pt-PT.coffee
@@ -51,11 +51,11 @@
     players: "jogadores" # Hover over a level on /play
     hours_played: "horas jogadas" # Hover over a level on /play
     items: "Itens" # Tooltip on item shop button from /play
-#    unlock: "Unlock" # For purchasing items and heroes
-#    confirm: "Confirm"
-#    owned: "Owned" # For items you own
-#    locked: "Locked"
-#    skills_granted: "Skills Granted" # Property documentation details
+    unlock: "Desbloquear" # For purchasing items and heroes
+    confirm: "Confirmar"
+    owned: "Obtido" # For items you own
+    locked: "Bloqueado"
+    skills_granted: "Habilidades Garantidas" # Property documentation details
     heroes: "Heróis" # Tooltip on hero shop button from /play
     achievements: "Conquistas" # Tooltip on achievement list button from /play
     account: "Conta" # Tooltip on account button from /play
@@ -109,12 +109,12 @@
     recovery_sent: "E-mail de recuperação enviado."

   items:
-#    primary: "Primary"
-#    secondary: "Secondary"
+    primary: "Primários"
+    secondary: "Secundários"
     armor: "Armadura"
     accessories: "Acessórios"
     misc: "Vários"
-#    books: "Books"
+    books: "Livros"

   common:
     loading: "A carregar..."
@@ -183,7 +183,7 @@
   play_level:
     done: "Concluir"
     home: "Início" # Not used any more, will be removed soon.
-#    level: "Level" # Like "Level: Dungeons of Kithgard"
+    level: "Nível" # Like "Level: Dungeons of Kithgard"
     skip: "Saltar"
     game_menu: "Menu do Jogo"
     guide: "Guia"
@@ -296,11 +296,11 @@

   inventory:
     choose_inventory: "Equipar Itens"
-#    equipped_item: "Equipped"
-#    available_item: "Available"
-#    should_equip: "(double-click to equip)"
-#    equipped: "(equipped)"
-#    locked: "(locked)"
+    equipped_item: "Equipado"
+    available_item: "Disponível"
+    should_equip: "(clica duas vezes para equipares)"
+    equipped: "(equipado)"
+    locked: "(bloqueado)"

   choose_hero:
     choose_hero: "Escolhe o Teu Herói"
@@ -317,9 +317,9 @@
     attack: "Ataque" # Can also translate as "Attack"
     health: "Vida"
     speed: "Velocidade"
-#    regeneration: "Regeneration"
-#    range: "Range" # As in "attack or visual range"
-#    blocks: "Blocks" # As in "this shield blocks this much damage"
+    regeneration: "Regeneração"
+    range: "Alcance" # As in "attack or visual range"
+    blocks: "Bloqueia" # As in "this shield blocks this much damage"
     skills: "Habilidades"

   save_load:


Yeti follows you when you tell him to

Yeti follows you when you tell him to

--- app/models/Event.coffee
+++ app/models/Event.coffee
@@ -69,6 +69,7 @@
                 "Follow me and everything will be alright!"
               ]
             dm.showDialog data, ->
+              that.y.moveToPosition(player.position, 30)
               that.unblockInput()


Fix copyurlview for the latest folder schema.

--- client/app/MainApp/filetree/itemsubviews/copyurlview.coffee
+++ client/app/MainApp/filetree/itemsubviews/copyurlview.coffee
@@ -3,21 +3,24 @@
   constructor: ->
     super

-    @path = FSHelper.plainPath @getData().path
-    @publicPath = @path.replace \
-      ////home/(.*)/Web/(.*)///, "http://$1.#{KD.config.userSitesDomain}/$2"
+    {path}      = @getData()
+    hostname    = FSHelper.getVMNameFromPath path
+    @publicPath = FSHelper.isPublicPath path

     @inputUrlLabel  = new KDLabelView
       cssClass      : 'public-url-label'
       title         : 'Public URL'
-      click         :=>
-        @focusAndSelectAll()
+      click         : => @focusAndSelectAll()

     @inputUrl       = new KDInputView
       label         : @inputUrlLabel
       cssClass      : 'public-url-input'

-    @inputUrl.setValue @publicPath
+    KD.getSingleton('vmController').fetchVMDomains hostname, (err, domains)=>
+      if domains?.length > 0 and not err
+        @publicPath = domains.first
+        @inputUrl.setValue (FSHelper.plainPath path).replace \
+          ////home/(.*)/Web/(.*)///, "http://#{@publicPath}/$2"

   focusAndSelectAll:->
     @inputUrl.setFocus()
@@ -28,9 +31,7 @@
     super

   pistachio:->
-    hasNoPublicPath = @publicPath is @path
-
-    if hasNoPublicPath
+    unless @publicPath
       """

This #{@getData().type} can not be reached over a public URL

       """


Sidebar: Rename unread message count and indicator properties

--- client/app/lib/activity/sidebar/sidebarworkspaceitem.coffee
+++ client/app/lib/activity/sidebar/sidebarworkspaceitem.coffee
@@ -31,9 +31,9 @@
     href   = groupifyLink path
     @title = new CustomLinkView { href, title }

-    @unreadCount = new KDCustomHTMLView
-      tagName    : 'cite'
-      cssClass   : 'count hidden'
+    @unreadIndicator = new KDCustomHTMLView
+      tagName  : 'cite'
+      cssClass : 'count hidden'

     iconOptions = {}

@@ -67,17 +67,17 @@
       @emit 'WorkspaceDeleted', wsId

-  setUnreadCount: (unreadCount = 0) ->
+  setUnreadCount: (count = 0) ->

-    @count = unreadCount
+    @unreadCount = count

-    if unreadCount is 0
-      @unreadCount.hide()
+    if count > 0
+      @unreadIndicator.updatePartial count
+      @unreadIndicator.show()
+      @setClass 'unread'
+    else
+      @unreadIndicator.hide()
       @unsetClass 'unread'
-    else
-      @unreadCount.updatePartial unreadCount
-      @unreadCount.show()
-      @setClass 'unread'

   pistachio: ->
@@ -85,6 +85,5 @@
       <figure></figure>
       {{> @title}}
       {{> @settingsIcon}}
-      {{> @unreadCount}}
+      {{> @unreadIndicator}}
     """
-


up.change() now resolves "&" in { target } selector

--- lib/assets/javascripts/unpoly/classes/change/from_content.coffee
+++ lib/assets/javascripts/unpoly/classes/change/from_content.coffee
@@ -22,7 +22,7 @@
     if u.isElementish(target)
       target = { target: e.toSelector(target) }
     else if u.isString(target)
-      target = { target }
+      target = { target: e.resolveSelector(target, base.origin) }

     return u.merge(base, target)


Comment out tests for `-` that are failing due to `k`

I don’t want to delete these tests because they will be useful to have when `k` (and correspondingly `j`) is fixed. Otherwise, they would have to be rewritten at that time, or they would not get written and `-` and `+` would not be as well-tested.

The comments explaining why these tests are commented out and saying when they can be re-enabled should protect these tests from being left disabled when they shouldn’t be.

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -544,7 +544,8 @@
         it "moves the cursor to the first character of the previous line", ->
           expect(editor.getCursorScreenPosition()).toEqual [0, 0]

-      describe "as a selection", ->
+      # commented out because this fails due to a bug in `k`; re-enable when `k` is fixed
+      xdescribe "as a selection", ->
         beforeEach ->
           keydown('d')
           keydown('-')
@@ -562,7 +563,8 @@
         it "moves to the first character of the previous line (directly above)", ->
           expect(editor.getCursorScreenPosition()).toEqual [1, 2]

-      describe "as a selection", ->
+      # commented out because this fails due to a bug in `k`; re-enable when `k` is fixed
+      xdescribe "as a selection", ->
         beforeEach ->
           keydown('d')
           keydown('-')
@@ -580,7 +582,8 @@
         it "moves the cursor to the first character of the previous line", ->
           expect(editor.getCursorScreenPosition()).toEqual [1, 2]

-      describe "as a selection", ->
+      # commented out because this fails due to a bug in `k`; re-enable when `k` is fixed
+      xdescribe "as a selection", ->
         beforeEach ->
           keydown('d')
           keydown('-')
@@ -602,7 +605,8 @@
         it "moves the cursor to the first character of that many lines previous", ->
           expect(editor.getCursorScreenPosition()).toEqual [1, 0]

-      describe "as a selection", ->
+      # commented out because this fails due to a bug in `k`; re-enable when `k` is fixed
+      xdescribe "as a selection", ->
         beforeEach ->
           keydown('d')
           keydown('3')
@@ -615,6 +619,7 @@
   describe "the + keybinding (which should also be equivalent to the enter keybinding)", ->
     # TODO copy the tests for the - keybinding into here, and then
     #  change the values to expect downwards instead of upwards movement
+    # to ease editing, copy the tests only after enabling all disabled tests for -

   describe "the gg keybinding", ->
     beforeEach ->