improve logic of compression wherein it dynamically detects if it should be hidden or not

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -92,6 +92,7 @@
             o.el            = $ el
             o.cloneEl       = o.el.clone() # Reference to the old untouched element
             o.browserWindow = $ window
+            o.windowWidth   = o.browserWindow.width()

             o.opts.onLoad(_this) if $.isFunction(o.opts.onLoad)

@@ -119,17 +120,23 @@
                 # set the state to compressed
                 current.data o.stateKey, 'compressed'

-                # slice the items beginning to the second element up to the second to the last element
                 if holder.length is 0
-                    hiddenItems = items
-                        .eq(1)
-                        .detach()
-
                     holder = ($ "
#{ o.opts.holder.text }

").insertAfter items.first()
-                else
-                    hiddenItems = items
-                        .eq(2)
-                        .detach()
+
+                # initialize an array for storage of the hidden items
+                hiddenItems = []
+
+                # loop through the breadcrumb children starting from the element after the holder element until the element before the active element
+                current
+                    .children(".#{ o.opts.holder.class }")
+                    .nextUntil('.active')
+                    .each ->
+                        crumb = $ this
+
+                        hiddenItems.push(crumb.detach().get(0)) if o.optimalCrumbHeight isnt o.el.height()
+
+                        # delete the reference since it does not correctly point it anymore the element
+                        crumb = null

                 o.opts.onCompress(_this) if $.isFunction(o.opts.onCompress)

@@ -192,12 +199,24 @@
                 )
                 .get()

-            optimalCrumbHeight = _.max crumbHeights
-
-            if optimalCrumbHeight isnt o.el.height()
-                o.compress()
-            else
-                console.log 'must be decompressed'
+            o.optimalCrumbHeight = _.max crumbHeights
+
+            o.compress() if o.optimalCrumbHeight isnt o.el.height()
+
+            if o.windowWidth isnt o.browserWindow.width()
+                if o.browserWindow.width() < o.windowWidth and optimalCrumbHeight isnt o.el.height()
+                    console.log 'decreasing'
+
+                    o.compress()
+                else
+                    console.log 'increasing'
+
+                o.windowWidth = o.browserWindow.width()
+
+            # if optimalCrumbHeight isnt o.el.height()
+            #     o.compress()
+            # else
+            #     console.log 'must be decompressed'

             # if typeof optimalWidth is "undefined"
             #     if optimalCrumbHeight isnt o.el.height()


removed unneccessary options

removed unneccessary options

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -9,14 +9,6 @@
     pluginName = 'responsiveBreadcrumbs'
     defaults =
         activeClass:            'active'
-        compressed:
-            wrapperClass: "#{ pluginName.toLowerCase() }-compressed-wrapper"
-            beforeOpen:   (obj) ->
-            open:         (obj) ->
-            afterOpen:    (obj) ->
-            beforeClose:  (obj) ->
-            close:        (obj) ->
-            afterClose:   (obj) ->
         debounceTime:           200
         enhanceAnimation:       true
         exposeItems:            false


sort dashboard

sort dashboard

--- kapuut/dashboard.coffee
+++ kapuut/dashboard.coffee
@@ -9,7 +9,7 @@
 if Meteor.isClient
     Template.dashboard.helpers
         hasCorrectAnswer: -> _.isNumber Template.currentData().correctAnswer
-        quizzes: -> Quizzes.find {}
+        quizzes: -> Quizzes.find {}, sort: [["lastmod", "desc"]]

     Template.dashboard.events
         "click #new": (evt) ->


Moved all jumpTo logic into abstract

Moved all jumpTo logic into abstract

--- lib/goto/abstract-goto.coffee
+++ lib/goto/abstract-goto.coffee
@@ -13,6 +13,8 @@
     clickEventSelectors: ''
     manager: {}
     gotoRegex: ''
+    jumpLine: null
+    jumpWord: ''

     ###*
      * Initialisation of Gotos
@@ -28,6 +30,12 @@
         atom.workspace.observeTextEditors (editor) =>
             @registerMarkers editor
             @registerEvents editor
+
+        atom.workspace.onDidChangeActivePaneItem (paneItem) =>
+            if paneItem instanceof TextEditor && @jumpWord != '' && @jumpWord != undefined
+                @jumpTo(paneItem, @jumpWord, @jumpLine)
+                @jumpLine = null
+                @jumpWord = ''

         # When you go back to only have 1 pane the events are lost, so need
         # to re-register.
@@ -143,3 +151,33 @@
     ###
     canGoto: (term) ->
         return term.match(@gotoRegex)?.length > 0
+
+    ###*
+     * Gets the regex used when looking for a word within the editor
+     * @param  {string} term Term being search.
+     * @return {regex}       Regex to be used.
+    ###
+    getJumpToRegex: (term) ->
+
+    ###*
+     * Jumps to a word within the editor
+     * @param  {TextEditor} editor The editor that has the function in.
+     * @param  {string} word       The word to find and then jump to.
+     * @return {boolean}           Whether the finding was successful.
+    ###
+    jumpTo: (editor, word, line) ->
+        bufferPosition = @parser.findBufferPositionOfWord(editor, word, @getJumpToRegex(word), line)
+        if bufferPosition == null
+            return false
+
+        # Small delay to wait for when a editor is being created.
+        setTimeout(() ->
+            editor.setCursorBufferPosition(bufferPosition, {
+                autoscroll: false
+            })
+            # Separated these as the autoscroll on setCursorBufferPosition
+            # didn't work as well.
+            editor.scrollToScreenPosition(editor.screenPositionForBufferPosition(bufferPosition), {
+                center: true
+            })
+        , 100)


Use /\x22/g instead of /"/g in eco.compile heredoc to avoid tripping up syntax highlighters

--- lib/eco/compiler.coffee
+++ lib/eco/compiler.coffee
@@ -42,7 +42,7 @@
             .replace(/&/g, '&')
             .replace(/</g, '<')
             .replace(/>/g, '>')
