test git-diff

test git-diff

--- spec/models/git-diff-spec.coffee
+++ spec/models/git-diff-spec.coffee
@@ -4,26 +4,46 @@

 textEditor =
   getPath: -> pathToRepoFile
+  getURI: -> pathToRepoFile
+
+diffPane =
+  splitRight: -> undefined

 describe "GitDiff", ->
   beforeEach ->
+    atom.config.set 'git-plus.includeStagedDiff', true
     spyOn(atom.workspace, 'getActiveTextEditor').andReturn textEditor
+    spyOn(atom.workspace, 'open').andReturn Promise.resolve(textEditor)
+    spyOn(git, 'cmd').andReturn Promise.resolve('diffs')
+    waitsForPromise ->
+      GitDiff repo, file: pathToRepoFile
+

   describe "when git-plus.includeStagedDiff config is true", ->
     it "calls git.cmd and specifies 'HEAD'", ->
-      atom.config.set 'git-plus.includeStagedDiff', true
-      spyOn(git, 'cmd').andReturn Promise.resolve('diffs')
-      GitDiff repo, file: pathToRepoFile
       expect('HEAD' in git.cmd.mostRecentCall.args[0]).toBe true
-
-  describe "when git-plus.wordDiff config is true", ->
-    it "calls git.cmd and uses '--word-diff' flag", ->
-      atom.config.set 'git-plus.wordDiff', true
-      spyOn(git, 'cmd').andReturn Promise.resolve('diffs')
-      GitDiff repo, file: pathToRepoFile
-      expect('--word-diff' in git.cmd.mostRecentCall.args[0]).toBe true
-
-  describe "when a file is not specified", ->
-    it "checks for the current open file", ->
-      GitDiff repo
-      expect(atom.workspace.getActiveTextEditor).toHaveBeenCalled()
+#
+#   describe "when git-plus.wordDiff config is true", ->
+#     it "calls git.cmd and uses '--word-diff' flag", ->
+#       atom.config.set 'git-plus.wordDiff', true
+#       spyOn(git, 'cmd').andReturn Promise.resolve('diffs')
+#       GitDiff repo, file: pathToRepoFile
+#       expect('--word-diff' in git.cmd.mostRecentCall.args[0]).toBe true
+#
+#   describe "when a file is not specified", ->
+#     it "checks for the current open file", ->
+#       GitDiff repo
+#       expect(atom.workspace.getActiveTextEditor).toHaveBeenCalled()
+#
+# describe "when git-plus.openInPane config is true", ->
+#   beforeEach ->
+#     atom.config.set 'git-plus.openInPane', true
+#     spyOn(atom.workspace, 'open').andReturn Promise.resolve textEditor
+#     spyOn(atom.workspace, 'paneForURI').andReturn textEditor
+#     spyOn(diffPane, 'splitRight')
+#     waitsForPromise ->
+#       GitDiff(repo)
+#
+#   describe "when git-plus.splitPane config is not set", ->
+#     it "defaults to splitRight", ->
+#       expect(diffPane.splitRight).toHaveBeenCalled()


style(matchers): add white space between division

--- specs/units/matchers.spec.coffee
+++ specs/units/matchers.spec.coffee
@@ -83,7 +83,7 @@
       it -> shouldnt beWithin(1.1).of(13)

   context 'with floats', ->
-    subject -> 22.0/7
+    subject -> 22.0 / 7

     context 'integer delta', ->
       it -> should beWithin(1).of(3.0)
@@ -105,7 +105,7 @@
       it -> shouldnt beWithin(0.001).of(10.1)

   context 'with float and integer', ->
-    subject -> 22.0/7
+    subject -> 22.0 / 7

     context 'integer delta', ->
       it -> should beWithin(1).of(3)


First draft at nested selectors

First draft at nested selectors

--- src/jsontocss.coffee
+++ src/jsontocss.coffee
@@ -2,16 +2,28 @@

 JSONtoCSS = (object, options) ->

-  JSONtoString = do (object) ->
+  JSONtoString = (object) ->
     cssContent = ''
+    queue = []

     for own key, value of object
       if typeof object is 'object'
         for own subKey, subValue of value
           cssContent += "#{key} {"
           if typeof subValue is 'object'
-            cssContent += JSONtoString subValue
+            console.log subValue
+            newObject = {}
+            newObject[subKey] = subValue
+            queue.push JSONtoString newObject
           else
             cssContent += "#{subKey}: #{subValue};"
           cssContent += '}'
-     cssContent+
+
+
+       for q in queue
+         cssContent += "#{key} #{q}"
+
+     cssContent
+
+  JSONtoString object


do not use uploadcare.__exports in tests

--- test/dummy/spec/javascripts/spec.js.coffee
+++ test/dummy/spec/javascripts/spec.js.coffee
@@ -7,7 +7,8 @@
 # = require_tree ./
 # = require_tree ./fixtures/data

-uploadcare = uploadcare.__exports;
+# FIXME: tests should use uploadcare.__exports like regular dev env.
+
 window.$ = window.jQuery = uploadcare.jQuery

 window.UPLOADCARE_PUBLIC_KEY = 'demopublickey'


resolving merge conflicts

resolving merge conflicts

--- src/netrunner.coffee
+++ src/netrunner.coffee
@@ -52,7 +52,6 @@
   text += "*Type*: #{card.type} - #{card.subtype}\n"
   if card.type == "Agenda"
     text += "*Adv/Pts*: #{card.advancementcost} \/#{card.agendapoints}\n"
-
   if card.type == "ICE" || card.type == "Upgrade" || card.type == "Asset"
     text += "*Rez Cost*: #{card.cost}\n"
     if card.type == "ICE"


conditional

conditional

--- models/article.coffee
+++ models/article.coffee
@@ -57,7 +57,7 @@
           footerArticles: footerArticles
           slideshowArtworks: slideshowArtworks
           vertical: vertical
-          featuredVerticalArticles: verticalArticles.featuredToVertical(vertical)
+          featuredVerticalArticles: verticalArticles.featuredToVertical(vertical) if vertical
         )

   isTopTier: ->


adds collecting institution to partner name method

--- models/artwork.coffee
+++ models/artwork.coffee
@@ -163,7 +163,9 @@
     @get('collecting_institution')?.length > 0

   partnerName: ->
-    if @has 'partner'
+    if @hasCollectingInstitution()
+      @get('collecting_institution')
+    else if @has 'partner'
       @get('partner').name
     else
       ''


Fix ui navigation white blinking

Fix ui navigation white blinking

--- app/src/app.coffee
+++ app/src/app.coffee
@@ -28,8 +28,9 @@

     navigate: (layoutName, viewController) ->
       @router.once 'routed', (event, data) =>
-        oldUrl = if data.oldUrl? then data.oldUrl.parseAsUrl() else {hash: '', pathname: '', params: ''}
+        oldUrl = if @_lastUrl? then @_lastUrl.parseAsUrl() else {hash: '', pathname: '', params: -> ''}
         newUrl = data.url.parseAsUrl()
+        @_lastUrl = newUrl
         @currentUrl = data.url

         controller = null
@@ -48,7 +49,7 @@
           @_navigationController.push new viewController()
           @_navigationController.render @_navigationControllerSelector()
         else
-          if @_navigationController.topViewController().constructor.name == viewController.name and oldUrl.pathname == newUrl.pathname and newUrl.params == oldUrl.params # Check if only hash part of url change
+          if @_navigationController.topViewController().constructor.name == viewController.name and oldUrl.pathname == newUrl.pathname and _.isEqual(newUrl.params(), oldUrl.params()) # Check if only hash part of url change
             @_navigationController.handleAction(actionName, parameters)
           else
             controller = new viewController(newUrl.params())


enh: use capture

enh: use capture

--- application.coffee
+++ application.coffee
@@ -477,6 +477,7 @@
   updatePopulation()

 doCanvasMouseDown = (e) ->
+  canvas.setCapture true
   e.preventDefault()
   [x,y] = getCanvasCursorPosition e, canvas
   unless (e.button is 0) and not e.shiftKey
@@ -748,14 +749,55 @@
     @timer = null
     @callback()

+GENERIC_TF_TEMPLATE="""//Generic transistion function, coded in JS
+{
+  //number of states
+  'states': 2,
+
+  //Neighbors sum calculation. By default - sum of all.
+  //'plus': function(s,x){ return s+x; },
+  //'plusInitial': 0,
+
+  //Transition function. Takes current state and sum, returns new state.
+  'next': function(x, s){
+    if (s==2) return x;
+    if (s==3) return 1;
+    return 0;
+  }
+}
+"""
+
+binaryTransitionFunc2GenericCode = (binTf) ->
+  row2condition = (row) -> ("s==#{sum}" for nextValue, sum in row when nextValue).join(" || ")
+
+  conditionBorn = row2condition binTf.table[0]
+  conditionStay = row2condition binTf.table[1]
+
+  code = ["""//Automatically generated code for binary rule #{binTf}
+{
+    //number of states
+    'states': 2,
+
+    //Neighbors sum calculation is default. Code for reference.
+    //'plus': function(s,x){ return s+x; },
+    //'plusInitial': 0,
+
+    //Transition function. Takes current state and sum, returns new state.
+    'next': function(x, s){
+        if (x==1 && (#{conditionStay})) return 1;
+        if (x==0 && (#{conditionBorn})) return 1;
+        return 0;
+     }
+}"""]
+
 # ============ Bind Events =================
 E("btn-reset").addEventListener "click", doReset
 E("btn-step").addEventListener "click", doStep
 #E("canvas").addEventListener "click", doCanvasClick
-E("canvas").addEventListener "mousedown", doCanvasMouseDown, true
+E("canvas").addEventListener "mousedown", doCanvasMouseDown
 E("canvas").addEventListener "mouseup", doCanvasMouseUp
-E("canvas").addEventListener "mousemove", doCanvasMouseMove, true
-E("canvas").addEventListener "mousedrag", doCanvasMouseMove, true
+E("canvas").addEventListener "mousemove", doCanvasMouseMove
+E("canvas").addEventListener "mousedrag", doCanvasMouseMove
 E("btn-set-rule").addEventListener "click", doSetRule
 E("btn-set-rule-generic").addEventListener "click", (e)->
   doSetRuleGeneric()
@@ -772,48 +814,6 @@
 E('btn-disable-generic-rule').addEventListener 'click', doDisableGeneric
 E('btn-export-close').addEventListener 'click', doExportClose
 #initialize
-GENERIC_TF_TEMPLATE="""//Generic transistion function, coded in JS
-{
-  //number of states
-  'states': 2,
-
-  //Neighbors sum calculation. By default - sum of all.
-  //'plus': function(s,x){ return s+x; },
-  //'plusInitial': 0,
-
-  //Transition function. Takes current state and sum, returns new state.
-  'next': function(x, s){
-    if (s==2) return x;
-    if (s==3) return 1;
-    return 0;
-  }
-}
-"""
-
-binaryTransitionFunc2GenericCode = (binTf) ->
-  row2condition = (row) -> ("s==#{sum}" for nextValue, sum in row when nextValue).join(" || ")
-
-  conditionBorn = row2condition binTf.table[0]
-  conditionStay = row2condition binTf.table[1]
-
-  code = ["""//Automatically generated code for binary rule #{binTf}
-{
-    //number of states
-    'states': 2,
-
-    //Neighbors sum calculation is default. Code for reference.
-    //'plus': function(s,x){ return s+x; },
-    //'plusInitial': 0,
-
-    //Transition function. Takes current state and sum, returns new state.
-    'next': function(x, s){
-        if (x==1 && (#{conditionStay})) return 1;
-        if (x==0 && (#{conditionBorn})) return 1;
-        return 0;
-     }
-}"""]
-
-
 if not E('generic-tf-code').value
   E('generic-tf-code').value = GENERIC_TF_TEMPLATE

@@ -833,12 +833,9 @@
   '3': (e) -> paintStateSelector.setState 3
   '4': (e) -> paintStateSelector.setState 4
   '5': (e) -> paintStateSelector.setState 5
-  #M
   'M': doMemorize
-  #U
   'U': doRemember
   'UA': doClearMemory
-  #H
   'H': doNavigateHome
   'HS': doStraightenView


Changes on AJAX module

Changes on AJAX module

--- src/quo.ajax.coffee
+++ src/quo.ajax.coffee
@@ -1,5 +1,5 @@
 ###
-  QuoJS 2.0
+  QuoJS 2.0.3
   (c) 2011, 2012 Javi Jiménez Villar (@soyjavi)
   http://quojs.tapquo.com
 ###
@@ -72,7 +72,7 @@
                 , settings.timeout)
             xhr
         else
-            console.error "ERROR: Unable to make jsonp synchronous call."
+            console.error "QuoJS.ajax: Unable to make jsonp synchronous call."

     $$.get = (url, data, success, dataType) ->
         url += $$.serializeParameters(data)
@@ -111,7 +111,7 @@
                 _xhrSuccess _parseResponse(xhr, settings), xhr, settings
                 return
         else
-            _xhrError "QuoJS » $$.ajax", xhr, settings
+            _xhrError "QuoJS.ajax: Unsuccesful request", xhr, settings
             return

     _xhrSuccess = (response, xhr, settings) ->
@@ -123,8 +123,8 @@
         return

     _xhrHeaders = (xhr, settings) ->
