Router: Emit 'RouteChanged' event when history manager navigated.

--- src/lib/core/Router.coffee
+++ src/lib/core/Router.coffee
@@ -37,6 +37,7 @@
       unless @latestHandledRoute is path
         @handleRoute_ path
         @latestHandledRoute = path
+        @emit 'RouteChanged', path

     {routes} = options


Fix jschardet being undefiend if Iconv fails - Ref #411

--- src/lib/models/file.coffee
+++ src/lib/models/file.coffee
@@ -8,7 +8,6 @@
 try
 	Iconv = require('iconv').Iconv
 catch err
-	jschardet = null
 	Iconv = null

 # Local


update Liquid.async to use Q and caolan's async.js

--- src/liquid/async.coffee
+++ src/liquid/async.coffee
@@ -1,302 +1,63 @@
-PROMISE_API = ['asap', 'then', 'always', 'done', 'fail', 'progress', 'timeout', 'pipe', 'when', 'status']
+Q = require "q"
+async = require "async"

-async =
-  debug: false
+nodeify = (deferred) ->
+  (err, result) ->
+    if err then deferred.reject(err) else deferred.resolve(result)

-  parallel:
-    forEach: (array, callback) ->
-      async.promise (p) ->
-        p.resolve()
+denodify = (cb) ->
+  [
+    (result) -> cb null, result
+    (error) -> cb error
+  ]

-    map: (array, callback) ->
-      async.promise (p) ->
-        result = []
-        total = 0
-        done = false
-
-        array.forEach (item, index) ->
-          total++
-          async.when(callback(item, index, array))
-            .done (item) ->
-              result[index] = item
-              total--
-              p.resolve(result) if done and total == 0
-            .fail ->
-              p.reject arguments...
-
-        done = true
-        p.resolve(result) if total == 0
+module.exports = Async =

   # Resolves when all items are resolved.
   forEach: (array, callback) ->
-    sequence = async.when(true)
-    array.forEach (item) -> sequence.when(-> callback(item))
-    sequence
+    Async.promise (deferred) ->
+      iterator = (item, cb) ->
+        Q.when(item).then((i) -> callback(i)).nodeify cb

-  # Resolves with the mapped array once all items are resolved.
-  # Rejects if any item rejects.
+      async.forEach array, iterator, nodeify(deferred)
+
   map: (array, callback) ->
-    async.promise (p) ->
-      result = []
+    Async.promise (deferred) ->
+      iterator = (item, cb) ->
+        Q.when(item).then((i) -> callback(i)).nodeify cb

-      sequence = async.when(true)
+      async.map array, iterator, nodeify(deferred)

-      array.forEach (item, index) ->
-        sequence = sequence
-          .when(-> callback(item, index, array))
-          .done((v) -> result[index] = v)
+  reduce: (array, callback, memo) ->
+    Async.promise (deferred) ->
+      Q.when(memo).then (memo) ->
+        iterator = (memo, item, cb) ->
+          Q.when(item).then((i) -> callback(memo, i)).nodeify cb

-      sequence.done -> p.resolve(result)
-      sequence.fail -> p.reject(arguments...)
-
-  reduce: (array, callback, initialValue) ->
-    previousValue = async.when(initialValue)
-
-    array.forEach (item) ->
-      previousValue = previousValue.when((prev) -> callback(prev, item))
-
-    previousValue
+        async.reduce array, memo, iterator, nodeify(deferred)

   some: (array, callback) ->
-    O = Object(array)
-    len = O.length >>> 0
+    Async.promise (deferred) ->
+      iterator = (item, cb) ->
+        Q.when(item).then((i) -> callback(i)).then cb

-    throw new TypeError() if typeof fun is not "function"
-
-    async.promise (p) ->
-      _next = (k) ->
-        if k >= len
-          return p.resolve(false)
-
-        if k of array
-          kValue = array[k]
-          async.when(callback(kValue))
-            .done (test) -> if test then p.resolve(true) else _next(k+1)
-            .fail(p.reject)
-        else
-          _next(k+1)
-
-      _next(0)
+      async.some array, iterator, nodeify(deferred)

   detect: (array, callback) ->
-    async.promise (p) ->
-      async.some array, (item) ->
-          async.when(callback(item))
-            .when (test) ->
-              p.resolve(item) if test
-        .done (test) ->
-          p.resolve(false) unless test
-        .fail(p.resolve)
+    Async.promise (deferred) ->
+      iterator = (item, cb) ->
+        Q.when(item).then((i) -> callback(i)).then cb

-  defer: ->
-    new Deferred()
+      async.detect array, iterator, nodeify(deferred)

   promise: (callback) ->
-    #try
-      deferred = async.defer()
-      callback(deferred)
-      deferred.promise()
-    #catch e
-    #  deferred = async.defer()
-    #  deferred.reject(e)
-    #  deferred
-
-  when: (v) ->
-    if async.isPromise(v)
-      v
-    else
-      deferred = async.defer()
-      deferred.resolve(v)
-      deferred.promise()
-
-  join: (promises...) ->
-    async.parallel.forEach(promises, (item) -> item)
-
-  isPromise: (v) ->
-    v and v.isPromise
-
-class Promise
-  isPromise: true
-
-class Deferred
-  constructor: ->
-
-    # Private API
-
-    progressHandlers = []
-    resultHandlers = []
-    results = []
-
-    done = resolved = rejected = false
-    cachedPromise = timeoutHandle = dirtyHandle = error = undefined
-
-    markDirty = =>
-      return unless done
-      return if dirtyHandle
-
-      dirtyHandle = setTimeout(notify, 0)
-
-    notify = =>
-      throw new Error("Not done yet.") unless done
-
-      # clear dirty
-      clearTimeout(dirtyHandle) if dirtyHandle
-      dirtyHandle = undefined
-
-      while handler = resultHandlers.shift()
-        try
-          if handler.always or (rejected and handler.onReject)
-            handler.callback.call(@, error, results...)
-          else if resolved and handler.onResolve
-            handler.callback.apply(@, results)
-        catch e
-          if async.debug
-            setTimeout((-> throw e), 0) # let this fail on the event-loop
-          else
-            console.log "Failed to execute handler: %s", e
-
-
-      @
-
-    @status = =>
-      if resolved
-        "resolved"
-      else if rejected
-        "rejected"
-      else
-        "pending"
-
-    @inspect = =>
-      "Promise status=#{@status()}, error=#{error}"
-
-    @toJSON = =>
-      {
-        isPromise: true
-        status: @status(),
-        error: error
-        results: results
-      }
-
-    @promise = =>
-      cachedPromise or= do =>
-        result = new Promise()
-        PROMISE_API.forEach (f) => result[f] = @[f]
-        result.inspect = -> "ViewOf" + @inspect()
-        result.toJSON = @toJSON
-        result
-
-    @asap = =>
-      isAsap = true
-      @
-
-    @then = @always = (callback) =>
-      resultHandlers.push { callback: callback, always: true }
-      markDirty()
-      @
-
-    @done = (callback) =>
-      resultHandlers.push { callback: callback, onResolve: true }
-      markDirty()
-      @
-
-    @fail = (callback) =>
-      resultHandlers.push { callback: callback, onReject: true }
-      markDirty()
-      @
-
-    @progress = (callback) =>
-      progressHandlers.push { callback: callback }
-      @
-
-    @timeout = (delay) =>
-      clearTimeout(timeoutHandle) if timeoutHandle
-
-      timeout = =>
-        unless done and timeoutHandle
-          @reject("time-out: #{delay}ms")
-
-
-      timeoutHandle = setTimeout(timeout, delay)
-      @always -> clearTimeout(timeoutHandle)
-
-    @pipe = (promise) =>
-      @done -> promise.resolve(arguments...)
-      @fail -> promise.reject(arguments...)
-
-    @when = (callback) =>
-      async.promise (p) =>
-        @fail =>
-          p.reject(arguments...)
-
-        @done =>
-          try
-            result = callback(arguments...)
-          catch e
-            console.log e.stack
-            return p.reject(e)
-
-          if async.isPromise(result)
-            result.pipe(p)
-          else
-            p.resolve(result)
-
-    @drain = (other) =>
-      if async.isPromise(other)
-        other.pipe(@)
-      else
-        @resolve(other)
-
-    @reject = (_error, _results...) =>
-      throw new Error("Promise was already done.") if done
-      done = rejected = true
-      error = _error
-      results = _results
-      notify()
-      @
-
-    @resolve = (_results...) =>
-      throw new Error("Promise was already done.") if done
-      done = resolved = true
-      results = _results
-      notify()
-      @
-
-    @node = (error, args...) =>
-      if error
-        @reject(arguments...)
-      else
-        @resolve(args...)
-
-    @now = =>
-      notify()
-      @
-
-    @tryUnwrap = =>
-      now()
-
-      if done
-        @eval()
-      else
-        @
-
-    @eval = =>
-      if resolved
-        _results[0]
-      else if rejected
-        throw error
-      else
-        throw new Error("This Promise wasn't resolved yet.")
-
-  @
+    deferred = Q.defer()
+    callback deferred
+    deferred.promise

 module.exports = async
-###