-            .replace(/"/g, '"');
+            .replace(/\x22/g, '"');
         };
       }
       (function() {


Fix increaseIndentPattern

Fix increaseIndentPattern

--- settings/language-haskell.cson
+++ settings/language-haskell.cson
@@ -1,7 +1,7 @@
 '.source.haskell':
   'editor':
     'commentStart': '-- '
-    'increaseIndentPattern': '((^.*(=|\\bdo|\\bwhere|\\bthen|\\belse|\\bof)\\s*$)|(^.*\\bif(?!.*\\bthen\\b.*\\belse\\b.*).*$))'
+    'increaseIndentPattern': '(((=|\\bdo|\\bwhere|\\bthen|\\belse|\\bof)\\s*)|(\\bif(?!.*\\bthen\\b.*\\belse\\b.*).*))$'
 '.source.cabal':
   'editor':
     'commentStart':'-- '


spaces

spaces

--- app/assets/js/app.coffee
+++ app/assets/js/app.coffee
@@ -22,7 +22,7 @@

 App.MonitorRoute = Ember.Route.extend
-	activate: (controller) ->
+  activate: (controller) ->
     $('.top-bg').animate
       height: 200


removed extra logs calls out of stream.coffee

--- app/coffee/stream.coffee
+++ app/coffee/stream.coffee
@@ -22,8 +22,8 @@
     @stream = streamModel
     @$container = $('#entryField .matrix')
     @activateLightBox()
+
     # adds the loading wheel
-
     $('#entry-filter, #users-filter').change( (event) =>
       $('#entry-filter-wrap .spinner').show() if event.currentTarget.id is 'entry-filter'
       $('#users-filter-wrap .spinner').show() if event.currentTarget.id is 'users-filter'
@@ -78,10 +78,7 @@
     $.getJSON("/stream.json", {filters: filters}).promise()
   updateFilters: ->
     @entryFilter = $('#entry-filter').val()
-    @usersFilter = $('#users-filter').val()
-
-    log '@entryFilter: ' + @entryFilter
-    log '@usersFilter: ' + @usersFilter
+    @usersFilter = $('#users-filter').val()

   filterOptions: ->
     type: @entryFilter


Fixed client tests

Fixed client tests

--- test/client.coffee
+++ test/client.coffee
@@ -131,7 +131,7 @@
         doc.insert 0, 'hi', =>
           test.strictEqual doc.snapshot, 'hi'
           test.strictEqual doc.getText(), 'hi'
-          @model.getSnapshot @name, ({snapshot}) ->
+          @model.getSnapshot @name, (error, {snapshot}) ->
             test.strictEqual snapshot, 'hi'
             test.done()

@@ -183,11 +183,11 @@
         doc.on 'remoteop', (op) ->
           test.deepEqual op, [{i:'hi', p:0}]

-          test.expect 4
-          test.done()
-
-        @model.applyOp @name, {v:0, op:[{i:'hi', p:0}]}, (version, error) ->
-          test.ifError error
+          test.expect 3
+          test.done()
+
+        @model.applyOp @name, {v:0, op:[{i:'hi', p:0}]}, (error, version) ->
+          test.fail error if error

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

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

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

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

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

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

     'created locally is set on new docs': (test) ->
@@ -348,7 +348,7 @@
           # Also need to test that ops sent afterwards get sent correctly.
           # because that behaviour IS CURRENTLY BROKEN

-          @model.getSnapshot @name, ({snapshot}) ->
+          @model.getSnapshot @name, (error, {snapshot}) ->
             test.strictEqual snapshot, ''
             test.done()

@@ -377,7 +377,7 @@
           e = expectCalls 3, =>
             # The b's are successfully deleted, the ds are added by the server and the
             # op to delete the cs is denied.
-            @model.getSnapshot @name, ({snapshot}) ->
+            @model.getSnapshot @name, (error, {snapshot}) ->
               test.deepEqual snapshot, 'acdDCA'
               test.done()

@@ -409,7 +409,7 @@
         doc.insert 0, 'hi', =>
           test.strictEqual doc.getText(), 'hi'

-          @model.getSnapshot @name, (data) ->
+          @model.getSnapshot @name, (error, data) ->
             test.strictEqual data.snapshot, 'hi'
             doc.close()
             test.done()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color2.coffee
+++ test/color2.coffee
@@ -288,6 +288,16 @@
   equal color2.b, 150
   equal color2.a, 1.0

+test "#desaturate$", ->
+  color = Color2(69, 232, 150)
+
+  color.desaturate$(0.3)
+
+  equal color.r, 100
+  equal color.g, 200
+  equal color.b, 150
+  equal color.a, 1.0
+
 test "#darken", ->
   color = Color2(45, 64, 39)


Added logic to get parameters from Box

Added logic to get parameters from Box

--- routes/templatesRoute.coffee
+++ routes/templatesRoute.coffee
@@ -10,6 +10,7 @@
     onBeforeAction: ->
         console.log(this.params)
         console.log(this.request)
+        console.log(this.request.body)
         @next()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

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

-test "
+test "" ->

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine.coffee
+++ test/engine.coffee
@@ -183,6 +183,6 @@
 test "#setState", ->
   engine = Engine()

-  engine.se
+  engine.setState "

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

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

-test "integration", ->
+test "Integration", ->

 test "#setState", 3, ->
   engine = Engine()


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -16,7 +16,7 @@
   enemy.include(Follow)
   enemy.follow(player)

-  ok enemy.I.velocity.equal(Point(1, 0))
+  ok enemy.I.velocity.equal(Point(1, 0)), ''

   fastEnemy = GameObject
     x: 50


Follow mixin

Follow mixin

--- test/follow.coffee
+++ test/follow.coffee
@@ -17,4 +17,6 @@

   enemy.follow(player)

+  equ
+
 module()


fixing some blatant bugs

fixing some blatant bugs

--- src/compiler.coffee
+++ src/compiler.coffee
@@ -419,8 +419,8 @@
   d2i: { compile: (b) -> b.push new Expr "util.float2int($0)",b.pop2() }
   d2l: { compile: (b) -> b.push2 new Expr "gLong.fromNumber($0)",b.pop2() }  # doesn't handle +/- inf edge cases
   d2f: { compile: (b) -> b.push new Expr "util.wrap_float($0)",b.pop2() }
-  i2b: { compile: (b) -> b.push new Expr "util.truncate($0, 8)",b.pop2() }
-  i2c: { compile: (b) -> b.push "$0&0xFFFF",b.pop() }
+  i2b: { compile: (b) -> b.push new Expr "util.truncate($0, 8)",b.pop() }
+  i2c: { compile: (b) -> b.push new Expr "$0 & 0xFFFF",b.pop() }
   i2s: { compile: (b) -> b.push new Expr "util.truncate($0, 16)",b.pop() }

   ireturn: { compile: (b) -> b.add_stmt "return #{b.pop()}" }


Replace @isCpp by extended grammar check

--- lib/linter-clang.coffee
+++ lib/linter-clang.coffee
@@ -14,14 +14,12 @@
   # A string, list, tuple or callable that returns a string, list or tuple,
   # containing the command line (with arguments) used to lint.

-  isCpp: false
   @cmd: ''

   editor: null

   errorStream: 'stderr'
-

   lintFile: (filePath, callback) ->
     # parse space separated string
@@ -50,10 +48,16 @@
     args.push '-x'
     args.push @language

-    if @isCpp
+    if editor.getGrammar().name == 'C++'
       flag = atom.config.get 'linter-clang.clangDefaultCppFlags'
+    else if editor.getGrammar().name == 'Objective-C++'
+      flag = atom.config.get 'linter-clang.clangDefaultObjCppFlags'
+    else if editor.getGrammar().name == 'C'
+      flag = atom.config.get 'linter-clang.clangDefaultCFlags'
+    else if editor.getGrammar().name == 'Objective-C'
+      flag = atom.config.get 'linter-clang.clangDefaultObjCFlags'
     else
-      flag = atom.config.get 'linter-clang.clangDefaultCFlags'
+      console.log "linter-clang error: unknown grammar '#{editor.getGrammar().name}'"

     flagSplit = parseSpaceString flag

@@ -133,19 +137,15 @@
     if editor.getGrammar().name == 'C++'
       @language = 'c++'
       # @flag = '-std=c++11'
-      @isCpp = true
     if editor.getGrammar().name == 'Objective-C++'
       @language = 'objective-c++'
       # @flag = ''
-      @isCpp = true
     if editor.getGrammar().name == 'C'
       @language = 'c'
       # @flag = ''
-      @isCpp = false
     if editor.getGrammar().name == 'Objective-C'
       @language = 'objective-c'
       # @flag = ''
-      @isCpp = false

     super(editor)


:art: cleanup

:art: cleanup

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -27,8 +27,8 @@
       @initOverwrittenByEditor()
       @editor.transact =>
         @countTimes =>
-          @_takenIndex = 0
-          for selection in selections
+          for selection, i in selections
+            @selectionIndex = i
             @mutate(selection) if @isMovable(selection)

   initOverwrittenByEditor: ->
@@ -43,7 +43,7 @@
       overwrittenByEditor.set(@editor, overwrittenArea)

   getSelectedTexts: ->
-    @editor.getSelections().map((selection) -> selection.getText()).join('')
+    @editor.getSelections().map((selection) -> selection.getText()).join("\n")

   isOverwrite: ->
     atom.config.get('vim-mode-plus-move-selected-text.overwrite')
@@ -82,19 +82,18 @@
         overwrittenArea.unshift(replacedText)
         overwrittenArea.pop()
       when 'right'
-        [rest..., overwritten] = overwrittenArea[@_takenIndex]
-        overwrittenArea[@_takenIndex] = replacedText + rest.join('')
-        @_takenIndex++
+        [rest..., overwritten] = overwrittenArea[@selectionIndex]
+        overwrittenArea[@selectionIndex] = replacedText + rest.join('')
         overwritten
       when 'left'
-        [overwritten, rest...] = overwrittenArea[@_takenIndex]
-        overwrittenArea[@_takenIndex] = rest.join('') + replacedText
-        @_takenIndex++
+        [overwritten, rest...] = overwrittenArea[@selectionIndex]
+        overwrittenArea[@selectionIndex] = rest.join('') + replacedText
         overwritten

   isLinewise: ->
     switch @vimState.submode
-      when 'linewise' then true
+      when 'linewise'
+        true
       when 'characterwise', 'blockwise'
         @editor.getSelections().some (selection) ->
           not swrap(selection).isSingleRow()
@@ -126,21 +125,15 @@
           @editor.setTextInBufferRange([eof, eof], "\n")
         true

-  getRangeTranslationSpec: (wise) ->
-    if wise is 'linewise'
-      switch @direction
-        when 'up' then [[-1, 0], [0, 0]]
-        when 'down' then [[0, 0], [1, 0]]
-    else if 'characterwis'
-      switch @direction
-        when 'up' then [[-1, 0], [-1, 0]]
-        when 'down' then [[1, 0], [1, 0]]
-
   # Characterwise
   # -------------------------
   moveCharacterwise: (selection) ->
     reversed = selection.isReversed()
-    translation = @getRangeTranslationSpec('characterwise')
+
+    translation = switch @direction
+      when 'up' then [[-1, 0], [-1, 0]]
+      when 'down' then [[1, 0], [1, 0]]
+
     fromRange = selection.getBufferRange()
     toRange = fromRange.translate(translation...)
     @swapTextInRange(fromRange, toRange)
@@ -166,7 +159,10 @@
     reversed = selection.isReversed()

     # Pre mutate
-    translation = @getRangeTranslationSpec('linewise')
+    translation = switch @direction
+      when 'up' then [[-1, 0], [0, 0]]
+      when 'down' then [[0, 0], [1, 0]]
+
     swrap(selection).translate(translation)

     lineTexts = swrap(selection).lineTextForBufferRows()
@@ -226,7 +222,11 @@

   moveCharacterwise: (selection) ->
     reversed = selection.isReversed()
-    translation = @getRangeTranslationSpec()
+
+    translation = switch @direction
+      when 'right' then [[0, 0], [0, +1]]
+      when 'left' then [[0, -1], [0, 0]]
+
     swrap(selection).translate(translation)
     newText = @rotate(selection.getText())
     range = selection.insertText(newText)
@@ -244,11 +244,6 @@
         replacedText = @getOverwrittenText(replacedText) if @isOverwrite()
         moving.join('') + replacedText

-  getRangeTranslationSpec: ->
-    switch @direction
-      when 'right' then [[0, 0], [0, +1]]
-      when 'left' then [[0, -1], [0, 0]]
-
 class MoveSelectedTextLeft extends MoveSelectedTextRight
   direction: 'left'


:fire: manual update for vB prop is no longer necessary

--- lib/move-selected-text.coffee
+++ lib/move-selected-text.coffee
@@ -68,9 +68,6 @@
         fn(selection)
         swrap(selection).saveProperties()
         swrap(selection).fixPropertyRowToRowRange()
-      if @submode is 'blockwise'
-        for blockwiseSelection in @vimState.getBlockwiseSelections()
-          blockwiseSelection.saveProperties()

   execute: ->
     @withGroupChanges =>
@@ -196,8 +193,6 @@
           @duplicateBlockwise(blockwiseSelection)
         for $selection in swrap.getSelections(@editor)
           $selection.saveProperties()
-        for blockwiseSelection in @getBlockwiseSelections()
-          blockwiseSelection.saveProperties()

         isOneHeight = (blockwiseSelection) -> blockwiseSelection.getHeight() is 1
         if wasCharacterwise and @vimState.getBlockwiseSelections().every(isOneHeight)


#update #21

#update #21

--- lib/native-app-starter.coffee
+++ lib/native-app-starter.coffee
@@ -12,6 +12,8 @@
       name = @nativeRE.exec url
       @run name[1]
   run: (name) ->
+    console.log name
+    console.log settings.nativeApps
     if settings.nativeApps[name]?
       @execute settings.nativeApps[name]
     else


[grunt] properly retrive context value in Nunjucks' `config()`

--- tasks/grunt-nunjucks-2-html.coffee
+++ tasks/grunt-nunjucks-2-html.coffee
@@ -252,22 +252,23 @@
           ###
           env.addGlobal 'config', (prop, value, merge = true) ->
             ctx           = this.ctx
+            ctxValue      = _.get(ctx, prop)
             valueIsArray  = Array.isArray(value)
             valueIsObject = typeof value == 'object' and value and not valueIsArray

             # Set if `value` provided
             if value != undefined

-              if not merge or not ctx.hasOwnProperty(prop)
+              if not merge or not ctxValue
                 _.set(ctx, prop, value)
-              else if ctx.hasOwnProperty(prop)
-                result = if valueIsObject then _.merge(value, ctx[prop]) else if valueIsArray then _.union(value, ctx[prop]) else ctx[prop]
+              else
+                result = if valueIsObject then _.merge(value, ctxValue) else if valueIsArray then _.union(value, ctxValue) else ctxValue
                 _.set(ctx, prop, result)

               return

             # Get if no `value` provided
-            else return _.get(ctx, prop)
+            else return ctxValue

           ###*
            * Get information about page and its childs from specified object.


Properly detect session filepath

Properly detect session filepath

--- lib/editor-proxy.coffee
+++ lib/editor-proxy.coffee
@@ -142,4 +142,5 @@

   # Returns the current editor's file path
   getFilePath: ->
-    return ""
+    # is there a better way to get this?
+    return @editSession.buffer.file.path


Simplified CLI and some APIs. And some small twists

--- lib/flash-loader.coffee
+++ lib/flash-loader.coffee
@@ -69,6 +69,7 @@
     when 'win32' then null  # TODO

 getFlashVersion = (path) ->
+  dbg.ex.flashLoader?.assert path, 'No flash player path passed in!'
   return '' if not validatePath path
   switch PLATFORM
     when 'darwin'
@@ -91,7 +92,7 @@

 useFlashLoc = 'auto'
 CLIFlashPath = ''
-reCLIArg = /^--flash-player=(.+)$/i
+reCLIArg = /^--flash=(.+)$/i
 parseCLIArg = (arg) ->
   if (m = reCLIArg.exec arg)?
     value = m[1]
@@ -110,7 +111,7 @@
   #    try to find built-in flash player.
   # 3. If 2 failed, try to find Google Chrome integrated flash player.
   # 4. If 3 failed, try to find globally installed flash player.
-  switch loc
+  switch loc.toLowerCase()
     when 'auto'
       flashPath = getBuiltInFlashPath()
       flashPath ?= findChromeFlashPath()
@@ -150,7 +151,7 @@

 if process.type is 'browser'
   exports.parseCLIArg = parseCLIArg
-  exports.loadFlashPlayer = load
+  exports.load = load

-exports.getFlashPlayerVersion = getFlashVersion
+exports.getFlashVersion = getFlashVersion
 exports.getAllVersions = getAllVersions


Switch to interval rate limiting

Switch to interval rate limiting

--- lib/getThrottles.coffee
+++ lib/getThrottles.coffee
@@ -9,7 +9,7 @@

 createThrottle = (name, rate) ->
   debug 'createThrottle', name, rate
-  limiter.rule name, max: rate, interval: windowRate
+  limiter.rule name, max: rate, interval: windowRate, mode: 'interval'
   rateLimit: (id="", callback=->) =>
     debug 'rateLimit', name, id
     limiter.drop(name, id, callback)


Added istitle and isupper

Added istitle and isupper

--- src/builtin/string.coffee
+++ src/builtin/string.coffee
@@ -109,4 +109,25 @@
       for char in @value
         if not char in spaces
           return false
-      return true+      return true
+
+  istitle: ->
+    words = @value.split ' '
+    for word in words
+      if not String(word[0]).isupper()
+        return false
+      if word.length == 1
+        continue
+      if not String(word[0]).islower()
+        return false
+    return true
+
+  isupper: ->
+    uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+    if @value.__len__() == 0
+      return false
+    else
+      for char in @value
+        if not char in uppers
+          return false
+      return true


get rid of create proposal button for homepage

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -128,7 +128,7 @@
     if !proposals || !users.users
       return ProposalsLoading()

-    clusters = get_all_clusters()
+    clusters = get_all_lists()

     hues = getNiceRandomHues clusters.length
     colors = {}
@@ -161,7 +161,7 @@

       ManualProposalResort sort_key: @local.key

-      ListHeading
+      ListHeader
         cluster:
           key: "list/#{dummy_cluster}"
           name: dummy_cluster
@@ -283,23 +283,15 @@
             list: cluster
             index: index

-
-        if permit('create proposal') > 0 && customization('homepage_show_new_proposal_button') \
-            && homepage_tabs.filter not in ['About', 'FAQ'] && ( !subdomain.SSO_domain || current_user.logged_in)
-          A
-            style:
-              color: logo_red
-              marginTop: 35
-              display: 'inline-block'
-              borderBottom: "1px solid #{logo_red}"
-            href: '/proposal/new'
-
-            translator 'engage.add_new_proposal_button', "Create new proposal"
+        if current_user.is_admin && homepage_tabs.filter not in ['About', 'FAQ']
+
+          NewList()
+

   typeset : ->
     subdomain = fetch('/subdomain')
     if subdomain.name == 'RANDOM2015' && $('.MathJax').length == 0
-      MathJax.Hub.Queue(["Typeset",MathJax.Hub,".proposal_homepage_name"])
+      MathJax.Hub.Queue(["Typeset", MathJax.Hub, ".proposal_homepage_name"])

   componentDidMount : -> @typeset()
   componentDidUpdate : -> @typeset()


Added session update for certain cases

Added session update for certain cases

--- lib/personal-app.coffee
+++ lib/personal-app.coffee
@@ -22,7 +22,7 @@
 test_hostname = "127.0.0.1"
 test_port = 7357

-_BACKOFF_DELAY = 1000 #1s
+_BACKOFF_DELAY = 2000 #2s

 code_regex = /^[a-z0-9]{20}$/gi
 code_regex.compile code_regex
@@ -314,7 +314,7 @@
                         json_res += chunk
                     res.on "end", () ->
                         if @statusCode == 403 and _QPS_REGEX.test(json_res)
-                            setTimeout @request, _BACKOFF_DELAY, options, callback
+                            setTimeout @upload_file, _BACKOFF_DELAY, gem_id, filename, buf
                         if @statusCode != 200 then return deferred.reject new Error("status #{@statusCode}\t#{json_res}")
                         return_obj = JSON.parse json_res
                         callback null, return_obj if callback? and typeof callback == 'function'
@@ -591,14 +591,24 @@
                 state: req.query.state
                 redirect_uri: sess.redirect_uri
             promise.then (access_obj) ->
-                sess[key] = val for key,val of access_obj
-                req.personal.client = new PersonalClient access_obj
+                for own key,val of access_obj
+                    sess[key] = val
+                req.personal.client = new PersonalClient
+                    client_id: connect_opts.get "client_id"
+                    client_secret: connect_opts.get "client_secret"
+                    access_token: sess.access_token
+                    refresh_token: sess.refresh_token
+                    expiration: sess.expiration
+                    redirect_uri: sess.redirect_uri
+                    sandbox: connect_opts.get "sandbox"
+                req.personal.logged_in = true
                 next()
             ,(err) ->
                 next(err)
             promise.fin () ->
                 sess.state = null
                 #TODO: remove code, state, and personal from query (redirect)
+
             return

     #we need to create the url for login and auth


.

Former-commit-id: 721cb5ea402ca139b2da8259d624cdc1952ac461 [formerly a6d54de35ad052ebab981bde6bc95447821bb9b7] [formerly 0f83f31482e96492e6fb0435dac1db49692305f0 [formerly 29630d1998f13ef65b0d9fecddd9baa2ae2e3656]] [formerly c80d3ed3d1b8f0fbafd311edfedb4bb9b39955e0 [formerly 2ad80600b14aa515c2c6a3c788238045d14c9a35] [formerly 60f7bc3072a540c5c785e8d4d2dc2744d6542043 [formerly 60f7bc3072a540c5c785e8d4d2dc2744d6542043 [formerly 61fe168bbe8e97dac67efd3b40a6474c39075145]]]]
Former-commit-id: 36342b0640618aa52ea4e524f2421ef9b66baae1 [formerly 084f8743bba74a1e1a2932046fc1d233dfa1eb18] [formerly cd471917591d99b63b3ebf9cf104227938358b4f [formerly 78ca91b4f9149191f1a2e7370e12fa90096a8396]]
Former-commit-id: ce6cc083ffb00ee0374b0d3eefee264f3ad9309a [formerly 6f76f637d63ba455f4470d8fea331cab8df9d968]
Former-commit-id: ef5394a6a07a65135086389a5ce2fbfaa02bf7a7
Former-commit-id: c9b0064a71991963bc1e0d7110ee5b5bd30abd95

--- @client/homepage.coffee
+++ @client/homepage.coffee
@@ -801,10 +801,13 @@

         SPAN
           style:
-            color: '#aaa'
+            color: '#999'
             fontSize: 12
-            fontWeight: 600
             cursor: 'pointer'
+          if score < 0
+            score *= -1
+            "–"
+
           score

         if @local.hover_score


Change row index for the decoration dummy code

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -50,7 +50,7 @@
     @miniEditorView.minimapView = this
     @miniEditorView.setEditorView(@editorView)

-    range = @editor.getBuffer().rangeForRow(46)
+    range = @editor.getBuffer().rangeForRow(0)
     marker = @editor.markBufferRange(range)
     decoration = @decorateMarker(marker, type: 'line', scope: ".minimap .lines .git-line-added")


Tests: Fix master test that relied on a stream we were deleting

--- test/master.coffee
+++ test/master.coffee
@@ -6,6 +6,7 @@
 Logger          = $src "logger"

 MasterHelper    = require "./helpers/master"
+StreamHelper    = require "./helpers/stream"

 weak = require "weak"

@@ -105,19 +106,26 @@
             done()

         it "should destroy a stream when asked", (done) ->
-            stream = weak(mm.master.master.streams[STREAM1.key])
-            mm.master.master.removeStream stream, (err) =>
+            stream2 = StreamHelper.getStream "mp3"
+
+            mm.master.master.createStream stream2, (err,status) ->
                 throw err if err

-                expect(mm.master.master.streams).to.not.have.property STREAM1.key
+                expect(mm.master.master.streams).to.have.property stream2.key

-                if global.gc
-                    global.gc()
-                    expect(stream.key).to.be.undefined
-                    done()
-                else
-                    console.log "Skipping GC deref test. Run with --expose-gc"
-                    done()
+                stream = weak(mm.master.master.streams[stream2.key])
+                mm.master.master.removeStream stream, (err) =>
+                    throw err if err
+
+                    expect(mm.master.master.streams).to.not.have.property stream2.key
+
+                    if global.gc
+                        global.gc()
+                        expect(stream.key).to.be.undefined
+                        done()
+                    else
+                        console.log "Skipping GC deref test. Run with --expose-gc"
+                        done()

     # -- Slaves -- #


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/matrix.coffee
+++ test/matrix.coffee
@@ -87,7 +87,7 @@

     p = Point(10, 10)

-    equals(matrix.transformPoint(p))
+    ok

   test ".rotation", ->
     matrix = Matrix.rotation(Math.PI / 2)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/random.coffee
+++ test/random.coffee
@@ -17,6 +17,6 @@

   a = Random.angleBetween(0.25.turns, 0.5.turns)
   ok a < 0.5.turns
-  ok a > 0
+  ok a > 0.25.

 module()


Prevent about route from executing if it is already at the top

--- apps/about/client.coffee
+++ apps/about/client.coffee
@@ -11,6 +11,7 @@
   initialize: ->
     @$header          = $('#main-layout-header')
     @$jumpContainer   = $('#about-page-jump')
+    @$window          = $(window)
     @jump             = new JumpView threshold: @_getThreshold()

     @$jumpContainer.html @jump.$el.css
@@ -18,7 +19,8 @@
       right: 'inherit'

   toTop: ->
-    mediator.trigger 'scroll:top'
+    if @$window.scrollTop() != 0
+      mediator.trigger 'scroll:top'

   toSection: (slug) ->
     _.defer => mediator.trigger 'scroll:position', @_positionFromSlug(slug)


tween with arbitrary easing function

tween with arbitrary easing function

--- src/drawable.coffee
+++ src/drawable.coffee
@@ -161,8 +161,7 @@

     transform = Matrix.translation(center.x.floor(), center.y.floor())

-    if I.scale? and I.scale != 1
-      transform = transform.concat(Matrix.scale(I.scale))
+    transform = transform.concat(Matrix.scale(I.scale, I.scale ))
     transform = transform.concat(Matrix.rotation(I.rotation)) if I.rotation
     transform = transform.concat(Matrix.HORIZONTAL_FLIP) if I.hflip
     transform = transform.concat(Matrix.VERTICAL_FLIP) if I.vflip


renaming before and after transform methods

--- src/drawable.coffee
+++ src/drawable.coffee
@@ -66,12 +66,12 @@
   ###
   draw: (canvas) ->

-    self.trigger 'before_transform', canvas
+    self.trigger 'beforeTransform', canvas

     canvas.withTransform self.getTransform(), (canvas) ->
       self.trigger 'draw', canvas

-    self.trigger 'after_transform', canvas
+    self.trigger 'afterTransform', canvas

     return self


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/drawable.coffee
+++ src/drawable.coffee
@@ -86,6 +86,7 @@
   I ||= {}

   Object.reverseMerge I,
+    alpha: 1
     color: "#196"
     hflip: false
     vflip: false
@@ -104,25 +105,33 @@
     )

   self.bind 'draw', (canvas) ->
-    if sprite = I.sprite
-      if sprite.draw?
-        sprite.draw(canvas, -sprite.width / 2, -sprite.height / 2)
+    if I.alpha && I.alpha != 1
+      previousAlpha = I.alpha
+
+      canvas.context().globalAlpha = I.alpha
+
+    if I.sprite
+      if I.sprite.draw?
+        I.sprite.draw(canvas, 0, 0)
       else
         warn?("Sprite has no draw method!")
     else
       if I.radius?
         canvas.drawCircle
-          x: 0
-          y: 0
+          x: I.width/2
+          y: I.height/2
           radius: I.radius
           color: I.color
       else
         canvas.drawRect
-          x: -I.width/2
-          y: -I.height/2
+          x: 0
+          y: 0
           width: I.width
           height: I.height
           color: I.color
+
+    if I.alpha && I.alpha != 1
+      I.alpha = previousAlpha

   ###*
   Draw does not actually do any drawing itself, instead it triggers all of the draw events.
@@ -159,6 +168,8 @@
     transform = transform.concat(Matrix.HORIZONTAL_FLIP) if I.hflip
     transform = transform.concat(Matrix.VERTICAL_FLIP) if I.vflip

+    transform = transform.concat(Matrix.translation(-I.width/2, -I.height/2))
+
     if I.spriteOffset
       transform = transform.concat(Matrix.translation(I.spriteOffset.x, I.spriteOffset.y))


Folder upload support (for Chrome)

Folder upload support (for Chrome)

 - fixes #74
 - tested on Chrome 27 (on Mac)

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -622,11 +622,29 @@
     return unless e.dataTransfer
     files = e.dataTransfer.files
     @emit "selectedfiles", files
-    @handleFiles files if files.length
+    if files.length
+      items = e.dataTransfer.items
+      if items and items.length and (items[0].webkitGetAsEntry or items[0].getAsEntry)
+        @handleItems items
+      else
+        @handleFiles files
+    return

   handleFiles: (files) ->
     @addFile file for file in files
+
+  handleItems: (items) ->
+    for item in items
+      if item.webkitGetAsEntry
+        entry = item.webkitGetAsEntry()
+        if entry.isFile
+          @addFile item.getAsFile()
+        else if entry.isDirectory
+          @addDirectory entry, entry.name
+      else
+        @addFile item.getAsEntry()
+    return

   # If `done()` is called without argument the file is accepted
   # If you call it with an error message, the file is rejected
@@ -668,6 +686,26 @@
         if @options.enqueueForUpload
           file.status = Dropzone.QUEUED
           @processQueue()
+
+  addDirectory: (entry, path) ->
+    dirReader = entry.createReader()
+    dirReader.readEntries \
+      (entries) =>
+        for entry in entries
+          if entry.isFile
+            entry.file (file) =>
+              return if file.name.substring(0, 1) is '.'
+              file.fullPath = path + '/' + file.name;
+              @addFile file
+              return
+          else if entry.isDirectory
+            @addDirectory entry, path + '/' + entry.name
+        return
+      ,
+      (error) ->
+        console.log error
+        return
+    return

   # Can be called by the user to remove a file
   removeFile: (file) ->


Adding a space before the # and 2 sentences.

--- lib/text-objects.coffee
+++ lib/text-objects.coffee
@@ -9,9 +9,9 @@
     @editor.selectWord()
     [true]

-#SelectInsideQuotes and the next class defined (SelectInsideBrackets) are
-#almost-but-not-quite-repeated code, they are different is because of the depth
-#checks in the bracket matcher.
+# SelectInsideQuotes and the next class defined (SelectInsideBrackets) are
+# almost-but-not-quite-repeated code. They are different is because of the depth
+# checks in the bracket matcher.

 class SelectInsideQuotes extends TextObject
   constructor: (@editor, @char) ->
@@ -57,9 +57,9 @@
     {select,end} = @findClosingQuote(start)
     select

-#SelectInsideBrackets and the previous class defined (SelectInsideQuotes) are
-#almost-but-not-quite-repeated code, they are different is because of the depth
-#checks in the bracket matcher.
+# SelectInsideBrackets and the previous class defined (SelectInsideQuotes) are
+# almost-but-not-quite-repeated code. They are different is because of the depth
+# checks in the bracket matcher.

 class SelectInsideBrackets extends TextObject
   constructor: (@editor, @beginChar, @endChar) ->


Minor reordering

Minor reordering

--- library/exposure/apidoc.coffee
+++ library/exposure/apidoc.coffee
@@ -81,8 +81,8 @@
     # documented function is implemented. Please refer to `Document`
     # class and its module implementation for more information on it.
     @GET (method, service, kernel) ->
+        @relevant "ts33kr.github.io/granite/exposure/apidoc.html"
         @github "ts33kr", "granite", "library/exposure/apidoc.coffee"
-        @relevant "ts33kr.github.io/granite/exposure/apidoc.html"
         @synopsis "Get all of the APIs available in the system"
         @outputs "An array of objects, each describes a service"
         @notes "See the Document class for the API structure"


Add minor logic enh (may blow up)

Add minor logic enh (may blow up)

--- library/nucleus/compose.coffee
+++ library/nucleus/compose.coffee
@@ -80,6 +80,7 @@
         hierarchy = _.drop hierarchy, pivotal + 1
         func = _.head(hierarchy).prototype?[name]
         return func unless func is current
+        @upstack _.head(hierarchy), name

 # A complicated piece of functionality for merging arbitrary classes
 # into the linear hierarchical inheritance chain of existing class.


reorder the config hierarchy

reorder the config hierarchy

--- library/nucleus/scoping.coffee
+++ library/nucleus/scoping.coffee
@@ -53,12 +53,12 @@
     # implementation takes care only of loading the proper config.
     # The kernel invokes this prior to proceeding its operations.
     incorporate: (kernel) ->
+        nconf.overrides(@overrides or @constructor.OVERRIDES or {})
         assert fpath = "#{nconf.get "layout:config"}/#{@tag}.json"
         logger.info "Incorporating up the #{@tag.bold} scope".cyan
         logger.info "Assuming the #{fpath.underline} config".cyan
         exists = fs.existsSync fpath; nconf.file fpath if exists
         nconf.defaults(@defaults or @constructor.DEFAULTS or {})
-        nconf.overrides(@overrides or @constructor.OVERRIDES or {})
         for directory in nconf.get("env:dirs") or new Array
             assert _.isNumber mode = nconf.get "env:mode"
             msg = "Environment mkdir at %s with 0%s mode".yellow


rework loading class

rework loading class

--- lib/util/loading.coffee
+++ lib/util/loading.coffee
@@ -2,23 +2,28 @@

 module.exports =
 class Loading
-  emitter: new Emitter
+
+  status: 0
+
+  constructor: ->
+    @emitter = new Emitter
+
   onWorking: (f) -> @emitter.on 'working', f
   onDone: (f) -> @emitter.on 'done', f
-
-  lastStatus: 0
-  status: 0
-
-  working: -> @status++; @update()
-  done: -> if @status > 0 then @status--; @update()
-  reset: -> @status = 0; @update()
-
   isWorking: -> @status > 0

-  update: ->
-    if @status != @lastStatus
-      if @isWorking()
-        @emitter.emit 'working', @status
-      else
-        @emitter.emit 'done', @status
-    @lastStatus = @status
+  working: ->
+    @status++
+    if @status is 1
+      @emitter.emit 'working'
+
+  done: ->
+    if @isWorking()
+      @status--
+      if not @isWorking()
+        @emitter.emit 'done'
+
+  reset: ->
+    if @isWorking()
+      @status = 0
+      @emitter.emit 'done'


updating build team members

updating build team members

--- scripts/name-expander.coffee
+++ scripts/name-expander.coffee
@@ -20,7 +20,7 @@

 dert_mermbers = ["CDERCKERSERN", "ERVER", "JKERL", "CHIN YERN", "KERLERN", "ERNDRERW GERSERN"]

-build_members = ["mvuong", "mstewart", "jromigh", "pmckeown", "pmckeown2", "pmckeown_home2", "zeffron"]
+build_members = ["mvuong", "mstewart", "jromigh", "zeffron", "ilinkletter", "eyap"]

 merlin_remote_members = [
   cliff,


SidebarMachineBox: When machineItem clicked toggle or route.

--- client/app/lib/activity/sidebar/sidebarmachinebox.coffee
+++ client/app/lib/activity/sidebar/sidebarmachinebox.coffee
@@ -51,6 +51,11 @@

     @addSubView @machineItem = new NavigationMachineItem {}, machineData

+    @machineItem.on 'click', =>
+      if @isMachineRunning() then @toggleList()
+      else kd.singletons.router.handleRoute @machineItem.machineRoute
+
+
   createWorkspacesList: ->

     { machine, workspaces } = @getData()


avatarview:  render badge

avatarview:  render badge

--- client/app/lib/commonviews/avatarviews/avatarview.coffee
+++ client/app/lib/commonviews/avatarviews/avatarview.coffee
@@ -6,7 +6,7 @@
 JView = require '../../jview'
 LinkView = require '../linkviews/linkview'
 isKoding = require '../../util/isKoding'
-
+TeamFlux = require 'app/flux/teams'

 module.exports = class AvatarView extends LinkView

@@ -67,6 +67,8 @@
         placement : 'right'
         direction : 'center'

+    @badge = new KDCustomHTMLView
+
     super options, data

     if @detailedAvatar?
@@ -155,7 +157,27 @@

     kd.getSingleton('groupsController').ready =>

-      nickname = @getData().profile?.nickname
+      { _id } = @getData()
+
+      nickname = profile?.nickname
+
+      options =
+        limit : 10
+        sort  : { timestamp: -1 } # timestamp is at relationship collection
+        skip  : 0
+      TeamFlux.actions.fetchMembers(options).then =>
+        TeamFlux.actions.fetchMembersRole().then =>
+
+          { reactor } = kd.singletons
+
+          teamMemberRoles = reactor.evaluate ['TeamMembersRoleStore']
+          myRole = teamMemberRoles.toJS()[_id]
+
+          myRole = 'member' unless myRole
+          cssClass = "badge #{myRole}"
+
+          @badge.setClass cssClass
+          @badge.setAttribute 'title', myRole

       href = if payload?.channelIntegrationId
         "/Admin/Integrations/Configure/#{payload.channelIntegrationId}"
@@ -208,6 +230,7 @@

   pistachio: ->
     '''
+    {{> @badge}}
     {{> @gravatar}}
     {{> @cite}}
     '''


Client: Activity: Updated for coffee style

--- client/app/lib/components/common/autosizetextarea.coffee
+++ client/app/lib/components/common/autosizetextarea.coffee
@@ -47,9 +47,9 @@

   render: ->

-    <textarea {...this.props}
+    <textarea {...@props}
       ref      ='textarea'
       onChange = { @bound 'onChange' }
       onInput  = { @bound 'updateSize' }>
-      {this.props.children}
+      {@props.children}
     </textarea>


uncommented some tests

uncommented some tests

--- test/unit/wd-by-method-test.coffee
+++ test/unit/wd-by-method-test.coffee
@@ -608,8 +608,8 @@
     @setAsyncScriptTimeout 5000
     exprCond = "$('#waitForConditionInBrowser .child').length > 0"
     (@waitForConditionInBrowser exprCond, 2000, 200).should.be.true
-    #(@waitForConditionInBrowser exprCond, 2000).should.be.true
-    #(@waitForConditionInBrowser exprCond).should.be.true
+    (@waitForConditionInBrowser exprCond, 2000).should.be.true
+    (@waitForConditionInBrowser exprCond).should.be.true
     @setAsyncScriptTimeout 0

   it "close", WdWrap ->


Create the test user lazily on first hammersport.

--- src/index.coffee
+++ src/index.coffee
@@ -18,9 +18,16 @@

 module.exports = (robot) ->

+  createTestUser = ->
+    someone = robot.brain.userForId '2',
+      name: 'someone'
+      room: 'thechalkcircle'
+    theCircle.getChallenger(someone)
+
   theCircle = new ChalkCircle(robot)

   robot.respond /hammersport (\S+)/i, (msg) ->
+    createTestUser() if process.env.HUBOT_DEBUG?

     challengers = []
     for username in [msg.message.user.name, msg.match[1]]
@@ -46,11 +53,6 @@
     theCircle.withActiveMatch msg, (m) -> m.chooseMove msg

   if process.env.HUBOT_DEBUG?
-    someone = robot.brain.userForId '2',
-      name: 'someone'
-      room: 'thechalkcircle'
-    theCircle.getChallenger(someone)
-
     robot.respond /dhammer accept/i, (msg) ->
       user = robot.brain.userForName 'someone'
       msg.message.user = user


Use "dhammer" for debugging hammer commands.

--- src/index.coffee
+++ src/index.coffee
@@ -46,14 +46,19 @@
     theCircle.withActiveMatch msg, (m) -> m.chooseMove msg

   if process.env.HUBOT_DEBUG?
-    robot.respond /hammer setup/i, (msg) ->
+    robot.respond /dhammer setup/i, (msg) ->
       user = robot.brain.userForId '2',
         name: 'someone'
         room: 'thechalkcircle'
       theCircle.getChallenger(user)
       msg.reply "Ready."

-    robot.respond /hammer proxyaccept (\S+)/i, (msg) ->
-      user = robot.brain.userForName msg.match[1]
+    robot.respond /dhammer accept/i, (msg) ->
+      user = robot.brain.userForName 'someone'
       msg.message.user = user
       theCircle.withActiveMatch msg, (m) -> m.challengeAccepted msg
+
+    robot.respond /dhammer (\d+)/i, (msg) ->
+      user = robot.brain.userForName 'someone'
+      msg.message.user = user
+      theCircle.withActiveMatch msg, (m) -> m.chooseMove msg


Fixing error when missing paths.public option and adding more logging

--- src/index.coffee
+++ src/index.coffee
@@ -1,4 +1,4 @@
-fs = require "fs"
+fs = require 'fs'
 RegExp.quote = require 'regexp-quote'

@@ -21,6 +21,7 @@

   constructor: (@config) ->
     return unless @config.keyword
+    @publicPath = fs.realpathSync(@config.paths?.public ? 'public')
     @keywordConfig = @config.keyword or {}
     @filePattern = @keywordConfig.filePattern ? /\.(js|css|html)$/
     @keywordMap = @keywordConfig.map or {}
@@ -57,11 +58,16 @@
     addMap @keywordMap
     keywords = (RegExp.quote(keyword) for key, replacer of @globalMap)
     @globalRE = RegExp('\\{\\!(' + keywords.join('|') + ')\\!\\}', 'g')
+    console.log @globalRE

   onCompile: (generatedFiles) ->
     return unless @filePattern
-    @prepareKeywords()
-    @processFolder @config.paths.public
+    try
+      @prepareGlobalRegExp()
+    catch e
+      console.log "#{e.stack}".split /\n/g
+      throw e
+    @processFolder @publicPath
     if (extraFiles = @keywordConfig.extraFiles) and extraFiles.length
       extraFiles.forEach (file) => @processFile file


exposed cut-function in public api

exposed cut-function in public api

--- src/index.coffee
+++ src/index.coffee
@@ -9,5 +9,6 @@
 C.trapezoid = require "./trapezoid"
 C.skeleton = require "./skeleton"
 C.snap = require "./snap"
+C.cut = require "./cut"

 module.exports = C


Support computed html node

Support computed html node

--- src/index.coffee
+++ src/index.coffee
@@ -155,6 +155,14 @@

     # Raw html
     when 'html'
+      if node.subtype is 'computed'
+        k = key+'_'+node.tagName
+        props = node.attrs.slice() # make a sharrow copy
+        props.key = k
+        if node.children?
+          $ node.tagName, props, toChildren(node, defs, k)
+        else
+          $ node.tagName, props
       if node.subtype is 'folded'
         k = key+'_'+node.tagName
         props = getPropsFromHTMLNode(node, ATTR_WHITELIST) ? {}


Make all connections closed before close the server

--- src/index.coffee
+++ src/index.coffee
@@ -41,14 +41,23 @@
         @log "Server started http://#{opt.host}:#{opt.port}"

         stoped = false;
+        sockets = [];

         server.on 'close', =>
           if (!stoped)
             stoped = true
             @log "Server stopped"
+
+        server.on "connection", (socket) =>
+          sockets.push socket
+          socket.on "close", =>
+            sockets.splice sockets.indexOf(socket), 1

         stopServer = =>
           if (!stoped)
+            sockets.forEach (socket) =>
+              socket.destroy()
+
             server.close()
             process.nextTick( ->
               process.exit(0);


dont unnecessarily focus

dont unnecessarily focus

Fixes #185

--- src/core/selection.coffee
+++ src/core/selection.coffee
@@ -135,16 +135,18 @@
     selection = this._getNativeSelection()
     return unless selection
     if startNode?
-      @doc.root.focus()   # Some reason need to focus before removing ranges otherwise cannot set them
+      # Some reason need to focus before removing ranges otherwise cannot set them
+      @doc.root.focus() unless this.checkFocus()
       nativeRange = this._getNativeRange()
       if !nativeRange? or startNode != nativeRange.startContainer or startOffset != nativeRange.startOffset or endNode != nativeRange.endContainer or endOffset != nativeRange.endOffset
-        selection.removeAllRanges() if nativeRange?
+        # IE9 requires removeAllRanges() regardless of value of
+        # nativeRange or else formatting from toolbar does not work
         selection.removeAllRanges()
         nativeRange = @document.createRange()
         nativeRange.setStart(startNode, startOffset)
         nativeRange.setEnd(endNode, endOffset)
         selection.addRange(nativeRange)
-        @doc.root.focus()   # IE11 needs refocus
+        @doc.root.focus() unless this.checkFocus()
     else
       selection.removeAllRanges()
       @doc.root.blur()


polyglot reference in the comments

polyglot reference in the comments

--- src/ensemble.coffee
+++ src/ensemble.coffee
@@ -139,7 +139,7 @@
           note: 1
           name: null

-  # See _./polyglot.coffee_ for assumptions.
+  # See _upon/src/polyglot.coffee_ for assumptions.
   # Try using different attributes.key for the various itemerge types.
   # This will come in handy for minor ojects and means a more uniform id (any)
   # can be selected instead of mixing ids, even if wthout the risk of overlap,


Assign statements material id equal to contest id

--- server/cron/downloadMaterials.coffee
+++ server/cron/downloadMaterials.coffee
@@ -10,7 +10,7 @@

 downloadAndParse = (href) ->
     jar = request.jar()
-    page = await request
+    page = await request
         url: href
         jar: jar
     document = (new JSDOM(page, {url: href})).window.document
@@ -37,11 +37,11 @@
     if not data
         logger.error("Can't find content for page " + href)
         return undefined
-
+
     mod = data.getElementsByClassName('modified')
     for m in mod
         m.parentElement.removeChild(m)
-
+
     material = new Material
         _id: id,
         order: 0,
@@ -91,20 +91,20 @@
     if not data
         logger.error("Can't find statement for problem " + href)
         return undefined
-
+
     re = new RegExp '.*view3.php\\?id=\\d+&chapterid=(\\d+)'
     res = re.exec href
     id = res[1]
-
+
     name = document.getElementsByTagName("title")[0]
     if not name
         logger.error("Can't find name for problem " + href)
         return undefined
-
+
     text = "
" + name.innerHTML + "
"
     for tag in data
         text += "
" + tag.innerHTML + "
"
-
+
     material = new Material
         _id: "p" + id,
         order: order,
@@ -122,7 +122,7 @@
         logger.error("Found several tocs in statements " + href)
         return undefined
     toc = toc[0]
-
+
     hrefs = []
     tags = toc.getElementsByTagName("a")
     for tag in tags
@@ -130,7 +130,7 @@
             hrefs.push(tag.href)
         else
             logger.error("Strange link in statements toc: " + tag.href + " " + href)
-
+
     return hrefs

 parseStatements = (activity, order) ->
@@ -138,29 +138,33 @@
         logger.error("Found resource with >2 children " + activity.innerHTML)
         return undefined
     a = activity.children[1]
-
+
+    re = new RegExp 'view.php\\?id=(\\d+)'
+    res = re.exec a.href
+    id = res[1]
+
     hrefs = await getProblemsHrefsFromStatements(a.href)
     hrefs2 = await getProblemsHrefsFromStatements(hrefs[0])
     hrefs.splice(0, 0, hrefs2[0])
-
+
     name = a.innerHTML
     logger.info(name + ": found " + hrefs.length + " problems: " + hrefs.join(" "))
-
+
     materials = []
     for href, i in hrefs
         materials.push(getProblem(href, i))
-
+
     materials = await finalizeMaterialsList(materials)
-
-    material = new Material
-        _id: activity.id
+
+    material = new Material
+        _id: id
         order: order
         type: "problems"
         text: name
         materials: materials
     await material.upsert()
     return material
-
+

 parseActivity = (activity, order) ->
     if activity.classList.contains("label")
@@ -182,7 +186,7 @@
     for activity, i in activities
         materials.push(parseActivity(activity, i))
     materials = await finalizeMaterialsList(materials)
-
+
     material = new Material
         _id: +id
         order: id
@@ -191,12 +195,12 @@
         materials: materials
     await material.upsert()
     return material
-
+

 export default downloadMaterials = ->
     logger.info("Start downloading materials")
     document = await downloadAndParse(url)
-
+
     materials = []

     for sectionId in [1..10]
@@ -204,9 +208,9 @@
         if not section
             continue
         materials.push(parseSection(section, sectionId))
-
+
     materials = await finalizeMaterialsList(materials)
-
+
     mainPageMaterial = new Material
         _id: "main"
         order: 0


Fix default values if not passed

Fix default values if not passed

--- node_modules_koding/koding-worker/main.coffee
+++ node_modules_koding/koding-worker/main.coffee
@@ -20,11 +20,14 @@
     options.pid        or= process.pid
     options.interval   or= 10
     options.kontrold or=
-      host        : 'localhost'
-      port        : '5672'
-      login       : 'guest'
-      password    : 'guest'
-      vhost       : '/'
+      mongo         :
+        host        : '127.0.0.1'
+      rabbitmq      :
+        host        : 'localhost'
+        port        : '5672'
+        login       : 'guest'
+        password    : 'guest'
+        vhost       : '/'

     super options, data


Don't die if the user doesn't send a request

--- src/Carrot.coffee
+++ src/Carrot.coffee
@@ -202,8 +202,9 @@
           (fbResponse) =>
             if fbResponse
               @ajaxPost("#{@scheme}://posts.gocarrot.com/requests/#{carrotResponse.request_id}/ids", {platform_id: fbResponse.request})
-              for receivingUser in fbResponse.to
-                @ajaxPost("#{@scheme}://parsnip.gocarrot.com/request_send", {platform_id: carrotResponse.request_id, posting_user_id: @udid, user_id: receivingUser})
+              if fbResponse && fbResponse.to
+                for receivingUser in fbResponse.to
+                  @ajaxPost("#{@scheme}://parsnip.gocarrot.com/request_send", {platform_id: carrotResponse.request_id, posting_user_id: @udid, user_id: receivingUser})

             callback(carrotResponse, fbResponse) if callback
         )


JSImageView's bug fix

JSImageView's bug fix

--- JSKit/03_JSImageView.coffee
+++ JSKit/03_JSImageView.coffee
@@ -15,7 +15,7 @@
 		if (@_image?)
 			preimg = new Image()
 			preimg.src = @_image._imagepath
-			img = ""
+			img = ""
 			if ($(@_viewSelector).length)
 				if ($(@_viewSelector+"_image").length)
 					$(@_viewSelector+"_image").remove()


updated send more

updated send more

--- communist.coffee
+++ communist.coffee
@@ -35,6 +35,8 @@
 	else
 		_func = fun
 		@send = (data..., cb) ->
+			send = (m)->
+				cb null, m
 			try
 				cb null, _func(data...)
 			catch err


More tests for Perl 6 identifiers with a dash

--- spec/grammar-perl6-spec.coffee
+++ spec/grammar-perl6-spec.coffee
@@ -40,11 +40,47 @@
         'source.perl6'
         'variable.other.identifier.perl6'
       ]
+
     it "should match unicode identifiers", ->
       {tokens} = grammar.tokenizeLine('$cööl-páttérn')
       expect(tokens[0]).toEqual value: '$cööl-páttérn', scopes: [
         'source.perl6'
         'variable.other.identifier.perl6'
+      ]
+
+    it "identifiers with dash can contain other keywords", ->
+      {tokens} = grammar.tokenizeLine('start-from-here')
+      expect(tokens.length).toEqual 1
+      expect(tokens[0]).toEqual value: 'start-from-here', scopes: [
+        'source.perl6'
+      ]
+
+    it "identifiers with dash can contain other keywords", ->
+      {tokens} = grammar.tokenizeLine('start-here')
+      expect(tokens.length).toEqual 1
+      expect(tokens[0]).toEqual value: 'start-here', scopes: [
+        'source.perl6'
+      ]
+
+    it "identifiers with dash can contain other keywords", ->
+      {tokens} = grammar.tokenizeLine('is-required')
+      expect(tokens.length).toEqual 1
+      expect(tokens[0]).toEqual value: 'is-required', scopes: [
+        'source.perl6'
+      ]
+
+    it "identifiers with dash can contain other keywords", ->
+      {tokens} = grammar.tokenizeLine('is-utf8')
+      expect(tokens.length).toEqual 1
+      expect(tokens[0]).toEqual value: 'is-utf8', scopes: [
+        'source.perl6'
+      ]
+
+    it "identifiers with dash can contain other keywords", ->
+      {tokens} = grammar.tokenizeLine('is-')
+      expect(tokens.length).toEqual 1
+      expect(tokens[0]).toEqual value: 'is-', scopes: [
+        'source.perl6'
       ]

   describe "strings", ->


take out stray console.log

take out stray console.log

--- src/flux/task-plan.coffee
+++ src/flux/task-plan.coffee
@@ -374,7 +374,6 @@

     isFeedbackImmediate: (id) ->
       plan = @_getPlan(id)
-      console.info('is fb immediate',  plan?.is_feedback_immediate)
       plan?.is_feedback_immediate

     getEcosystemId: (id, courseId) ->


clean up the state file if no state passed in

--- services/clsi/app/coffee/ResourceWriter.coffee
+++ services/clsi/app/coffee/ResourceWriter.coffee
@@ -25,11 +25,21 @@
 				ResourceWriter.storeSyncState request.syncState, basePath, callback

 	storeSyncState: (state, basePath, callback) ->
-		logger.log state:state, basePath:basePath, "writing sync state"
-		fs.writeFile Path.join(basePath, ".resource-sync-state"), state, {encoding: 'ascii'}, callback
+		stateFile = Path.join(basePath, ".resource-sync-state")
+		if not state? # remove the  file if no state passed in
+			logger.log state:state, basePath:basePath, "clearing sync state"
+			fs.unlink stateFile, (err) ->
+				if err? and err.code isnt 'ENOENT'
+					return callback(err)
+				else
+					return callback()
+		else
+			logger.log state:state, basePath:basePath, "writing sync state"
+			fs.writeFile stateFile, state, {encoding: 'ascii'}, callback

 	checkSyncState: (state, basePath, callback) ->
-		fs.readFile Path.join(basePath, ".resource-sync-state"), {encoding:'ascii'}, (err, oldState) ->
+		stateFile = Path.join(basePath, ".resource-sync-state")
+		fs.readFile stateFile, {encoding:'ascii'}, (err, oldState) ->
 			# ignore errors, return true if state matches, false otherwise (including errors)
 			return callback(null, if state is oldState then true else false)


add setVesselId method

add setVesselId method

--- server/methods/custom.coffee
+++ server/methods/custom.coffee
@@ -1,3 +1,5 @@
+import { Vessels } from '/imports/api/vessels/vessels.coffee'
+
 Meteor.methods

 	setProjectUsersProfile: (projectId) -> if Meteor.isServer
@@ -14,3 +16,20 @@
 			console.log 'done'
 		else
 			console.log 'no permission'
+
+	setVesselId: -> if Meteor.isServer
+		console.log 'setVesselId'
+
+		if Roles.userIsInRole Meteor.userId(), 'support', Roles.GLOBAL_GROUP
+			vessels = Vessels.find().fetch()
+
+			for vessel in vessels
+				oldId = vessel._id
+				vessel._id = vessel._id._str
+
+				Vessels.insert vessel
+				Vessels.remove oldId
+
+			console.log 'done'
+		else
+			console.log 'no permission'


Pass queryModalRegion for public queries too since owners can edit them

--- src/coffee/cilantro/ui/workflows/workspace.coffee
+++ src/coffee/cilantro/ui/workflows/workspace.coffee
@@ -44,6 +44,7 @@

             if c.isSupported('2.2.0')
                 @public_queries.show new @regionViews.queries
+                    queryModalRegion: @queryModal
                     collection: @data.public_queries
                     context: @data.context
                     view: @data.view


prevent double refreshing of account

prevent double refreshing of account

--- server/models/account.coffee
+++ server/models/account.coffee
@@ -41,10 +41,16 @@

 Account::doASAP = (operation, callback) ->
-    ImapPool.get(@id or @).doASAP operation, callback
+    ImapPool.get(@id).doASAP operation, callback

 Account::isTest = ->
     @accountType is 'TEST'
+
+Account::isRefreshing = ->
+    ImapPool.get(@id).isRefreshing
+
+Account::setRefreshing = (value) ->
+    ImapPool.get(@id).isRefreshing = value

 # Public: refresh all accounts
 #
@@ -54,6 +60,7 @@
         async.eachSeries accounts, (account, cb) ->
             log.debug "refreshing account #{account.label}"
             return cb null if account.isTest()
+            return cb null if account.isRefreshing()
             account.imap_fetchMails limit, onlyFavorites, cb
         , callback

@@ -216,6 +223,7 @@
 Account::imap_fetchMails = (limitByBox, onlyFavorites = false, callback) ->
     log.debug "account#imap_fetchMails", limitByBox, onlyFavorites
     account = this
+    @setRefreshing true

     @imap_refreshBoxes (err, toFetch, toDestroy) ->

@@ -248,6 +256,7 @@
                 box.destroyAndRemoveAllMessages cb

             , (err) ->
+                @setRefreshing false
                 reporter.onDone()
                 callback null


Ensure type is being properly converted for comparison

--- src/persistence.coffee
+++ src/persistence.coffee
@@ -122,6 +122,8 @@
         body.template.data = body.template.data.filter (field) ->
           oldValue = state[camelize field.name]
           value = field.value
+          if field.name is 'type'
+            value = camelize(value)
           isSame =
             value is oldValue or
             (value and oldValue and value.valueOf() is oldValue.valueOf())


lodchart: fancier example, with two instances of the chart

--- lodchart/test_lodchart.coffee
+++ lodchart/test_lodchart.coffee
@@ -1,11 +1,27 @@

-mychart = lodChart().pointsize(0)
+h = 450
+w = 1000
+margin = {left:60, top:40, right:40, bottom: 40, inner:5}
+halfh = (h+margin.top+margin.bottom)
+totalh = halfh*2
+totalw = (w+margin.left+margin.right)
+
+mychart_em = lodChart().lodvarname("lod.em").height(h).width(w).margin(margin)
+mychart_hk = lodChart().lodvarname("lod.hk").height(h).width(w).margin(margin)

 d3.json "scanone.json", (data) ->
-  d3.select("div#chart")
-    .datum(data)
-    .call(mychart)
-#  xscale = mychart.xscale()
-#  console.log xscale[2](30)
-#  lodcurve = mychart.lodcurve()
-#  console.log lodcurve+  svg = d3.select("div#chart")
+          .append("svg")
+          .attr("height", totalh)
+          .attr("width", totalw)
+
+  chart1 = svg.append("g").attr("id", "chart1")
+
+  chart2 = svg.append("g").attr("id", "chart2")
+              .attr("transform", "translate(0, #{halfh})")
+
+  chart1.datum(data)
+    .call(mychart_em)
+
+  chart2.datum(data)
+    .call(mychart_hk)


Check that votes_count == votes.length

Check that votes_count == votes.length

--- src/getrecentposts.coffee
+++ src/getrecentposts.coffee
@@ -103,6 +103,9 @@
       else
         posts = _.flatten postses
         console.log "#{Date.now() - gpfdmstart} to retrieve #{posts.length} posts"
+        _.each posts, (post) ->
+          if post?.votes?.length != post.votes_count
+            console.dir {votesLength: post?.votes?.length, voteCount: post.votes_count, post: post.id, "Vote counts don't match up"}
         callback null, posts, days

 module.exports = getRecentPosts


Try a different food image for a while

Try a different food image for a while

--- scripts/nom.coffee
+++ scripts/nom.coffee
@@ -1,8 +1,7 @@
 # Time for food
 #
-# nom - http://i.imgur.com/Ytq3S.gif
-# food - http://i.imgur.com/Ytq3S.gif
+# nom|food - http://i.imgur.com/Ytq3S.gif

 module.exports = (robot) ->
   robot.hear /\bnom\b|food/i, (msg) ->
-    msg.send "http://i.imgur.com/Ytq3S.gif"
+    msg.send "http://i.imgur.com/sUsqL.gif" # was "http://i.imgur.com/Ytq3S.gif"


i have to listen first before i pipe everything to the buffer

--- src/post-buffer.coffee
+++ src/post-buffer.coffee
@@ -5,10 +5,10 @@
         @callback = null
         @got_all_data = no
         @stream = new BufferStream(size:'flexible')
-        req.pipe(@stream)
         req.on 'end', =>
             @got_all_data = yes
             @callback?(@stream.buffer)
+        req.pipe(@stream)

     onEnd: (@callback) =>
         @callback(@stream.buffer) if @got_all_data
@@ -17,6 +17,7 @@
         @stream.pipe(dest, options)
         @stream.setSize('none')
         dest # Allow for unix-like usage: A.pipe(B).pipe(C)
+
 # exports

 module.exports = PostBuffer


atom updates

atom updates

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -45,16 +45,14 @@
   "highlight-selected":
     lightTheme: true
   "language-babel": {}
-  linter: {}
+  linter:
+    showErrorPanel: false
   "linter-eslint":
     globalNodePath: "/usr/local"
     useGlobalEslint: true
   "linter-jshint":
     disableWhenNoJshintrcFileInPath: true
-  "linter-pep8":
-    ignoreErrorCodes: [
-      "E501"
-    ]
+  "linter-pep8": {}
   metrics:
     userId: "f0927718331c072990cb750aafe7bbb7c24695ca"
   minimap:
@@ -79,6 +77,7 @@
     togglePackages: [
       "linter-xo"
       "linter-jshint"
+      "linter-eslint"
     ]
   "tree-view":
     hideIgnoredNames: true


Added css-modules support.

Added css-modules support.

--- src/setup/frontend/webpack-config.coffee
+++ src/setup/frontend/webpack-config.coffee
@@ -68,6 +68,10 @@
       }
       {
         loader: "css-loader"
+        query:
+          modules: yes
+          camelCase: yes
+          localIdentName: "[path][name]--[local]-[hash:base64:10]"
       }
       {
         loader: "stylus-loader"


Remove failing test since Travis works.

Remove failing test since Travis works.

--- spec/javascripts/foobar_spec.js.coffee
+++ spec/javascripts/foobar_spec.js.coffee
@@ -1,6 +1,3 @@
 # spec/javascripts/foobar_spec.js.coffee
 describe "foobar", ->
   it 'works', -> expect(1 + 1).toEqual(2);
-
-describe "wanted failure", ->
-  it 'fails', -> expect(1 + 1).toEqual(42);


Removed unneeded () from noop.

Removed unneeded () from noop.

--- src/amazon.coffee
+++ src/amazon.coffee
@@ -7,7 +7,7 @@
 invoke          = require("./request").invoke

 # Used whan a callback is not provided.
-noop = () -> true
+noop = -> true

 class AmazonEC2Client extends events.EventEmitter


Minor fix.

Minor fix.

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -236,6 +236,7 @@
       # Mounts the VDI into the VBD.
       $wait xapi.call 'VBD.insert', CD_drive, VDIref
   else
+    $wait xapi.call 'VM.provision', ref
     VM = $wait xapi.call 'VM.get_record', ref

   # The VM should be properly created.


added analytics tracking to background page and fixed a few bugs

--- src/lib/background.coffee
+++ src/lib/background.coffee
@@ -89,6 +89,8 @@
   substring: 'Linux'
   title:     'Linux'
 ]
+# Extension ID of the production version of Template.
+REAL_EXTENSION_ID = 'dcjnfaoifoefmnbhhlbppaebgnccfddf'
 # List of URL shortener services supported by Template.
 SHORTENERS        = [
   # Setup [bitly](http://bit.ly).
@@ -241,11 +243,15 @@
 # -----------------

 # Details of the current browser.
-browser         =
+browser           =
   title:   'Chrome'
   version: ''
+# Indicate whether or not Template has just been installed.
+isNewInstall      = no
+# Indicate whether or not Template is currently running the production build.
+isProductionBuild = EXTENSION_ID is REAL_EXTENSION_ID
 # Name of the user's operating system.
-operatingSystem = ''
+operatingSystem   = ''

 # Private functions
 # -----------------
@@ -342,97 +348,123 @@
   # Info requests are simple, just send some useful information back. Done!
   if request.type in ['info', 'version']
     return sendResponse? id: EXTENSION_ID, version: ext.version
-  chrome.windows.getCurrent (win) ->
-    chrome.tabs.query
-      active:   yes
-      windowId: win.id
-    , (tabs) ->
-      data             = {}
-      popup            = chrome.extension.getViews(type: 'popup')[0]
-      shortCalled      = no
-      shortPlaceholder = "short#{utils.random 1000, 99999}"
-      tab              = tabs[0]
-      # Attempt to copy `str` to the system clipboard while handling the
-      # potential for failure.
-      # Finally, increment the usage of `template` and update the statistics.
-      copyOutput = (template, str) ->
-        if str
-          ext.copy str
+  data             = null
+  output           = null
+  popup            = chrome.extension.getViews(type: 'popup')[0]
+  shortCalled      = no
+  shortPlaceholder = "short#{utils.random 1000, 99999}"
+  tab              = null
+  template         = null
+  windowId         = null
+  # Create a runner to manage this asynchronous mess.
+  runner = new utils.Runner()
+  runner.push chrome.windows, 'getCurrent', (win) ->
+    windowId = win.id
+    runner.next()
+  runner.pushPacked chrome.tabs, 'query', ->
+    [active: yes, windowId: windowId, (tabs) ->
+      tab = tabs[0]
+      runner.next()
+    ]
+  runner.push null, ->
+    # Called whenever the `short` tag is found to indicate that the URL needs
+    # shortened.
+    # Replace the `short` tag with the unique placeholder so that it can be
+    # rendered again later with the short URL.
+    shortCallback = ->
+      shortCalled = yes
+      "{#{shortPlaceholder}}"
+    # If the popup is currently displayed, hide the template list and show a
+    # loading animation.
+    if popup
+      $('#item', popup.document).hide()
+      $('#loadDiv', popup.document).show()
+    # Attempt to derive the contextual template data.
+    try
+      switch request.type
+        when 'menu'
+          data     = buildDerivedData tab, request.data, shortCallback
+          template = getTemplateWithMenuId request.data.menuItemId
+        when 'popup', 'toolbar'
+          data     = buildStandardData tab, shortCallback
+          template = getTemplateWithKey request.data.key
+        when 'shortcut'
+          data     = buildStandardData tab, shortCallback
+          template = getTemplateWithShortcut request.data.key
+      if data? and template? then runner.next() else runner.finish()
+    catch error
+      # Oops! Something went wrong so we should probably let the user know.
+      runner.finish
+        message: i18n.get if error instanceof URIError
+          'copy_fail_uri'
         else
-          ext.message = i18n.get 'copy_fail_empty'
-          ext.status  = no
-          showNotification()
-        updateTemplateUsage template.key
+          'copy_fail_error'
+        success: no
+  runner.pushPacked null, addAdditionalData, ->
+    [tab, data, ->
+      if template.content
+        output = Mustache.to_html template.content, data
+        # If any `short` tags are found and replaced, the URL shortener service
+        # needs to be called in order to replace the placeholder with the real
+        # short URL.
+        if shortCalled then runner.next() else runner.finish
+          contents: output
+          success:  yes
+          template: template
+      else
+        # Display the *empty contents* error message if the contents of the
+        # template itself is empty.
+        runner.finish
+          contents: ''
+          success:  yes
+          template: template
+    ]
+  runner.pushPacked null, callUrlShortener, ->
+    [data.url, (response) ->
+      if response.success and response.shortUrl
+        # Short URL was successfully returned so re-render the output.
+        newData = {}
+        newData[shortPlaceholder] = response.shortUrl
+        runner.finish
+          contents: Mustache.to_html output, newData
+          success:  yes
+          template: template
+      else
+        # Aw man, something went wrong. Let the user down gently.
+        response.message ?= i18n.get 'shortener_error', response.shortener
+        runner.finish
+          message:  response.message
+          success:  no
+          template: template
+    ]
+  runner.run (result) ->
+    type  = request.type[0].toUpperCase() + request.type.substr 1
+    value = request.data.key.charCodeAt 0 if request.type is 'shortcut'
+    analytics.track 'Requests', 'Processed', type, value
+    if result?
+      # Ensure that the user is notified if they have attempted to copy an
+      # empty string to the system clipboard.
+      if result.success and not result.contents
+        result.message = i18n.get 'copy_fail_empty'
+        result.success = no
+      if result.success
+        ext.copy result.contents
+      else
+        ext.message = result.message
+        ext.status  = no
+        showNotification()
+      if result.template?
+        updateTemplateUsage result.template.key
         updateStatistics()
-      # Called whenever the `short` tag is found to indicate that the URL
-      # needs shortened.
-      # Replace the `short` tag with the unique placeholder so that it can be
-      # rendered again later with the short URL.
-      shortCallback = ->
-        shortCalled = yes
-        "{#{shortPlaceholder}}"
-      # If the popup is currently displayed, hide the template list and show a
-      # loading animation.
+    else
+      # Close the popup if it's still open and the user wants it closed.
+      # Otherwise, reset the popup so that it can be used further.
       if popup
-        $('#item', popup.document).hide()
-        $('#loadDiv', popup.document).show()
-      # Attempt to derive the contextual template data.
-      try
-        switch request.type
-          when 'menu'
-            data     = buildDerivedData tab, request.data, shortCallback
-            template = getTemplateWithMenuId request.data.menuItemId
-          when 'popup'
-            data     = buildStandardData tab, shortCallback
-            template = getTemplateWithKey request.data.key
-          when 'shortcut'
-            data     = buildStandardData tab, shortCallback
-            template = getTemplateWithShortcut request.data.key
-      catch error
-        # Oops! Something went wrong so we should probably let the user know.
-        if error instanceof URIError
-          ext.message = i18n.get 'copy_fail_uri'
+        if store.get 'toolbar.close'
+          popup.close()
         else
-          ext.message = i18n.get 'copy_fail_error'
-        ext.status = no
-        showNotification()
-        return
-      if template
-        addAdditionalData tab, data, ->
-          unless template.content
-            # Display the *empty template* error message.
-            copyOutput template
-            return
-          output = Mustache.to_html template.content, data
-          if shortCalled
-            # At least one `short` tag was found and replaced, so now we need
-            # to call the URL shortener service and replace that with the
-            # actual short URL.
-            callUrlShortener data.url, (response) ->
-              if response.success and response.shortUrl
-                # Short URL was successfully returned, so get rendering.
-                newData = {}
-                newData[shortPlaceholder] = response.shortUrl
-                copyOutput template, Mustache.to_html output, newData
-              else
-                # Aw man, something went wrong. Let the user down gently.
-                unless response.message
-                  response.message = i18n.get 'shortener_error',
-                    response.shortener
-                ext.message = response.message
-                ext.status  = no
-                showNotification()
-          else
-            copyOutput template, output
-      else
-        # Close the popup if it's still open and the user wants it closed.
-        # Otherwise, reset the popup so that it can be used further.
-        if popup
-          if store.get 'toolbar.close'
-            popup.close()
-          else
-            $('#loadDiv', popup.document).hide()
-            $('#item', popup.document).show()
+          $('#loadDiv', popup.document).hide()
+          $('#item', popup.document).show()

 # Display a desktop notification informing the user on whether or not the copy
 # request was successful.
@@ -441,7 +473,7 @@
 showNotification = ->
   if store.get 'notifications.enabled'
     webkitNotifications.createHTMLNotification(
-      chrome.extension.getURL 'pages/notification.html'
+      utils.url 'pages/notification.html'
     ).show()
   else
     ext.reset()
@@ -473,45 +505,51 @@
 # Increment the usage for the template with the specified `key` and persist the
 # changes.
 updateTemplateUsage = (key) ->
-  template.usage++ for template in ext.templates when template.key is key
+  for template in ext.templates when template.key is key
+    template.usage++
+    break
   store.set 'templates', ext.templates
+  analytics.track 'Templates', 'Used', template.title,
+    if template.readOnly then 1 else 0

 # Increment the usage for the URL shortener service with the specified `name`
 # and persist the changes.
-updateUrlShortenerUsage = (name) ->
+updateUrlShortenerUsage = (name, oauth) ->
   store.modify name, (shortener) ->
     shortener.usage++
+  shortener = ext.queryUrlShortener (shortener) ->
+    shortener.name is name
+  analytics.track 'Shorteners', 'Used', shortener.title, if oauth then 1 else 0

 # Data functions
 # --------------

 # Extract additional information from `tab` and add it to `data`.
 addAdditionalData = (tab, data, callback) ->
-  chrome.cookies.getAll url: data.url, (cookies) ->
-    names     = []
-    cookies or= []
+  # Create a runner to simplify this process.
+  runner = new utils.Runner()
+  runner.push chrome.cookies, 'getAll', url: data.url, (cookies = []) ->
+    names = []
     # Extract the names of each cookie.
     names.push cookie.name for cookie in cookies
     $.extend data,
-      cookie: ->
+      cookie:  ->
         (text, render) ->
           name = render text
           # Attempt to find the value for the cookie name.
           return cookie.value for cookie in cookies when cookie.name is name
           ''
       cookies: names
-    # Try to prevent pages hanging because content script wasn't executed.
-    if isProtectedPage tab
-      $.extend data,
-        selection:      ''
-        selectionlinks: []
-      callback?()
-    else
-      chrome.tabs.sendRequest tab.id, {}, (response) ->
-        $.extend data,
-          selection:      response.text or ''
-          selectionlinks: response.urls or []
-        callback?()
+    # Try to prevent pages hanging because content script should not have been
+    # executed.
+    if isProtectedPage tab then runner.finish() else runner.next()
+  runner.push chrome.tabs, 'sendRequest', tab.id, {}, (response) ->
+    runner.finish response
+  runner.run (result = {}) ->
+    $.extend data,
+      selection:      result.text ? ''
+      selectionlinks: result.urls ? []
+    callback?()

 # Creates an object containing data based on information derived from the
 # specified tab and menu item data.
@@ -705,6 +743,8 @@
   store.rename 'update_progress', 'updates', {}
   store.modify 'updates', (updates) ->
     updates.settings ?= []
+    # Determine whether or not the user has just installed Template.
+    isNewInstall = updates.settings.length is 0
     # Check if the settings need updated for 0.1.0.0.
     if '0.1.0.0' not in updates.settings
       # Update the settings for 0.1.0.0.
@@ -964,17 +1004,22 @@
 # `callback` will be called with the result once it has been received from the
 # URL shortener service.
 callUrlShortener = (url, callback) ->
-  callUrlShortenerHelper (service) ->
+  service = getActiveUrlShortener()
+  # Create a runner to control the dependencies in the asynchronous processes.
+  runner = new utils.Runner()
+  if service.oauth and service.isOAuthEnabled()
+    runner.push service.oauth, 'authorize', ->
+      runner.next();
+  runner.push null, ->
+    oauth = no
     sUrl  = service.url()
     title = service.title
     unless sUrl
       # Should never happen... Really just a sanity check.
-      callback?(
+      return runner.finish
         message:   i18n.get 'shortener_config_error', title
         shortener: title
         success:   no
-      )
-      return
     try
       params = service.getParameters(url) or {}
       # Build the HTTP request for the URL shortener service.
@@ -983,46 +1028,36 @@
       req.setRequestHeader 'Content-Type', service.contentType
       # Setup the OAuth header, if required.
       if service.oauth and service.isOAuthEnabled()
+        oauth = yes
         req.setRequestHeader 'Authorization',
           service.oauth.getAuthorizationHeader sUrl, service.method, params
       # Wait for the response and let the service handle it before passing the
-      # result to `callback`.
+      # result to `callback` via the runner.
       req.onreadystatechange = ->
         if req.readyState is 4
           if req.status is 200
-            updateUrlShortenerUsage service.name
-            callback?(
+            updateUrlShortenerUsage service.name, oauth
+            runner.finish
               shortUrl:  service.output req.responseText
               shortener: title
               success:   yes
-            )
           else
             # Something went wrong so let's tell the user.
-            callback?(
+            runner.finish
               message:   i18n.get 'shortener_error', title
               shortener: title
               success:   no
-            )
       # Finally, send the HTTP request.
       req.send service.input url
     catch error
       # Aw snap! Notify the user via `callback`.
       log.error error
-      callback?(
+      runner.finish
         message:   i18n.get 'shortener_error', title
         shortener: title
         success:   no
-      )
-
-# Determine when `callback` is called depending on whether or not the active
-# URL Shortener supports [OAuth](http://oauth.net).
-callUrlShortenerHelper = (callback) ->
-  service = getActiveUrlShortener()
-  if service.oauth and service.isOAuthEnabled()
-    service.oauth.authorize ->
-      callback? service
-  else
-    callback? service
+  runner.run (result = {}) ->
+    callback? result

 # Retrieve the active URL shortener service.
 getActiveUrlShortener = ->
@@ -1147,18 +1182,23 @@
     chrome.browserAction.onClicked.addListener (tab) ->
       onRequest
         data: key: store.get 'toolbar.key'
-        type: 'popup'
+        type: 'toolbar'
     # Add listeners for internal and external requests.
     chrome.extension.onRequest.addListener onRequest
     chrome.extension.onRequestExternal.addListener (req, sender, cb) ->
-      return cb?() if isBlacklisted sender
-      onRequest req, sender, cb
+      blocked = isBlacklisted sender
+      analytics.track 'External Requests', 'Started', sender.id,
+        if blocked then 0 else 1
+      if blocked then cb?() else onRequest req, sender, cb
     # Derive the browser and OS information.
     browser.version = getBrowserVersion()
     operatingSystem = getOperatingSystem()
     # It's nice knowing what version is running.
-    $.getJSON chrome.extension.getURL('manifest.json'), (data) =>
+    $.getJSON utils.url('manifest.json'), (data) =>
       @version = data.version
+      if isNewInstall
+        analytics.track 'Installs', 'New', @version,
+          if isProductionBuild then 1 else 0
       # Execute content scripts now that we know the version.
       executeScriptsInExistingWindows()

@@ -1235,7 +1275,7 @@
     title    = i18n.get 'name'
     if not template or store.get 'toolbar.popup'
       # Use Template's details to style the browser action.
-      chrome.browserAction.setIcon  path:  chrome.extension.getURL image
+      chrome.browserAction.setIcon  path:  utils.url image
       chrome.browserAction.setTitle title: title
       # Show the popup when the browser action is clicked.
       chrome.browserAction.setPopup popup: 'pages/popup.html'
@@ -1245,7 +1285,7 @@
         image = getImagePathForTemplate template if template.image isnt 0
         title = template.title
       # Potentially change the style of the browser action.
-      chrome.browserAction.setIcon  path:  chrome.extension.getURL image
+      chrome.browserAction.setIcon  path:  utils.url image
       chrome.browserAction.setTitle title: title
       # Disable the popup, effectively enabling the listener for
       # `chrome.browserAction.onClicked`.


Use paddingTop/Left, as this also works in firefox

--- js-library/app/js/jail_iframe/classes/content_box.coffee
+++ js-library/app/js/jail_iframe/classes/content_box.coffee
@@ -2,7 +2,7 @@
   $element = $(element)
   offset = $element.offset()

-  top: offset.top + parseInt window.getComputedStyle(element)['padding-top']
-  left: offset.left + parseInt window.getComputedStyle(element)['padding-left']
+  top: offset.top + parseInt window.getComputedStyle(element).paddingTop
+  left: offset.left + parseInt window.getComputedStyle(element).paddingLeft
   width: $element.width()
   height: $element.height()


changing evaluation of asset compiler endpoint

--- lib/assets/javascripts/code_sync/editor/index.coffee
+++ lib/assets/javascripts/code_sync/editor/index.coffee
@@ -10,7 +10,6 @@

   autoRender: true

-  assetCompilationEndpoint: CodeSync.get("assetCompilationEndpoint")

   defaultExtension: ".coffee"

@@ -21,6 +20,8 @@

   initialize: (@options={})->
     _.extend(@, @options)
+
+    @assetCompilationEndpoint = CodeSync.get("assetCompilationEndpoint")

     @assetsCollection = new CodeSync.ProjectAssets()

@@ -143,7 +144,7 @@
   loadAsset: (@currentPath)->
     $.ajax
       type: "GET"
-      url: "http://localhost:9295/source?path=#{ @currentPath }"
+      url: "#{ CodeSync.get("assetCompilationEndpoint") }?path=#{ @currentPath }"
       success: (response)=>

         @_nameInput.setValue(@currentPath)


add handler

add handler

--- lib/generators/webrtc_rails/templates/main.js.coffee
+++ lib/generators/webrtc_rails/templates/main.js.coffee
@@ -1,7 +1,11 @@
 class @WebRTC
-  onConnected: ->
-  onHangedUp: ->
-  onReconnectingStarted: ->
+  onWebSocketConnected: ->
+  onWebSocketReconnectingStarted: ->
+  onWebSocketReConnected: ->
+  onWebRTCConnected: ->
+  onWebRTCReconnectingStarted: ->
+  onWebRTCReconnected: ->
+  onWebRTCHangedUp: ->

   constructor: (userToken, @localOutput, @remoteOutput) ->
     @localOutput = @localOutput[0] || @localOutput
@@ -33,7 +37,6 @@
       track.enabled = enabled

   hangUp: ->
-    @onHangedUp()
     @_sendMessage(type: 'hangUp')
     @_hangedUp = true

@@ -58,6 +61,7 @@
         @connect(@_remoteUserID)

     @_webSocket.onclose = (event) =>
+      @onWebSocketReconnectingStarted()
       @_webSocketInitialize(userToken)

     @_webSocket.onmessage = (data) =>
@@ -65,11 +69,14 @@
       switch event['type']
         when 'myUserID'
           @_myUserID = event['myUserID']
-          console.log("userID: " + @_myUserID)
+          if @_webSocketConnected
+            @onWebSocketReConnected()
+          else
+            @onWebSocketConnected()
+            @_webSocketConnected = true
         when 'call'
           @_remoteUserID = event['remoteUserID']
         when 'hangUp'
-          @onHangedUp()
           @_hangedUp = true
           @_sendMessage(type: 'hangUpAnswer')
           @_stop()
@@ -85,8 +92,12 @@
             @_onCandidate(event)

   _addNetworkEventListener: ->
-    window.addEventListener('offline', (event) =>
+    window.addEventListener('online', (event) =>
       @_webSocket.close()
+    )
+    window.addEventListener('online', (event) =>
+      @_webSocket = null
+      @_webSocketInitialize()
     )

   _startHeartbeat: ->
@@ -110,7 +121,6 @@
       ))

   _sendMessage: (message) ->
-    console.log("send: " + message['type'])
     @_sendValue('sendMessage',
       userID: String(@_remoteUserID)
       message: message
@@ -178,11 +188,13 @@
     peer.oniceconnectionstatechange = (event) =>
       switch peer.iceConnectionState
         when 'disconnected'
-          @onReconnectingStarted()
           @_reconnectPeer()
+          @onWebRTCReconnectingStarted()
         when 'connected', 'completed'
           if @_hangedUp
-            @onConnected()
+            @onWebRTCConnected()
+          else
+            @onWebRTCReconnected()
           @_hangedUp = false

     peer.addStream(@_localStream)
@@ -204,7 +216,7 @@
         @_peerConnection.setLocalDescription(sessionDescription)
         @_sendSDP(sessionDescription)
       ->
-        console.log 'Create Offer failed'
+        console.log('Create Offer failed')
       @_mediaConstraints
     )


Added events to client.

Added events to client.

--- src/client.coffee
+++ src/client.coffee
@@ -7,7 +7,7 @@
     @id = uuid()

 # A client submits tasks to a broker.
-module.exports = class Client
+module.exports = class Client extends EventEmitter
   constructor: (@options) ->
     @handles = {}
     @_connect()
@@ -49,9 +49,11 @@
       handle.callback null, handle
       handle.callback = undefined
     handle.emit "submit"
+    @emit "submit", handle

   _completed: (handle, data) ->
     handle.emit "complete", data
+    @emit "complete", handle, data
     @_removeHandle handle

   _failed: (handle, data) ->
@@ -59,6 +61,7 @@
       handle.callback data, handle
       handle.callback = undefined
     handle.emit "error", data unless handle.listeners("error").length is 0
+    @emit "error", handle, data unless @listeners("error").length is 0
     @_removeHandle handle

   _addHandle: (handle) ->


Turn off pre-render test

Turn off pre-render test

--- apps/artist/index.coffee
+++ apps/artist/index.coffee
@@ -7,14 +7,13 @@
 routes = require './routes'
 sections = require './sections'
 timeout = require 'connect-timeout'
-{ maybePrerender } = require './prerender/middleware'

 app = module.exports = express()
 app.set 'views', __dirname + '/templates'
 app.set 'view engine', 'jade'

 app.get '/artist/:id/follow', routes.follow
-app.get '/artist/:id', timeout('25s'), maybePrerender, routes.index
+app.get '/artist/:id', timeout('25s'), routes.index
 _.map sections, ({ slug }) ->
   app.get "/artist/:id/#{slug}", routes.tab
 app.get '/artist/data/:id/:section', routes.data # Temporary


Check for can_index on the user, not the connection

--- apps/block/routes.coffee
+++ apps/block/routes.coffee
@@ -19,7 +19,7 @@
     res.locals.sd.BLOCK = block.toJSON()
     res.locals.sd.COMMENTS = comments.toJSON()
     res.locals.sd.CURRENT_ACTION = 'block'
-    canIndexConnections = _.all block.get('connections'), (c) -> c.can_index
+    canIndexConnections = _.all block.get('connections'), (c) -> c.user?.can_index
     canIndexUser = block.get('user')?.can_index

     res.render 'index',


Wordsmith a comment.

Wordsmith a comment.

--- src/TinyMock.coffee
+++ src/TinyMock.coffee
@@ -5,10 +5,7 @@
 # The mock() function sets up a mocking environment or scope where
 # a test function can set method call expectations on objects.  It
 # then runs the test function, checks that all expectations were
-# met, and finally cleans up the environment.  If the test function
-# throws an error, mock() will still clean up the environment, but
-# it will not check the expectations;  there is no need to add
-# noise to the thrown error.  To illustrate:
+# met, and finally cleans up the environment:
 #
 #   fs = require("fs")                                  # a dependency
 #   sut = new Sut()                                     # something under test


cleaning

cleaning

--- lib/deployers/heroku/index.coffee
+++ lib/deployers/heroku/index.coffee
@@ -13,8 +13,8 @@
       name: null

     @errors =
-      not_installed: "You need to install heroku first. We'll open the download page for you"
-      not_authenticated: "You are not logged in to heroku -- try running `heroku login`"
+      not_installed: "Heroku toolbelt not installed -- we'll open the download page for you"
+      not_authenticated: "You are not logged in to heroku, try `heroku login`"

   deploy: (cb) ->
     console.log "deploying #{@path} to Heroku"
@@ -51,13 +51,11 @@
     if exec('git branch -r | grep heroku').output != '' then return

     console.log 'creating app on heroku...'.grey
-    cmd = exec "heroku create #{@config.name || ''}"
-    if cmd.code > 0 then throw cmd.output
+    execute "heroku create #{@config.name || ''}"

   push_code = ->
     console.log 'pushing master branch to heroku (this may take a few seconds)...'.grey
-    cmd = exec 'git push heroku master'
-    if (cmd.code > 0) then throw cmd.output
+    execute 'git push heroku master'

   #
   # @api private
@@ -66,4 +64,8 @@
   sync = (func, ctx) ->
     fn.lift(func.bind(@))

+  execute = (cmd) ->
+    cmd = exec(cmd)
+    if (cmd.code > 0) then throw cmd.output
+
 module.exports = Heroku


common.readGraph: Don't do callback inside catch

Makes for very confusing error messages if happening inside callback

--- src/common.coffee
+++ src/common.coffee
@@ -27,16 +27,17 @@
   fbp = require 'fbp'

   ext = path.extname filepath
-  fs.readFile filepath, { encoding: 'utf-8' }, (err, contents) =>
+  fs.readFile filepath, { encoding: 'utf-8' }, (err, contents) ->
     return callback err if err
     try
       if ext == '.fbp'
         graph = fbp.parse contents
       else
         graph = JSON.parse contents
-      return callback null, graph
     catch e
       return callback e
+    return callback null, graph
+

 # Note: relies on convention
 exports.queueName = (role, port) ->


Add utility to extend an existing URL with new query parameters

--- tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.coffee
+++ tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/utils.coffee
@@ -15,27 +15,43 @@
 ## t5/core/utils
 #
 # A few handy functions.
-define ["underscore"], (_) ->
+define ["underscore"],

-  trim = (input) ->
-    if String.prototype.trim
-      input.trim()
-    else
-      input.replace(/^\s+/, '').replace(/\s+$/, '')
+  (_) ->

-  exports =
-    startsWith: (string, pattern) -> (string.lastIndexOf pattern) is 0
-    # Trims leading and trailing whitespace from a string. Delegates to String.prototype.trim if present.
-    trim: trim
-    # Determines if the input is a blank string, or null, or an empty array.
-    isBlank: (input) ->
+    trim = (input) ->
+      if String.prototype.trim
+        input.trim()
+      else
+        input.replace(/^\s+/, '').replace(/\s+$/, '')

-        return true if input is null
+    # Extends a URL, adding parameters and values from the params object. The values must already
+    # be URI encoded.
+    extendURL = (url, params) ->

-        if _.isArray input
-          return input.length is 0
+      sep = if url.indexOf("?") >= 0 then "&" else "?"

-        return (exports.trim input).length is 0
+      for name, value of params
+        url = url + sep + name + "=" + value
+        sep = "&"

-    # Splits the input string into words separated by whitespace
-    split: (str) -> _(str.split " ").reject((s) -> s is "")
+      return url
+
+    exports =
+      trim: trim
+      extendURL: extendURL
+
+      startsWith: (string, pattern) -> (string.lastIndexOf pattern) is 0
+      # Trims leading and trailing whitespace from a string. Delegates to String.prototype.trim if present.
+      # Determines if the input is a blank string, or null, or an empty array.
+      isBlank: (input) ->
+
+          return true if input is null
+
+          if _.isArray input
+            return input.length is 0
+
+          return (exports.trim input).length is 0
+
+      # Splits the input string into words separated by whitespace
+      split: (str) -> _(str.split " ").reject((s) -> s is "")


remove Chinese

remove Chinese

--- src/shapes/Line.coffee
+++ src/shapes/Line.coffee
@@ -71,7 +71,7 @@
 		b = p1.y - a * p1.x
 		return Math.abs(a * point.x + b - point.y) / Math.sqrt(a * a + 1)

-	# 这个是自己推导的
+	# this one is inferred by myself
 	distanceTo2: (point) ->
 		p1 = @points[0]
 		p2 = @points[1]
@@ -81,7 +81,7 @@
 		czY = a * czX + b
 		return Bu.bevel(czX - point.x, czY - point.y)

-	# get foot point from a point 计算垂足点
+	# get foot point from a point
 	# save to footPoint or create a new point
 	footPointFrom: (point, footPoint) ->
 		p1 = @points[0]


Disable flaky test

Disable flaky test

--- spec/server/functional/achievement.spec.coffee
+++ spec/server/functional/achievement.spec.coffee
@@ -330,7 +330,7 @@
 describe 'automatically achieving achievements', ->
   beforeEach addAllAchievements

-  it 'happens when an object\'s properties meet achievement goals', utils.wrap (done) ->
+  xit 'happens when an object\'s properties meet achievement goals', utils.wrap (done) ->
     # load achievements on server
     @achievements = yield Achievement.loadAchievements()
     expect(@achievements.users.length).toBe(2)