-        settings.headers["Content-Type"] = settings.contentType  if settings.contentType
-        settings.headers["Accept"] = MIME_TYPES[settings.dataType]  if settings.dataType
+        settings.headers["Content-Type"] = settings.contentType if settings.contentType
+        settings.headers["Accept"] = MIME_TYPES[settings.dataType] if settings.dataType
         for header of settings.headers
             xhr.setRequestHeader header, settings.headers[header]
         return
@@ -132,7 +132,7 @@
     _xhrTimeout = (xhr, settings) ->
         xhr.onreadystatechange = {}
         xhr.abort()
-        _xhrError "QuoJS » $$.ajax : timeout exceeded", xhr, settings
+        _xhrError "QuoJS.ajax: Timeout exceeded", xhr, settings
         return

     _parseResponse = (xhr, settings) ->
@@ -143,7 +143,7 @@
                     response = JSON.parse(response)
                 catch error
                     response = error
-                    _xhrError "Parse Error", xhr, settings
+                    _xhrError "QuoJS.ajax: Parse Error", xhr, settings
             else response = xhr.responseXML    if settings.dataType is "xml"
         response


Overload options.styles instead of new key

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -83,7 +83,6 @@
     this.addStyles(DEFAULT_STYLES)
     # Ensure user specified styles are added after modules'
     _.defer(_.bind(this.addStyles, this, @options.styles)) if @options.styles?
-    _.defer(_.bind(this.addStyles, this, @options.stylesheet)) if @options.stylesheet?

   addContainer: (className, before = false) ->
     refNode = if before then @root else null
@@ -100,13 +99,13 @@
       style.appendChild(@root.ownerDocument.createTextNode(css))
       @root.ownerDocument.head.appendChild(style)
     else if typeof css == 'string'
-      # external stylesheet
       link = @root.ownerDocument.createElement('link')
       DOM.setAttributes(link,
         type: 'text/css'
         rel: 'stylesheet'
-        href: css
+        href: css
       )
       @root.ownerDocument.head.appendChild(link)
-
+
+
 module.exports = Renderer


work without www dir in project

work without www dir in project

--- src/steroids.coffee
+++ src/steroids.coffee
@@ -48,7 +48,7 @@
     return contents

   detectSteroidsProject: ->
-    return fs.existsSync(paths.application.configDir) and fs.existsSync(paths.application.wwwDir)
+    return fs.existsSync(paths.application.configDir) and (fs.existsSync(paths.application.appDir) or fs.existsSync(paths.application.wwwDir))

   debug: (options = {}) =>
     return unless steroidsCli.options.debug


API support for omitting styles in renderSVG*

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -727,7 +727,7 @@
     svg.setAttribute 'version', '1.1'
     #svg.appendChild defs = doc.createElementNS SVGNS, 'defs'
     ## <style> tags for CSS
-    for style in styles.styles
+    for style in styles?.styles ? []
       svg.appendChild styleTag = doc.createElementNS SVGNS, 'style'
       styleTag.textContent = style.css
     ## Look up all symbols in the drawing.


Clean up a little better on preroll abort timer and listener count

* Abort timer needs to be cleared regardless of the preroll response code
* Try to be a little more thorough about the situations in which we
  uncount listeners

--- src/podroller.coffee
+++ src/podroller.coffee
@@ -282,6 +282,9 @@
             prerollEnd      = prerollStart + (predata?.length || 0)
             fileStart       = prerollEnd

+            _decListener = _.once =>
+                @listeners--
+
             # write id3?
             if k.id3? && rangeStart < k.id3.length
                 debug "#{req.count}: Writing id3 of ", k.id3.length, rangeStart, rangeEnd
@@ -326,15 +329,19 @@
                 # connection aborted.  destroy our stream
                 debug "#{req.count}: (connection end) wrote #{ res.socket?.bytesWritten } bytes. #{@listeners} active downloads."
                 rstream?.destroy() if rstream?.readable
+                _decListener()
+

             req.connection.on "close", =>
                 debug "#{req.count}: (conn close) in close. #{@listeners} active downloads."
-                @listeners--
+                _decListener()

             req.connection.setTimeout 30*1000, =>
                 # handle connection timeout
+                debug "#{req.count}: Connection timeout. Ending."
                 res.end()
                 rstream?.destroy() if rstream?.readable
+                _decListener()

     #----------

@@ -344,7 +351,6 @@
         cb = _u.once cb

         # short-circuit if we're missing any options
-        debug "#{count}: preroller opts is ", @options.preroll, key
         unless @options.preroll?.server &&
         @options.preroll?.key &&
         @options.preroll?.path
@@ -376,6 +382,10 @@
         debug "firing preroll request", count
         req = http.get opts, (rres) =>
             debug "#{count}: got preroll response ", rres.statusCode
+
+            # clear our abort timer
+            clearTimeout req_t
+
             if rres.statusCode == 200
                 # collect preroll and return it so length can be computed

@@ -385,8 +395,6 @@

                 buffers = []
                 buf_len = 0
-
-                clearTimeout req_t

                 rres.on "readable", =>
                     while chunk = rres.read()


Fix problemas de this

Fix problemas de this

--- src/processor.coffee
+++ src/processor.coffee
@@ -6,8 +6,9 @@
   class Processor extends EventEmitter

     constructor: (@runner) ->
+      super()

-    process: (context, message) ->
+    process: (context, message) =>
       @emit "started", { context, message }
       Promise.method(@runner) message
       .tap => @emit "successful", { context, message }


fix segment symbol placement

fix segment symbol placement

--- src/ui/utils.coffee
+++ src/ui/utils.coffee
@@ -38,5 +38,4 @@
           coordinates: target
         }
       }
-
-
+    prev = point


Fixed variables

Fixed variables


Add toURIString() method to PostType for encoding fragment

--- src/tent-client.js.coffee
+++ src/tent-client.js.coffee
@@ -44,6 +44,9 @@

     toStringWithoutFragment: =>
       "#{@base}/v#{@version}"
+
+    toURIString: =>
+      "#{@base}/v#{@version}##{encodeURIComponent(@fragment || '')}"

   class @HTTP
     @MEDIA_TYPES = {


add package to atom

add package to atom

--- atom/packages.cson
+++ atom/packages.cson
@@ -1,6 +1,8 @@
 packages: [
   "atom-beautify"
+  "autocomplete-php"
   "file-icons"
+  "highlight-selected"
   "linter"
   "minimap"
   "package-sync"


remove ctags add symbols tree view

remove ctags add symbols tree view

--- atom/packages.cson
+++ atom/packages.cson
@@ -4,7 +4,6 @@
   "aligner-php"
   "aligner-ruby"
   "aligner-scss"
-  "atom-ctags"
   "atom-ternjs"
   "autocomplete-clang"
   "autocomplete-elixir"
@@ -79,6 +78,7 @@
   "sort-lines"
   "ssh-config"
   "swap-selection"
+  "symbols-tree-view"
   "toggle-quotes"
   "turbo-javascript"
   "underscorejs-snippets"


Add atom teletype

Add atom teletype

--- atom/packages.cson
+++ atom/packages.cson
@@ -39,4 +39,5 @@
   "ssh-config"
   "svg-preview"
   "tabs-to-spaces"
+  "teletype"
 ]


Make tab stop in apex snippet more useful

--- atom/snippets.cson
+++ atom/snippets.cson
@@ -881,4 +881,4 @@

   'www to apex':
     'prefix': 'apex'
-    'body': 'RewriteCond %{HTTP_HOST} ^www\\\\.(.+)$ [NC]\nRewriteRule ^(.*)$ ${1:http}://%1/\\$1 [L,R=301]'
+    'body': 'RewriteCond %{HTTP_HOST} ^www\\\\.(.+)$ [NC]\nRewriteRule ^(.*)$ http$1://%1/\\$1 [L,R=301]'


primus: update client reconnect code to new api.

--- salvus/smc-webapp/client_browser.coffee
+++ salvus/smc-webapp/client_browser.coffee
@@ -113,7 +113,8 @@

     _fix_connection: () =>
         console.log("websocket --_fix_connection... ")
-        @_conn.reconnect()
+        @_conn.end()
+        @_conn.open()

     _cookies: (mesg) =>
         $.ajax(url:mesg.url, data:{id:mesg.id, set:mesg.set, get:mesg.get, value:mesg.value})


roll back async method for moment

roll back async method for moment

--- libraries/metrics/timeAsyncMethod.coffee
+++ libraries/metrics/timeAsyncMethod.coffee
@@ -5,15 +5,14 @@
 	if typeof obj[methodName] != 'function'
 		throw new Error("[Metrics] expected object property '#{methodName}' to be a function")

+	key = "#{prefix}.#{methodName}"
+
 	realMethod = obj[methodName]
-	keys = prefix.split(".")
-	key = keys[0].toLowerCase()
+	startPrefix = prefix.split(".")[0]

-
-	if keys[1]?
-		methodName = "#{keys[1]}_#{methodName}"
-
-	console.log "Async method", keys, methodName
+	endPrefix = prefix.split(".")[1]
+	modifedMethodName = "#{endPrefix}_methodName"
+	console.log "Async method", prefix, key, methodName, modifedMethodName
 	obj[methodName] = (originalArgs...) ->

 		[firstArgs..., callback] = originalArgs
@@ -23,8 +22,11 @@
 				logger.log "[Metrics] expected wrapped method '#{methodName}' to be invoked with a callback"
 			return realMethod.apply this, originalArgs

-		console.log("creating timer for async method")
-		timer = new metrics.Timer(key, null, {method: methodName})
+		console.log("creating timer for async method", prefix, startPrefix, modifedMethodName)
+
+
+		timer = new metrics.Timer(prefix, null, {method: methodName})
+		console.log("changed timer for async method", prefix, methodName, key)

 		realMethod.call this, firstArgs..., (callbackArgs...) ->
 			elapsedTime = timer.done()


Fix image target appearing as the thumb due to the object not being copied correctly.

--- src/ui/models/conv.coffee
+++ src/ui/models/conv.coffee
@@ -29,7 +29,7 @@
             if index - 2 >= 0
                 cont.segment.splice(index - 2, 3)

-            thumburl = url
+            thumburl = new URL(url.toString())
             thumburl.searchParams.set 'url_type', 'FIFE_URL'
             thumburl.searchParams.set 'sz', 's512'


noticed an issue in syncdoc that could cause trouble

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -428,7 +428,7 @@

                 patch = undefined
                 synced_before = false
-                if @_last_sync?
+                if @_last_sync? and @dsync_client?
                     # We have sync'd before.
                     @_presync?() # give syncstring chance to be updated by true live.
                     patch = @dsync_client._compute_edits(@_last_sync, @live())
@@ -595,7 +595,7 @@
                 @codemirror.setOption('readOnly', @readonly)
                 @codemirror1.setOption('readOnly', @readonly)

-                if @_last_sync?
+                if @_last_sync? and @dsync_client?
                     #console.log("We have sync'd before.")
                     synced_before = true
                     patch = @dsync_client._compute_edits(@_last_sync, @live())


fix null bug

fix null bug

--- src/selection.coffee
+++ src/selection.coffee
@@ -41,6 +41,7 @@
   }

 _nativeRangeToRange = (nativeRange) ->
+  return null unless nativeRange?
   start = new ScribePosition(@editor, nativeRange.startContainer, nativeRange.startOffset)
   end = new ScribePosition(@editor, nativeRange.endContainer, nativeRange.endOffset)
   if start.index <= end.index


fix event handlers' binding

fix event handlers' binding

--- static/index.coffee
+++ static/index.coffee
@@ -116,8 +116,9 @@
   edit = (content = contentSrc) ->
     $content.html content
     $content.attr('contentEditable', 'true').focus()
+    $body.addClass 'editing'
     $content.on 'blur', keepFocus
-    $body.addClass 'editing'
+    $body.off 'keydown', maybeStartEditing
     $body.off 'keydown', disableSave
     $body.on 'keydown', maybeSave
     $body.on 'keydown', maybeCancelEditing
@@ -133,8 +134,8 @@
     $editorCode.html content

     $editorCode.attr 'contentEditable', 'false'
+    $body.removeClass 'editing'
     $editorCode.off 'blur', keepFocus
-    $body.removeClass 'editing'
     $body.on 'keydown', maybeStartEditing
     $body.on 'keydown', disableSave
     $body.off 'keydown', maybeSave
@@ -200,7 +201,6 @@

     lock '', 'Loading...'
     $body.off 'keydown', maybeStartEditing
-    $body.off 'keydown', maybeCancelEditing
     $.ajax({
       url: "tastes/#{filename}"
     }).done(done).fail(fail)


Default fileTypes to empty array when undefined

This is the case for certain grammars in the Java
TextMate bundle.

--- src/app/text-mate-grammar.coffee
+++ src/app/text-mate-grammar.coffee
@@ -26,6 +26,7 @@
     @initialRule = new Rule(this, {@scopeName, patterns})
     @repository = {}
     @firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch
+    @fileTypes ?= []

     for name, data of repository
       data = {patterns: [data], tempName: name} if data.begin? or data.match?


Remove extra initialization of scoped properties

--- src/app/text-mate-package.coffee
+++ src/app/text-mate-package.coffee
@@ -74,8 +74,6 @@
   getGrammars: -> @grammars

   loadScopedProperties: ->