-delay = (timespan, action) ->
-  async.promise (p) ->
-    later = ->
-      p.resolve(action())
-    setTimeout(later, timespan)
+return

 aPost = ->
   return {
@@ -317,33 +78,35 @@

 reducer = (prev, next) ->
   if next == 0 then throw new Error("Division by zero.")
-  delay 0, -> prev / next
+  Q.delay(1).then(-> prev / next)

-async.reduce([2, 5, 10], reducer, 100).done(-> console.log arguments)
-async.reduce([2, 5,  0], reducer, 100).fail(-> console.log arguments)
+Async.reduce([2, 5, 10], reducer, 100).nodeify -> console.log "Reduce 1: %j", arguments
+Async.reduce([2, 5,  0], reducer, 100).nodeify (err) -> console.log "Reduce 2: %s", err

-mapper = ->
-  called = 0
-  (item, index, array) ->
-    delay 10 * (array.length - index), -> called++
+mapper = (item) -> Q.delay(1).then -> item + 1
+Async.map([1,2,3], mapper).nodeify -> console.log "Map 1: %j", arguments

-async.map([1,1,1], mapper()).always(-> console.log "Sequential: %j", arguments)
-async.parallel.map([1,1,1], mapper()).always(-> console.log "Parallel: %j", arguments)
+detector = (item) -> Q.delay(1).then -> item % 42 == 0
+Async.some([1,5,7], detector).nodeify -> console.log "Some 1: %j", arguments
+Async.some([1,5,42], detector).nodeify -> console.log "Some 2: %j", arguments

-detector = (item) ->
-  delay 0, -> item % 42 == 0
-
-async.some([1,5,7], detector).always(console.log)
-async.some([1,5,42], detector).always(console.log)
-async.detect([1,5,7], detector).always(console.log)
-async.detect([1,5,84], detector).always(console.log)
+Async.detect([1,5,7], detector).nodeify -> console.log "Detect 1: %j", arguments
+Async.detect([1,5,84], detector).nodeify -> console.log "Detect 2: %j", arguments

 getPosts()
-  .done(-> console.log "Received posts.")
-  .when((posts) -> posts[0].getAuthor())
-  .done(-> console.log "Received author.")
-  .when((author) -> author.name)
-  .done((name) -> console.log "Name: %s", name)
+  .then (posts) ->
+    console.log "Received posts."
+    posts[0].getAuthor()
+  .then (v) ->
+    console.log "Received author."
+    v
+  .then (author) ->
+    author.name
+  .then (name) ->
+    console.log "Name: %s", name
+    name
   .timeout(500)
-  .fail((e) -> console.log "Failed: %s.", e)
-  .done(-> console.log "Success.")+  .fail (e) ->
+    console.log "Failed: %s.", e
+  .done ->
+    console.log "Success."


More lifecycle tests for nested rules

More lifecycle tests for nested rules

--- spec/command-nested-rules.coffee
+++ spec/command-nested-rules.coffee
@@ -329,41 +329,41 @@
             newLast.className = 'box'
             container.firstElementChild.appendChild(newLast)

-          , NewElementReplacesAnother = (e) ->
+          NewElementReplacesAnother = (e) ->
             expect(stringify(engine.lastWorkerCommands)).to.eql stringify [
                 ["remove",".group .vessel::parent .box:last-child$vessel1$box4"],
                 ['lte', ['get$','width','$box5', '.group .vessel::parent .box:last-child$vessel1'], ["number",100]]
               ]
             container.firstElementChild.classList.remove('group')

-          , ParentDoesntMatchAnymore = (e) ->
+          ParentDoesntMatchAnymore = (e) ->
             expect(stringify(engine.lastWorkerCommands)).to.eql stringify [
                 ['remove', '.group .vessel$vessel1', '.group .vessel::parent .box:last-child$vessel1']
               ]
             container.firstElementChild.classList.add('group')

-          , ParentMatchesAgain = (e) ->
+          ParentMatchesAgain = (e) ->
             expect(stringify(engine.lastWorkerCommands)).to.eql stringify [
                 ['lte', ['get$','width','$box2', '.group .vessel::parent .box:last-child$vessel1'], ["number",100]],
                 ['lte', ['get$','width','$box5', '.group .vessel::parent .box:last-child$vessel1'], ["number",100]]
               ]
             container.appendChild(clone)

-          , ParentIsCloned = (e) ->
+          ParentIsCloned = (e) ->
             expect(stringify(engine.lastWorkerCommands)).to.eql stringify [
                 ['lte', ['get$','width','$box12', '.group .vessel::parent .box:last-child$vessel11'], ["number",100]],
                 ['lte', ['get$','width','$box14', '.group .vessel::parent .box:last-child$vessel11'], ["number",100]]
               ]
             container.replaceChild(container.firstElementChild, container.lastElementChild)

-          , CloneIsReplaced = (e) ->
+          CloneIsReplaced = (e) ->
             expect(stringify(engine.lastWorkerCommands)).to.eql stringify [
                 ['remove', '$vessel11', '.group .vessel$vessel11', '.group .vessel::parent .box:last-child$vessel11']
               ]
             box2 = container.getElementsByClassName('box')[2]
             box2.parentNode.removeChild(box2)

-          , ChildIsRemovedAnotherMatches = (e) ->
+          ChildIsRemovedAnotherMatches = (e) ->
             expect(stringify(engine.lastWorkerCommands)).to.eql stringify [
                 ['remove', '$box2'],
                 ['lte', ['get$', 'width', '$box1', '.group .vessel::parent .box:last-child$vessel1'],["number",100]],
@@ -371,7 +371,7 @@
             vessel = container.getElementsByClassName('vessel')[0]
             vessel.parentNode.removeChild(vessel)

-          , IntermediateElementIsRemoved = (e) ->
+          IntermediateElementIsRemoved = (e) ->
             expect(stringify(engine.lastWorkerCommands)).to.eql stringify [
                 ['remove', '$vessel1', '$box1', '.group .vessel$vessel1', '.group .vessel::parent .box:last-child$vessel1'],
               ]
@@ -421,17 +421,135 @@

           """
-
-        listener = (e) ->
-
-          expect(engine.lastWorkerCommands).to.eql [
+
+        box1 = container.getElementsByClassName('box')[1]
+        vessel0 = container.getElementsByClassName('vessel')[0]
+        Scenario done, container, [
+          TwoElementsMatch = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
               ['eq', ['get$','x','$box1', '.vessel .box'], ['number',100]]
               ['eq', ['get$','x','$box2', '.vessel .box'], ['number',100]]
             ]
-          container.removeEventListener 'solved', listener
-          done()
-        container.addEventListener 'solved', listener
-
+            box1.classList.remove('box')
+          ChildDoesntMatchAnymore = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['remove', '.vessel .box$box1']
+            ]
+            box1.classList.add('box')
+          ChildMatchesAgain = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['eq', ['get$','x','$box1', '.vessel .box'], ['number',100]]
+            ]
+            vessel0.classList.remove('vessel')
+          ParentDoesntMatchAnymore = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['remove', '.vessel .box$box1', '.vessel .box$box2']
+            ]
+            vessel0.classList.add('vessel')
+          ParentMatchesAgain = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['eq', ['get$','x','$box1', '.vessel .box'], ['number',100]]
+              ['eq', ['get$','x','$box2', '.vessel .box'], ['number',100]]
+            ]
+            window.zzz = true;
+            box1.parentNode.removeChild(box1)
+          ChildIsRemoved = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['remove', '$box1']
+            ]
+            vessel0.insertBefore(box1, vessel0.firstChild)
+          ChildIsAdded = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['eq', ['get$','x','$box1', '.vessel .box'], ['number',100]]
+            ]
+
+        ]
+        engine = GSS(container)
+
+        sheet = new GSS.StyleSheet
+          engine: engine
+          rules: rules
+
+        sheet.install()
+
+    describe '2 level /w multiple selectors in parent', (e) ->
+      it 'Runs commands from sourceNode', (done) ->
+        rules = [
+          {
+            type:'ruleset'
+            selectors: ['.vessel', '#group1']
+            rules: [
+              {
+                type:'ruleset'
+                selectors: ['.box:last-child']
+                rules: [
+                  {
+                    type:'constraint',
+                    cssText:'::[x] == 100',
+                    commands: [
+                      ["eq", ["get$","x",["$reserved","::this"]], ["number",100]]
+                    ]
+                  }
+                ]
+              }
+            ]
+          }
+
+        ]
+        container.innerHTML =  """
+

+

+

+

+

+

+

+

+

+          """
+
+        box2 = container.getElementsByClassName('box')[2]
+        vessel0 = container.getElementsByClassName('vessel')[0]
+        Scenario done, container, [
+          TwoElementsMatch = (e) ->
+            expect(stringify(engine.lastWorkerCommands)).to.eql stringify([
+                          ['eq', ['get$','x','$box2', '.vessel .box:last-child, #group1 .box:last-child'], ['number',100]]
+                          ['eq', ['get$','x','$box4', '.vessel .box:last-child, #group1 .box:last-child'], ['number',100]]
+                        ])
+            box2.classList.remove('box')
+
+          ChildDoesntMatchAnymore = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['remove', '.vessel .box:last-child, #group1 .box:last-child$box2']
+            ]
+            box2.classList.add('box')
+          ChildMatchesAgain = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['eq', ['get$','x','$box2', '.vessel .box:last-child, #group1 .box:last-child'], ['number',100]]
+            ]
+            vessel0.classList.remove('vessel')
+          ParentDoesntMatchAnymore = (e) ->
+            expect(stringify engine.lastWorkerCommands).to.eql stringify [
+              ['remove', '.vessel .box:last-child, #group1 .box:last-child$box2']
+            ]
+            vessel0.classList.add('vessel')
+          ParentMatchesAgain = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['eq', ['get$','x','$box2', '.vessel .box:last-child, #group1 .box:last-child'], ['number',100]]
+            ]
+            vessel0.removeChild(box2)
+          ChildIsRemovedSoAnotherChildMatchesTheSelectorNow = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['remove', '$box2']
+              ['eq', ['get$','x','$box1', '.vessel .box:last-child, #group1 .box:last-child'], ['number',100]]
+            ]
+            vessel0.appendChild(box2)
+          ChildIsAddedBack = (e) ->
+            expect(engine.lastWorkerCommands).to.eql [
+              ['remove', ".vessel .box:last-child, #group1 .box:last-child$box1"]
+              ['eq', ['get$','x','$box2', '.vessel .box:last-child, #group1 .box:last-child'], ['number',100]]
+            ]
+        ]
         engine = GSS(container)

         sheet = new GSS.StyleSheet


:art:

:art:

--- spec/package-manager-spec.coffee
+++ spec/package-manager-spec.coffee
@@ -58,16 +58,17 @@
       expect(console.warn.argsForCall[0][0]).toContain("Could not resolve")

     describe "when the package is deprecated", ->
+      grim = require 'grim'
       includeDeprecatedAPIs = null

       beforeEach ->
-        {includeDeprecatedAPIs} = require('grim')
+        {includeDeprecatedAPIs} = grim

       afterEach ->
-        require('grim').includeDeprecatedAPIs = includeDeprecatedAPIs
+        grim.includeDeprecatedAPIs = includeDeprecatedAPIs

       it "returns null", ->
-        require('grim').includeDeprecatedAPIs = false
+        grim.includeDeprecatedAPIs = false
         expect(atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'packages', 'wordcount'))).toBeNull()
         expect(atom.packages.isDeprecatedPackage('wordcount', '2.0.9')).toBe true
         expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.0')).toBe true


fixed  array

fixed  array

--- src/model/statsd.coffee
+++ src/model/statsd.coffee
@@ -37,8 +37,8 @@
     var path = "/render?target=transformNull(summarize(stats.counters." + domain + ".Channels." + allowedIds[i] + ".Statuses.*.count,'1week'))&from=-1weeks&format=json";
     var raw = yield fetchData(path);

-    var failed = raw.data !== undefined  ? raw.data[0][1] : 0;
-    var successful = raw.data1 !== undefined  ? raw.data1[0][1] : 0;
+    var failed = raw.data !== undefined  ? raw.data[1][0] : 0;
+    var successful = raw.data1 !== undefined  ? raw.data1[1][0] : 0;

     data.push({
       _id : {"channelID": allowedIds[i] },


finishing the last of the sync -> async conversion for natives

refs #140

--- src/natives/main.coffee
+++ src/natives/main.coffee
@@ -216,8 +216,12 @@
   fd = fd_obj.get_field rs, 'Ljava/io/FileDescriptor;fd'
   rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), "Bad file descriptor" if fd is -1
   unless fd in [1, 2]
-    # appends by default in the browser, not sure in actual node.js impl
-    _this.$pos += fs.writeSync(fd, new Buffer(bytes.array), offset, len, _this.$pos)
+    # normal file
+    buf = new Buffer(bytes.array)
+    rs.async_op (cb) ->
+      fs.write fd, buf, offset, len, _this.$pos, (err, num_bytes) ->
+        _this.$pos += num_bytes
+        cb()
     return
   rs.print util.chars2js_str(bytes, offset, len)
   if node?
@@ -885,8 +889,11 @@
         o 'writeBytes([BII)V', (rs, _this, byte_arr, offset, len) ->
             fd_obj = _this.get_field rs, 'Ljava/io/RandomAccessFile;fd'
             fd = fd_obj.get_field rs, 'Ljava/io/FileDescriptor;fd'
-            _this.$pos += fs.writeSync(fd, new Buffer(byte_arr.array), offset,
-                                       len, _this.$pos)
+            buf = new Buffer(byte_arr.array)
+            rs.async_op (cb) ->
+              fs.write fd, buf, offset, len, _this.$pos, (err, num_bytes) ->
+                _this.$pos += num_bytes
+                cb()
         o 'close0()V', (rs, _this) ->
             fd_obj = _this.get_field rs, 'Ljava/io/RandomAccessFile;fd'
             fd = fd_obj.get_field rs, 'Ljava/io/FileDescriptor;fd'


Fixes #1280, regex and compound division mixup.

--- lexer.coffee
+++ lexer.coffee
@@ -575,7 +575,7 @@

 # Regex-matching-regexes.
 REGEX = /// ^
-  / (?! \s )       # disallow leading whitespace
+  / (?! [\s=] )       # disallow leading whitespace or equals signs
   [^ [ / \n \\ ]*  # every other thing
   (?:
     (?: \\[\s\S]   # anything escaped


Allowing expressions to be used inside strings; syntax is $\{...\}

--- lexer.coffee
+++ lexer.coffee
@@ -112,7 +112,7 @@
 exports.Lexer: class Lexer

   # Scan by attempting to match tokens one at a time. Slow and steady.
-  tokenize: (code) ->
+  tokenize: (code, rewrite) ->
     @code    : code  # The remainder of the source code.
     @i       : 0     # Current character position we're parsing.
     @line    : 0     # The current line.
@@ -123,7 +123,8 @@
       @chunk: @code.slice(@i)
       @extract_next_token()
     @close_indentation()
-    (new Rewriter()).rewrite @tokens
+    return (new Rewriter()).rewrite @tokens if (rewrite ? true)
+    return @tokens

   # At every position, run through this list of attempted matches,
   # short-circuiting if any of them succeed.
@@ -340,34 +341,51 @@
   assignment_error: ->
     throw new Error 'SyntaxError: Reserved word "' + @value() + '" on line ' + @line + ' can\'t be assigned'

-  # Replace variables and block calls inside double-quoted strings.
+  # Replace variables and expressions inside double-quoted strings.
   interpolate_string: (escaped) ->
     if escaped.length < 3 or escaped.indexOf('"') isnt 0
       @token 'STRING', escaped
     else
+      lexer: null
       tokens: []
       quote: escaped.substring(0, 1)
       escaped: escaped.substring(1, escaped.length - 1)
       while escaped.length
-        identifier_match: escaped.match /(^|[\s\S]*?(?:[\\]|\\\\)?)(\$([a-zA-Z_]\w*))/
-        if identifier_match
-          [group, before, identifier]: identifier_match
+        expression_match: escaped.match /(^|[\s\S]*?(?:[\\]|\\\\)?)(\${[\s\S]*?(?:[^\\]|\\\\)})/
+        if expression_match
+          [group, before, expression]: expression_match
           if before.substring(before.length - 1) is '\\'
-            tokens.push ['STRING', quote + before.substring(0, before.length - 1) + identifier + quote] if before.length
+            tokens.push ['STRING', quote + before.substring(0, before.length - 1) + expression + quote] if before.length
           else
             tokens.push ['STRING', quote + before + quote] if before.length
-            tokens.push ['IDENTIFIER', identifier.substring(1)]
+            lexer: new Lexer() if not lexer?
+            nested: lexer.tokenize '(' + expression.substring(2, expression.length - 1) + ')', rewrite: no
+            nested.pop()
+            tokens.push ['TOKENS', nested]
           escaped: escaped.substring(group.length)
         else
-          tokens.push ['STRING', quote + escaped + quote]
-          escaped: ''
+          identifier_match: escaped.match /(^|[\s\S]*?(?:[\\]|\\\\)?)(\$([a-zA-Z_]\w*))/
+          if identifier_match
+            [group, before, identifier]: identifier_match
+            if before.substring(before.length - 1) is '\\'
+              tokens.push ['STRING', quote + before.substring(0, before.length - 1) + identifier + quote] if before.length
+            else
+              tokens.push ['STRING', quote + before + quote] if before.length
+              tokens.push ['IDENTIFIER', identifier.substring(1)]
+            escaped: escaped.substring(group.length)
+          else
+            tokens.push ['STRING', quote + escaped + quote]
+            escaped: ''
       if tokens.length > 1
         for i in [tokens.length - 1..1]
           if tokens[i][0] is 'STRING' and tokens[i - 1][0] is 'STRING'
             tokens.splice i - 1, 2, ['STRING', quote + tokens[i - 1][1].substring(1, tokens[i - 1][1].length - 1) +
                                                        tokens[i][1].substring(1, tokens[i][1].length - 1) + quote]
       for each, i in tokens
-        @token each[0], each[1]
+        if each[0] is 'TOKENS'
+          @token nested[0], nested[1] for nested in each[1]
+        else
+          @token each[0], each[1]
         @token '+', '+' if i < tokens.length - 1

   # Helpers


Keep track of the timeout's id and cancel it when updater is stopped.

This is another attempt to get rid of the exception where the updater runs
more than once.

--- scripts/gh-book/remote-updater.coffee
+++ scripts/gh-book/remote-updater.coffee
@@ -35,6 +35,7 @@

     stop: () ->
       @keepUpdating = false
+      @_runningPromise and @_runningPromise.timeoutId and clearTimeout(@_runningPromise.timeoutId)
       promise = @_runningPromise or (new $.Deferred()).resolve(@)

       return promise.always () =>
@@ -92,7 +93,8 @@
             return onceAll(commitsPromises).then () => @lastSeenSha = lastSeenSha

       return @_runningPromise.always () =>
+        @_runningPromise and @_runningPromise.timeoutId and clearTimeout(@_runningPromise.timeoutId)
         @_runningPromise = false
       .then () =>
-        setTimeout (() => @pollUpdates()), UPDATE_TIMEOUT if @keepUpdating
+        @_runningPromise.timeoutId = setTimeout (() => @pollUpdates()), UPDATE_TIMEOUT if @keepUpdating


Apply eventclass configuration in JS

Apply eventclass configuration in JS

--- orgviz/static/orgviz.coffee
+++ orgviz/static/orgviz.coffee
@@ -105,13 +105,10 @@
   calendar = $("#calendar")
   getEventclasses = -> (k for k, v of eventclasses when v.attr("checked"))
   getEventfilters = -> (k for k, v of eventfilters when v.attr("checked"))
-  eventclasses =
-    deadline: $("#eventclass-deadline")
-    scheduled: $("#eventclass-scheduled")
-    stp: $("#eventclass-stp")
-    closed: $("#eventclass-closed")
-    clock: $("#eventclass-clock")
-    misc: $("#eventclass-misc")
+  eventclasses = {}
+  for ec in $('input.eventclass-checkbox')
+    ec = $(ec)
+    eventclasses[ec.data('name')] = ec

   eventfilters = {}
   $(".eventfilter").each (idx, elem) ->
@@ -194,13 +191,6 @@
     .bind("keydown", "right", -> cal.fullCalendar "next")
     .bind("keydown", "j", getCalScrollUpDown(cal, 100))
     .bind("keydown", "k", getCalScrollUpDown(cal, -100))
-    # change eventclass
-    .bind("keydown", "z", getEventclassToggleFunc(cal, "deadline"))
-    .bind("keydown", "x", getEventclassToggleFunc(cal, "scheduled"))
-    .bind("keydown", "c", getEventclassToggleFunc(cal, "stp"))
-    .bind("keydown", "v", getEventclassToggleFunc(cal, "closed"))
-    .bind("keydown", "b", getEventclassToggleFunc(cal, "clock"))
-    .bind("keydown", "n", getEventclassToggleFunc(cal, "misc"))
     # change views
     .bind("keydown", "q", -> cal.fullCalendar "changeView", "month")
     .bind("keydown", "w", -> cal.fullCalendar "changeView", "basicWeek")
@@ -217,6 +207,13 @@
     # open timeline page
     .bind("keydown", "shift+l",
       -> window.open $("#timeline_link").attr("href") )
+
+  for ec in $('input.eventclass-checkbox')
+    ec = $(ec)
+    key = ec.data("key")
+    if key
+      fun = getEventclassToggleFunc cal, ec.data("name")
+      doc.bind "keydown", key, fun

 #### Get a function to check the input checkbox and start/stop auo-reload


Respect 404 etc from box when sending data request.

--- server/code/model/data_request.coffee
+++ server/code/model/data_request.coffee
@@ -24,6 +24,8 @@
     , (err, resp, body) =>
       if err
         cb err
+      else if resp.statusCode != 200
+        cb "Box response: #{resp.statusCode} #{body}"
       else if parseInt(body) is NaN
         cb "Returned ticket ID is not a number: #{body}"
       else
@@ -51,4 +53,4 @@
     if command?
       return "'#{command?.replace /'/g, "'\"'\"'" }'"
     else
-      return "''"+      return "''"


avoid null error on dispose when view has no subviews

--- src/chaplin/views/view.coffee
+++ src/chaplin/views/view.coffee
@@ -365,7 +365,7 @@
       return if @disposed

       # Dispose subviews
-      subview.dispose() for subview in @subviews
+      subview.dispose() for subview in @subviews if @subviews?

       # Unbind handlers of global events
       @unsubscribeAllEvents()


Activity: temp routes

Activity: temp routes

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -3,6 +3,28 @@
   KD.registerAppClass this,
     name         : 'Activity'
     searchRoute  : '/Activity?q=:text:'
+
+  handleChannel = (type, slug) ->
+    {router, appManager} = KD.singletons
+    appManager.open 'Activity', (app) -> app.getView().navigateTo type, slug
+
+
+  KD.registerRoutes 'Activity',
+
+    '/:name?/Activity/Public' : -> handleChannel 'public'
+
+    '/:name?/Activity/Topic/:slug?' : ({params:{name, slug}, query}) ->
+      handleChannel 'topic', slug
+
+    '/:name?/Activity/Post/:slug?' : ({params:{name, slug}, query}) ->
+      handleChannel 'post', slug
+
+    '/:name?/Activity/Message/:slug?' : ({params:{name, slug}, query}) ->
+      handleChannel 'message', slug
+
+    '/:name?/Activity/Chat/:slug?' : ({params:{name, slug}, query}) ->
+      handleChannel 'chat', slug
+

   constructor: (options = {}) ->


Update markers after search

Update markers after search

--- client/ace/lib/acefindandreplaceview.coffee
+++ client/ace/lib/acefindandreplaceview.coffee
@@ -88,6 +88,14 @@
     methodName = if direction is 'prev' then 'findPrevious' else 'find'
     @getDelegate().ace.editor[methodName] @findInput.getValue(), @getSearchOptions()
     @findInput.focus()
+    @highlight()
+
+
+  highlight: ->
+
+    { editor } = @getDelegate().ace
+    editor.session.highlight editor.$search.$options.re
+    editor.renderer.updateBackMarkers()

   replace:    -> @replaceHelper no


activity: clear selected threads on leaving activity routes

--- client/activity/lib/reactivityroutes.coffee
+++ client/activity/lib/reactivityroutes.coffee
@@ -19,6 +19,9 @@
     components:
       content: ChannelThreadPane
       modal: BrowsePublicChannelsModal
+  onLeave: ->
+    threadActions.changeSelectedThread null
+    messageActions.changeSelectedMessage null
   childRoutes: [
     path: '/NewChannel'
     components:


Prevent random refresh of account creation screen, refs #206

--- client/app/components/account-config.coffee
+++ client/app/components/account-config.coffee
@@ -240,7 +240,7 @@
                         errors[field] = t 'config error ' + field
                     @setState errors: errors
             else
-                if not props.isWaiting
+                if not props.isWaiting and not _.isEqual(props, @props)
                     @setState @_accountToState null

     getInitialState: ->
@@ -702,7 +702,7 @@
                     favorite = favorites.get(mailbox.get('id'))?
                     MailboxItem {accountID: @state.id.value, mailbox, favorite}
                 catch error
-                    console.log error, favorites
+                    console.error error, favorites
             .toJS()
         form className: 'form-horizontal',


AvatarArea: Update tooltip text.

AvatarArea: Update tooltip text.

--- client/app/lib/avatararea/avatararea.coffee
+++ client/app/lib/avatararea/avatararea.coffee
@@ -37,7 +37,7 @@
       @avatar = new AvatarStaticView
         cssClass   : 'avatar-image-wrapper'
         attributes :
-          title    : 'View your public profile'
+          title    : 'View your notifications and account settings'
         size       :
           width    : 25
           height   : 25
@@ -97,4 +97,4 @@
       {{> @accountIcon}}
       {{> @avatar}}
       {{> @notificationsIcon}}
-      """+      """


Adjust Efficiency plugin to get image data, display fake calculation.

A work in progress, but allows work on the algorithm for real calcs.
See TODO comments in file.

--- client/plugins/efficiency/efficiency.coffee
+++ client/plugins/efficiency/efficiency.coffee
@@ -1,9 +1,13 @@
 window.plugins.efficiency =
+  efficiencyDIV: null
+
   emit: (div, item) ->
     wiki.log 'efficiency', div, item
     div.addClass 'data'
-    $('
').addClass('readout').appendTo(div).text("63%")
+    $('
').addClass('readout').appendTo(div).text("0%")
+    this.efficiencyDIV = div
     $('
').html(wiki.resolveLinks(item.text||'efficiency')).appendTo(div)
+
   bind: (div, item) ->
     lastThumb = null
     div.find('p:first')
@@ -25,13 +29,39 @@
       who.last()

     calculate = (div) ->
-      src = $(div).find('img').get(0).src
+      data = getImageData(div);
+      value = calculatePercentage(data);
+      setEfficiencyReadoutValue value
+
+    getImageData = (div) ->
+      src = $(div).find('img').get(0)
       c = $ '<canvas id="myCanvas" width="200" height="100" style="border:1px solid #c3c3c3;">'
       d = c.get(0).getContext("2d");
       img=new Image()
       img.src=src
-      d.drawImage(img,0,0);
-      e = d.getImageData(0, 0, c.width, c.height);
-      console.log e
+      d.drawImage(src,0,0);
+      #TODO change getImageData call to use dimensions like c.width, c.height),
+      #they are currently both zero for some reason, and that triggers an exception.
+      #wiki.log 'c.width c.height ', c.width(), c.height()
+      imageData = d.getImageData(0, 0, 200, 100);
+      imageData.data

+    calculatePercentage = (data) ->
+      #wiki.log  'Efficiency calcPercentage  Start'
+      # TODO add real calcs here. Returning a constant now to work out the update issues.
+      # Data format info. CanvasPixelArray
+      # https://developer.mozilla.org/en-US/docs/DOM/CanvasPixelArray
+      # http://www.w3.org/TR/2011/WD-2dcontext-20110525/#canvaspixelarray
+      # https://developer.mozilla.org/en-US/docs/HTML/Canvas/Pixel_manipulation_with_canvas
+      # Thinking of 4-Bins, to track the distribution of 8-bit data in 4D. RGB + Transparency.
+      # Find the bins is with greatest numbers of pixels, with a gap between bins. Calc percentage.
+      #wiki.log 'Efficiency calculatePercentage num Bytes of data (4 per pixel) ', data.length
+      #wiki.log 'Efficiency calculatePercentage some bytes doe 1st pixel. R, G, B, T ', data[0], data[1], data[2], data[3]
+      return 47
+      #wiki.log  'Efficiency calculatePercentage  End'
+
+    setEfficiencyReadoutValue = (value) =>
+      if this.efficiencyDIV
+        $(this.efficiencyDIV)[0].innerHTML = '
' + value + '%

Pattern Efficiency from Photographic Statistics
'
+
     calculate locate()


stack editor: delete stack template removed from members' editor

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -58,7 +58,7 @@
       if not @isMine and stackTemplate
         @tabView.setClass 'StackEditorTabs isntMine'
         @warningView.show()
-        @secondaryActions.hide()
+        @deleteStack.hide()
         @saveButton.setClass 'isntMine'
         @inputTitle.setClass 'template-title isntMine'

@@ -92,7 +92,7 @@
       cssClass             : 'StackEditor-SecondaryActions'

-    @secondaryActions.addSubView new CustomLinkView
+    @secondaryActions.addSubView @deleteStack = new CustomLinkView
       cssClass : 'HomeAppView--button danger'
       title    : 'DELETE STACK TEMPLATE'
       click    : @bound 'deleteStack'


stack-editor: dont disable savebutton anymore

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -175,11 +175,7 @@
     @createOutputView()
     @createMainButtons()

-    # if stackTemplate is initialized make save button disabled
-    @saveButton.disable()  if stackTemplate?.machines.length
-
     @providersView.on 'ItemSelected', (credentialItem) =>
-      @saveButton.enable() # credential change, enable button
       credential = credentialItem.getData()

       @credentialStatusView.setCredential credential
@@ -188,7 +184,6 @@
       credentialItem.inuseView.show()

     @providersView.on 'ItemDeleted', (credential) =>
-      @saveButton.enable() # credential change, enable button
       { identifier } = credential.getData()
       if identifier in @credentialStatusView.credentials
         @credentialStatusView.setCredential() # To unset active credential since it's deleted
@@ -240,7 +235,6 @@

       editorView.on 'EditorReady', =>
         ace.on 'FileContentChanged', =>
-          @saveButton.enable()
           @changedContents[key] = ace.isContentChanged()

@@ -308,10 +302,6 @@
           cssClass: 'cloned-from'
           partial: "  #{originalTemplate.title}"
           click: -> kd.singletons.router.handleRoute "/Stack-Editor/#{originalTemplate._id}"
-
-        @saveName.show()
-
-

     kd.singletons.reactor.observe valueGetter, (value) =>


stack-editor: disable delete stack template button

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -67,8 +67,11 @@
     @addSubView @secondaryActions = new kd.CustomHTMLView
       cssClass             : 'StackEditor-SecondaryActions'

+    deleteStackCssClas = unless @isMine then ' isntMine' else ''
+    cssClass = "HomeAppView--button danger#{deleteStackCssClas}"
+
     @secondaryActions.addSubView new CustomLinkView
-      cssClass : 'HomeAppView--button danger'
+      cssClass : cssClass
       title    : 'DELETE STACK TEMPLATE'
       click    : @bound 'deleteStack'


Correct an error.

Correct an error.

--- src/FileManager/FilesCtrl.coffee
+++ src/FileManager/FilesCtrl.coffee
@@ -47,7 +47,7 @@
     if Selection.isSingle()
       file = Selection.getFirst()
       index = {}
-      $scope.share  []
+      $scope.share = []
       for name in file.metadata.sharedWith ? []
         if not index[name]?
           index[name] = true


Added `period?` for #22

Added `period?` for #22

--- src/scripts/time.coffee
+++ src/scripts/time.coffee
@@ -303,7 +303,7 @@
     user.directMessage msg, Logger, attachments

   # Returns the hours worked for the given time period
-  robot.respond /(hours|today|week|month|year)+[\?\!\.¿¡]/i, id: 'time.hours', userRequired: true, (res) ->
+  robot.respond /.*(hours|today|week|month|year|period)+[\?\!\.¿¡]/i, id: 'time.hours', userRequired: true, (res) ->
     user = Organization.getUserBySlackName res.message.user.name
     tz = user.timetable.timezone.name
     now = moment.tz(tz)
@@ -320,8 +320,6 @@
           continue
         else if not punch.elapsed and not punch.times.block
           continue
-        else if not punch.mode is 'out'
-          continue
         else
           attachments.push punch.slackAttachment()
     else if mode is 'month'
@@ -332,6 +330,20 @@
       startOfYear = moment.tz({hour: 0, minute: 0, second: 0}, tz).startOf("year")
       report = user.toRawPayroll(startOfYear, now)
       dateArticle = "this year"
+    else if mode is 'period'
+      periodStart = moment({hour: 0, minute: 0, second: 0}).day("Sunday")
+      if not Organization.calendar.isPayWeek()
+        periodStart = periodStart.subtract(1, 'weeks')
+      periodEnd = periodStart.clone().add(2, 'weeks')
+      report = user.toRawPayroll(periodStart, periodEnd)
+      dateArticle = "this pay period (#{periodStart.format('M/DD')} to #{periodEnd.format('M/DD')})"
+      for punch in user.punches
+        if punch.date.isBefore(periodStart) or punch.date.isAfter(periodEnd)
+          continue
+        else if not punch.elapsed and not punch.times.block
+          continue
+        else
+          attachments.push punch.slackAttachment()
     else
       earlyToday = moment.tz({hour: 0, minute: 0, second: 0}, tz)
       report = user.toRawPayroll(earlyToday, now)


add some simple tests to hooks-list-item

--- tests/unit/components/hooks-list-item-test.coffee
+++ tests/unit/components/hooks-list-item-test.coffee
@@ -2,16 +2,23 @@

 moduleForComponent 'hooks-list-item', 'HooksListItemComponent', {
   # specify the other units that are required for this test
-  # needs: ['component:foo', 'helper:bar']
+  needs: ['component:hook-switch']
 }

 test 'it renders', ->
-  expect 2

-  # creates the component instance
-  component = @subject()
-  equal component._state, 'preRender'
+  attributes = {
+    id: 10000,
+    name: "foo-bar",
+    owner_name: "foo",
+    description: "A foo repo",
+    active: true,
+    urlGithub: "https://github.com/foo/foobar",
+    slug: "foo/foo-bar"
+  }
+  component = @subject(hook: attributes)
+  @append()

-  # appends the component to the page
-  @append()
-  equal component._state, 'inDOM'
+  ok component.$().hasClass('active'), 'component should have active class'
+  ok component.$('.travis-switch').hasClass('active'), 'switch should have active class'
+  equal component.$('.profile-repo span').text().trim(), 'A foo repo', 'repo description should be displayed'


Add route to handle /teams similar to v1

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -329,6 +329,14 @@
 			AuthenticationController.httpAuth,
 			CompileController.getFileFromClsiWithoutUser

+		webRouter.get '/teams', (req, res, next) ->
+			# Match v1 behaviour - if the user is signed in, show their teams list
+			# Otherwise show some information about teams
+			if AuthenticationController.isUserLoggedIn(req)
+				res.redirect('/user/subscription')
+			else
+				res.redirect("#{settings.v1Api.host}/teams")
+
 		#Admin Stuff
 		webRouter.get  '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index
 		webRouter.get  '/admin/user', AuthorizationMiddlewear.ensureUserIsSiteAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon


Kill vestiges of CSON from TextMatePackage

--- src/app/text-mate-package.coffee
+++ src/app/text-mate-package.coffee
@@ -4,7 +4,6 @@
 plist = require 'plist'
 _ = require 'underscore'
 TextMateGrammar = require 'text-mate-grammar'
-CSON = require 'cson'
 async = require 'async'

 module.exports =
@@ -39,7 +38,7 @@
   deactivate: ->
     # we should remove grammars and unregister properties, snippets, etc

-  legalGrammarExtensions: ['plist', 'tmLanguage', 'tmlanguage', 'cson', 'json']
+  legalGrammarExtensions: ['plist', 'tmLanguage', 'tmlanguage']

   loadGrammars: (done) ->
     fsUtils.isDirectoryAsync @syntaxesPath, (isDirectory) =>


refactored the 2nd

refactored the 2nd

--- workers/social/lib/social/models/guest.coffee
+++ workers/social/lib/social/models/guest.coffee
@@ -128,10 +128,5 @@
     JAppStorage = require './appstorage'
     callback null, new JAppStorage options

-  fetchMyPermissions: secure (client, callback)->
-    JAccount = require './account'
-    JAccount.prototype.fetchMyPermissions.call this, client, callback
-
-  fetchMyPermissionsAndRoles: secure (client, callback)->
-    JAccount = require './account'
-    JAccount.prototype.fetchMyPermissionsAndRoles.call this, client, callback
+  fetchMyPermissions: (require './account')::fetchMyPermissions
+  fetchMyPermissionsAndRoles: (require './account')::fetchMyPermissionsAndRoles


Delay searching until 500ms after typing

--- src/scripts/modules/media/tabs/metadata/metadata.coffee
+++ src/scripts/modules/media/tabs/metadata/metadata.coffee
@@ -9,7 +9,7 @@
   require('less!./metadata')

   s2Defaults = width: 300
-  s2Multi = _.extend {}, s2Defaults, minimumInputLength: 2
+  s2Multi = _.extend {}, s2Defaults, {minimumInputLength: 2, ajax: quietMillis: 500}

   # Lookup because select2 only allows string values
   # but we need to keep the name and the id for each user


Client: Activity: Updated deletePostButtonHandler and closeDeletePostModal methods to prevent event default

--- client/activity/lib/components/chatlistitem/index.coffee
+++ client/activity/lib/components/chatlistitem/index.coffee
@@ -167,14 +167,16 @@
     onClose            : @bound "closeBlockUserPromptModal"

-  deletePostButtonHandler: ->
-
+  deletePostButtonHandler: (event) ->
+
+    kd.utils.stopDOMEvent event
     ActivityFlux.actions.message.removeMessage @props.message.get('id')
     @closeDeletePostModal()

-  closeDeletePostModal: ->
-
+  closeDeletePostModal: (event) ->
+
+    kd.utils.stopDOMEvent event
     @setState isDeleting: no


client/redux: update subscription module selectors

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/redux/modules/payment/subscription.coffee
+++ client/app/lib/redux/modules/payment/subscription.coffee
@@ -1,5 +1,7 @@
 immutable = require 'app/util/immutable'
 { createSelector } = require 'reselect'
+dateDiffInDays = require 'app/util/dateDiffInDays'
+
 customer = require './customer'

 { makeNamespace, expandActionType,
@@ -64,15 +66,34 @@
 # Selectors
 ##

-plan = (state) -> state.subscription.plan
+plan = (state) -> state.subscription?.plan

-isTrial = (state) -> state.subscription.status is 'trialing'
+isTrialing = (state) -> state.subscription?.status is 'trialing'

-endsAt = (state) -> state.subscription.current_period_end
+hasNoCard = (state) -> not state.creditCard

-pricePerSeat = (state) -> state.subscription.plan.amount / 100
+isTrial = createSelector(
+  isTrialing
+  hasNoCard
+  (trialing, noCard) -> noCard and trialing
+)

-trialDays = (state) -> plan(state).trial_period_days
+
+endsAt = (state) ->
+  if sub = state.subscription
+  then Number(sub.current_period_end) * 1000
+  else Date.now()
+
+pricePerSeat = (state) ->
+
+  if p = plan(state) then p.amount / 100 else 0
+
+trialDays = (state) -> plan(state)?.trial_period_days
+
+daysLeft = createSelector(
+  endsAt
+  (end) -> dateDiffInDays(new Date(Number end), new Date)
+)

 module.exports = _.assign reducer, {
@@ -83,7 +104,7 @@
   LOAD, CREATE, REMOVE

   # Selectors
-  plan, isTrial, endsAt, pricePerSeat
+  plan, isTrial, endsAt, pricePerSeat, trialDays, daysLeft
 }


Use dynamically generated HTML in the fingers autocomplete test.

--- src/Fingers/specs/autocomplete.coffee
+++ src/Fingers/specs/autocomplete.coffee
@@ -1,18 +1,31 @@
+fingersScripts = [ "lib/jquery.js", "lib/arms-common.js", "lib/jquery-ui/jquery-ui-1.8.7.custom.min.js", "lib/ckeditor/ckeditor.js", "lib/ckeditor/adapters/jquery.js", "debug/fingers.js" ]
 editor = "input:eq(1)"

 describe "Autocomplete",
 {
-	"given an autocomplete in edit mode": [
-		loadPage "autocomplete.html"
-        $("div.static-view.field").click()
+	"given simple autocomplete UI": [
+		html """
+			 <label for="test">Look up/Autocomplete</label>
+			 <input type="text" name="test" class="editable view-type-ahead" data-lookupUrl="/lookup/Publishers" />
+			 """
+		loadScripts fingersScripts
+		execute(-> fingers.controller())
 	]

-	"when 'ph', DOWN, ENTER are typed": [
-		$(editor).type "ph"
-		wait 500 # allow time for drop down menu to appear
-		$(editor).type DOWN, ENTER
+	inner: [
+		{
+			"given an autocomplete in edit mode": [
+				$("div.static-view.field").click()
+			]
+
+			"when 'ph', DOWN, ENTER are typed": [
+				$(editor).type "ph"
+				wait 500 # allow time for drop down menu to appear
+				$(editor).type DOWN, ENTER
+			]
+
+			"then 'php' is selected":
+				"div.static-view.field": should.haveText "php"
+		}
 	]
-
-	"then 'php' is selected":
-		"div.static-view.field": should.haveText "php"
 }


reducing min size to 15

reducing min size to 15

--- app/assets/javascripts/components/mark/tools/rectangle-tool/index.cjsx
+++ app/assets/javascripts/components/mark/tools/rectangle-tool/index.cjsx
@@ -4,7 +4,7 @@
 DeleteButton    = require 'components/buttons/delete-mark'
 MarkButtonMixin = require 'lib/mark-button-mixin'

-MINIMUM_SIZE = 50
+MINIMUM_SIZE = 15
 DELETE_BUTTON_ANGLE = 45
 DELETE_BUTTON_DISTANCE_X = 12
 DELETE_BUTTON_DISTANCE_Y = 0


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -13,7 +13,7 @@

   self.bind "update", (elapsedTime) ->
     for event in I.everyEvents
-      while event.lastFired
+      while event.lastFired < I.age

   ###*
   Execute fn every n frames.


Add some debug stuff

Add some debug stuff

--- index.coffee
+++ index.coffee
@@ -2,6 +2,7 @@
 util           = require 'util'
 {EventEmitter} = require 'events'
 debug          = require('debug')('meshblu-edison-servo')
+debug = require('debug')('meshblu:edison-servo:index')
 Servo = require './src/servo'

 MESSAGE_SCHEMA =
@@ -34,12 +35,14 @@
     @servos = {}

   onMessage: (message) =>
+    debug 'onMessage', message
     payload = message.payload;

     @servos.left?.psMicroSeconds  payload.left  if payload.left?
     @servos.right?.psMicroSeconds payload.right if payload.right?

   onConfig: (device) =>
+    debug 'onConfig', device
     @setOptions device.options

   setOptions: (options={}) =>


Expose jobs count and last exit code

Expose jobs count and last exit code

--- index.coffee
+++ index.coffee
@@ -22,3 +22,11 @@
     update: (done) ->
       system.host (err, host) ->
         done err, host.split('.', 1)[0]
+
+  register 'jobsCount',
+    update: ->
+      parseInt process.env.IMPROMPTU_JOBS_COUNT, 10
+
+  register 'lastExitCode',
+    update: ->
+      parseInt process.env.IMPROMPTU_LAST_EXIT_CODE, 10


Moved from CoffeeScript to JS

Moved from CoffeeScript to JS

--- index.coffee
+++ index.coffee
@@ -1,44 +0,0 @@
-module.exports = (originalObj) ->
-    flattenedObj = {}
-
-    # This takes advantage of JS's passing object by reference
-    iterate(originalObj, flattenedObj);
-
-    return flattenedObj
-
-iterate = (obj, flat, path="") ->
-    for key, val of obj
-        if obj.hasOwnProperty(key)
-            keyPath = path + key
-            checkType(key, val, flat, keyPath)
-
-arrIterate = (arr, flat, path) ->
-    for val, idx in arr
-        arPath = path + '[' + idx + ']'
-        checkType(val, arr[idx], flat, arPath)
-
-
-checkType = (key, val, obj, path) ->
-    type = typeof val
-
-    # Throw error if unsupported values are passed
-    if type is "function" or type is "xml"
-        throw new Error("Unsupported type passed!")
-
-    # If is normal value, just assign it to the flattened object
-    if type is "boolean" or type is "string" or type is "number"
-        objKey = path or key
-        obj[objKey] = val
-        return
-
-    # else is an object.  We do this to not worry about 'undefined' or similar non-values.
-    if type is 'object'
-        # If Array
-        if Array.isArray(val)
-            arrIterate val, obj, path
-            return
-
-        # Else really is an object, so run it through iterate again.
-        else
-            objPath = path + '.'
-            return iterate( val, obj, objPath)


fixed

fixed

--- index.coffee
+++ index.coffee
@@ -30,11 +30,12 @@

         else
-          console.log 'Oh no! error ' + res.text
-  return next()
+          console.log 'Oh no! error ' + res.er
+

 module.exports = Say
+Say 'hello'


Remove obsolete semicolon from bundle call

--- index.coffee
+++ index.coffee
@@ -26,7 +26,7 @@
       return next()

     styleguideFilePath = file.path.replace /\..*$/g, ''
-    exec "bundle exec livingstyleguide compile #{file.path};", (error, stdout, stderr) =>
+    exec "bundle exec livingstyleguide compile #{file.path}", (error, stdout, stderr) =>
       if error
         err = new gutil.PluginError PLUGIN_NAME, error
         @emit 'error', err


pause live updates when zooming in

pause live updates when zooming in

--- app/assets/javascripts/states/monitoring/views/metric_detail.js.coffee
+++ app/assets/javascripts/states/monitoring/views/metric_detail.js.coffee
@@ -130,10 +130,17 @@
         if reset
           view.set_zoom_level_label(zoom)
           view.$("button#reset").addClass("disabled")
-        else
-          view.$("button#reset").removeClass("disabled")
-          view.$("div.zoom_level button .text").text("Custom")
+          return

+        view.$("button#reset").removeClass("disabled")
+        view.$("div.zoom_level button .text").text("Custom")
+
+        # disable live updates if not near right edge (within 30 minutes)
+        [minX, maxX] = view.graph.xAxisRange()
+        [dMinX, dMaxX] = view.graph.xAxisExtremes()
+        if dMaxX - maxX > 1800000
+          view.ui.live.button.removeClass("active")
+          view.update_live()

       @bixby_graph = new Bixby.monitoring.Graph()
       @bixby_graph.touch_enabled = true


Changed item rendering order

Changed item rendering order

--- style.coffee
+++ style.coffee
@@ -21,6 +21,8 @@
 		style.textWatchingNodes.push node
 		if 'onTextChange' of node
 			node.onTextChange textChangeListener, style
+		if 'onChildrenChange' of node
+			node.onChildrenChange textChangeListener, style
 		if 'onVisibleChange' of node
 			node.onVisibleChange textChangeListener, style

@@ -138,7 +140,6 @@
 					for style in stylesToRender
 						style.waiting = false
 						style.renderItem()
-						style.findItemParent()
 						style.file.readyToUse = true

 					globalShowDelay = 0
@@ -189,15 +190,17 @@
 				@classes = utils.clone(classes.items())

 		@item.visible = true
+
+		if @lastItemParent
+			@item.parent = @lastItemParent
+		@findItemParent()
+
 		@item.document.node = @node
 		@item.document.visible = true
 		@baseText = @getTextObject()?.text or ''
 		@updateText()
 		@updateVisibility()
 		@syncClassAttr('')
-
-		if @lastItemParent
-			@item.parent = @lastItemParent

 		for name of @attrs
 			val = @node.attrs.get name
@@ -499,6 +502,8 @@
 			while elem = @textWatchingNodes.pop()
 				if 'onTextChange' of elem
 					elem.onTextChange.disconnect textChangeListener, @
+				if 'onChildrenChange' of elem
+					elem.onChildrenChange.disconnect textChangeListener, @
 				if 'onVisibleChange' of elem
 					elem.onVisibleChange.disconnect textChangeListener, @

@@ -554,10 +559,11 @@

 		@node._documentStyle = @
 		@node.style = @item
-		@item.visible = false
-
-		if @isLink()
-			@item.linkUri = @getLinkUri()
+
+		if @item
+			@item.visible = false
+			if @isLink()
+				@item.linkUri = @getLinkUri()

 		# text changes
 		if @getTextObject()


disable ioClient

disable ioClient

--- lib/appHelper.coffee
+++ lib/appHelper.coffee
@@ -11,7 +11,7 @@
 sailsBin         = path.join localDependecies, 'sails/bin/sails.js'
 Sails            = require (path.join localDependecies, 'sails/lib/app')
 sailsLift        = require (path.join localDependecies, 'sails/bin/sails-lift')
-_ioClient        = require('./sails.io')(require('socket.io-client'))
+# _ioClient        = require('./sails.io')(require('socket.io-client'))
 forceRequire     = require 'force-require'

 # -- GLOBALS ------------------------------------------------------------------


Fix bug that caused pages with no metadata to fail

--- src/plugins/markdown-page.coffee
+++ src/plugins/markdown-page.coffee
@@ -15,7 +15,7 @@

 parseMetadata = (source, callback) ->
   try
-    callback null, yaml.load(source)
+    callback null, yaml.load(source) or {}
   catch error
     callback error


shouldShowBreadcrumbs config

shouldShowBreadcrumbs config

--- lib/atom-lupa.coffee
+++ lib/atom-lupa.coffee
@@ -10,6 +10,10 @@
       autoRefresh:
           type: 'boolean'
           default: true
+      shouldShowBreadcrumbs:
+          type: 'boolean'
+          default: 'false'
+          description: '(Experimental feature. Restart of Atom is required)'

   activate: (state) ->


added command and arguments to Caboose.app

--- lib/cli/index.coffee
+++ lib/cli/index.coffee
@@ -3,7 +3,15 @@

 base = new Path(__dirname)
 for file in base.readdir_sync()
-  options = file.require()
-  switchback.command(file.basename, options) if options.method
+  do (file) ->
+    options = file.require()
+
+    if options.method
+      _method = options.method
+      options.method = ->
+        Caboose.app.command = file.basename
+        Caboose.app.arguments = arguments
+        _method(arguments...)
+      switchback.command(file.basename, options)

 module.exports = switchback


properly handle paths that are affected by more than one clip

clipping paths need to be intersected down the scene graph to get composite clips for any path node

--- AI2ASS.coffee
+++ AI2ASS.coffee
@@ -8,6 +8,8 @@
   org = doc.rulerOrigin
   currLayer = doc.activeLayer
   drawCom = 0
+  tempLayer = null
+  black = new RGBColor();

   countPathItems = ( obj ) ->
     recurse = ( obj ) ->
@@ -26,6 +28,47 @@
     recurse obj
     return count

+  makeClip = ( clippingPath ) ->
+    clip = {
+      tempGroup: null
+
+      add: (clippingPath) ->
+        # prepare a group to apply the pathfinder effect to
+        unless tempLayer?
+          tempLayer = doc.layers.add()
+          tempLayer.name = "AI2ASS_tmp"
+          tempLayer.zOrder(ZOrderMethod.SENDTOBACK)
+        unless @tempGroup
+          @tempGroup = tempLayer.groupItems.add()
+
+        # copy all path into the group and make sure it has a fill
+        copy = clippingPath.duplicate(@tempGroup, ElementPlacement.PLACEATBEGINNING)
+        copy.filled = true
+        copy.stroked = false
+        copy.cliping = false
+        copy.fillColor = black
+
+        if @tempGroup.pageItems.length > 1
+          # select the group, apply the pathfinder and expand
+          prevSelection = doc.selection
+          doc.selection = [@tempGroup]
+          app.executeMenuCommand("Live Pathfinder Intersect")
+          app.executeMenuCommand("expandStyle")
+          # expanding created a new group
+          @tempGroup = doc.selection[0]
+          # restore previous selection
+          doc.selection = prevSelection
+
+      copy: -> return makeClip @tempGroup.pageItems[0]
+      get: -> return @tempGroup.pageItems[0]
+      getASS: ->
+        drawing = ASS_createDrawingFromPoints @tempGroup.pageItems[0].pathPoints
+        return "\\clip(#{drawing.join ' '})"
+    }
+
+    clip.add clippingPath
+    return clip
+
   output = {
     pathCnt: null
     processedPathCnt: 0
@@ -35,6 +78,10 @@
     process: ( obj, clip ) ->
       if not @pathCnt?
         @pathCnt = countPathItems obj
+
+
+      #if clip?
+      #  clip = clip.clone()

       unless obj.hidden
         switch obj.typename
@@ -53,8 +100,13 @@
           when "GroupItem"
             if obj.clipped
               clipPath = (pI for pI in obj.pageItems when pI.clipping)[0]
-              clip = ASS_createDrawingFromPoints(clipPath.pathPoints).join(" ")
+              if clip?
+                clip = clip.copy()
+                clip.add clipPath
+              else
+                clip = makeClip clipPath
               @processedPathCnt += 1
+
             for subPageItem in obj.pageItems when not subPageItem.clipping
               @process subPageItem, clip

@@ -68,13 +120,15 @@

             @processedPathCnt += 1

-    appendPath: ( path, clip ) ->
+    appendPath: ( path, clipObj ) ->
       stroke = manageColor path, "strokeColor", 3
       fill = manageColor path, "fillColor", 1
       layerName = path.layer.name
       layerNum = path.layer.zOrderPosition
-
-      prefix = @prefix stroke, fill, clip, layerNum, layerName
+      opacity = manageOpacity path
+      clip = if clipObj? then clipObj.getASS() else ""
+
+      prefix = @prefix stroke, fill, clip, opacity, layerNum, layerName

       if not @assSections[layerNum]?
         @assSections[layerNum] = {}
@@ -84,9 +138,8 @@

       Array.prototype.push.apply @assSections[layerNum][prefix], ASS_createDrawingFromPoints path.pathPoints

-    prefix: (stroke, fill, clip) ->
-      clipTag = if clip? then "\\clip(#{clip})" else ""
-      "{\\an7\\pos(0,0)#{stroke}#{fill}#{clipTag}\\p1}"
+    prefix: (stroke, fill, clip, opacity) ->
+      "{\\an7\\pos(0,0)#{stroke}#{fill}#{opacity}#{clip}\\p1}"

     suffix: -> "{\\p0}"

@@ -162,9 +215,8 @@
       output.prefix = -> ""
       output.suffix = -> ""
     when "line"
-      output.prefix = (stroke, fill, clip, layerNum, layerName) ->
-        clipTag = if clip? then "\\clip(#{clip})" else ""
-        "Dialogue: #{layerNum},0:00:00.00,0:00:00.00,AI,#{layerName},0,0,0,,{\\an7\\pos(0,0)#{stroke}#{fill}#{clipTag}\\p1}"
+      output.prefix = (stroke, fill, clip, opacity, layerNum, layerName) ->
+        "Dialogue: #{layerNum},0:00:00.00,0:00:00.00,AI,#{layerName},0,0,0,,{\\an7\\pos(0,0)#{stroke}#{fill}#{opacity}#{clip}\\p1}"
       output.suffix = -> ""

@@ -178,21 +230,19 @@
     (p1 && p2)

   zeroPad = ( num ) ->
-    if num < 16
-      "0#{num.toString 16}"
-    else
-      num.toString 16
+    hexStr = num.toString(16).toUpperCase()
+    return if num < 16 then "0#{hexStr}" else hexStr

   handleGray = ( theColor ) ->
     pct = theColor.gray
     pct = Math.round (100-pct)*255/100
-    "&H#{zeroPad pct}#{zeroPad pct}#{zeroPad pct}&".toUpperCase( )
+    "&H#{zeroPad pct}#{zeroPad pct}#{zeroPad pct}&"

   handleRGB = ( theColor ) ->
     r = Math.round theColor.red # why am I rounding these?
     g = Math.round theColor.green
     b = Math.round theColor.blue
-    "&H#{zeroPad b}#{zeroPad g}#{zeroPad r}&".toUpperCase( )
+    "&H#{zeroPad b}#{zeroPad g}#{zeroPad r}&"

   manageColor = ( currPath, field, ASSField ) ->
     fmt = ""
@@ -217,6 +267,12 @@
     # "PatternColor"
     # "SpotColor"

+  manageOpacity = (currPath) ->
+    if currPath.opacity >= 100
+      return ""
+
+    return "\\alpha&H#{zeroPad 255 - Math.round(currPath.opacity/100) * 255}&"
+
   ASS_createDrawingFromPoints = ( pathPoints ) ->
     drawing.new()


Removed a few errant minus signs that accidentally got pasted in

--- src/ui/draggable.coffee
+++ src/ui/draggable.coffee
@@ -66,7 +66,7 @@

 		delta =
 			x: touchEvent.clientX - @_start.x
- -			y: touchEvent.clientY - @_start.y
+ 			y: touchEvent.clientY - @_start.y

 		# Correct for current drag speed
 		correctedDelta =
@@ -75,7 +75,7 @@
 			t: event.timeStamp

 		@view.x = @_start.x + correctedDelta.x - @_offset.x
- -		@view.y = @_start.y + correctedDelta.y - @_offset.y
+ 		@view.y = @_start.y + correctedDelta.y - @_offset.y

 		@_deltas.push correctedDelta


Explicitly set key values for maps as strings

--- lib/clog/clog.coffee
+++ lib/clog/clog.coffee
@@ -2,19 +2,19 @@
 kv  = require "../kayvee"

 LOG_LEVELS = {
-  Debug:    "debug",
-  Info:     "info",
-  Warning:  "warning",
-  Error:    "error",
-  Critical: "critical",
+  "Debug":    "debug",
+  "Info":     "info",
+  "Warning":  "warning",
+  "Error":    "error",
+  "Critical": "critical",
 }

 LOG_LEVEL_ENUM = {
-  debug:    0,
-  info:     1,
-  warning:  2,
-  error:    3,
-  critical: 4,
+  "debug":    0,
+  "info":     1,
+  "warning":  2,
+  "error":    3,
+  "critical": 4,
 }

 class Logger


tweak method name

tweak method name

--- lib/component.coffee
+++ lib/component.coffee
@@ -19,11 +19,11 @@
     @templatePath = templatePath + 'component/'
     @destPath = opts.directory + @compName.snake + '/'

-    @copyTemplate()
+    @copyAllTemplates()
     @renameAllTemplates()

-  copyTemplate: () ->
+  copyAllTemplates: () ->
     fs.copySync(@templatePath, @destPath)
     puts "\nComponent"
     puts "    Created: #{@compName.snake}.html"


error text to failed to make

error text to failed to make

--- src/components/async-button.cjsx
+++ src/components/async-button.cjsx
@@ -10,25 +10,25 @@
     isDone: React.PropTypes.bool
     isFailed: React.PropTypes.bool
     waitingText: React.PropTypes.any # TODO: This should be a Component or array
-    errorText: React.PropTypes.any
+    failedText: React.PropTypes.any
     doneText: React.PropTypes.any

   getDefaultProps: ->
     isDone: false
     isFailed: false
     waitingText: 'Loading…'
-    errorText: 'Error. Please refresh'
+    failedText: 'Error. Please refresh'
     doneText: ''

   render: ->
     {className, disabled} = @props
     {isWaiting, isDone, isFailed} = @props
-    {children, waitingText, errorText, doneText} = @props
+    {children, waitingText, failedText, doneText} = @props

     if isFailed
       # TODO: Turn this into a link that reloads the page
       stateClass = 'is-error'
-      text = errorText
+      text = failedText
     else if isWaiting
       stateClass = 'is-waiting'
       text = waitingText


add clojure support.

add clojure support.

--- lib/languages.coffee
+++ lib/languages.coffee
@@ -14,6 +14,11 @@
     nameMatchers:      ['.cpp', '.hpp', '.c++', '.h++', '.cc', '.hh', '.cxx', '.hxx']
     pygmentsLexer:     'cpp'
     singleLineComment: ['//']
+
+  Clojure:
+    nameMatchers:      ['.clj']
+    pygmentsLexer:     'clojure'
+    singleLineComment: [';;']

   CoffeeScript:
     nameMatchers:      ['.coffee']


lifecycle: remove comments

lifecycle: remove comments

--- lib/lifecycle.coffee
+++ lib/lifecycle.coffee
@@ -320,7 +320,6 @@
       ctx.execute
         # su -l oozie -c "/usr/lib/oozie/bin/oozied.sh start"
         cmd: "su -l #{oozie_user.name} -c \"/usr/lib/oozie/bin/oozied.sh start\""
-        # code_skipped: 1
       , callback
   oozie_stop: (ctx, callback) ->
     {oozie_user} = ctx.config.ryba
@@ -330,14 +329,9 @@
       ctx.execute
         # su -l oozie -c "/usr/lib/oozie/bin/oozied.sh stop"
         cmd: "su -l #{oozie_user.name} -c \"/usr/lib/oozie/bin/oozied.sh stop\""
-        # code_skipped: 1
       , callback
   hbase_master_status: (ctx, callback) ->
-    # {hbase_pid_dir, hbase_user} = ctx.config.ryba
     ctx.log "HBase Master status"
-    # lifecyle.is_pidfile_running ctx, "#{hbase_pid_dir}/hbase-#{hbase_user.name}-master.pid", (err, running) ->
-    #   ctx.log "HBase Master status: #{if running then 'RUNNING' else 'STOPED'}"
-    #   callback err, running
     ctx.execute
       cmd: "service hbase-master status"
       code_skipped: [1, 3]


Write a failing test

Write a failing test

--- test/lib/when-test.coffee
+++ test/lib/when-test.coffee
@@ -56,6 +56,13 @@
       Given -> @when(@testDouble()).thenReturn('B', 'C')
       Then -> @testDouble() == 'B'

+  describe 'stubbing actions with `thenDo` instead of `thenReturn`', ->
+    Given -> @verify = requireSubject('lib/verify')
+    Given -> @someAction = @function()
+    Given -> @when(@testDouble(55)).thenDo(@someAction)
+    When -> @testDouble(55)
+    Then -> @verify(@someAction(55))
+
   describe 'config object', ->
     describe 'ignoring extra arguments', ->
       context 'for a no-arg stubbing', ->


Fixed test cases for "asyncWhile" function

--- test/t9-asyncWhile.coffee
+++ test/t9-asyncWhile.coffee
@@ -18,7 +18,7 @@
     it = asyncWhile -> 'TEST'
     expect(it).to.be.an.instanceOf(AsyncIterator)

-  it 'General Use Case', ->
+  it 'General Use Case', (done)->

     testString = ''

@@ -33,7 +33,7 @@
       done()

-  it 'General Use Case with Zero Iteration', ->
+  it 'General Use Case with Zero Iteration', (done)->

     testString = ''

@@ -44,6 +44,6 @@
       testString += 'A'
       next()
     .finally ->
-      expect(testString).to.equal('AAAAAAAAAA')
+      expect(testString).to.equal('')
       done()


Fix removeClass call

Fix removeClass call

--- lib/tree-view.coffee
+++ lib/tree-view.coffee
@@ -106,7 +106,7 @@

   attach: ->
     return unless atom.project.getPath()
-    @removeClass('panel-right, panel-left')
+    @removeClass('panel-right panel-left')
     if atom.config.get('tree-view.showOnRightSide')
       @addClass('panel-right')
       atom.workspaceView.appendToRight(this)


[Fixes #15] Added option to customize the height of footer and total footer.

--- src/directives/table_view.coffee
+++ src/directives/table_view.coffee
@@ -26,6 +26,8 @@
 ## - mac-table-fluid-width:       When true, width of the table is 100% of the parent container    (default false)
 ## - mac-table-column-width:      The minimum width of a column                                    (default 140)
 ## - mac-table-header-height:     The height of the header row                                     (default mac-table-row-height)
+## - mac-table-total-footer-height: The height of the total footer row                             (default mac-table-row-height)
+## - mac-table-footer-height:     The height of the footer row                                     (default mac-table-row-height)
 ## - mac-table-row-height:        The height of each row in the table                              (default 20)
 ## - mac-table-num-display-rows:  The total number of rows to display                              (default 10)
 ## - mac-table-cell-padding:      Should match the css padding value on each cell                  (default 8)
@@ -74,6 +76,8 @@
         width:                 800
         columnWidth:           140
         headerHeight:          20
+        totalFooterHeight:     20
+        footerHeight:          20
         rowHeight:             20
         numDisplayRows:        10
         cellPadding:           8
@@ -103,8 +107,10 @@
       # Calculate all the options based on defaults
       opts = util.extendAttributes "macTable", defaults, attrs

-      # Default header height to row height if header height is not defined
-      opts.headerHeight   = opts.rowHeight unless attrs.macTableHeaderHeight?
+      # Default special row height to row height if height is not defined
+      opts.headerHeight      = opts.rowHeight unless attrs.macTableHeaderHeight?
+      opts.totalFooterHeight = opts.rowHeight unless attrs.macTableTotalFooterHeight?
+      opts.footerHeight      = opts.rowHeight unless attrs.macTableFooterHeight?

       # Total header should show if total is calculated locally
       opts.hasTotalFooter = opts.calculateTotalLocally if opts.calculateTotalLocally
@@ -389,7 +395,12 @@
             return {}

           css        = angular.copy $scope.columnsCss[column]
-          css.height = opts.headerHeight if section is "header"
+          css.height =
+            switch section
+              when "header"       then opts.headerHeight
+              when "total-footer" then opts.totalFooterHeight
+              when "footer"       then opts.footerHeight
+              else                     css.height

           return css

@@ -429,10 +440,12 @@
                               opts.numDisplayRows
                             else
                               Math.min dataLength, opts.numDisplayRows
-          totalRows      += opts.hasFooter + opts.hasTotalFooter
-          elementHeight   = cellOuterHeight * totalRows
-          elementHeight  += opts.hasHeader * (opts.headerHeight + 2 * opts.cellPadding + opts.borderWidth)
-          elementHeight  += 2 * opts.borderWidth
+          paddings       = 2 * opts.cellPadding
+          elementHeight  = cellOuterHeight * totalRows
+          elementHeight += opts.hasFooter * (opts.footerHeight + paddings)
+          elementHeight += opts.hasTotalFooter * (opts.totalFooterHeight + paddings)
+          elementHeight += opts.hasHeader * (opts.headerHeight + paddings + opts.borderWidth)
+          elementHeight += 2 * opts.borderWidth

           # Update the width and height of the whole table
           width = if opts.fluidWidth then "100%" else (opts.width - 2 * opts.borderWidth)


Mekko: rotated labels

Mekko: rotated labels

--- app/assets/javascripts/d3/mekko.coffee
+++ app/assets/javascripts/d3/mekko.coffee
@@ -73,6 +73,7 @@
       @width = @available_width() - (margins.left + margins.right)
       @height = @outer_height() - (margins.top + margins.bottom)
       @series_height = 190 # the rest of the height will be taken by the legend
+      @label_offset = @series_height + 10
       @svg = d3.select(@container_selector())
         .append("svg:svg")
         .attr("height", @height + margins.top + margins.bottom)
@@ -115,18 +116,18 @@

       # vertical sector label
       @sectors
+        .append('g')
+        .attr('class', 'sector_label')
+        .attr('transform', "translate(0, #{@label_offset})")
         .append("svg:text")
-        .attr('class', 'sector_label')
-        .text((d) -> d.get 'key')
-        .attr("transform", "rotate(90)")
-        .attr("text-anchor", "start")
-        .attr('x', 200)
+        .attr("text-anchor", "end")
+        .attr("transform", "rotate(-70)")

       @draw_legend
         svg: @svg
         series: @carrier_list.models
         width: @width
-        vertical_offset: @series_height + 130
+        vertical_offset: @series_height + 120
         columns: if @carrier_list.length > 9 then 3 else 2

       $("#{@container_selector()} rect.carrier").qtip
@@ -154,11 +155,11 @@
           "translate(#{old})"
         )

-      @label_offset = 0
-      @svg.selectAll("text.sector_label")
+      @svg.selectAll("g.sector_label")
         .data(@sector_list.models, (d) -> d.get 'key' )
         .transition().duration(500)
-        .attr("dy", (d) => -@x(d.total_value() / 2))
+        .attr('transform', (d) => "translate(#{@x(d.total_value() / 2)}, #{@label_offset})")
+        .select('text')
         .text((d) -> "#{d.get 'key'} #{parseInt d.total_value()} PJ")

       # we need to track the offset for every sector


Call Shortcut in DOM ready script

Call Shortcut in DOM ready script

--- app/assets/javascripts/domready.coffee
+++ app/assets/javascripts/domready.coffee
@@ -13,6 +13,7 @@
   Orientation.dropdown()
   Orientation.headingLink()
   Orientation.search()
+  Orientation.shortcut()

   Orientation.search
     $element : $( '.js-articleSearch' )


Fixed radio button re-selection to work

Fixed radio button re-selection to work

--- app/assets/javascripts/forms.js.coffee
+++ app/assets/javascripts/forms.js.coffee
@@ -3,7 +3,6 @@
   delegateFor.delegate ".status-buttons .radio input,.buttons .radio input", "click", () ->
     $(this).parents('.status-buttons,.buttons').first().find('.btn').removeClass('checked')
     $(this).parents('.btn:first').addClass('checked')
-    return false

 uniformify = (node) ->
   $("select, input:checkbox, input:radio, input:file, input:text, textarea, submit", node).uniform()


Corrige dados de pagamentos e pagamentos acumulados

Antes só estávamos considerando Pago, agora consideramos Pago + RP-Pago

--- app/assets/javascripts/graph.js.coffee
+++ app/assets/javascripts/graph.js.coffee
@@ -16,14 +16,18 @@
     pagoParameters =
       measure: "pago"

+    rppagoParameters =
+      measure: "rp-pago"
+
     $.when(
       $.getJSON(openspendingUrl, autorizadoParameters),
-      $.getJSON(openspendingUrl, $.extend(autorizadoParameters, pagoParameters))
+      $.getJSON(openspendingUrl, $.extend(autorizadoParameters, pagoParameters)),
+      $.getJSON(openspendingUrl, $.extend(autorizadoParameters, rppagoParameters))
     )

-  buildGraph = (svgElement) -> (autorizado, pago) ->
+  buildGraph = (svgElement) -> (autorizado, pago, rppago) ->
      nv.addGraph () ->
-         data = processData(autorizado, pago)
+         data = processData(autorizado, pago, rppago)
          chart = nv.models.multiChart()
                .x((d,i) -> parseInt(d.x))
                .color(d3.scale.category20c().range())
@@ -45,7 +49,7 @@

          chart

-  processData = (autorizado, pago) ->
+  processData = (autorizado, pago, rppago) ->
     accumulateAmounts = (values, element) ->
       length = values.length
       amount = if length > 0 then values[length-1].y else 0
@@ -56,18 +60,23 @@
       }
       values

+    pagamentos = pago[0].drilldown.map (element, i) ->
+      rppago_amount = rppago[0].drilldown[i]["rp-pago"]
+      element.amount = element.pago + element["rp-pago"]
+      element
+
     [
       {
         key: "Pagamentos",
         yAxis: 1,
         type: "bar",
-        values: pago[0].drilldown.map (element) -> { x: element.time.month, y: element.pago }
+        values: pagamentos.map (element) -> { x: element.time.month, y: element.amount }
       },
       {
         key: "Pagamentos Acumulados",
         yAxis: 1,
         type: "line",
-        values: pago[0].drilldown.reduce(accumulateAmounts, [])
+        values: pagamentos.reduce(accumulateAmounts, [])
       },
       {
         key: "Autorizado",


Fix mouse select getting stuck in visual mode

Only respond to left clicks.

--- lib/vim-state.coffee
+++ lib/vim-state.coffee
@@ -123,13 +123,15 @@
       @showCursors()

     selectionWatcher = null
-    handleMouseDown = =>
+    handleMouseDown = (evt) =>
+      return unless evt.button is 0 # Only care about left button
       point = @editor.getLastCursor().getBufferPosition()
       tailRange = Range.fromPointWithDelta(point, 0, +1)
       selectionWatcher = @editor.onDidChangeSelectionRange ({selection}) ->
         selection.setBufferRange(selection.getBufferRange().union(tailRange))

-    handleMouseUp = ->
+    handleMouseUp = (evt) ->
+      return unless evt.button is 0 # Only care about left button
       handleSelectionChange()
       selectionWatcher?.dispose()
       selectionWatcher = null


Joining string for iOS seems to be wrong?

Missing a comma, maybe..

--- CGDraw.coffee
+++ CGDraw.coffee
@@ -71,7 +71,7 @@
 	IOS_fixCoords = ( coordArr ) ->
 		coordArr[0] = round2 (coordArr[0] + org[0])*@scaleX
 		coordArr[1] = round2 (doc.height - (org[1] + coordArr[1]))*@scaleY
-		coordArr.join " "
+		coordArr.join ", "

 	# for CoreGraphics on mac, the origin is the bottom-left corner.
 	Mac_fixCoords = ( coordArr ) ->


Bug fix

Bug fix

--- src/assets/scripts/coffee/elements/NS.label.coffee
+++ src/assets/scripts/coffee/elements/NS.label.coffee
@@ -1,13 +1,12 @@
 window.NS.label = (id)->

-  elem = document.getElementById(id)
+  if (!document.getElementById(id))             then return else elem  = document.getElementById(id)
+  if (elem.nodeName.toLowerCase() isnt 'input') then return else input = elem

-  if (elem and elem.nodeName.toLowerCase() isnt 'input')   then return
-  if (input.getAttribute('type') is 'checkbox' or 'radio') then input = elem else return
+  if (input.getAttribute('type') is 'radio')
+    if (!input.checked) then input.checked = true

-  if (!input.checked)
-    input.checked = true
-  else if (input.getAttribute('type') is 'checkbox')
-    input.checked = false
+  if (input.getAttribute('type') is 'checkbox')
+    if (!input.checked) then input.checked = true else input.checked = false

   return


showing pet icon for pet events

showing pet icon for pet events

--- src/coffee/controllers/player-overview-ctrl.coffee
+++ src/coffee/controllers/player-overview-ctrl.coffee
@@ -39,6 +39,7 @@
       'guild': ['fa-network']
       'combat': ['fa-newspaper-o faa-pulse animated']
       'event': ['fa-gift faa-shake animated']
+      'pet': ['fa-paw']

     $scope.praying = no
     $scope.prayText = 'Pray to RNGesus'


Clipped actual percent to no less than .0001, so the text doesn't run off the circle

--- src/progress_circle.coffee
+++ src/progress_circle.coffee
@@ -1,14 +1,16 @@
-# Shape size defaults
+# Size & position defaults
 svgHeight = 500
 svgWidth = 500
-innerRadius = 75
-outerRadius = 100
+innerRadius = 90
+outerRadius = 110
 cornerRadius = 20
-circleRadius = 60
+circleRadius = 70
 innerPercent = .15
 outerPercent = .50
-innerThickness = 10
-outerThickness = 20
+innerThickness = 5
+outerThickness = 18
+defaultTextYPos = 20
+defaultTextPercentYPos = -3

 # Color defaults
 defaultActualArcColor = '#78C000'
@@ -22,13 +24,12 @@
 # Font defaults
 defaultTextFontSize = "15px"
 defaultTextPercentFontSize = "40px"
+defaultTextFontFamily = "sans-serif"
+defaultTextPercentFontFamily = "sans-serif"

 # Timing defaults
 defaultDuration = 750
 defaultDelay = 100
-
-actualArc = {}
-expectedArc = {}

 app.directive "progressCircle",  ->
     restrict: "EA"
@@ -54,19 +55,19 @@
             svg.append("text")
                 .text "Progress"
                 .attr "font-size", defaultTextFontSize
-                .attr "font-family", "sans-serif"
+                .attr "font-family", defaultTextFontFamily
                 .attr "fill", defaultTextColor
                 .style "text-anchor", "middle"
-                .attr("transform", "translate(0, 15)")
+                .attr("transform", "translate(0, #{defaultTextYPos})")

             # Percent value
             svg.append("text")
                 .text percent*100+"%"
                 .attr "font-size", defaultTextPercentFontSize
-                .attr "font-family", "sans-serif"
+                .attr "font-family", defaultTextPercentFontFamily
                 .attr "fill", defaultTextColor
                 .style "text-anchor", "middle"
-                .attr("transform", "translate(0, -5)")
+                .attr("transform", "translate(0, #{defaultTextPercentYPos})")

         # Create and return SVG arc
         createArc = (percent, radius, thickness) ->
@@ -171,7 +172,7 @@

             if (attrs.actual > 1)
                 attrs.actual = 1
-            if (attrs.actual < 0)
+            if (attrs.actual < .0001)
                 attrs.actual = 0

             if (attrs.expected > 1)


WIP - protomixin tests

WIP - protomixin tests

--- src/protomixin.spec.coffee
+++ src/protomixin.spec.coffee
@@ -1,7 +1,7 @@
 fdescribe 'mix.it.protomixin', ->

   mixit = require './index'
-  {beforeOnce, _} = require './util/spec'
+  {beforeOnce, _, MIXINS} = require './util/spec'

   it 'should allow protomixins', ->
     expect(_.isFunction mixit.enable_protomixin).toBe true
@@ -18,18 +18,18 @@
             @mixinto_proto non_Mixin
         ).toThrow new TypeError 'Expected a Mixin instance'

+    it 'should mix into the prototype', ->
+      class Foo
+        @mixinto_proto MIXINS.default()

-#    it 'should mix into the prototype', ->
-#      class Foo
-#        @mixinto_proto MIXINS.default()
-#
-#      expect(Foo::foo).toBe 'bar'
-#      expect(Foo::bar).toBe 1
-#      expect(Foo::baz()).toEqual ['bar']
-#
-#      expect(Foo.foo).toBeUndefined()
-#      expect(Foo.bar).toBeUndefined()
-#      expect(Foo.baz).toBeUndefined()
+        constructor: (@foo) ->
+
+      expect(Foo::foo).toBe 'bar'
+      expect(Foo::bar).toBe 1
+      expect(Foo::baz()).toEqual ['bar']
+
+      expect(new Foo('instance_foo').baz()).toEqual ['instance_foo']
+
 #
 #    it 'should be order-dependent', ->
 #      class Foo


interpolate string to avoid using + btw str

--- script.coffee
+++ script.coffee
@@ -80,7 +80,7 @@
         1000 / @time

     show: ->
-        @rate().toFixed(1) + ' fps'
+        "#{@rate().toFixed(1)} fps"

 boss = new class
@@ -147,17 +147,17 @@

     show_lives: ->
         if @lives > 3
-            'lives: ♥×' + @lives
+            "lives: ♥× #{@lives}"
         else if @lives > 0
-            'lives: ' + ('♥' for [0...Math.max(@lives, 0)]).join('')
+            "lives: #{('♥' for [0...Math.max(@lives, 0)]).join('')}"
         else
             'lives: ☠'

     show_lvl: ->
-        'level: ' + @lvl
+        "level: #{@lvl}"

     show_score: ->
-        'score: ' + @score
+        "score: #{@score}"

     make_summary: ->
         @make_tweet()


return

return

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -91,7 +91,7 @@
         @removePrivateContent client, groupId, tempRes, callback
       else
         callback null, tempRes
-    collectRelations
+    return collectRelations

   runQuery:(query, options, callback)->
     {startDate, client} = options


remove console.log

remove console.log

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -60,7 +60,6 @@
     collections = {}
     wantedOrder = []
     for obj in resultSet
-      console.log "-------", obj
       collections[obj.name] ||= []
       collections[obj.name].push obj.id
       wantedOrder.push idx: obj.id+'_'+obj.name


JGroup: modifyData: modifier for JGroupData for team admins

--- workers/social/lib/social/models/group/index.coffee
+++ workers/social/lib/social/models/group/index.coffee
@@ -134,6 +134,8 @@
           (signature Object, Function)
         ]
         modify:
+          (signature Object, Function)
+        modifyData:
           (signature Object, Function)
         fetchPermissions: [
           (signature Function)
@@ -1049,6 +1051,49 @@
     else
       kallback()

+
+  # modifies JGroupData related with the JGroup instance
+  #
+  # @param {Object} data
+  #   data to modify, only 'github.organizationToken' is allowed for now
+  #   set data to `null` to unset this if needed, requires admin privileges
+  #
+  # @option data [String] "github.organizationToken" github oauth token
+  #
+  # @example api
+  #
+  #   {
+  #     "github.organizationToken": "foobarbaz"
+  #   }
+  #
+  # @return [Error] includes error if something went wrong
+  #
+  modifyData : ->
+  modifyData : permit
+    advanced : [
+      { permission: 'edit own groups', validateWith : Validators.group.admin }
+      { permission: 'edit groups',     superadmin   : yes }
+    ]
+    success  : (client, _data, callback) ->
+
+      # it's only allowed to change followings
+      whitelist  = ['github.organizationToken']
+
+      # handle $set and $unset cases in one
+      operation  = { $set: {}, $unset: {} }
+      for item in whitelist
+        key = "data.#{item}"
+        if _data[item]?
+        then operation.$set[key]   = _data[item]
+        else operation.$unset[key] = _data[item]
+
+      @fetchData (err, data) ->
+        return callback err  if err
+
+        data.update operation, (err) ->
+          callback err
+
+
   modify     : permit
     advanced : [
       { permission: 'edit own groups', validateWith : Validators.group.admin }


Scripts: removed KD.profile

Scripts: removed KD.profile

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -27,7 +27,6 @@
     encodedCustomPartial = JSON.stringify customPartial, replacer
     currentGroup         = JSON.stringify currentGroup
     userAccount          = JSON.stringify delegate
-    profile              = JSON.stringify delegate.profile

     usePremiumBroker = usePremiumBroker or options.client.context.group isnt "koding"

@@ -35,8 +34,6 @@
     <script>
       console.time("Framework loaded");
       console.time("Koding.com loaded");
-      console.time("singletons registered I");
-      console.time("singletons registered II");
     </script>

     <!-- SEGMENT.IO -->
@@ -52,7 +49,6 @@
     <script>KD.config.usePremiumBroker=#{usePremiumBroker}</script>
     <script>KD.customPartial=#{encodedCustomPartial}</script>
     <script>KD.campaignData=#{encodedCampaignData}</script>
-    <script>KD.profile=#{profile}</script>
     <script src='/a/js/kd.libs.#{KONFIG.version}.js'></script>
     <script src='/a/js/kd.#{KONFIG.version}.js'></script>
     <script src='/a/js/koding.#{KONFIG.version}.js'></script>


PremiumSupport: define usePremiumBroker property

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -10,10 +10,12 @@

   {argv} = require 'optimist'

-  prefetchedFeeds = {}
-  customPartial   = {}
-  campaignData    = {}
-  currentGroup    = {}
+  prefetchedFeeds  = {}
+  customPartial    = {}
+  campaignData     = {}
+  currentGroup     = {}
+  usePremiumBroker = no
+
   {bongoModels, client, intro, landing, slug} = options

   createHTML = ->


fix(newsletter): Don't make API request if email is the same

--- src/components/newsletter-signup.cjsx
+++ src/components/newsletter-signup.cjsx
@@ -14,7 +14,7 @@
     @state = {status: 'Pending'}

   componentWillReceiveProps: (nextProps) =>
-    @_onGetStatus(nextProps)
+    @_onGetStatus(nextProps) if not _.isEqual(@props, nextProps)

   componentDidMount: =>
     @_onGetStatus()


Small fix for emailsender: Do not send mails to unsubscribed recipients.

--- workers/emailsender/main.coffee
+++ workers/emailsender/main.coffee
@@ -58,7 +58,6 @@
         console.error err  if err
         return emailContent.update $set: {status: 'unsubscribed'}, (err)->
           console.error err  if err
-          cb()
       else
         cb()
   else


[test] RedisModel##formatJobs

[test] RedisModel##formatJobs

--- test/index.coffee
+++ test/index.coffee
@@ -295,6 +295,50 @@

       done()

+  describe "##unknownKeysForIds", ->
+
+  describe "##formatJobs", ->
+    queues   = []
+    jobIds   = []
+
+    before (done) ->
+      ideally   = errify done
+      data      = {name: "testjob"}
+
+      validStates = ["active", "completed", "delayed", "failed", "wait"]
+      for state in validStates
+        await fakeJob queuename, data, state, ideally defer {queue, job}
+        queues = [queue]
+        jobIds.push job.jobId
+
+      done()
+
+    after (done) ->
+      ideally = errify done
+
+      for queue in queues
+        await qCleaner(queue).asCallback ideally defer _
+      queues  = []
+      jobIds  = []
+
+      done()
+
+    it "should return jobs formatted with state, sorted by id", (done) ->
+      ideally = errify done
+      list    = ids: {}
+      list.ids[queuename] = jobIds
+      await instance.fullKeysForList list, ideally defer keys
+
+      await instance.formatJobs queuename, keys, ideally defer jobs
+      lastId = 0
+      for job in jobs
+        expect(job.state).to.exist
+        id = Number job.id
+        expect(id).to.be.above lastId
+        lastId = id
+
+      done()
+
   describe "##remove", ->
     it "should remove jobs by id", (done) ->
       ideally = errify done


Do not eval xhr responseText in Dialog error message

--- app/assets/javascripts/alchemy/alchemy.dialog.js.coffee
+++ app/assets/javascripts/alchemy/alchemy.dialog.js.coffee
@@ -135,7 +135,7 @@
         error_type = "error"
         if status_message
           error_header = status_message
-          console.error eval(xhr.responseText)
+          console.error(xhr.responseText)
         else
           error_header = "#{xhr.statusText} (#{xhr.status})"
         error_body = "Please check log and try again."


EDSC-295: fixed timeline scrolling in Firefox

--- app/assets/javascripts/modules/timeline/timeline.coffee
+++ app/assets/javascripts/modules/timeline/timeline.coffee
@@ -526,22 +526,41 @@
         allowWheel = false
         setTimeout((-> allowWheel = true), 300)

-      L.DomEvent.on svg, 'mousewheel', (e) =>
-        return unless allowWheel
-
+      getTime = (e) =>
         draggable = @root.find(@scope('.draggable'))[0]
         origin = @_getTransformX(draggable)

         x = e.clientX - svg.clientLeft - origin
         time = @positionToTime(x)
-        deltaX = e.wheelDeltaX
-        deltaY = e.wheelDeltaY
+
+      doScroll = (deltaX, deltaY, e) =>
+        time = getTime(e)
         if Math.abs(deltaY) > Math.abs(deltaX)
           levels = if deltaY > 0 then -1 else 1
           @_deltaZoom(levels, time)
           rateLimit()
         else if deltaX != 0
           @_pan(deltaX)
+
+      # Safari
+      L.DomEvent.on svg, 'mousewheel', (e) =>
+        return unless allowWheel
+
+        deltaX = e.wheelDeltaX
+        deltaY = e.wheelDeltaY
+        doScroll(deltaX, deltaY, e)
+
+        e.preventDefault()
+
+      # Chrome/Firefox
+      L.DomEvent.on svg, 'wheel', (e) =>
+        return unless allowWheel
+        return if e.type == "mousewheel"
+
+        # 'wheel' deltas are opposite from 'mousewheel'
+        deltaX = -e.deltaX
+        deltaY = -e.deltaY
+        doScroll(deltaX, deltaY, e)

         e.preventDefault()


fix views mixinable method and remove _.string dependency

--- app/assets/javascripts/ultimate/backbone/view.js.coffee
+++ app/assets/javascripts/ultimate/backbone/view.js.coffee
@@ -6,7 +6,7 @@

   # Rest mixinNames
   @mixinable: ->
-    @mixinNames = _.clone(@mixinNames)
+    @mixinNames = if @mixinNames then _.clone(@mixinNames) else []

   # Mixins support
   @include: (mixin, name = null) ->
@@ -17,11 +17,11 @@
     _.extend @::, mixin

   mixinsEvents: (events = {}) ->
-    _.reduce( @constructor.mixinNames,  ( (memo, name) -> _.extend(memo, _.result(@, _.string.camelize(name + 'Events'))) ), events, @ )
+    _.reduce( @constructor.mixinNames,  ( (memo, name) -> _.extend(memo, _.result(@, name + 'Events')) ), events, @ )

   mixinsInitialize: ->
     for name in @constructor.mixinNames
-      @[_.string.camelize(name + 'Initialize')]? arguments...
+      @[name + 'Initialize']? arguments...
     @

   # TODO comment for this trick


Refactored member activity stream UI logic

--- static/js/member.coffee
+++ static/js/member.coffee
@@ -34,60 +34,72 @@
         @$el.empty()
         coll.each @add_item

+class ActivityFeedControl
+    constructor: (@state) ->
+        @type_state = @state.sub "type"
+        @kw_state = @state.sub "keyword"
+
+    feed_view: (view) => @state.on (opts={}) =>
+        view.filter opts
+
+    scores_view: (view) => @state.on (opts={}) =>
+        view.filter_keyword @kw_state.get()
+        types = @type_state.get()
+        if types?
+            types = (type for type of types)
+        view.filter_type types
+
+    tagcloud: (el) =>
+        tagcloud_buttons = el.find("li a")
+        kw_state = @kw_state
+        tagcloud_buttons.click (ev) ->
+            ev.preventDefault()
+            btn = $(@)
+            if btn.hasClass 'active'
+                kw_state.update undefined
+            else
+                kw_state.update btn.data "id"
+        @kw_state.on (value) ->
+            tagcloud_buttons.removeClass "active"
+            if not value?
+                return
+            tagcloud_buttons.filter("[data-id='#{value.toLowerCase()}']").addClass "active"
+
+    controls: (el) =>
+        kw_state = @kw_state
+        type_state = @type_state
+
+        kw_state.on (value) ->
+            el.find(".tag-filter-input").val(value ? "")
+        el.find(".tag-filter-input").change -> kw_state.update $(@).val()
+
+        filter_buttons = $(".feed-filter-buttons")
+
+        filter_buttons.find(".disable-filters").click ->
+            type_state.update undefined
+
+        type_state.on (types) ->
+            filter_buttons.find(".disable-filters").toggleClass "active", not types?
+
+        filter_buttons.find(".filter-button").each ->
+            btn = $(@)
+            state = type_state.sub(btn.data("feed-type"))
+            btn.click -> state.update if btn.hasClass("active") then undefined else 1
+            state.on (value) -> btn.toggleClass "active", Boolean(value)
+
+
 $ ->
-    party_list = new PartyList party_json
-
     tags = ({name: x[0], id: x[0].toLowerCase(), count: x[1]} for x in keyword_activity)
     tags = _.sortBy tags, (x) -> x.name
+    tagcloud = $("#member-tag-cloud")
     if tags.length > 0
-        $("#member-tag-cloud").tag_cloud tags
+        tagcloud.tag_cloud tags
     else
-        $("#member-tag-cloud").append("
Ei asiasanoitettua aktiivisuutta
")
+        tagcloud.append("
Ei asiasanoitettua aktiivisuutta
")

-    feed_view = new MemberActivityFeedView member
+    setup = new ActivityFeedControl hashstate.sub "as"
+    setup.feed_view new MemberActivityFeedView member
+    setup.scores_view member_activity_scores_view
+    setup.tagcloud tagcloud
+    setup.controls $(".feed-filters")

-    mpact_state = hashstate.sub "as"
-    type_state = mpact_state.sub "type"
-    kw_state = mpact_state.sub "keyword"
-    mpact_state.on (opts={}) ->
-        feed_view.filter opts
-
-        member_activity_scores_view.filter_keyword opts.keyword
-        types = opts.type
-        if types?
-            types = (type for type of types)
-        member_activity_scores_view.filter_type types
-
-    tagcloud_buttons = $("#member-tag-cloud li a")
-    tagcloud_buttons.click (ev) ->
-        ev.preventDefault()
-        btn = $(@)
-        if btn.hasClass 'active'
-            kw_state.update undefined
-        else
-            kw_state.update btn.data "id"
-
-    kw_state.on (value) ->
-        $(".feed-filters .tag-filter-input").val(value ? "")
-        tagcloud_buttons.removeClass "active"
-        if not value?
-            return
-        tagcloud_buttons.filter("[data-id='#{value.toLowerCase()}']").addClass "active"
-
-    filter_buttons = $(".feed-filter-buttons .filter-button")
-
-    $(".feed-filter-buttons .disable-filters").click ->
-        type_state.update undefined
-
-    type_state.on (types) ->
-        $(".feed-filter-buttons .disable-filters").toggleClass "active", not types?
-
-    filter_buttons.each ->
-        btn = $(@)
-        state = type_state.sub(btn.data("feed-type"))
-        btn.click -> state.update if btn.hasClass("active") then undefined else 1
-        state.on (value) -> btn.toggleClass "active", Boolean(value)
-
-    $(".feed-filters .tag-filter-input").change -> kw_state.update $(@).val()
-
-