-    @scopedProperties = []
-
     for grammar in @getGrammars()
       if properties = @propertiesFromTextMateSettings(grammar)
         selector = syntax.cssSelectorFromScopeSelector(grammar.scopeName)


embed in content display

embed in content display

--- client/landing/app/Applications/Activity.kdapplication/ContentDisplays/StatusUpdate.coffee
+++ client/landing/app/Applications/Activity.kdapplication/ContentDisplays/StatusUpdate.coffee
@@ -1,6 +1,6 @@
 class ContentDisplayStatusUpdate extends ActivityContentDisplay

-  constructor:(options = {}, data)->
+  constructor:(options = {}, data={})->

     options.tooltip or=
       title     : "Status Update"
@@ -8,6 +8,13 @@
       selector  : "span.type-icon"

     super options,data
+
+    embedOptions = $.extend {}, options, {
+      hasDropdown : no
+      delegate : @
+    }
+
+    @embedBox = new EmbedBox embedOptions, data?.link

     origin =
       constructorName  : data.originType
@@ -39,6 +46,27 @@
     @setTemplate @pistachio()
     @template.update()

+    # If there is embed data in the model, use that!
+    if @getData()?.link
+      if not ("embed" in @getData().link.link_embed_hidden_items)
+        @embedBox.show()
+        @embedBox.embedExistingData @getData()?.link?.link_embed, {}
+      else
+        # no need to show stuff if it should not be shown. not even in the code
+        @embedBox.hide()
+        @embedBox.destroy()
+
+    # This will involve heavy load on the embedly servers - every client
+    # will need to make a request.
+    else
+      urls = @$("span.data > a")
+      for url in urls
+        if $(url).attr("href").match(/([a-zA-Z]+\:\/\/)?(\w+:\w+@)?([a-zA-Z\d.-]+\.[A-Za-z]{2,4})(:\d+)?(\/.*\S)?/g)
+          firstUrl = $(url).attr "href"
+
+      if firstUrl then @embedBox.embedUrl firstUrl, {}
+      else @embed
+
     # temp for beta
     # take this bit to comment view
     if @getData().repliesCount? and @getData().repliesCount > 0
@@ -46,6 +74,13 @@
       commentController.fetchAllComments 0, (err, comments)->
         commentController.removeAllItems()
         commentController.instantiateListItems comments
+
+  render:=>
+    super
+    data = @getData().link or {}
+    @embedBox.setEmbedHiddenItems data.link_embed_hidden_items
+    @embedBox.setEmbedImageIndex data.link_embed_image_index
+    @embedBox?.embedExistingData data.link_embed, {}

   pistachio:->

@@ -60,6 +95,7 @@

{{@utils.applyTextExpansions #(body)}}

+        {{> @embedBox}}

              by {{> @author}}


fix path saving

fix path saving

--- client/app/views/konnector.coffee
+++ client/app/views/konnector.coffee
@@ -143,7 +143,9 @@
     getFolderPath: (slug, name) ->
         id = $("##{slug}-#{name}-input").val()
         value = ''
-        value = (path.path for path in @paths when path.id is id)
+        path = _.findWhere(@paths, id: id)
+        value = path.path if path?
+        return value

     # Add a change listener to update the "open folder" link each time


create internal fn for self self doc; change api fn names to be more descriptive

--- client/common/multi-select.coffee
+++ client/common/multi-select.coffee
@@ -12,7 +12,7 @@
   ).property('content', 'parentView.selection', 'parentView.selection.isFulfilled')

   labelPathDidChange: (->
-    labelPath = Ember.get(this, 'parentView.optionLabelPath') # todo: try other syntax
+    labelPath = @get('parentView.optionLabelPath')
     return if not labelPath

     Ember.defineProperty(@, 'label', Ember.computed(->
@@ -51,35 +51,42 @@
     content = @get('content')
     selection = @get('selection')
     selectedIndexes = if content then Ember.EnumerableUtils.indexesOf(content, selection) else [-1]
-    options = @$('input[type=checkbox]')
+    $checkboxes = @$('input[type=checkbox]')

-    if options
-      options.each ->
-        # todo: what is @index for?? and does this function need bound?
-        adjusted = if @index > -1 then @index else -1  # todo: better name (this is for if prompt offset)
-        @selected = Ember.EnumerableUtils.indexOf(selectedIndexes, adjusted) > -1
+    _setCheckboxesInDom = ->
+      $checkboxes.each (indx) ->
+        @checked = Ember.EnumerableUtils.indexOf(selectedIndexes, indx) > -1
+
+    _setCheckboxesInDom() if $checkboxes
   ).observes 'selection.@each'

-  _triggerChange: (->
+  triggerChange: (->
     selection = @get('selection')
     @selectionDidChange() if not Ember.isNone(selection)

-    @_change()
+    @domDidChange()
   ).on('didInsertElement')

-  _change: (->
-    options = this.$('input[type=checkbox]:checked')
+  domDidChange: (->
+    $checkboxes = @$('input[type=checkbox]')
     content = @get('content')
     selection = @get('selection')

     return if not content

-    if options
-      selectedIndexes = options.map((indx) -> indx).toArray()
-      newSelection = content.objectsAt selectedIndexes
+    _findNewSelection = ->
+      selectedIndexes = $checkboxes.map((indx, checkbox) ->
+        indx if $(checkbox).is(':checked')
+      ).toArray()
+      content.objectsAt selectedIndexes

+    _updateSelection = ->
       if Ember.isArray selection
         Ember.EnumerableUtils.replace selection, 0, Ember.get(selection, 'length'), newSelection
       else
         @set('selection', newSelection)
+
+    if $checkboxes
+      newSelection = _findNewSelection()
+      _updateSelection()
   ).on('change')


Changed text on register page

Changed text on register page

--- client/components/Register.coffee
+++ client/components/Register.coffee
@@ -124,7 +124,7 @@
             <form onSubmit={@tryRegister}>
                 <FieldGroup
                     id="username"
-                    label="Имя пользователя"
+                    label="Логин"
                     type="text"
                     setField={@setField}
                     state={@state}/>


Remove extra console logs

Remove extra console logs

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -411,7 +411,6 @@

     app.post '/api/setCommentViewed/:commentId', ensureLoggedIn, wrap (req, res) ->
         comment = await SubmitComment.findById(req.params.commentId)
-        console.log "Set comment viewed ", req.params.commentId, ""+req.user?.userKey(), "" + comment?.userId
         if ""+req.user?.userKey() != "" + comment?.userId
             res.status(403).send('No permissions')
             return
@@ -610,11 +609,9 @@
             name1 = user.name
             name2 = user.name.split(' ').reverse().join(' ')
             re = XRegExp("(^|[^\\p{L}])((#{name1})|(#{name2}))($|[^\\p{L}])", "iug")
-            console.log "Look for strings #{name1}, #{name2}, #{re}"
             context = {}
             el = <StaticRouter context={context}><UserNameRaw user={user} theme={"light"}/></StaticRouter>
             html = renderToString(el)
-            console.log "html=#{html}"
             text = text.replace(re, "$1#{html}$5")
         # assume that if page contains <head>, then it is html
         text = text.replace("<head>", '<head><link rel="stylesheet" href="https://algoprog.ru/bundle.css"/><base href="' + url + '"/>')


Added downloadMaterials api

Added downloadMaterials api

--- server/api/setupApi.coffee
+++ server/api/setupApi.coffee
@@ -13,6 +13,7 @@
 import logger from '../log'

 import {updateAllResults} from '../calculations/updateResults'
+import downloadMaterials from '../cron/downloadMaterials'

 export default setupApi = (app) ->
     app.post '/api/register', (req, res, next) ->
@@ -62,9 +63,15 @@

     app.get '/api/material/:id', (req, res) ->
         res.json(await Material.findById(req.params.id))
-
+
     app.get '/api/updateResults', connectEnsureLogin.ensureLoggedIn(), (req, res) ->
         if not req.user?.admin
             res.status(403).send('No permissions')
         await updateAllResults()
         res.send('OK')
+
+    app.get '/api/downloadMaterials', connectEnsureLogin.ensureLoggedIn(), (req, res) ->
+        if not req.user?.admin
+            res.status(403).send('No permissions')
+        await downloadMaterials()
+        res.send('OK')


repair click on selection

repair click on selection

--- src/typeahead.coffee
+++ src/typeahead.coffee
@@ -10,6 +10,7 @@
 	transclude: true
 	restrict: "E"
 	compile: (element, attributes) ->
+		needHide = false
 		# $el used to be called $input but:
 		# http://walpurgisriot.github.io/blog/2013/12/16/the-worst-thing-about-coffeescript
 		$el = element.find "input"
@@ -22,7 +23,11 @@
 			$input = element.find "input"

 			# we don't use ng-blur to make sure typeahead can have a custom ng-blur too
-			$input[0].addEventListener "blur", -> hideList()
+			$input[0].addEventListener "blur", ->
+				needHide = true
+				$timeout ->
+					hideList() if needHide
+				, 150

 			$timeout ->
 				$ul = element.find "ul"
@@ -62,9 +67,11 @@
 				currentEl.className += " active" if currentEl
 				null
 			select = (el) ->
-				$timeout ->
-					$input.val(el.innerHTML).triggerHandler("input")
-					hideList()
+				if el.tagName is "LI"
+					text = angular.element(el).text()
+					$timeout ->
+						$input.val(text).triggerHandler("input")
+						hideList()
 				null
 			scope.typeaheadKeydown = (evt) ->
 				switch evt.which
@@ -81,7 +88,9 @@
 						currentEl.className = "show" if currentEl
 						currentEl = null
 				null
-			scope.typeaheadSelect = (evt) -> select evt.target
+			scope.typeaheadSelect = (evt) ->
+				needHide = false
+				select evt.target
 			scope.typeaheadKeyup =(evt) ->
 				return if evt.which in [38, 40, 16, 32, 13, 27]
 				# we don't use ng-model (scope.term) here, because it might have been overwritten


removing select count code that was merged in

--- src/components/task-plan/homework/add-exercises.cjsx
+++ src/components/task-plan/homework/add-exercises.cjsx
@@ -11,10 +11,6 @@
 ExerciseDetails  = require '../../exercises/details'
 ExerciseCards    = require '../../exercises/cards'
 ScrollTo         = require '../../scroll-to-mixin'
-
-# SELECT_COUNT is a simple incrementing counter that is passed
-# to the ExerciseCards view and incremented to force it to re-render when needed
-SELECT_COUNT = 0

 AddExercises = React.createClass

@@ -33,7 +29,6 @@
   onShowCardViewClick:    -> @setState(currentView: 'cards')

   onExerciseToggle: (ev, exercise) ->
-    SELECT_COUNT += 1
     if @getExerciseIsSelected(exercise)
       TaskPlanActions.removeExercise(@props.planId, exercise)
     else


sort clusters by default by the one with the newest proposal

--- @client/shared.coffee
+++ @client/shared.coffee
@@ -94,6 +94,10 @@
   all_clusters = _.uniq all_clusters
   all_clusters

+
+
+newest_in_cluster_on_load = {}
+
 window.clustered_proposals = ->
   proposals = fetch '/proposals'
   homepage_list_order = customization 'homepage_list_order'
@@ -103,9 +107,23 @@

   all_clusters = get_all_clusters()

+  # By default sort proposals by the newest of the proposals.
+  # But we'll only do this on page load, so that clusters don't move
+  # around when someone adds a new proposal.
+  if Object.keys(newest_in_cluster_on_load).length == 0
+    for proposal in proposals.proposals
+      cluster = (proposal.cluster or 'Proposals').trim()
+      time = (new Date(proposal.created_at).getTime())
+      if !newest_in_cluster_on_load[cluster] || time > newest_in_cluster_on_load[cluster]
+        newest_in_cluster_on_load[cluster] = time
+
   for cluster in all_clusters
     sort = homepage_list_order.indexOf cluster
-    sort = 9999999999 if sort < 0
+    if sort < 0
+      if newest_in_cluster_on_load[cluster]
+        sort = homepage_list_order.length + ((new Date()).getTime() - newest_in_cluster_on_load[cluster])
+      else
+        sort = 9999999999999

     clusters[cluster] =
       key: "list/#{cluster}"


social sharing icons

social sharing icons

--- @client/shared.coffee
+++ @client/shared.coffee
@@ -109,6 +109,45 @@

 window.POINT_MOUTH_WIDTH = 17

+
+window.FB_SHARE = ReactiveComponent
+  displayName: 'FB_SHARE'
+
+  render: ->
+    IFRAME
+      src: "https://www.facebook.com/plugins/share_button.php?href=#{encodeURI(@props.href)}&layout=button&size=large&mobile_iframe=false&appId=#{@props.appId}&width=73&height=28"
+      width: @props.width or "73"
+      height: @props.height or "28"
+      style:
+        border: 'none'
+        overflow: 'hidden'
+      scrolling: "no"
+      frameborder: "0"
+      allowTransparency: "true"
+
+
+
+window.TWEET = ReactiveComponent
+  displayName: 'Tweet'
+
+  render: ->
+    url = """https://platform.twitter.com/widgets/tweet_button.html?size=l&count=none&id=twitter-widget-0&lang=en"""
+    for url_param in ['hashtags', 'original_referer', 'related', 'text', 'url']
+      if @props[url_param]
+        if url_param == 'url'
+          @props[url_param] = encodeURI(@props[url_param])
+        url += "&#{url_param}=#{@props[url_param]}"
+
+
+    IFRAME
+      src: url
+      scrolling: "no"
+      frameBorder: "0"
+      allowTransparency: "true"
+      className: "twitter-share-button twitter-tweet-button twitter-share-button twitter-count-none"
+      style:
+        width: 80
+        height: 28

 #### Layout


Well that was dumb. Fixes #122

Well that was dumb. Fixes #122

--- tasks/server.coffee
+++ tasks/server.coffee
@@ -102,4 +102,4 @@
           if userConfig.drawRoutes?
             _(app.routes).each (route, name) -> app.routes[name] = []
             userConfig.drawRoutes(app)
-            addBodyParserCallbackToRoutes(app.routes)
+            addBodyParserCallbackToRoutes(app)


URLύX

URLύX

--- AppStoreClient.coffee
+++ AppStoreClient.coffee
@@ -6,7 +6,8 @@

 module.exports = class AppStoreClient

-  API = "http://itunes.apple.com/search?"
+  # API = "http://itunes.apple.com/search?"
+  API = "http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/wa/wsSearch?"
   ANDROID_URL = "http://play.google.com/store/search?q=tsuyoshi+hyuga"
   URL=API#"http://coecorsproxy.appspot.com/"

@@ -87,10 +88,10 @@
       unless ENV_PRODUCTION then Ti.API.debug "obj=#{obj}"

       cur = require("AppStoreClient/AppStoreClient").getTzOff()
-      IOS_URL = unless obj? then "http://itunes.apple.com/search?term=tsuyoshi+hyuga&country=#{cur}&media=software&entity=software"
+      IOS_URL = unless obj? then API+"term=tsuyoshi+hyuga&country=#{cur}&media=software&entity=software"
       else
         obj.country ?= cur
-        "http://itunes.apple.com/search?"+setParameter obj
+        API+setParameter obj
       url = IOS_URL#"https://itunes.apple.com/search?term=tsuyoshi+hyuga&country=#{Ti.Locale.getCurrentCountry()}&media=software&entity=software"
       unless ENV_PRODUCTION then Ti.API.debug "url=#{url}"
       client = Ti.Network.createHTTPClient(


Fixes path to NTB connector script

Fixes path to NTB connector script

--- coffee/nasjonalturbase.api.coffee
+++ coffee/nasjonalturbase.api.coffee
@@ -4,7 +4,7 @@

 express = require 'express'
 app     = express()
-turbase = require '../src/turbase'
+turbase = require './turbase'

 # Logging
 app.use express.logger()


fix german DE_SMALLSHIPSONLY

fix german DE_SMALLSHIPSONLY

--- coffeescripts/cards-common.coffee
+++ coffeescripts/cards-common.coffee
@@ -5773,6 +5773,7 @@
             .replace(/%DE_SCUMONLY%/g, 'Nur für Abschaum & Kriminelle.')
             .replace(/%DE_GOZANTIONLY%/g, 'Nur für Kreuzer der Gozanti-Klasse.')
             .replace(/%DE_LIMITED%/g, 'Limitiert.')
+            .replace(/%DE_SMALLSHIPONLY%/g, 'Nur für kleine Schiffe.')
             .replace(/%FR_HUGESHIPONLY%/g, 'Vaisseau immense uniquement.')
             .replace(/%FR_LARGESHIPONLY%/g, 'Grand vaisseau uniquement.')
             .replace(/%FR_REBELONLY%/g, 'Rebelle uniquement.')


Avoid referencing 'find all' button by class name in spec

--- spec/find-view-spec.coffee
+++ spec/find-view-spec.coffee
@@ -1334,7 +1334,7 @@
     describe "replace all", ->
       describe "when the replace all button is pressed", ->
         it "replaces all matched text", ->
-          $('.find-and-replace .btn-all').click()
+          findView.replaceAllButton.click()
           expect(findView.resultCounter.text()).toEqual('no results')
           expect(editor.getText()).not.toMatch /items/
           expect(editor.getText().match(/\bcats\b/g)).toHaveLength 6


proper html/head tags

proper html/head tags

--- docs/html.coffee
+++ docs/html.coffee
@@ -3,21 +3,21 @@
 domo           = require "../"

 module.exports = (data) ->
-  json = JSON.stringify data, null, 2

   DOCUMENT type: "html",
+    HTML lang: "en",
+      HEAD {},
+        META charset: "utf-8"

-    META charset: "utf-8"
+        SCRIPT type: "text/coffeescript",
+          String readFileSync resolve __dirname, "dom.coffee"

-    SCRIPT type: "text/coffeescript",
-      String readFileSync resolve __dirname, "dom.coffee"
+        SCRIPT {src} for src in [
+          "lib/domo.js"
+          "docs/vendor/prettify.js"
+          "docs/vendor/coffee.js"
+        ]

-    SCRIPT {src} for src in [
-      "lib/domo.js"
-      "docs/vendor/prettify.js"
-      "docs/vendor/coffee.js"
-    ]
-
-    SCRIPT "domo.stats = #{json}"
+        SCRIPT "domo.stats = " + JSON.stringify data, null, 2

   .outerHTML


mark object elements clickable

mark object elements clickable

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -384,7 +384,9 @@
             # Note(gdh1995): Here we should allow special elements to get focus,
             # <select>: latest Chrome refuses `mousedown` event, and we can only
             #     focus it to let user press space to activate the popup menu
-            if clickEl.nodeName.toLowerCase() in ["input", "select"] or clickEl.tabIndex >= 0
+            # <object> & <embed>: for Flash games which have their own key event handlers
+            #     since we have been able to blur them by pressing `Escape`
+            if clickEl.nodeName.toLowerCase() in ["input", "select", "object", "embed"] or clickEl.tabIndex >= 0
               clickEl.focus()
             linkActivator clickEl

@@ -690,6 +692,8 @@
                              (element.readOnly and DomUtils.isSelectable element))
       when "button", "select"
         isClickable ||= not element.disabled
+      when "object", "embed"
+        isClickable = true
       when "label"
         isClickable ||= element.control? and not element.control.disabled and
                         (@getVisibleClickable element.control).length == 0


Too low level of implementation.

Too low level of implementation.

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -26,7 +26,8 @@
     it "does not throw an error even if no operation is pending", ->
       # cancel operation pushes an empty input operation
       # doing this without a pending operation throws an exception
-      expect(-> vimState.operationStack.push(new Input(''))).toThrow()
+      expect(-> editor.normalModeInputView.viewModel.cancel()).toThrow()
+

       # make sure normalModeInputView is created
       keydown('/')


:fire: Redundant lines

:fire: Redundant lines

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -430,7 +430,6 @@
     it "resets the font size to the window's starting font size", ->
       originalFontSize = atom.config.get('editor.fontSize')

-      atom.config.set('editor.fontSize', originalFontSize)
       workspace.increaseFontSize()
       expect(atom.config.get('editor.fontSize')).toBe originalFontSize + 1
       workspace.resetFontSize()
@@ -443,7 +442,6 @@
     it "does nothing if the font size has not been changed", ->
       originalFontSize = atom.config.get('editor.fontSize')

-      atom.config.set('editor.fontSize', originalFontSize)
       workspace.resetFontSize()
       expect(atom.config.get('editor.fontSize')).toBe originalFontSize


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -278,7 +278,15 @@
         path.enter().append("path")
             .attr("class", "pie")
             .attr("fill",(d, i) -> return color i)
-            .data(piedata)
+        #    .data(piedata)
+
+        arcs = svg.selectAll("g.slice")
+            .data(pie)
+            .enter().append("svg:g").attr("class", "slice")
+
+        arcs.append("svg:path").attr("fill", (d, i) -> color i)
+            .attr("fill-opacity", 0.4).attr("d", arc)
+
         #path.transition()
         #    .duration(1500)
         #    #.attrTween("d", pieTween)


newpie experiments

newpie experiments

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -273,7 +273,7 @@
         totalTickets = 0
         for pd in dataSet
             totalTickets += pd.value
-        totalTickets = if totalTickets == 'asdf' then totalTickets else 'No Results'
+        totalTickets = if totalTickets != 0 then totalTickets else 'No Results'
         totalLabel.append('text')
             .text(totalTickets)
             .attr('font-size',radiusi + 'px')


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -281,13 +281,13 @@

         path.transition()
             .duration(1500)
-            .attrTween("d", pieTween)
-            #.attrTween("d", (d,i) ->
-            #    i = d3.interpolate({startAngle: 0,endAngle: 0}, {startAngle: d.startAngle,endAngle: d.endAngle})
-            #    return (t) ->
-            #        b = i(t)
-            #        return arc(b)
-            #)
+            #.attrTween("d", pieTween)
+            .attrTween("d", (d,i) ->
+                i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
+                return (t) ->
+                    b = i(t)
+                    return arc(b)
+            )

         path.exit()
             .transition()


changed if statement

changed if statement

--- jslibraryboilerplate_coffee.coffee
+++ jslibraryboilerplate_coffee.coffee
@@ -61,8 +61,7 @@
   # sample method to change text of an element
   text: (value) ->
     # no element
-    if !this[0]
-      return this
+    return this if !this[0]

     # get value
     if !value


karma doesn't considere 0px*0px is not visible


check if mpoint exists

check if mpoint exists

--- kites/sharedHosting/mounter.coffee
+++ kites/sharedHosting/mounter.coffee
@@ -39,14 +39,19 @@
      #  mountpoint : String # mountpoint for remount drive

      {mountpoint} = options
-
-     fs.mkdir mountpoint, 0755,(err)->
-       if err?
-         log.error error = "[ERROR] Couldn't create mountpoint #{mountpoint}: #{err.message}"
-         callback error
+
+     fs.stat mountpoint, (err,stats)->
+       if stats
+         log.info info = "[OK] #{mountpoint} already exists"
+         callback null, info
        else
-         log.info info = "[OK] mountpoint #{mountpoint} created"
-         callback null, info
+         fs.mkdir mountpoint, 0755,(err)->
+           if err?
+             log.error error = "[ERROR] Couldn't create mountpoint #{mountpoint}: #{err.message}"
+             callback error
+           else
+             log.info info = "[OK] mountpoint #{mountpoint} created"
+             callback null, info

     mountFtpDrive : (options, callback)->

@@ -90,12 +95,12 @@

       options.mountpoint = path.join config.usersPath, username, config.baseMountDir, ftphost

-      exec "/bin/umount #{mountpoint}",(err, stdout, stderr)=>
+      exec "/bin/umount #{options.mountpoint}",(err, stdout, stderr)=>
         if err
-          log.error error = "[ERROR] can't umount #{mountpoint}: #{stderr}"
+          log.error error = "[ERROR] can't umount #{options.mountpoint}: #{stderr}"
           callback error
         else
-          log.info info = "[OK] directory #{mountpoint} umounted"
+          log.info info = "[OK] directory #{options.mountpoint} umounted"
           @remountVE options,(err,res)->
             if err
               callback err


Updated signature test

Updated signature test

--- test/all/signature.coffee
+++ test/all/signature.coffee
@@ -87,3 +87,7 @@
     expect(udefine).to.have.property('defaultConfig')
     expect(defConProp).to.be.a('function')

+  it 'udefine has property .export', ->
+    expect(udefine).to.have.property 'export'
+    expect(udefine.export).to.be.a 'function'
+    expect(udefine.export({})).to.be.a 'function'


Switching to new container names.

Switching to new container names.

--- app/assets/javascripts/controllers/projects-ctrl.coffee
+++ app/assets/javascripts/controllers/projects-ctrl.coffee
@@ -3,8 +3,9 @@

   controllers.controller('ProjectsCtrl', ($scope, $route, $http, $location) ->
     $scope.images = [
-      { value: "dit4c/ipython", label: "iPython" },
-      { value: "dit4c/rstudio", label: "RStudio" }
+      { value: "dit4c/project-base", label: "Base" },
+      { value: "dit4c/project-ipython", label: "iPython" },
+      { value: "dit4c/project-rstudio", label: "RStudio" }
     ]

     $scope.projects = $route.current.locals.projects
@@ -58,7 +59,6 @@
           .get('/projects/checkNew?name='+name)
           .then (response) ->
             $scope.nameCheck = response.data
-            console.log($scope.nameCheck)

   )


EDSC-427: Allow pinch gesture for zooming the timeline

--- app/assets/javascripts/modules/timeline/timeline.coffee
+++ app/assets/javascripts/modules/timeline/timeline.coffee
@@ -121,7 +121,7 @@
       @fire('drag')

     _onUp: (e) ->
-      if @animate && @_positions.length > 0
+      if @animate && @_positions?.length > 0
         dx = @_newPos.x - @_positions[0]
         dt = +new Date() - @_times[0]
         v = dx/dt
@@ -573,8 +573,8 @@
         x = e.clientX - svg.clientLeft - origin
         time = @positionToTime(x)

-      doScroll = (deltaX, deltaY, e) =>
-        time = getTime(e)
+      doScroll = (deltaX, deltaY, time) =>
+        return unless allowWheel
         if Math.abs(deltaY) > Math.abs(deltaX)
           levels = if deltaY > 0 then -1 else 1
           @_deltaZoom(levels, time)
@@ -583,26 +583,37 @@
           @_pan(deltaX)

       # Safari
-      L.DomEvent.on svg, 'mousewheel', (e) =>
-        return unless allowWheel
-
+      L.DomEvent.on svg, 'mousewheel', (e) ->
         deltaX = e.wheelDeltaX
         deltaY = e.wheelDeltaY
-        doScroll(deltaX, deltaY, e)
+        doScroll(deltaX, deltaY, getTime(e))

         e.preventDefault()

       # Chrome/Firefox
-      L.DomEvent.on svg, 'wheel', (e) =>
-        return unless allowWheel
+      L.DomEvent.on svg, 'wheel', (e) ->
         return if e.type == "mousewheel"

         # 'wheel' deltas are opposite from 'mousewheel'
         deltaX = -e.deltaX
         deltaY = -e.deltaY
-        doScroll(deltaX, deltaY, e)
+        doScroll(deltaX, deltaY, getTime(e))

         e.preventDefault()
+
+      touchSeparation = 0
+      touchCenter = 0
+      L.DomEvent.on svg, 'touchstart', (e) ->
+        return unless e.touches && e.touches.length == 2
+        center = (e.touches[0].clientX + e.touches[1].clientX) / 2
+        time = getTime(clientX: center)
+        touchSeparation = Math.abs(e.touches[0].clientX - e.touches[1].clientX)
+        e.preventDefault()
+
+      L.DomEvent.on svg, 'touchmove', (e) ->
+        return unless e.touches && e.touches.length == 2
+        deltaY = Math.abs(e.touches[0].clientX - e.touches[1].clientX) - touchSeparation
+        doScroll(0, -deltaY, touchCenter)

     _setupDragBehavior: (svg) ->
       self = this


added a harry potter category

added a harry potter category

--- src/app/common/tags.coffee
+++ src/app/common/tags.coffee
@@ -31,6 +31,18 @@
     {"name": "captain america", "regex": /captain ?(america|handsome)/},
     {"name": "fantastic four", "regex": /fantastic ?(four|4)|f4|ff/}
   ],
+  'harry potter': [
+    {"name": "harry potter", "regex" : /harry ?potter/},
+    {"name": "draco malfoy", "regex": /draco ?malfoy/},
+    {"name": "cho chang", "regex": /cho ?chang/},
+    {"name": "hermione granger", "regex": /hermione ?granger/},
+    {"name": "ron weasley", "regex": /ron ?weasley/},
+    {"name": "dumbledore", "regex": /dumbledore/},
+    {"name": "sirius black", "regex": /sirius ?black/},
+    {"name": "the marauders", "regex": /(the ?)?marauders/},
+    {"name": "dementors", "regex": /dementors?/},
+    {"name": "hagrid", "regex": /hagrid/}
+  ],
   'cartoons': [
     {"name": "adventure time", "regex" : /adventure ?time/},
     {"name": "my little pony", "regex": /my ?little ?pony/},


:lipstick:

:lipstick:

--- src/app/text-buffer.coffee
+++ src/app/text-buffer.coffee
@@ -439,8 +439,8 @@
   #   hasTail - if `false`, the marker is created without a tail
   #
   # Returns a {Number} representing the new marker's ID.
-  markRange: (range, attributes={}) ->
-    @text.markRange(range, attributes)
+  markRange: (range, options={}) ->
+    @text.markRange(range, options)

   # Constructs a new marker at a given position.
   #


use Math.floor

use Math.floor


Typo fixed

Typo fixed

--- app/assets/javascripts/hunters.js.coffee
+++ app/assets/javascripts/hunters.js.coffee
@@ -63,7 +63,7 @@
 @delete_minion = (minion_id) ->
   del_callback = () ->
     @json_post '/minions/destroy',
-      {confirm: "yes", id: action_id}
+      {confirm: "yes", id: minion_id}
       () ->
         $("#minion_" + minion_id).remove()
   @confirm_dialog("You are about to delete this minion forever, continue?", del_callback)


Define queensEight as a global var

Define queensEight as a global var

--- Web/app/js/app.coffee
+++ Web/app/js/app.coffee
@@ -1 +1 @@
-queensEight = angular.module 'QueensEight', []+window.queensEight = angular.module 'QueensEight', []


minor clean up

minor clean up

--- JSTest.coffee
+++ JSTest.coffee
@@ -14,7 +14,7 @@
     expect: (conditional) ->
         message = "#{@tests ? "1"}: #{@description ? "test"}"

-        passed = =>
+        passed = ->
             console.log "#{message} -- OK"

         failed = =>
@@ -22,7 +22,7 @@
             console.error "#{message} -- Actual: #{conditional},
                                             Expected: #{@expected}"

-        notify = (result) =>
+        notify = (result) ->
             if not result then do failed else do passed

         is: (@expected) =>


Alert if a task is killed due to slave decom

--- SingularityUI/app/controllers/TaskDetail.coffee
+++ SingularityUI/app/controllers/TaskDetail.coffee
@@ -132,23 +132,36 @@
                 app.caughtError()
                 delete @models.resourceUsage

-    getAlerts: (taskHistory) =>
+    getAlerts: (requestTaskHistory) =>
+        task = @models.task
         alerts = []
         # Is this a scheduled task that has been running much longer than previous ones?
-        if @models.task.attributes.task.taskRequest.request.requestType == 'SCHEDULED' and @models.task.get('isStillRunning')
-            times = taskHistory.models.map (t) =>
+        if task.attributes.task.taskRequest.request.requestType == 'SCHEDULED' and task.get('isStillRunning')
+            times = requestTaskHistory.models.map (t) =>
                 return t.get('updatedAt') - t.get('taskId').startedAt
             avg = times.reduce (p, c) ->
                 return p + c

             avg = Math.round avg / times.length
-            current =  new Date().getTime() - @models.task.get('task').taskId.startedAt
+            current =  new Date().getTime() - task.get('task').taskId.startedAt
             # Alert if current uptime is longer than twice the average
             if current > (avg * 2)
                 alerts.push
                   title: 'Warning:',
                   message: 'This scheduled task has been running longer than twice the average for the request and may be stuck.',
                   level: 'warning'
+        # Was this task killed by a discomissioning slave?
+        if !task.get('isStillRunning')
+            updates = task.get('taskUpdates')
+            decomMessage = updates.filter (u) =>
+              return u.statusMessage?.indexOf('DECOMISSIONING') != -1 and u.taskState == 'TASK_CLEANING'
+            killedMessage = updates.filter (u) =>
+              return u.taskState == 'TASK_KILLED'
+            if decomMessage.length > 0 and killedMessage.length > 0
+              alerts.push
+                title: 'Alert:',
+                message: 'This task was killed due to a slave discomissioning.',
+                level: 'danger'
         return alerts

     refresh: ->


Made native ArrayBuffer and DataView tests optional

--- test/test_jettison.coffee
+++ test/test_jettison.coffee
@@ -241,5 +241,6 @@
         value = schema.parse(string)
         expect(value).to.deep.equal(expectedValue)

-describeJettison()
+if global.ArrayBuffer? and global.DataView?
+  describeJettison()
 describeJettison(withPolyfills: true)


Add Refresh Alias

Add Refresh Alias

--- BackofficeBundle/Resources/public/coffee/adminFormView.coffee
+++ BackofficeBundle/Resources/public/coffee/adminFormView.coffee
@@ -1,11 +1,12 @@
 adminFormView = Backbone.View.extend(
   el: '#OrchestraBOModal'
   events:
-    'keyup input#none' : 'refreshAlias'
+    'keyup input#node_name' : 'refreshAlias'
   initialize: (options) ->
     @url = options.url
+    @aliasIsRefreshable = false
     @method = if options.method then options.method else 'GET'
-    _.bindAll this, "refreshAlias", "bindEvent", "render", "call", "addEventOnForm"
+    _.bindAll this, "refreshAlias"
     @call()
     return
   call: ->
@@ -29,8 +30,8 @@
     $("[data-prototype]").each ->
       PO.formPrototypes.addPrototype $(this)
       return
+    @aliasIsRefreshable = $('input#node_alias').val() is ''
     @addEventOnForm()
-    @bindEvent()
   addEventOnForm: ->
     viewContext = this
     $("form", @$el).on "submit", (e) ->
@@ -44,9 +45,6 @@
             Backbone.history.loadUrl(Backbone.history.fragment)
     return
   refreshAlias: (event) ->
-    console.log 'RefreshAlias'
-    $('input#node_alias').val(event.target.value.replace(/[^a-z0-9]/gi,'_'))
-  bindEvent: ->
-    if $('input#node_alias').val() is ''
-      @events['keyup input#node_name'] = "refreshAlias"
+    if @aliasIsRefreshable
+      $('input#node_alias').val(event.target.value.replace(/[^a-z0-9]/gi,'_'))
 )


Fix autoscroll

Fix autoscroll

--- app/assets/javascripts/bare_timetable.js.coffee
+++ app/assets/javascripts/bare_timetable.js.coffee
@@ -5,20 +5,21 @@
     $(this).attr("data-stage-time") < timestamp
   ).last()

-  lastOfThePast.scrollTo() if lastOfThePast.length > 0
+  if lastOfThePast.length > 0
+    scrollAmount = lastOfThePast.offset().top - $(".navbar-fixed-top").height() - 5
+  else
+    scrollAmount = 0
+
+  console.log "Scroll amount: " + scrollAmount
+
+  $('html, body').scrollTop(scrollAmount)

 $ ->
-  $.fn.scrollTo = ->
-    $('html, body').animate({
-      scrollTop: $(this).offset().top - $(".navbar-fixed-top").height() - 5 + 'px'
-    }, 'slow')
-    return this
-
   if (window.location.hash == "#autoreload")
-    # window.setInterval ->
-    #   document.location.reload(true)
-    # , 10000
+    window.setInterval ->
+      document.location.reload(true)
+    , 3000000 # every 5 minutes

     window.setInterval ->
       jumpToCurrent()
-    , 1000
+    , 2000 # every 2 seconds


Removed /checkout/confirm route

Removed /checkout/confirm route

--- app/assets/javascripts/sprangular/routes.coffee
+++ app/assets/javascripts/sprangular/routes.coffee
@@ -71,14 +71,6 @@
         order: (Cart) ->
           Cart.reload().then -> Cart.current

-    .when '/checkout/confirm',
-      requires: {user: true, cart: true}
-      controller: 'CheckoutConfirmCtrl'
-      templateUrl: 'checkout/confirm.html'
-      resolve:
-        order: (Cart) ->
-          Cart.reload().then -> Cart.current
-
     .when '/checkout/complete',
       controller: 'CheckoutCompleteCtrl'
       templateUrl: 'checkout/complete.html'


Make enter in add to set form submit.

Make enter in add to set form submit.

--- app/assets/javascripts/thumb_selector.js.coffee
+++ app/assets/javascripts/thumb_selector.js.coffee
@@ -70,5 +70,8 @@

   $('.add-to-set').click add_to_set

+  $('#add-to-set-name').keypress (event) ->
+    add_to_set(event) if event.keyCode == 13
+
 $(document).ready ->
   thumb_selector_init()


Fix missing heart

Fix missing heart

--- app/scripts/components/player/PlayerInfo.coffee
+++ app/scripts/components/player/PlayerInfo.coffee
@@ -7,7 +7,7 @@
     @player = player
     @score = Crafty.e('2D, DOM, Text, HUD')
       .attr(w: 220, h: 20)
-      .positionHud(x: x, y: 10, z: 2)
+      .positionHud(x: x, y: 10, z: 200)
       .textFont(
         size: '10px'
         family: 'Press Start 2P'
@@ -17,14 +17,14 @@

     @lives = Crafty.e('2D, DOM, Text, HUD')
       .attr(w: 220, h: 20)
-      .positionHud(x: x, y: 30, z: 2)
+      .positionHud(x: x, y: 30, z: 200)
       .textFont(
         size: '10px'
         family: 'Press Start 2P'
       )
     @heart = Crafty.e('2D, WebGL, ColorEffects, heart, HUD')
       .attr(w: 16, h: 16)
-      .positionHud(x: x - 2, y: 26, z: 2)
+      .positionHud(x: x - 2, y: 26, z: 200)
       .colorOverride(player.color(), 'partial')

     if @player.has('Color')
@@ -44,19 +44,19 @@
     playerColor = @player.color()
     @boosts['speedb'] = Crafty.e('2D, WebGL, speedBoost, ColorEffects, HUD')
       .attr(w: 16, h: 16)
-      .positionHud(x: x + 50, y: 29, z: 2)
+      .positionHud(x: x + 50, y: 29, z: 200)
       .colorOverride(playerColor, 'partial')
     @boosts['rapidb'] = Crafty.e('2D, WebGL, rapidFireBoost, ColorEffects, HUD')
       .attr(w: 16, h: 16)
-      .positionHud(x: x + 70, y: 28, z: 2)
+      .positionHud(x: x + 70, y: 28, z: 200)
       .colorOverride(playerColor, 'partial')
     @boosts['aimb'] = Crafty.e('2D, WebGL, aimBoost, ColorEffects, HUD')
       .attr(w: 16, h: 16)
-      .positionHud(x: x + 90, y: 28, z: 2)
+      .positionHud(x: x + 90, y: 28, z: 200)
       .colorOverride(playerColor, 'partial')
     @boosts['damageb'] = Crafty.e('2D, WebGL, damageBoost, ColorEffects, HUD')
       .attr(w: 16, h: 16)
-      .positionHud(x: x + 110, y: 28, z: 2)
+      .positionHud(x: x + 110, y: 28, z: 200)
       .colorOverride(playerColor, 'partial')

   updateBoostInfo: ->


Make wesabe.io.dir CommonJS-compatible.

Make wesabe.io.dir CommonJS-compatible.

--- application/chrome/content/wesabe/io/dir.coffee
+++ application/chrome/content/wesabe/io/dir.coffee
@@ -1,5 +1,3 @@
-wesabe.provide('io.dir')
-
 ################################################
 ################################################
 #
@@ -16,85 +14,84 @@
 ################################################
 #
 # Example use:
-# var dir = wesabe.io.dir.open('/test');
-# if (dir.exists()) {
-#  alert(wesabe.io.dir.path(dir));
-#  var arr = wesabe.io.dir.read(dir, true), i;
-#  if (arr) {
-#    for (i = 0; i < arr.length; ++i) {
-#      alert(arr[i].path);
-#    }
-#  }
-# }
-# else {
-#  var rv = wesabe.io.dir.create(dir);
-#  alert('Directory create: ' + rv);
-# }
+# path = dir.open('/test')
+# if path.exists()
+#   alert dir.path path
+#   children = dir.read path, true
+#   if children
+#     for child in children
+#       alert child
+# else
+#   path = dir.create dir
+#   alert "Directory create: #{path}"
 #
 # ---------------------------------------------
 # ----------------- Nota Bene -----------------
 # ---------------------------------------------
 # Some possible types for get are:
 #  'ProfD'       = profile
-#  'DefProfRt'     = user (e.g., /root/.mozilla)
+#  'DefProfRt'   = user (e.g., /root/.mozilla)
 #  'UChrm'       = %profile%/chrome
 #  'DefRt'       = installation
-#  'PrfDef'        = %installation%/defaults/pref
-#  'ProfDefNoLoc'    = %installation%/defaults/profile
+#  'PrfDef'      = %installation%/defaults/pref
+#  'ProfDefNoLoc'= %installation%/defaults/profile
 #  'APlugns'     = %installation%/plugins
-#  'AChrom'        = %installation%/chrome
+#  'AChrom'      = %installation%/chrome
 #  'ComsD'       = %installation%/components
-#  'CurProcD'      = installation (usually)
+#  'CurProcD'    = installation (usually)
 #  'Home'        = OS root (e.g., /root)
 #  'TmpD'        = OS tmp (e.g., /tmp)

-wesabe.io.dir =
-  sep        : '/'
-  dirservCID : '@mozilla.org/file/directory_service;1'
-  propsIID   : Components.interfaces.nsIProperties
-  fileIID    : Components.interfaces.nsIFile
+{open} = require 'io/file'

+
+sep        = if navigator.platform.toLowerCase().indexOf('win') > -1 then '\\' else '/'
+dirservCID = '@mozilla.org/file/directory_service;1'
+propsIID   = Components.interfaces.nsIProperties
+fileIID    = Components.interfaces.nsIFile
+
+_read = (dirEntry, recursive) ->
+  list = []
+  while dirEntry.hasMoreElements()
+    list.push(dirEntry.getNext().QueryInterface(Components.interfaces.nsILocalFile))
+
+  if recursive
+    list2 = []
+    for item in list
+      if item.isDirectory()
+        files = item.directoryEntries
+        list2 = _read(files, recursive)
+
+    list = list.concat(list2)
+
+  return list
+
+
+$dir =
   get: (type) ->
     try
-      dir = Components.classes[@dirservCID].createInstance(@propsIID).get(type, @fileIID)
-      return dir
+      Components.classes[dirservCID].createInstance(propsIID).get(type, fileIID)
     catch e
       return false

   create: (dir) ->
     try
-      dir.create(0x01, 0774)
+      dir.create 0x01, 0774
       return true
     catch e
       return false

   read: (dir, recursive=false) ->
     list = []
-    wesabe.tryCatch "wesabe.io.dir.read(#{dir})", =>
+    wesabe.tryCatch "dir.read(#{dir})", =>
       if dir.isDirectory()
         files = dir.directoryEntries
-        list = @_read(files, recursive)
-    return list
-
-  _read: (dirEntry, recursive) ->
-    list = []
-    while dirEntry.hasMoreElements()
-      list.push(dirEntry.getNext().QueryInterface(Components.interfaces.nsILocalFile))
-
-    if recursive
-      list2 = []
-      for item in list
-        if item.isDirectory()
-          files = item.directoryEntries
-          list2 = @_read(files, recursive)
-
-      list = list.concat(list2)
-
+        list = _read files, recursive
     return list

   unlink: (dir, recursive=false) ->
     try
-      dir.remove(recursive)
+      dir.remove recursive
       return true
     catch e
       return false
@@ -106,22 +103,22 @@
     str.split(/\/|\\/).join(join)

   join: (str, split) ->
-    str.split(split).join(@sep)
+    str.split(split).join(sep)

-wesabe.io.dir.__defineGetter__ 'profile', ->
-  @get('ProfD')
+$dir.__defineGetter__ 'profile', ->
+  $dir.get 'ProfD'

-wesabe.io.dir.__defineGetter__ 'chrome', ->
-  @get('AChrom')
+$dir.__defineGetter__ 'chrome', ->
+  $dir.get 'AChrom'

-wesabe.io.dir.__defineGetter__ 'root', ->
-  file = wesabe.io.file.open(@chrome.path + '/../../')
+$dir.__defineGetter__ 'root', ->
+  file = open $dir.chrome.path + '/../../'
   file.normalize()
   return file

-wesabe.io.dir.__defineGetter__ 'tmp', ->
-  @get('TmpD')
+$dir.__defineGetter__ 'tmp', ->
+  $dir.get 'TmpD'

-wesabe.io.dir.sep = '\\' if navigator.platform.toLowerCase().indexOf('win') > -1
+module.exports = $dir


working bezier

working bezier

--- bokehjs/src/coffee/renderer/glyph/bezier.coffee
+++ bokehjs/src/coffee/renderer/glyph/bezier.coffee
@@ -1,13 +1,87 @@
 define [
   "underscore"
+  "rbush"
   "renderer/properties"
   "./glyph"
-], (_, Properties, Glyph) ->
+], (_, rbush, Properties, Glyph) ->
+
+  # algorithm adapted from http://stackoverflow.com/a/14429749/3406693
+  _cbb = (x0, y0, x1, y1, x2, y2, x3, y3) ->
+    tvalues = []
+    bounds = [[], []]
+
+    for i in [0..2]
+      if (i == 0)
+        b = 6 * x0 - 12 * x1 + 6 * x2
+        a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3
+        c = 3 * x1 - 3 * x0
+      else
+        b = 6 * y0 - 12 * y1 + 6 * y2
+        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3
+        c = 3 * y1 - 3 * y0
+
+      if (Math.abs(a) < 1e-12) # Numerical robustness
+        if (Math.abs(b) < 1e-12) # Numerical robustness
+          continue
+        t = -c / b
+        if (0 < t and t < 1)
+          tvalues.push(t)
+        continue
+
+      b2ac = b * b - 4 * c * a
+      sqrtb2ac = Math.sqrt(b2ac)
+
+      if (b2ac < 0)
+        continue
+
+      t1 = (-b + sqrtb2ac) / (2 * a)
+      if (0 < t1 and t1 < 1)
+        tvalues.push(t1)
+
+      t2 = (-b - sqrtb2ac) / (2 * a)
+      if (0 < t2 and t2 < 1)
+        tvalues.push(t2)
+
+    j = tvalues.length
+    jlen = j
+    while (j--)
+      t = tvalues[j]
+      mt = 1 - t
+      x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3)
+      bounds[0][j] = x
+      y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3)
+      bounds[1][j] = y
+
+    bounds[0][jlen] = x0;
+    bounds[1][jlen] = y0;
+    bounds[0][jlen + 1] = x3;
+    bounds[1][jlen + 1] = y3;
+
+    return [
+      Math.min.apply(null, bounds[0]),
+      Math.max.apply(null, bounds[1]),
+      Math.max.apply(null, bounds[0])
+      Math.min.apply(null, bounds[1]),
+    ]

   class BezierView extends Glyph.View

     _fields : ['x0', 'y0', 'x1', 'y1', 'cx0', 'cy0', 'cx1', 'cy1']
     _properties: ['line']
+
+    _set_data: () ->
+      @index = rbush()
+      pts = []
+      for i in [0...@x0.length]
+        if isNaN(@x0[i] + @x1[i] + @y0[i] + @y1[i] + @cx0[i] + @cy0[i] + @cx1[i] + @cy1[i])
+          continue
+
+        [x0, y0, x1, y1] = _cbb(@x0[i], @y0[i], @x1[i], @y1[i], @cx0[i], @cy0[i], @cx1[i], @cy1[i])
+
+        pts.push([x0, y0, x1, y1, {'i': i}])
+
+      @index.load(pts)
+

     _map_data: () ->
       [@sx0,  @sy0]  = @renderer.map_to_screen(@x0,  @glyph.x0.units,  @y0,  @glyph.y0.units)


translated alchemy.translations.js.coffee to dutch

--- app/assets/javascripts/alchemy/alchemy.translations.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.translations.js.coffee
@@ -8,75 +8,100 @@
     page_dirty_notice:
       en: "You have unsaved changes on this page. They will be lost if you continue."
       de: "Sie haben ungesicherte Änderungen auf der Seite. Diese gehen verloren, wenn Sie fortfahren."
+      nl: "Er zijn wijzigingen op deze pagina aangebracht. Deze gaan verloren als u doorgaat."
     element_dirty_notice:
       en: "This element has unsaved changes. Do you really want to fold it?"
       de: "Dieses Element hat nicht gespeicherte Änderungen. Möchten Sie es wirklich einklappen?"
+      nl: "Dit element bevat niet opgeslagen wijzigingen. Wilt u dit echt inklappen?"
     warning:
       en: 'Warning!'
       de: 'Achtung!'
+      nl: 'Waarschuwing!'
     ok:
       en: 'Ok'
       de: 'Ok'
+      nl: 'Ok'
     cancel:
       en: 'Cancel'
       de: 'Abbrechen'
+      nl: 'Annuleren'
     browse:
       de: "durchsuchen"
       en: "browse"
+      nl: "bladeren"
     pending:
       de: "Wartend..."
       en: "Pending..."
+      nl: "Bezig..."
     uploading:
       de: "Ladend..."
       en: "Uploading..."
+      nl: "Uploaden..."
     remaining:
       de: " verbleibend."
       en: " remaining."
+      en: " over."
     complete:
       de: "Abgeschlossen"
       en: "Complete"
+      nl: "Klaar"
     cancelled:
       de: "Abgebrochen"
       en: "Cancelled"
+      nl: "Afgebroken"
     stopped:
       de: "Gestoppt"
       en: "Stopped"
+      nl: "Gestopt"
     upload_failed:
       de: "Fehlgeschlagen!"
       en: "Upload Failed!"
+      nl: "Upload gestopt!"
     file_too_big:
       de: "Datei ist zu groß!"
       en: "File is too big!"
+      nl: "Bestand is te groot!"
     upload_limit_exceeded:
       de: "Maximales Dateilimit erreicht."
       en: "Upload limit exceeded."
+      nl: "Uploadlimiet bereikt."
     validation_failed:
       de: "Validierung fehlgeschlagen. Ladevorgang angehalten."
       en: "Failed Validation. Upload skipped."
+      nl: "Validatiefout. Upload is stopgezet."
     zero_byte_file:
       de: "Datei hat keinen Inhalt!"
       en: "Cannot upload Zero Byte files!"
+      nl: "Bestand is leeg!"
     invalid_file:
       de: "Ungültiger Dateityp!"
       en: "Invalid File Type!"
+      nl: "Ongeldig bestandstype!"
     unknown_error:
       de: "Unbekannter Fehler!"
       en: "Unhandled Error!"
+      nl: "Onbekende fout!"
     drag_files_notice:
       de: "Oder ziehen Sie die Dateien hier rauf"
       en: "Or drag files over here"
+      nl: "Of sleep bestanden hier naartoe"
     drop_files_notice:
       de: "Lassen Sie die Dateien nun los"
       en: "Now drop the files"
+      nl: "Laat nu de bestanden los"
     queued_files_notice:
       de: "x Dateien in der Warteschlange."
       en: "Queued x files."
+      nl: "x bestanden in de wachtrij gezet."
     success_notice:
       de: "x Dateien hochgeladen."
       en: "Uploaded x files."
+      nl: "x bestanden geupload."
     url_validation_failed:
       de: 'Die URL hat kein gültiges Format.'
       en: 'The url has no valid format.'
+      nl: 'De url heeft geen geldig formaat.'
     click_to_edit:
       de: 'Klicken zum Bearbeiten'
       en: 'click to edit'
+      nl: 'klik om aan te passen'


Disabling the form field for address and not hiding it

--- app/assets/javascripts/mweb_events/app/events/_form.js.coffee
+++ app/assets/javascripts/mweb_events/app/events/_form.js.coffee
@@ -15,16 +15,15 @@

     # Location
     if $("#event_location")[0].value is ""
-        $(".event_address").hide()
+        $("#event_address").attr('disabled', true)

-    show_time = 500
-    show_address = ->
+    enable_address = ->
       if @value isnt ""
-        $(".event_address").show(show_time)
+        $("#event_address").removeAttr("disabled")
       else
-        $(".event_address").hide(show_time)
+        $("#event_address").attr("disabled", true)

-    $("#event_location").on 'input keyup', show_address
+    $("#event_location").on 'input keyup', enable_address

     # Description
     opts =


more little documentation for the rewriter ... moving along

--- rewriter.coffee
+++ rewriter.coffee
@@ -202,10 +202,10 @@
   # inwards, safely. The steps to accomplish this are:
   #
   # 1. Check that all paired tokens are balanced and in order.
-  # 2. Rewrite the stream with a stack: if you see an '(' or INDENT, add it
-  #    to the stack. If you see an ')' or OUTDENT, pop the stack and replace
+  # 2. Rewrite the stream with a stack: if you see an `EXPRESSION_START`, add it
+  #    to the stack. If you see an `EXPRESSION_END`, pop the stack and replace
   #    it with the inverse of what we've just popped.
-  # 3. Keep track of "debt" for tokens that we fake, to make sure we end
+  # 3. Keep track of "debt" for tokens that we manufacture, to make sure we end
   #    up balanced in the end.
   #
   rewrite_closing_parens: ->


Don't need this comment any more

Don't need this comment any more

--- app/assets/javascripts/papers.js.jsx.coffee
+++ app/assets/javascripts/papers.js.jsx.coffee
@@ -40,7 +40,6 @@
     bodyContentWrapper.html()

   savePaper: (url) ->
-    # TODO: make this nicer. It probably belongs in a nice function.
     $.ajax
       url: url
       method: "POST"


Use "instance" as scope reference

Use "instance" as scope reference

--- app/client/survey_admin/survey_admin.coffee
+++ app/client/survey_admin/survey_admin.coffee
@@ -1,14 +1,15 @@
 Parse = require 'parse'
+{Survey} = require '../../imports/models'

 Template.survey_admin.onCreated ->
   @fetched = new ReactiveVar false
   surveyId = @data.id
-  self = @
+  instance = @

   query = new Parse.Query Survey
   query.get(surveyId).then (survey) ->
-      self.fetched.set true
-      self.survey = survey
+      instance.fetched.set true
+      instance.survey = survey
     ,
     (obj, error) ->
       throw new Meteor.Error 'server', error.message


sync_rest_client now return $.promises

sync_rest_client now return $.promises

--- app/src/restclients/sync_rest_client.coffee
+++ app/src/restclients/sync_rest_client.coffee
@@ -5,30 +5,44 @@

   constructor: (addr) ->
     @authHttp = @http().authenticated()
-    @basePath = 'accountsettings/#{addr}'
+    @basePath = "accountsettings/#{addr}"

   # @param [Function] cb A callback function with object.md5 as argument.
-  get_settings_md5: (cb, ecb) ->
-    @authHttp.get(url: @basePath+'/md5', onSuccess: cb, onError: ecb)
+  # @return A jQuery promise
+  get_settings_md5: ->
+    @_promisify(url: @basePath+'/md5', (p) =>
+      @authHttp.get(p)
+    ).then (r) => r.md5

-  # @param [Function] cb cb(settings:Object) a callback function.
-  # @param [Function] ecb An ajax error callback function.
-  get_settings: (cb, ecb) ->
-    @authHttp.get(url: @basePath, onSuccess: cb, onError: ecb)
+  # @return A jQuery promise
+  get_settings: ->
+    @_promisify(url: @basePath, (p) =>
+      @authHttp.get(p)
+    ).then (r) => JSON.parse(r.settings)

   # @param [Object] settings data to put on sync server.
-  # @param [Function] cb A callback function with object.md5 as argument.
-  # @param [Function] ecb An ajax error callback function.
-  post_settings: (settings, cb, ecb) ->
-    @authHttp.post(url: @basePath, params: settings, onSuccess: cb, onError: ecb)
+  # @return A jQuery promise
+  post_settings: (settings) ->
+    @_promisify(url: @basePath, params: {settings: JSON.stringify(settings)}, (p) =>
+      @authHttp.post(p)
+    ).then (r) => r.md5

   # @param [Object] settings data to put on sync server.
-  # @param [Function] cb A callback function with object.md5 as argument.
-  # @param [Function] ecb An ajax error callback function.
-  put_settings: (settings, cb, ecb) ->
-    @authHttp.put(url: @basePath, params: settings, onSuccess: cb, onError: ecb)
+  # @return A jQuery promise
+  put_settings: (settings) ->
+    @_promisify(url: @basePath, params: {settings: JSON.stringify(settings)}, (p) =>
+      @authHttp.put(p)
+    ).then (r) => r.md5

-  # @param [Function] cb A callback function.
-  # @param [Function] ecb An ajax error callback function.
-  delete_settings: (cb, ecb) ->
-    @authHttp.delete(url: @basePath, onSuccess: cb, onError: ecb)
+  # @return A jQuery promise
+  delete_settings: ->
+    @_promisify(url: @basePath, (p) =>
+      @authHttp.delete(p)
+    )
+
+  # @param [Object] params An object given as argument to fct.
+  # @return A jQuery Deferred
+  _promisify: (params, fct) ->
+    d = $.Deferred()
+    fct(_.extend(params, {onSuccess: _.bind(d.resolve,d), onError: _.bind(d.reject,d)}))
+    d.promise()


Change compare color to nicer, higher-contrast yellow


Fix error with empty less file + sourcemaps

Half-blind fix for this gulp-less issue: https://github.com/plus3network/gulp-less/issues/122#issuecomment-74417877

--- lib/adapters/less.coffee
+++ lib/adapters/less.coffee
@@ -20,7 +20,7 @@
     @engine.render str, options, (err, res) ->
       if err then return deferred.reject(err)
       obj = { result: res.css }
-      if options.sourceMap
+      if options.sourceMap and res.map
         obj.sourcemap = JSON.parse(res.map)
         sourcemaps.inline_sources(obj.sourcemap).then (map) ->
           obj.sourcemap = map


Clean Remote interface

Clean Remote interface

--- src/objects/remote.proto.coffee
+++ src/objects/remote.proto.coffee
@@ -2,49 +2,49 @@
 Methods of remote [Node.js version]
 ###

-exports.ver = ->
-  "#{@dist}-#{@src.version}-x#{if @x64 then 64 else 86}"
+ver = (self)->
+  "#{self.dist}-#{self.src.version}-x#{if self.x64 then 64 else 86}"

-exports.msi = (full)->
-  r = "#{@ver()}.msi"
+msi = (self, full)->
+  out = "#{ver self}.msi"
   if full
-    fs.BuildPath cache, r
+    fs.BuildPath cache, out
   else
-    r
+    out

-exports.uri = (file = @msi false)->
-  "#{dists[@dist]}#{@src.version}/#{
-    if @x64 and not @$[0][0]
+uri = (self, file = msi self, false)->
+  "#{dists[self.dist]}#{self.src.version}/#{
+    if self.x64 and not self.$[0][0]
       'x64/'
     else
       ''
     }#{file}"

-exports.fetch = ->
-  echo "Fetching:", uri = @uri()
-  ajax uri, @msi true
+fetch = (self)->
+  echo "Fetching:", url = uri self
+  ajax url, msi self, true

-exports.extract = ->
-  echo "Extracting:", @msi()
-  if fs.FolderExists extract2 = fs.BuildPath cache, ver = @ver()
+extract = (self)->
+  echo "Extracting:", msi self
+  if fs.FolderExists extract2 = fs.BuildPath cache, v = ver self
     fs.DeleteFolder extract2
   sh.Run """
-    msiexec /a "#{@msi true}" TARGETDIR=#{extract2} /passive
+    msiexec /a "#{msi self, true}" TARGETDIR=#{extract2} /passive
     """, 1, true
-  if fs.FolderExists @dst = dst = fs.BuildPath install2, ver
+  if fs.FolderExists self.dst = dst = fs.BuildPath install2, v
     fs.DeleteFolder dst
   fs.MoveFolder each(fs.GetFolder(extract2).SubFolders).shift().Path, dst
   fs.DeleteFolder extract2

-exports.shortcuts = ->
-  return if 'node' == @dist
+iojs = (self)->
+  return if 'node' == self.dist
   echo "Creating shortcut..."
-  fs.CopyFile fs.BuildPath(@dst, "#{@dist}.exe"),
-    fs.BuildPath(@dst, "node.exe")
+  fs.CopyFile fs.BuildPath(self.dst, "#{self.dist}.exe"),
+    fs.BuildPath(self.dst, "node.exe")

-exports.prefix = ->
+prefix = (self)->
   echo "Adjusting NPM prefix..."
-  npmrc = fs.OpenTextFile fs.BuildPath(@dst, 'node_modules/npm/npmrc'), 8
+  npmrc = fs.OpenTextFile fs.BuildPath(self.dst, 'node_modules/npm/npmrc'), 8
   npmrc.WriteLine """

     # <hack dirty src="#{PACKAGE.homepage}">
@@ -53,32 +53,32 @@
     """
   npmrc.Close()

-exports.use = ->
-  echo "Using:", ver = @ver()
-  junction ver
+use = (self)->
+  echo "Using:", v = ver self
+  junction v

-exports.set64= (is64)->
-  @x64 = if '*' == is64 then null else is64 ? x64
+set64= (self, is64)->
+  self.x64 = if '*' == is64 then null else is64 ? x64

 exports.install = (is64)->
-  @set64 is64
-  @fetch()
-  @extract()
-  @shortcuts()
-  @prefix()
-  @use()
+  set64 @, is64
+  fetch @
+  extract @
+  iojs @
+  prefix @
+  use @

 # Install OpenSSL binary
 exports.O = (is64)->
-  @set64 is64
-  echo "Fetching:", uri = @uri cli = bat.O
-  ajax uri, fs.BuildPath install2, cli
+  set64 @, is64
+  echo "Fetching:", url = uri @, cli = bat.O
+  ajax url, fs.BuildPath install2, cli
   echo "Creating shortcut..."
   bat fs.GetBaseName junction.$()

 # Find local version matching
 exports.local = (is64)->
-  @set64 is64
+  set64 @, is64
   new vfilter.ctr @$[0], @dist, @x64
   .local()
   .last()


Tidy groups and collapse whitespace in regular expressions

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -249,61 +249,29 @@
 		beginCaptures:
 			1: {name: "entity.function.name.roff"}
 		patterns: [{
-			begin: '(?x)
-				(?<=\\.BI)
-				[ \\t]+
-				(
-					".*?
-					(?<!")"(?!")
-					|
-					[^"\\s]\\S*
-				)
-				[ \t]*
-				(?=")
-			'
-			end: '(?x)
-				(?<=")
-				|
-				(?<!\\\\)\\n
-			'
+			begin: '(?<=\\.BI)[ \\t]+(".*?(?<!")"(?!")|[^"\\s]\\S*)[ \t]*(?=")'
+			end: '(?<=")|(?<!\\\\)\\n'
+			patterns: [{include: "#italic-strings"}]
 			beginCaptures:
 				1:
 					name: "markup.bold.roff"
-					patterns: [
-						{include: "#bold-string"}
-						{include: "#bold-bareword"}
-					]
-			patterns: [
-				{include: "#italic-string"}
-				{include: "#italic-bareword"}
-			]
+					patterns: [{include: "#bold-strings"}]
 		}, {
-			begin: '(?x)
-				(?=
-					("[^"]*(?<!\\\\)")
-					|
-					\\S+
-				)
-			'
-			end: '(?x)
-				$
-				|
-				(?:^|[ \t]+|(?<="))
-				(
-					("[^"]*")
-					|
-					\\S+
-				)
-			'
-			patterns: [
-				{include: "#bold-string"},
-				{include: "#bold-bareword"}
-			]
+			begin: '(?=("[^"]*(?<!\\\\)")|\\S+)'
+			end: '$|(?:^|[ \t]+|(?<="))(("[^"]*")|\\S+)'
+			patterns: [{include: "#bold-strings"}]
 			endCaptures:
 				1: {name: "markup.italic.roff"}
 				2: {patterns: [{include: "#strings"}]}
 		}]

+
+	# Bold words ("Quoted"/Unquoted)
+	"bold-strings":
+		patterns: [
+			{include: "#bold-string"}
+			{include: "#bold-bareword"}
+		]

 	# Bold: "Quoted"
 	"bold-string":
@@ -322,7 +290,6 @@
 			}, {include: "#double-quoted-string"}]
 		}]

-
 	# Bold: Unquoted
 	"bold-bareword":
 		patterns: [{
@@ -339,6 +306,12 @@
 		}]

+	# Italic words ("Quoted"/Unquoted)
+	"italic-strings":
+		patterns: [
+			{include: "#italic-string"}
+			{include: "#italic-bareword"}
+		]

 	# Italic: "Quoted"
 	"italic-string":


removing sh tests for now

removing sh tests for now

--- test/placebo.coffee
+++ test/placebo.coffee
@@ -45,17 +45,3 @@

     it 'has a sh method', ->
       should.exist placebo.sh
-
-    it 'returns stdout when you run shell commands', ->
-      placebo.sh('echo Hello').should.equal 'Hello'
-
-    it 'throws stderr when you run shell commands', ->
-      command = 'cat this_file_does_not_exist'
-      result = 'cat: foo: input file is output file'
-      placebo.sh(command).should.throw result
-
-    it 'throws an exception when shell commands exit nonzero', ->
-      placebo.sh('exit 1').should.throw
-
-    it 'does not throw an exception when shell commands exit zero', ->
-      placebo.sh('exit 0').should.not.throw


Fixed failing test

Fixed failing test

--- test/session.coffee
+++ test/session.coffee
@@ -191,25 +191,25 @@
       it 'does not resend document snapshots when you reconnect' # ?? how do we test this at this level of abstraction?

       it 'fetches operations if the client already has a document snapshot at an old version', (done) ->
-        @userAgent.fetch = (collection, doc, callback) ->
+        @userAgent.fetch = (collection, docName, callback) ->
           assert.strictEqual collection, 'collection'
-          assert.strictEqual doc, 'docname'
+          assert.strictEqual docName, 'docname'
           callback null, {v:98, type:ottypes.text.uri, data:'old data'}

         doc = @connection.getOrCreate 'collection', 'docname'

         doc.fetch (err) =>
-          @userAgent.getOps = (collection, doc, from, to, callback) ->
+          @userAgent.getOps = (collection, docName, from, to, callback) ->
             assert.strictEqual collection, 'collection'
-            assert.strictEqual doc, 'docname'
+            assert.strictEqual docName, 'docname'
             assert.equal from, 98
             assert to is -1 or to is 100
-            callback null, [{v:98, op:[]},{v:99, op:[]}] # ops from 95 to 100
+            callback null, [{v:98, op:[]},{v:99, op:[]}] # ops from 98 to 100

           @userAgent.queryFetch = (index, query, opts, callback) ->
             callback null, [{data:'internet', type:ottypes.text.uri, v:100, docName:'docname', c:'collection'}]

-          @connection.createFetchQuery 'index', {a:5, b:6}, {docMode:'fetch', results:[doc]}, (err, results, extra) =>
+          @connection.createFetchQuery 'index', {a:5, b:6}, {docMode:'fetch', knownDocs:[doc]}, (err, results, extra) =>
             assert.ifError err

           doc.on 'op', (op) ->


testing out travis.ci, failing b/c hook.io

--- test/cases/model/criteriaTest.coffee
+++ test/cases/model/criteriaTest.coffee
@@ -116,6 +116,7 @@
       criteria.order "name", "asc"
       criteria.paginate perPage: 20, page: 2

+      # this doesn't work in node 0.4 b/c regular expressions aren't "equal" so to speak
       #assert.deepEqual criteria.conditions(), { id: { '$in': [1, 2, 3, 4, 5, 6] }, name: { '!~': /[a-z]/, '=~': /[0-9]/ } }
       assert.deepEqual criteria.options(), { limit : 20, offset : 20, sort: [["name", "asc"]] }


Set main sections as panels

Set main sections as panels

--- client/app/components/basic_components.coffee
+++ client/app/components/basic_components.coffee
@@ -1,5 +1,6 @@
 {
     div
+    section
     h3
     h4
     ul
@@ -19,9 +20,10 @@
 Container = React.createClass

     render: ->
-        div
+        section
             id: @props.id
             key: @props.key
+            className: 'panel'
         ,
             @props.children


Remove duplicate code for steambuddy.coffee

--- lib/js/steambuddy.coffee
+++ lib/js/steambuddy.coffee
@@ -1,16 +1,11 @@
 request = require('request');
 http = require('http');
-Parser = require('./parser.js');
 config = require('../../config.json');
-parser = new Parser();
 User = require('./user.js')
 SlackIntegration = require('./integrations/slack_integration.js')
 $q = require('q')
-async = require('async')

 STEAM_API_TOKEN = process.env.STEAM_API_TOKEN
-BASE_STEAM_URL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key='
-FULL_PLAYER_URL = BASE_STEAM_URL + process.env.STEAM_API_KEY + '&steamids='

 integrations = []
 usersToCheck = []
@@ -22,69 +17,14 @@
   console.log '####################'
   integrations.push(new SlackIntegration({token: process.env.SLACK_TOKEN}, User))

-  ((parseUser(user).then (result)-> usersToCheck.push(result)) for user in config.users)
-
   minutes = .1
   the_interval = minutes * 60 * 1000 #10 seconds
   setInterval( (=>
     tickIntegrations()
-    getOnlineUsers(usersToCheck)
   ), the_interval)

 tickIntegrations = ->
   integration.checkOnlineUsers() for integration in integrations
-
-sendNotifications = (user)->
-  integration.sendNotification(user.name, user.currentGame) for integration in integrations
-
-getOnlineUsers = (allUsers)->
-  onlineUsers = []
-  deferred = $q.defer()
-  async.each allUsers, (user, callback)->
-    isUserOnline(user).then (result)->
-      onlineUsers.push(result) if result and not result.error
-      callback()
-  , (err)->
-    sendNotifications(user) for user in onlineUsers if not err
-    deferred.resolve(onlineUsers) if not err
-
-  deferred.promise
-
-isUserOnline = (user)->
-  url = FULL_PLAYER_URL + user.id
-  deferred = $q.defer()
-
-  request url, (error, response, body)->
-    if !error && response.statusCode is 200
-      parsedResult = JSON.parse(body)
-      player = parsedResult.response.players[0]
-
-      return null if not player
-
-      game = player.gameextrainfo
-
-      if game and not user.isPlaying()
-        user.setInGame(game)
-        deferred.resolve(user)
-
-      else if not game
-        user.setInactive()
-        deferred.resolve(null)
-    else
-      console.log 'An error was encountered at', Date.now()
-      console.log 'status code:', response.statusCode
-      console.log 'url:', url
-      deferred.reject(error)
-
-  deferred.promise
-
-parseUser = (vanityName)->
-  deferred = $q.defer()
-  request "http://steamcommunity.com/id/#{vanityName}/?xml=1", (error, response, body)=>
-    parser.parse body, (err, result)->
-      deferred.resolve(new User({name: result.name, id: result.id})) if not err
-
-  deferred.promise

 status = ->
   console.log 'Checking status of connections'


revise after msg created 1. create rosters for the msg related parties 2. search for subscribed parties 3. increment rosters msg counter

--- api/models/Msg.coffee
+++ api/models/Msg.coffee
@@ -88,13 +88,17 @@
 		# find or create roster items for those subscribers for msg.from and msg.to
 		sails.services.roster
 			.subscribeAll values.from, values.to
-			.then (items) ->
+			.then ->
+				# search for all subscribed rosters
+				sails.models.roster
+					.find jid: values.to
+			.then (items) ->
 				# update all subscribers' roster item
 				Promise
 					.all _.map items, (roster) ->
 						roster.sent values
-					.then ->
-						cb()
+			.then ->
+				cb()
 			.catch cb

 	afterDestroy: (values, cb) ->


collaboration: move social channel event bindings to its own method

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -34,16 +34,6 @@

     @socialChannel = channel

-    @socialChannel.on 'AddedToChannel', (socialAccount) =>
-
-      socialHelpers.fetchAccount socialAccount, (account) =>
-
-        return  unless account
-
-        {nickname} = account.profile
-        @statusBar.createParticipantAvatar nickname, no
-        @watchParticipant nickname
-

   initPrivateMessage: (callback) ->

@@ -335,6 +325,7 @@
       return throwError err  if err

       @activateRealtimeManager doc
+      @bindSocialChannelEvents()
       @finderPane.on 'ChangeHappened', @bound 'syncChange'

@@ -381,6 +372,19 @@

   unwatchParticipant: (nickname) -> @myWatchMap.delete nickname
+
+
+  bindSocialChannelEvents: ->
+
+    @socialChannel.on 'AddedToChannel', (socialAccount) =>
+
+      socialHelpers.fetchAccount socialAccount, (account) =>
+
+        return  unless account
+
+        {nickname} = account.profile
+        @statusBar.createParticipantAvatar nickname, no
+        @watchParticipant nickname

   bindRealtimeEvents: ->


activity: show channel chat pane when a post modal is active

--- client/activity/lib/reactivityroutes.coffee
+++ client/activity/lib/reactivityroutes.coffee
@@ -24,13 +24,13 @@
     path: ':channelName/summary/:postSlug'
     components:
       feed: PublicFeedPane
-      chat: null
+      chat: PublicChatPane
       post: PostPane
   ,
     path: ':channelName/:postSlug'
     components:
       feed: null
-      chat: null
+      chat: PublicChatPane
       post: PostPane
   ]


Shortcuts: rearrange shortcuts modal to use modalviewwithforms

--- client/app/lib/shortcuts/views/modal.coffee
+++ client/app/lib/shortcuts/views/modal.coffee
@@ -3,58 +3,48 @@
 ShortcutsPane = require './pane'
 defaults      = require '../config'

-module.exports =
-
-class ShortcutsModal extends kd.ModalView
+module.exports = class ShortcutsModal extends kd.ModalViewWithForms

   constructor: (options={}, keyconfig) ->

-    options.title   or= 'Shortcuts'
-    options.content or= """
-

-
To change a shortcut, select it, click the key combination, and then type the new keys.

-

-    """
-    options.overlay  or= yes
-    options.width    or= 540
-    options.height   or= 600
-    options.cssClass or= 'shortcuts-modal'
+    options =
+      title                   : 'Shortcuts'
+      cssClass                : 'shortcuts-modal'
+      overlay                 : yes
+      width                   : 640
+      height                  : 'auto'
+      buttons                 :
+        restore               :
+          title               : 'Restore Defaults'
+          style               : 'solid light-gray medium'
+          loader              : color : '#444444'
+          callback            : -> modal.destroy()
+        save                  :
+          title               : 'Save'
+          cssClass            : 'solid green medium'
+      tabs                    :
+        hideHandleCloseIcons  : yes
+        enableMoveTabHandle   : no
+        cssClass              : 'shortcuts-tab'
+        forms                 : @prepareTabData keyconfig

-    super options, keyconfig
+    super options

   viewAppended: ->

-    @addSubView tabView = new kd.TabView
+  prepareTabData: (keyconfig) ->

-      hideHandleCloseIcons : true
-      enableMoveTabHandle  : false
-      cssClass             : 'shortcuts-tab'
+    forms = {}
+    keyconfig.forEach (collection) ->
+      displayData = defaults[collection.name]
+      forms[displayData.title] =
+        fields                :
+          view                :
+            itemClass         : ShortcutsPane
+            description       : displayData.description
+            collection        : collection

-      paneData: @data.map (collection) ->
-        displayData = defaults[collection.name]
-        return {
-          title    : displayData.title
-          closable : no
-          view     : new ShortcutsPane {},
-            title       : displayData.title
-            description : displayData.description
-            collection  : collection
-        }
+    return forms

-    tabView.showPaneByIndex 0

-    buttonBar = new kd.View
-      cssClass: 'buttons'
-
-    buttonBar.addSubView new kd.ButtonView
-      title    : 'Restore Defaults'
-      cssClass : 'solid light-gray medium'
-      callback : kd.noop
-
-    buttonBar.addSubView new kd.ButtonView
-      title    : 'Save'
-      cssClass : 'solid green medium'
-      callback : kd.noop
-
-    @addSubView buttonBar


Bind HTML5 drag events in IDEView

Bind HTML5 drag events in IDEView

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -32,7 +32,7 @@

     options.tabViewClass     = IDEApplicationTabView
     options.createNewEditor ?= yes
-    options.bind             = 'dragover drop'
+    options.bind             = 'dragover drop dragenter dragleave'
     options.addSplitHandlers ?= yes

     super options, data


stack-editor: emit a reload event after reinit

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -750,7 +750,7 @@

     return  unless foundStack

-    kd.singletons.computeController.reinitStack foundStack
+    kd.singletons.computeController.reinitStack foundStack, @lazyBound 'emit', 'Reload'

   handleGenerateStack: ->


Better checking to know if the user is aliased.

--- src/remote/services/hull.coffee
+++ src/remote/services/hull.coffee
@@ -10,9 +10,9 @@
       return unless me

       analytics = require('analytics')
-      sign_in_count = me.stats?.sign_in_count || 0
+      signInCount = me.stats?.sign_in_count || 0

-      if identified && sign_in_count <= 1
+      if identified && signInCount == 1
         analytics.alias(me.id)
         identified = true


Check canSee again during notification, to avoid deletion embarrasement

--- lib/notifications.coffee
+++ lib/notifications.coffee
@@ -125,6 +125,7 @@
       ## Don't send notifications to myself, if so requested.
       continue if diff.updators.length == 1 and diff.updators[0] == to.username and not notifySelf to
       ## Only notify people who can read the message!
+      ## Checked again during notification.
       ## xxx this is already checked by serverMessageSubscribers
       ## xxx what should behavior be for superuser?  Currently they see all...
       continue unless canSee msg, false, to
@@ -289,6 +290,8 @@
       notification.msg = Messages.findOne notification.message
     ## Some messages may have been superdeleted by now; don't email about them.
     messageUpdates = (notification for notification in messageUpdates when notification.msg?)
+    ## Ignore messages that have been hidden from this user since (e.g. deleted)
+    messageUpdates = (notification for notification in messageUpdates when canSee notification.msg, false, user)
     ## Don't notify about empty messages (e.g. initial creation without
     ## follow-up) -- wait for content.  xxx should check if diff is version 1!
     messageUpdates = (notification for notification in messageUpdates when not messageEmpty notification.msg)