stores selected state for project and branch

--- js/app.coffee
+++ js/app.coffee
@@ -26,7 +26,7 @@
     @projects_have_been_loaded = ko.observable(false)
     @recent_builds_have_been_loaded = ko.observable(false)
     @project_builds_have_been_loaded = ko.observable(false)
-    @selected = ko.observable({})
+    @selected = ko.observable({}) # Tracks what the dashboard is showing

     if window.renderContext.current_user
       @billing = ko.observable(new CI.inner.Billing)
@@ -139,9 +139,9 @@
     path = "/api/v1/project/#{project_name}"
     path += "/tree/#{encodeURIComponent(branch)}" if branch?

-    @selected
-      project_name: project_name,
-      branch: branch
+    # @selected
+    #   project_name: project_name,
+    #   branch: branch

     @builds.removeAll()
     @project_builds_have_been_loaded(false)
@@ -316,7 +316,9 @@
     @before '/.*', (cx) -> VM.maybeRouteErrorPage(cx)
     @get '^/tests/inner', (cx) -> VM.loadJasmineTests(cx)

-    @get '^/', (cx) => VM.loadRootPage(cx)
+    @get '^/', (cx) =>
+      VM.selected({})
+      VM.loadRootPage(cx)

     @get '^/add-projects', (cx) => VM.loadAddProjects cx
     @get '^/gh/:username/:project/edit(.*)',
@@ -324,11 +326,20 @@
     @get '^/account(.*)',
       (cx) -> VM.loadAccountPage cx, cx.params.splat
     @get '^/gh/:username/:project/tree/:branch',
-      (cx) -> VM.loadProject cx, cx.params.username, cx.params.project, cx.params.branch
+      (cx) ->
+        VM.selected
+          project_name: "#{cx.params.username}/#{cx.params.project}"
+          branch: cx.params.branch
+
+        VM.loadProject cx, cx.params.username, cx.params.project, cx.params.branch
     @get '^/gh/:username/:project/:build_num',
       (cx) -> VM.loadBuild cx, cx.params.username, cx.params.project, cx.params.build_num
     @get('^/gh/:username/:project',
-      (cx) -> VM.loadProject cx, cx.params.username, cx.params.project
+      (cx) ->
+        VM.selected
+          project_name: "#{cx.params.username}/#{cx.params.project}"
+
+        VM.loadProject cx, cx.params.username, cx.params.project

     @get '^/logout', (cx) -> VM.logout cx


fricas button bar

fricas button bar

--- salvus/page/buttonbar.coffee
+++ salvus/page/buttonbar.coffee
@@ -1040,8 +1040,81 @@
                     nlm(fn, p = c(200, 0.1), hessian = TRUE)
                     """

-
-
+    fricas:
+        help:
+            wrap:
+                insert : ")summary"
+        explain:
+             wrap:
+                left  : ")display operation "
+                right : ""
+        differentiate:
+            insert : 'differentiate(1 + x + x^2, x)'
+        integrate:
+            insert : 'integrate(1 + x + x^2, x)'
+        nintegrate:
+            insert : 'aromberg(sin, -%pi, %pi, 0.0001, 0.001, 2, 5, 20) -- aromberg(fn, a, b, epsrel, epsabs, nmin, nmax, nint)'
+        one-line function:
+            insert : 'f(x,y) == x * sin(y)'
+        matrix:
+            insert : "matrix [[1,%pi],[3, %e],[5,6]]"
+        vector:
+            insert : "vector [%pi, 2, 3, %e]"
+        factor:
+            insert : "factor 2015"
+        primes:
+            insert : "primes(1,100)"
+        mod:
+            insert : "12::IntegerMod(5)"
+        contfrac:
+            insert : "continuedFraction(%e::Expression Float)$NumericContinuedFraction(Float)"
+        determinant:
+            insert : "determinant matrix [[1,2], [3,4]]"
+        charpoly:
+            insert : "characteristicPolynomial matrix [[1,2], [3,4]]"
+        eigen:
+            insert : "eigenvectors matrix [[1,2, 3], [4,5,6], [7,8,9]]"
+
+        ring_CC:
+            insert : "Polynomial Complex Float"
+        ring_QQ:
+            insert : "Polynomial Fraction Integer"
+        ring_RR:
+            insert : "Polynomial Float"
+        ring_ZZ:
+            insert : "Polynomial Integer"
+        comment:
+            wrap:
+                left : "-- "
+                right: ""
+        assign:
+            insert : "a: = 5"
+        forloop:
+            insert : """
+                    for animal in ["dog", "cat", "mouse"] repeat
+                        output("Mammal: ",animal)
+                    """
+        function :
+            insert : """
+                    plus(x, y) ==
+                        output("x is ",x)
+                        output("and y is ",y)
+                        -- Functions return the value of their last statement
+                        return x + y
+
+                    output plus(2000, 15)
+                    """
+        ifelse:
+            insert : """
+                    a := 10
+                    if a > 10 then
+                        output("a is bigger than 10.")
+                      else -- This elseif clause is optional.
+                        if a < 10 then
+                            output("a is smaller than 10.")
+                          else -- The else clause is optional too.
+                            output("a is indeed 10.")
+                    """

 #
 # programmatically creating the menu entries and buttons
@@ -1558,4 +1631,4 @@
     bb.append(latexbar)

 # NOT READY YET.
-#initialize_latex_buttonbar()+#initialize_latex_buttonbar()


Use `ctrl-alt-[up|down]` for line moving in Atom.


Use `.atom-workspace` class in .atom/keymap.cson.

The `.workspace` class is deprecated.

--- home/.atom/keymap.cson
+++ home/.atom/keymap.cson
@@ -62,7 +62,7 @@
 #
 # https://github.com/atom-minimap/minimap
 ###############################################################################
-'.workspace':
+'.atom-workspace':
   # vim-like mapping to `<leader>m`, with comma as <leader>.
   ', m': 'minimap:toggle'
   # This mapping is the suggested mapping in the minimap package's


rename AWS secretAccessKey

rename AWS secretAccessKey

--- both/collections.coffee
+++ both/collections.coffee
@@ -32,7 +32,7 @@
 				region: 'eu-central-1'
 				bucket: 'jwmanagement-fs'
 				accessKeyId: process.env.AWS_ACCESS_KEY_ID
-				secretAccessKey: process.env.AWS_ACCESS_KEY
+				secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
 		]

 	@Pictures = new FS.Collection 'pictures',
@@ -47,5 +47,5 @@
 				bucket: 'jwmanagement-fs'
 				folder: 'images'
 				accessKeyId: process.env.AWS_ACCESS_KEY_ID
-				secretAccessKey: process.env.AWS_ACCESS_KEY
+				secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
 		]


keep pages visible after a recompile until they are ready to rerender

--- services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfViewer.coffee
@@ -22,7 +22,7 @@

 	App.controller 'pdfViewerController', ['$scope', '$q', '$timeout', 'PDFRenderer', '$element', 'pdfHighlights', 'pdfSpinner', ($scope, $q, $timeout, PDFRenderer, $element, pdfHighlights, pdfSpinner) ->
 		@load = () ->
-			$scope.pages = []
+			# $scope.pages = []

 			$scope.document = new PDFRenderer($scope.pdfSrc, {
 				scale: 1,


script I'm using for migration

script I'm using for migration

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -4577,6 +4577,7 @@
         end : 10
         limit : 1
         qlimit : 1000000
+        errors : required
         cb : undefined
     dbg = (m) -> winston.debug("bup_set_quotas: #{m}")
     dbg()
@@ -4686,6 +4687,13 @@
                 else
                     return 0
             work = work.slice(opts.start, opts.end)
+            g = (task, cb) ->
+                f (task, err) ->
+                    if err
+                        errors.push({task:task, error:err})
+                        winston.debug("error! -- #{err}")
+                        winston.debug("#{errors.length} ERRORS so far")
+                cb()
             async.mapLimit(work, opts.limit, f, cb)
     ], (err) -> opts.cb?(err))

@@ -4703,6 +4711,7 @@
         exclude : []
         only  : undefined      # if given, *ONLY* migrate projects in this list.
         reverse : false
+        sort_by_time : true    # if true, stort by time (with newest first)
         loop  : 0              # if >=1; call it again with same inputs once it finishes
         cb    : undefined      # cb(err, {project_id:errors when migrating that project})

@@ -4753,19 +4762,17 @@
                     if result?
                         dbg("got #{result.length} results in #{misc.walltime(t)} seconds")
                         result.sort (a,b) ->
+                            if opts.sort_by_time
+                                if a.last_edited > b.last_edited
+                                    return -1
+                                else if a.last_edited < b.last_edited
+                                    return 1
                             if a.project_id < b.project_id
                                 return -1
                             else if a.project_id > b.project_id
                                 return 1
                             else
                                 return 0
-
-                        if opts.start? and opts.stop?
-                            result = result.slice(opts.start, opts.stop)
-                        else if opts.start?
-                            result = result.slice(opts.start)
-                        else if opts.stop?
-                            result = result.slice(0, opts.stop)

                         dbg("filter out known abusers: before #{result.length}")
                         result = (x for x in result when not x.abuser?)
@@ -4785,7 +4792,16 @@
                                     if tm < x.last_edited
                                         v.push(x)
                                         break
-                        projects = v
+                        result = v
+                        if opts.start? and opts.stop?
+                            result = result.slice(opts.start, opts.stop)
+                        else if opts.start?
+                            result = result.slice(opts.start)
+                        else if opts.stop?
+                            result = result.slice(0, opts.stop)
+
+
+                        projects = result
                         todo = projects.length
                         dbg("of these -- #{todo} in the range remain to be migrated")
                     cb(err)
@@ -4953,11 +4969,10 @@
             get_snapshots
                 project_id : opts.project_id
                 cb         : (err, snapshots) ->
-                    # randomize so not all in same DC0...
-                    v = ([snaps[0], Math.random(), host] for host, snaps of snapshots when snaps?.length >=1)
-                    v.sort()
+                    v = ([snaps[0], h] for h, snaps of snapshots when snaps?.length >=1)
+                    v.sort()
                     v.reverse()
-                    v = (x[2] for x in v when x[2] != host)
+                    v = (x[1] for x in v when x[1] != host)
                     if host?
                         v = [host].concat(v)
                     dbg("v = #{misc.to_json(v)}")


sample tests updated

sample tests updated

--- sample-tests/sampleTest.coffee
+++ sample-tests/sampleTest.coffee
@@ -1,7 +1,7 @@

 casper.test.begin "Sample Test", 2, (test) ->
    casper.start "http://localhost:3000", ->
-      this.waitForSelector "body", ->
+      @waitForSelector "body", ->
          test.assert true, "True is true"

    casper.then ->


Added class for anchor button

Added class for anchor button

--- plugins/linkimg.coffee
+++ plugins/linkimg.coffee
@@ -33,7 +33,7 @@
             buttonset = jQuery ""
             buttonize = (type) =>
                 id = "#{@options.uuid}-#{type}"
-                buttonset.append jQuery("<input id=\"#{id}\" type=\"checkbox\" />#{type}").button()
+                buttonset.append jQuery("<input id=\"#{id}\" type=\"checkbox\" />#{type}").button()
                 button = jQuery "##{id}", buttonset
                 button.bind "change", (event) ->
                     # we need to save the current selection because we will loose focus


Let keyup and change both trigger removals.

--- vendor/assets/javascripts/modal.js.coffee
+++ vendor/assets/javascripts/modal.js.coffee
@@ -13,7 +13,9 @@
         field = $(section).find("input[name='#{name}']")
         errorSpan = $("
#{message}
")
         $(field).addClass('invalid').after errorSpan
-        $(field).one 'keyup', -> $(this).siblings(".error").remove()
+        ['keyup', 'change'].each ->
+          $(field).one this, -> $(this).siblings(".error").remove()
+
     return false

   true


fix resource-table's margin

fix resource-table's margin

--- views/akashic-resource-checkbox-area.cjsx
+++ views/akashic-resource-checkbox-area.cjsx
@@ -15,8 +15,8 @@
     @props.filterRules(rowChooseChecked)
     config.set "plugin.Akashic.resource.table.checkbox", JSON.stringify rowChooseChecked
   render: ->
-

-      <Grid id='akashic-records-filter'>
+

+      <Grid className='akashic-records-filter'>
         <Row>
         {
           for checkedVal, index in @props.tableTab


Fix support for running old unitest.TestCase tests, matching only test classes, not utility classes too.

--- lib/atom-python-test.coffee
+++ lib/atom-python-test.coffee
@@ -46,25 +46,38 @@
     filePath = file?.path
     selectedText = editor.getSelectedText()

+    testLineNumber = editor.getCursorBufferPosition().row
+    testIndentation = editor.indentationForBufferRow(testLineNumber)
+
     class_re = /class \w*\((\w*.*\w*)*\):/
     buffer = editor.buffer
-    for line, index in buffer.lines
+
+    # Starts searching backwards from the test line until we find a class. This
+    # guarantee that the class is a Test class, not an utility one.
+    reversedLines = buffer.lines[0...testLineNumber].reverse()
+
+    for line, classLineNumber in reversedLines
       startIndex = line.search(class_re)
-      if startIndex != -1
+
+      # We think that we have found a Test class, but this is guaranteed only if
+      # the test indentation is greater than the class indentation.
+      classIndentation = editor.indentationForBufferRow(classLineNumber)
+      if startIndex != -1 and testIndentation > classIndentation
         endIndex = line.indexOf('(')
         startIndex = startIndex + 6
         className = line[startIndex...endIndex]
         filePath = filePath + '::' + className
+        break

-    re = /test\w*/;
-    lineNumber = editor.getCursorBufferPosition().row
-    content = editor.buffer.lines[lineNumber]
+    re = /test(\w*|\W*)/;
+    content = editor.buffer.lines[testLineNumber]
     endIndex = content.indexOf('(')
     startIndex = content.search(re)
     testName = content[startIndex...endIndex]
     console.log(testName)
     if testName
       filePath = filePath + '::' + testName
+      console.log(filePath)
       args = [filePath, '-s']
       @executePyTest(args)


refactor max min zoom so that we can set specific maz zoom for different layers, and also attribution

--- dashboard/static/coffee/nmis.coffee
+++ dashboard/static/coffee/nmis.coffee
@@ -994,22 +994,25 @@
         ne = new L.LatLng 14.28567730018259, 15.00732421875
         country_bounds = new L.LatLngBounds sw, ne
         lmap = L.map(mapId,
-          maxZoom: 11
-          minZoom: 6
           maxBounds: country_bounds
         ).setView [centroid.lat, centroid.lng], 6
         attribution = '© modilabs'
-        baseLayer = L.tileLayer("http://b.tiles.mapbox.com/v3/modilabs.nigeria_base/{z}/{x}/{y}.png",attribution: attribution).addTo lmap
+        baseLayer = L.tileLayer("http://b.tiles.mapbox.com/v3/modilabs.nigeria_base/{z}/{x}/{y}.png",
+          attribution: attribution
+          maxZoom: 11
+          minZoom: 6
+        ).addTo lmap
         mapLayers = {}
         for mdgL in mdgLayers
           do ->
             curMdgL = mdgL
             tileset = mdgL.slug
-            attribution = "modilabs"
+            attribution = '© modilabs'
             ml = L.tileLayer "http://a.tiles.mapbox.com/v3/modilabs.{tileset}/{z}/{x}/{y}.png",
-                attribution: attribution
-                tileset: mdgL.slug
-
+              attribution: attribution
+              tileset: mdgL.slug
+              maxZoom: 9
+              minZoom: 6
             curMdgL.onSelect = ()->
               lmap.removeLayer baseLayer
               lmap.addLayer(ml)


Set settings order

Set settings order

--- lib/ide-haskell-repl.coffee
+++ lib/ide-haskell-repl.coffee
@@ -10,6 +10,7 @@
       description: '''
       Path to REPL command, e.g. ghci, cabal or stack.
       '''
+      order: 10
     commandArgs:
       type: 'array'
       default: ['repl']
@@ -19,6 +20,7 @@
       Comma-separated REPL command arguments, repl for cabal, ghci for stack,
       or none for ghci.
       '''
+      order: 20
   activate: (state) ->
     @disposables = new CompositeDisposable
     @editorMap = new WeakMap


:racehorse: Filter messages on set instead of get

--- lib/message-registry.coffee
+++ lib/message-registry.coffee
@@ -1,4 +1,4 @@
-{Emitter, TextEditor} = require('atom')
+{Emitter, TextEditor, CompositeDisposable} = require('atom')
 validate = require('./validate')
 helpers = require('./helpers')

@@ -6,15 +6,21 @@
   constructor: ->
     @updated = false
     @publicMessages = []
+    @subscriptions = new CompositeDisposable()
     @emitter = new Emitter
     @linterResponses = new Map()
     @editorMessages = new Map()

+    @subscriptions.add(@emitter)
+    @subscriptions.add atom.config.observe('linter.ignoredMessageTypes', (ignoredMessageTypes) =>
+      @ignoredMessageTypes = ignoredMessageTypes
+    )
     @shouldUpdatePublic = true
     requestAnimationFrame => @updatePublic()

   set: ({linter, messages, editor}) ->
     try validate.messages(messages) catch e then return helpers.error(e)
+    messages = messages.filter((entry) => @ignoredMessageTypes.indexOf(entry.type) is -1)
     if linter.scope is 'project'
       @linterResponses.set(linter, messages)
     else
@@ -50,7 +56,7 @@

   deactivate: ->
     @shouldUpdatePublic = false
-    @emitter.dispose()
+    @subscriptions.dispose()
     @linterResponses.clear()
     @editorMessages.clear()


Bug fix

Bug fix

--- lib/model_extensions.coffee
+++ lib/model_extensions.coffee
@@ -166,6 +166,10 @@
   model_type::clone = (key, value, options) ->
     return _original_clone.apply(@, arguments) unless model_type.schema and (schema = model_type.schema())

+    return @get('id') if @_orm_clone > 0
+    @_orm_clone or= 0
+    @_orm_clone++
+
     json = {}
     for key, value of @attributes

@@ -178,6 +182,7 @@
       else
         json[key] = value

+    delete @_orm_clone if --@_orm_clone is 0
     return new @constructor(json)

   model_type::cursor = (key, query) ->


Bug fix

Bug fix

--- lib/model_extensions.coffee
+++ lib/model_extensions.coffee
@@ -226,9 +226,9 @@
     delete @_orm_clone if --@_orm_clone is 0
     return new @constructor(json)

-  model_type::cursor = (key, query) ->
+  model_type::cursor = (key, query={}) ->
     schema = model_type.schema() if model_type.schema
     if schema and (relation = schema.relation(key))
-      return relation.cursor(@, key, query, callback)
+      return relation.cursor(@, key, query)
     else
       throw new Error "#{schema.model_name}::cursor: Unexpected key: #{key} is not a relation"


Added class and controller snippet

Added class and controller snippet

--- snippets/php.cson
+++ snippets/php.cson
@@ -1,4 +1,23 @@
 '.text.html.php':
+  'Class & Controller Silverstripe':
+    'prefix': 'classcontroller'
+    'body': """
+    <?php
+
+    /**
+     *
+     */
+    class $1 extends ${2:Page}
+    {
+    \t${0}
+    }
+    class ${1}_Controller extends ${2:Page}_Controller
+    {
+    \tfunction init() {
+    \t\tparent::init();
+    \t}
+    }
+    """
   'singular_name':
     'prefix': 'singular_name '
     'body': """


Fixed indentation

Fixed indentation

--- snippets/php.cson
+++ snippets/php.cson
@@ -193,11 +193,10 @@
      * Define the default values for all the $db fields
      * @var array
      */
-    private static $defaults = array(
-        \t'${1}' => '${2}'
-    );
-    """
-
+    private static \\$defaults = array(
+    \t\'${1}\' => \'${2}\'
+    );
+    """
   'default_sort':
     'prefix': 'default_sort '
     'body': """


Updated to psr2 standards

Updated to psr2 standards

--- snippets/php.cson
+++ snippets/php.cson
@@ -14,12 +14,6 @@
     'body': 'Debug::dump(${1});'

   #  Fields
-  'OptionsetField':
-    'prefix': 'optionsetfield'
-    'body': 'OptionsetField::create(\'${1:name}\',\'${2:title}\',\'${3:datalist}\')$0'
-  'DropdownField':
-    'prefix': 'dropdownfield'
-    'body': 'DropdownField::create(\'${1:name}\',\'${2:title}\',\'${3:datalist}\')$0'
   'HTMLEditorField':
     'prefix': 'htmlfield'
     'body': 'HtmlEditorField::create(\'${1:name}\',\'${2:title}\')$0'
@@ -32,10 +26,34 @@
   'TextareaField':
     'prefix': 'textareafield'
     'body': 'TextareaField::create(\'${1:name}\',\'${2:title}\')$0'
+  'OptionsetField':
+    'prefix': 'optionsetfield'
+    'body': """
+    OptionsetField::create(
+    \t\'${1:name}\',
+    \t\'${2:title}\',
+    \t\'${3:datalist}\'
+    )$0
+    """
+  'DropdownField':
+    'prefix': 'dropdownfield'
+    'body': """
+    DropdownField::create(
+    \t\'${1:name}\',
+    \t\'${2:title}\',
+    \t\'${3:datalist}\'
+    )$0
+    """
   'GridField':
     'prefix': 'gridfield'
-    'body': 'GridField::create(\'${1:name}\',\'${2:title}\',\'${3:datalist}\',\'${4:configuration}\')$0'
-
+    'body': """
+    GridField::create(
+    \t\'${1:name}\',
+    \t\'${2:title}\',
+    \t\'${3:datalist}\',
+    \'${4:configuration}\'
+    )$0
+    """

   #  Tabs
   'addFieldToTab':


Add missing fields to contact model

Add missing fields to contact model

--- server/models/contact.coffee
+++ server/models/contact.coffee
@@ -55,6 +55,9 @@
         binary        : Object
         _attachments  : Object
         accounts      : [Account]
+        initials      : String
+        sortedName    : String
+        ref           : String

     @cast: (attributes, target) ->
         target = super attributes, target


bugfix : rare case when same mail twice in a box

--- server/models/message.coffee
+++ server/models/message.coffee
@@ -101,8 +101,14 @@
 Message.fetchOrUpdate = (box, mid, uid, callback) ->
     Message.byMessageId box.accountID, mid, (err, existing) ->
         return callback err if err
-        if existing
+        if existing and not existing.isInMailbox box
             existing.addToMailbox box, uid, callback
+        else if existing
+            # this is the weird case when a message is in the box
+            # under two different UIDs
+            # @TODO : maybe mark the message to handle modification of
+            # such messages
+            return callback null
         else
             box.imap_fetchOneMail uid, callback

@@ -265,6 +271,9 @@
     mailboxIDs = @mailboxIDs or {}
     mailboxIDs[box.id] = uid
     @updateAttributes {mailboxIDs}, callback
+
+Message::isInMailbox = (box) ->
+    return @mailboxIDs[box.id]?

 # Public: remove a message from a mailbox in the cozy
 # if the message becomes an orphan, we destroy it


See #26. Improve error handling with YIFY Provider.

--- server/providers/yify.coffee
+++ server/providers/yify.coffee
@@ -16,8 +16,15 @@
         }, (err, response, body) =>
             return callback(err, []) if err

-            # Parse body
-            data = JSON.parse(body)
+            try
+                # Parse body
+                data = JSON.parse(body)
+            catch e
+                # API does not always return JSON
+                # In the case of a server error,
+                # an entire HTML webpage is returned!
+                return callback(null, [])
+                # Fail silently

             # Check for failures
             if data.status is "fail"


minor: lzw performance

minor: lzw performance

--- coffee/lzw.coffee
+++ coffee/lzw.coffee
@@ -2,7 +2,7 @@
 exports.lzw_encode = (s) ->
   return "" if s is ""
   dict = {}
-  data = (s + "").split("")
+  data = s#(s + "").split("")
   out = []
   currChar = undefined
   phrase = data[0]
@@ -14,24 +14,21 @@
     if dict[phrase + currChar]?
       phrase += currChar
     else
-      out.push (if phrase.length > 1 then dict[phrase] else phrase.charCodeAt(0))
+      out.push String.fromCharCode (if phrase.length > 1 then dict[phrase] else phrase.charCodeAt(0))
       dict[phrase + currChar] = code
       code++
       phrase = currChar
     i++
-  out.push (if phrase.length > 1 then dict[phrase] else phrase.charCodeAt(0))
+  out.push String.fromCharCode (if phrase.length > 1 then dict[phrase] else phrase.charCodeAt(0))
   i = 0

-  while i < out.length
-    out[i] = String.fromCharCode(out[i])
-    i++
   out.join ""

 # Decompress an LZW-encoded string
 exports.lzw_decode = (s) ->
   return "" if s is ""
   dict = {}
-  data = (s + "").split("")
+  data = s #(s + "").split("")
   currChar = data[0]
   oldPhrase = currChar
   out = [ currChar ]


Make compatible with coffee 1.1.3 bug

Make compatible with coffee 1.1.3 bug

--- lib/rails/beforesend.coffee
+++ lib/rails/beforesend.coffee
@@ -9,25 +9,25 @@
 # Skip for Zepto which doesn't have ajaxSetup but does already support
 # `ajaxBeforeSend`. It'd be better to feature test for the event and
 # see if we need to install it.
-return unless $.ajaxSetup
+if $.ajaxSetup

-# One caveat about using `$.ajaxSetup` is that its easily clobbered.
-# If anything else tries to register another global `beforeSend`
-# handler, ours will be overriden.
-#
-# To work around this, register your global `beforeSend` handler with:
-#
-#     $(document).bind('ajaxBeforeSend', function() {})
-#
-$.ajaxSetup
-  beforeSend: (xhr, settings) ->
-    # Skip if global events are disabled
-    return unless settings.global
+  # One caveat about using `$.ajaxSetup` is that its easily clobbered.
+  # If anything else tries to register another global `beforeSend`
+  # handler, ours will be overriden.
+  #
+  # To work around this, register your global `beforeSend` handler with:
+  #
+  #     $(document).bind('ajaxBeforeSend', function() {})
+  #
+  $.ajaxSetup
+    beforeSend: (xhr, settings) ->
+      # Skip if global events are disabled
+      return unless settings.global

-    # Default to document if context isn't set
-    element = settings.context || document
+      # Default to document if context isn't set
+      element = settings.context || document

-    # Provide a global version of the `beforeSend` callback
-    event = $.Event 'ajaxBeforeSend'
-    $(element).trigger event, [xhr, settings]
-    event.result
+      # Provide a global version of the `beforeSend` callback
+      event = $.Event 'ajaxBeforeSend'
+      $(element).trigger event, [xhr, settings]
+      event.result


generalized selector to work in any editor mode

--- lib/ricksum-provider.coffee
+++ lib/ricksum-provider.coffee
@@ -9,7 +9,7 @@
     rightLabel: 'Lorem Ricksum'

 module.exports =
-  selector: '.source'
+  selector: '*'
   getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix}) ->
     new Promise (resolve) ->
       matcher = matchers.find (val) -> val.indexOf(prefix) >= 0


change remember_me to write to a local quorum of replicas

--- salvus/hub.coffee
+++ salvus/hub.coffee
@@ -718,9 +718,10 @@
         ttl              = 24*3600 * 30     # 30 days

         @remember_me_db.set
-            key   : @hash_session_id
-            value : signed_in_mesg
-            ttl   : ttl
+            key         : @hash_session_id
+            value       : signed_in_mesg
+            ttl         : ttl
+            consistency : 'localQuorum'

         x = @hash_session_id.split('$')    # format:  algorithm$salt$iterations$hash
         @set_cookie


Url -> URL

Url -> URL

--- lib/runtime/frontend.coffee
+++ lib/runtime/frontend.coffee
@@ -20,7 +20,7 @@
       createWindow: (opts) =>
         w = new BrowserWindow opts
         if opts.url?
-          w.loadUrl opts.url
+          w.loadURL opts.url
         w.setMenu(null)
         wid = w.id
         @windows[wid] = w


WIP use filter mechanism to filter out candidate pair char

--- lib/text-object-pair.coffee
+++ lib/text-object-pair.coffee
@@ -10,21 +10,52 @@
   getLineTextToBufferPosition
 } = require './utils'

+getPatternForPair = (pair) ->
+  [open, close] = pair
+  if open is close
+    new RegExp("(#{_.escapeRegExp(open)})", 'g')
+  else
+    new RegExp("(#{_.escapeRegExp(open)})|(#{_.escapeRegExp(close)})", 'g')
+
+findPair = (editor, from, pair, direction, fn) ->
+  pattern = getPatternForPair(pair)
+  switch direction
+    when 'forward'
+      findPairForward(editor, from, pattern, fn)
+    when 'backward'
+      findPairBackward(editor, from, pattern, fn)
+
+findPairForward = (editor, from, pattern, fn) ->
+  scanRange = new Range(from, editor.buffer.getEndPosition())
+  editor.scanInBufferRange(pattern, scanRange, fn)
+
+findPairBackward = (editor, from, pattern, fn) ->
+  scanRange = new Range([0, 0], from)
+  editor.backwardsScanInBufferRange(pattern, scanRange, fn)
+
+# Take start point of matched range.
+backSlashPattern = _.escapeRegExp('\\')
+isEscapedCharAtPoint = (editor, point) ->
+  escaped = false
+  pattern = new RegExp("[^#{backSlashPattern}]#{backSlashPattern}")
+  scanRange = [[point.row, 0], point]
+  editor.backwardsScanInBufferRange pattern, scanRange, ({matchText, range, stop}) ->
+    if range.end.isEqual(point)
+      stop()
+      escaped = true
+  escaped
+
 # -------------------------
 class Pair extends TextObject
   @extend(false)
+
+  _newStyle: true # REMOVE after rewrite DONE
+
   allowNextLine: false
   adjustInnerRange: true
   pair: null
   wise: 'characterwise'
   supportCount: true
-
-  getPattern: ->
-    [open, close] = @pair
-    if open is close
-      new RegExp("(#{_.escapeRegExp(open)})", 'g')
-    else
-      new RegExp("(#{_.escapeRegExp(open)})|(#{_.escapeRegExp(close)})", 'g')

   # Return 'open' or 'close'
   getPairState: ({matchText, range, match}) ->
@@ -36,7 +67,6 @@
           when match[1] then 'open'
           when match[2] then 'close'

-  backSlashPattern = _.escapeRegExp('\\')
   pairStateInBufferRange: (range, char) ->
     text = getLineTextToBufferPosition(@editor, range.end)
     escapedChar = _.escapeRegExp(char)
@@ -48,39 +78,34 @@
     pattern = new RegExp(patterns.join('|'))
     ['close', 'open'][(countChar(text, pattern) % 2)]

-  # Take start point of matched range.
-  isEscapedCharAtPoint: (point) ->
-    found = false
-
-    bs = backSlashPattern
-    pattern = new RegExp("[^#{bs}]#{bs}")
-    scanRange = [[point.row, 0], point]
-    @editor.backwardsScanInBufferRange pattern, scanRange, ({matchText, range, stop}) ->
-      if range.end.isEqual(point)
-        stop()
-        found = true
-    found
-
-  findPair: (which, options, fn) ->
-    {from, pattern, scanFunc, scanRange} = options
-    @editor[scanFunc] pattern, scanRange, (event) =>
-      {matchText, range, stop} = event
-      unless @allowNextLine or (from.row is range.start.row)
-        return stop()
-      return if @isEscapedCharAtPoint(range.start)
-      fn(event)
-
-  findOpen: (from, pattern) ->
-    options =
-      scanFunc: 'backwardsScanInBufferRange'
-      scanRange: new Range([0, 0], from)
-      from: from
-      pattern: pattern
-
+  getFilters: (from) ->
+    filters = []
+    if not @allowNextLine
+      isNotSameLine = ({range, stop}) ->
+        if from.row isnt range.start.row
+          stop()
+          true
+        else
+          false
+
+      filters.push(isNotSameLine)
+
+    isEscaped = ({range}) => isEscapedCharAtPoint(@editor, range.start)
+
+    filters.push(isEscaped)
+
+    filters
+
+  findOpen: (from) ->
     stack = []
     found = null
-    @findPair 'open', options, (event) =>
+
+    filters = @getFilters(from)
+
+    findPair @editor, from, @pair, 'backward', (event) =>
       {range, stop} = event
+      return if filters.some((filter) -> filter(event))
+
       if @getPairState(event) is 'close'
         stack.push({range})
       else
@@ -89,17 +114,16 @@
       stop() if found?
     found

-  findClose: (from,  pattern) ->
-    options =
-      scanFunc: 'scanInBufferRange'
-      scanRange: new Range(from, @editor.buffer.getEndPosition())
-      from: from
-      pattern: pattern
-
+  findClose: (from) ->
     stack = []
     found = null
-    @findPair 'close', options, (event) =>
+
+    filters = @getFilters(from)
+
+    findPair @editor, from, @pair, 'forward', (event) =>
       {range, stop} = event
+      return if filters.some((filter) -> filter(event))
+
       if @getPairState(event) is 'open'
         stack.push({range})
       else
@@ -116,9 +140,13 @@

   getPairInfo: (from) ->
     pairInfo = null
-    pattern = @getPattern()
-    closeRange = @findClose(from, pattern)
-    openRange = @findOpen(closeRange.end, pattern) if closeRange?
+    if @_newStyle
+      closeRange = @findClose(from)
+      openRange = @findOpen(closeRange.end) if closeRange?
+    else
+      pattern = @getPattern()
+      closeRange = @findClose(from, pattern)
+      openRange = @findOpen(closeRange.end, pattern) if closeRange?

     unless (openRange? and closeRange?)
       return null
@@ -364,6 +392,9 @@
 tagPattern = /(<(\/?))([^\s>]+)[^>]*>/g
 class Tag extends Pair
   @extend(false)
+
+  _newStyle: false # REMOVE after rewrite DONE
+
   allowNextLine: true
   allowForwarding: true
   adjustInnerRange: false
@@ -393,6 +424,28 @@
       if entry.tagState is tagState
         return entry
     null
+
+  # Take start point of matched range.
+  isEscapedCharAtPoint: (point) ->
+    found = false
+
+    bs = backSlashPattern
+    pattern = new RegExp("[^#{bs}]#{bs}")
+    scanRange = [[point.row, 0], point]
+    @editor.backwardsScanInBufferRange pattern, scanRange, ({matchText, range, stop}) ->
+      if range.end.isEqual(point)
+        stop()
+        found = true
+    found
+
+  findPair: (which, options, fn) ->
+    {from, pattern, scanFunc, scanRange} = options
+    @editor[scanFunc] pattern, scanRange, (event) =>
+      {matchText, range, stop} = event
+      unless @allowNextLine or (from.row is range.start.row)
+        return stop()
+      return if @isEscapedCharAtPoint(range.start)
+      fn(event)

   findOpen: (from,  pattern) ->
     scanFunc = 'backwardsScanInBufferRange'


Remove un-used package import

Remove un-used package import

--- server-ce/migrations/4_update_user_features.coffee
+++ server-ce/migrations/4_update_user_features.coffee
@@ -4,7 +4,6 @@
 ObjectId = mongojs.ObjectId
 db = mongojs(Settings.mongo.url, ['users'])
 _ = require("underscore")
-BSON = db.bson.BSON

 handleExit = () ->


Removed unused query variable.

Removed unused query variable.

--- packages/rocketchat-lib/server/sendMessage.coffee
+++ packages/rocketchat-lib/server/sendMessage.coffee
@@ -105,20 +105,6 @@
 					# the mentioned user if mention isn't for all
 					RocketChat.models.Subscriptions.incUnreadForRoomIdAndUserIds message.rid, mentionIds, 1

-				query =
-					statusConnection: {$ne: 'online'}
-
-				if toAll
-					if room.usernames?.length > 0
-						query.username =
-							$in: room.usernames
-					else
-						query.username =
-							$in: []
-				else
-					query._id =
-						$in: mentionIds
-
 				userIdsToNotify = _.pluck(usersOfMention, '_id')

 				# If the message is @all, send a notification to all online room users except for the sender.


Don't pass elem to smart handlers; they don't need it.

--- src/Editor.coffee
+++ src/Editor.coffee
@@ -43,7 +43,7 @@
       caretOffset = range.startOffset
       textNode    = range.commonAncestorContainer

-      handler.apply this, [event, @elem, caretOffset, textNode]
+      handler.apply this, [event, caretOffset, textNode]

     # TODO intelligently handle Up Arrow at the beginning of a section
     # TODO intelligently handle Down Arrow at the end of a section
@@ -55,7 +55,7 @@
         if event.metaKey || event.ctrlKey
           event.preventDefault()

-  applySmartQuote: (event, elem, caretOffset, textNode) =>
+  applySmartQuote: (event, caretOffset, textNode) =>
     event.preventDefault()

     precededByWord = textNode.textContent[caretOffset - 1]?.match /\S/
@@ -67,7 +67,7 @@

     @replaceWith textNode, caretOffset, caretOffset, char

-  applySmartEmDash: (event, elem, caretOffset, textNode) =>
+  applySmartEmDash: (event, caretOffset, textNode) =>
     # The user typed "--", which we will now convert to an em dash.
     if "-" == textNode.textContent[caretOffset - 1]
       event.preventDefault()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -14,7 +14,7 @@
     zoom: 1
     transform: Matrix()
     scroll: Point(0, 0)
-    maxVelocity:
+    maxVelocity: Point(I)

   currentType = "centered"
   currentObject = null


Escaped characters deemed unsafe by JSHint

--- spec/byte-buffer-test.coffee
+++ spec/byte-buffer-test.coffee
@@ -187,16 +187,16 @@
   it 'can read and write UTF-8 strings', ->
     b = new ByteBuffer(22)

-    expect(b.writeString('Byte $¢€𝇇 Buffer')).toEqual(22)
+    expect(b.writeString('Byte $\u00A2\u20AC\uD834\uDDC7 Buffer')).toEqual(22)
     expect(b.index).toEqual(22)
     expect(b.toHex()).toEqual('42 79 74 65 20 24 C2 A2 E2 82 AC F0 9D 87 87 20 42 75 66 66 65 72')

     b.front()

-    expect(b.readString()).toEqual('Byte $¢€𝇇 Buffer')
+    expect(b.readString()).toEqual('Byte $\u00A2\u20AC\uD834\uDDC7 Buffer')

     b = new ByteBuffer(262140)
-    long = (new Array(1 << 16)).join('𝇇')
+    long = (new Array(1 << 16)).join('\uD834\uDDC7')
     expect(b.writeString(long)).toEqual(262140)

     b.front()
@@ -210,12 +210,12 @@
   it 'can read and write NULL-terminated C-strings', ->
     b = new ByteBuffer(27)

-    expect(b.writeCString('Byte $¢€𝇇 Buffer')).toEqual(23)
+    expect(b.writeCString('Byte $\u00A2\u20AC\uD834\uDDC7 Buffer')).toEqual(23)
     b.writeUnsignedInt(10)

     b.front()

-    expect(b.readCString()).toEqual('Byte $¢€𝇇 Buffer')
+    expect(b.readCString()).toEqual('Byte $\u00A2\u20AC\uD834\uDDC7 Buffer')
     expect(b.available).toEqual(4)

   it 'has writers that can be chained', ->
@@ -255,7 +255,7 @@
     b.implicitGrowth = true
     b.append(1)

-    expect(b.writeString('Byte $¢€𝇇 Buffer')).toEqual(22)
+    expect(b.writeString('Byte $\u00A2\u20AC\uD834\uDDC7 Buffer')).toEqual(22)
     expect(b.length).toEqual(26)

   it 'can be clipped', ->


Remove logging of screen lines in spec

Remove logging of screen lines in spec

--- spec/editor-view-spec.coffee
+++ spec/editor-view-spec.coffee
@@ -2652,7 +2652,6 @@
             expect(editor.getCursorBufferPosition()).toEqual [0, 0]
             expect(buffer.lineForRow(0)).toBe 'var quicksort = function () {'
             expect(buffer.lineForRow(13)).toBe 'var a = function() {'
-            editor.logScreenLines()
             expect(editor.isFoldedAtBufferRow(0)).toBe true
             expect(editor.isFoldedAtBufferRow(13)).toBe true


Remove window.debugContent

Remove window.debugContent

Whoops!

--- spec/editor-view-spec.coffee
+++ spec/editor-view-spec.coffee
@@ -2895,7 +2895,6 @@
         expect(editorView.lineElementForScreenRow(rowNumber).text()).toBe buffer.lineForRow(rowNumber)

     it "correctly calculates the position left for non-monospaced invisibles", ->
-      window.debugContent = true
       editorView.setShowInvisibles(true)
       editorView.setInvisibles tab: '♘'
       editor.setText('\tx')


spec now checks if the cursors are the same, not their markers

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -941,7 +941,7 @@
         it "returns the existing cursor", ->
           cursor1 = editor.addCursorAtScreenPosition([0, 2])
           cursor2 = editor.addCursorAtScreenPosition([0, 2])
-          expect(cursor2.marker).toBe cursor1.marker
+          expect(cursor2).toBe cursor1

     describe "addCursorAtBufferPosition(bufferPosition)", ->
       describe "when a cursor already exists at the position", ->


Started the LESS processor.

Started the LESS processor.

--- kelvin.coffee
+++ kelvin.coffee
@@ -3,6 +3,8 @@
 path = require 'path'
 _ = require 'underscore'
 crypto = require 'crypto'
+nap = require 'nap'
+fs = require 'fs'

 linkAttrs = {
   'href': '',
@@ -21,14 +23,16 @@

   parse: (locals) ->
     if (@assets)
-      return @assets
+      return {
+        assets: @assets
+      }
     @assets = {}
     for type, obj of @expandAssetGlobs locals.assets
-      for pkg, arr of obj
+      for name, arr of obj
         if _.isArray arr
           unless @assets[type]
             @assets[type] = {}
-          @assets[type][pkg] = @processPackage arr, type
+          @assets[type][name] = @processPackage name, arr, type
     return {
       assets: @assets
     }
@@ -48,10 +52,27 @@
         expandedAssets[key][pkg] = matches
     expandedAssets

-  processPackage: (files, type) ->
+  processPackage: (name, files, type) ->
     output = '\n'
     for file in files
-      output += link.join(' ')
+      contents = fs.readFileSync(path.resolve process.cwd() + '/contents/' + file).toString()
+      ext = path.extname file
+      if nap.preprocessors[ext]
+        contents = nap.preprocessors[ext] contents, file
+      filename = "/#{file}.#{hashContents(contents)}.css"
+      writeFile filename, contents
+      output += "<link href=\"#{filename}\" rel=\"stylesheet\" />\n"
     output

+hashContents = (contents) ->
+  md5 = crypto.createHash('md5')
+  md5.update contents
+  md5.digest('hex')
+
+writeFile = (filename, contents) =>
+  file = process.cwd() + '/build/' + filename
+  dir = path.dirname file
+  mkdirp.sync dir, '0755' unless path.existsSync dir
+  fs.writeFileSync file, contents ? ''
+
 module.exports = Kelvin


Update divider.cson to close html attributes in snippet

--- snippets/divider.cson
+++ snippets/divider.cson
@@ -1,25 +1,25 @@
 '.text.html':
   'ui-clearing-divider':
     'prefix': 'ui-clearing-divider'
-    'body': '

'
+    'body': '

'
   'ui-divider':
     'prefix': 'ui-divider'
-    'body': '

'
+    'body': '

'
   'ui-fitted-divider':
     'prefix': 'ui-fitted-divider'
-    'body': '

'
+    'body': '

'
   'ui-horizontal-divider':
     'prefix': 'ui-horizontal-divider'
-    'body': '

'
+    'body': '

'
   'ui-horizontal-icon-divider':
     'prefix': 'ui-horizontal-icon-divider'
-    'body': '

'
+    'body': '

'
   'ui-inverted-divider':
     'prefix': 'ui-inverted-divider'
-    'body': '

'
+    'body': '

'
   'ui-section-divider':
     'prefix': 'ui-section-divider'
-    'body': '

'
+    'body': '

'
   'ui-vertical-divider':
     'prefix': 'ui-vertical-divider'
-    'body': '

'
+    'body': '

'


Fix semicolon paren bug

Fix semicolon paren bug

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -251,7 +251,6 @@
     # If there are mutliple expressions we have on the same line,
     # add a semicolon block around them.
     wrapSemicolons: (expressions, depth) ->
-
       # We will keep track of the first and last
       # nodes on the current line, and their bounds.
       firstNode = lastNode =
@@ -375,7 +374,7 @@
                 @addBlock node, depth, 0, COLORS.COMMAND, null

                 for expr in node.body.expressions
-                  @mark expr, depth + 1, 0, null, indentDepth
+                  @addSocketAndMark expr, depth + 1, 0, indentDepth

               else
                 @mark node.body.unwrap(), depth + 1, 0, (wrappingParen ? node), indentDepth


Remove the @team notification on completed brews.

--- src/coffee.coffee
+++ src/coffee.coffee
@@ -236,8 +236,6 @@
           "To end the brew use: `#{robot.alias}fresh pot`"

   endBrew = (msg) ->
-    teamNotify = brewing.dibs.length < dibsLimit
-
     expiry = new Date (new Date()).getTime() + dibsDuration * 1000

     claims = []
@@ -274,7 +272,7 @@
         claims.push "_Unclaimed!_"

     threadMsg = threadedMsg(msg)
-    threadMsg.send "#{if teamNotify then '@team: ' else ''}Fresh Pot!!! #{msg.random freshPots} " +
+    threadMsg.send "Fresh Pot!!! #{msg.random freshPots} " +
         ":coffee: Claims (valid until #{dateformat expiry, 'h:MM:ss tt'}):\n" +
         ("#{(parseInt index) + 1}. #{claim}" for index, claim of claims).join("\n")


An intermediary commit

An intermediary commit

--- src/GestureController.coffee
+++ src/GestureController.coffee
@@ -113,6 +113,7 @@
             manager.tearDownRecipe data.name

     assertHand: (sign, handModel) =>
+        console.log "Hand direction #{handModel.direction}"
         sign_ok = true
         if(sign.grab)
             grabStrength = handModel.grabStrength


Profile: Instead of seperate follower/like/followee content displays and feeders a general purpose feeder is used

--- client/Social/Members/AppController.coffee
+++ client/Social/Members/AppController.coffee
@@ -23,6 +23,99 @@
     super options, data

     @on "LazyLoadThresholdReached", => @feedController?.loadFeed()
+
+  createContentDisplay:(account, callback)->
+    KD.singletons.appManager.setFrontApp this
+    contentDisplay = new KDView
+      cssClass : 'member content-display'
+      type     : 'profile'
+    contentDisplay.on 'handleQuery', (query)=>
+      @ready => @feedController?.handleQuery? query
+
+    @addProfileView contentDisplay, account
+    @addActivityView contentDisplay, account
+    @showContentDisplay contentDisplay
+    @utils.defer -> callback contentDisplay
+
+  addActivityView:(view, account)->
+    view.$('div.lazy').remove()
+    windowController = KD.getSingleton('windowController')
+
+    KD.getSingleton("appManager").tell 'Feeder', 'createContentFeedController', {
+      itemClass             : ActivityListItemView
+      listControllerClass   : MemberActivityListController
+      listCssClass          : "activity-related"
+      limitPerPage          : 8
+      useHeaderNav          : yes
+      delegate              : this
+      creator               : account
+      filter                :
+        statuses            :
+          noItemFoundText   : "#{KD.utils.getFullnameFromAccount account} has not shared any posts yet."
+          dataSource        : (selector, options = {}, callback)=>
+            options.originId = account.getId()
+            KD.getSingleton("appManager").tell 'Activity', 'fetchActivitiesProfilePage', options, callback
+        followers           :
+          loggedInOnly      : yes
+          itemClass          : GroupMembersPageListItemView
+          listControllerClass: MembersListViewController
+          noItemFoundText   : "No one is following #{KD.utils.getFullnameFromAccount account} yet."
+          dataSource        : (selector, options, callback)=>
+            options.groupId or= KD.getSingleton('groupsController').getCurrentGroup().getId()
+            account.fetchFollowersWithRelationship selector, options, callback
+
+            account.countFollowersWithRelationship selector, (err, count)=>
+              @setCurrentViewNumber 'followers', count
+        following           :
+          loggedInOnly      : yes
+          itemClass          : GroupMembersPageListItemView
+          listControllerClass: MembersListViewController
+          noItemFoundText   : "#{KD.utils.getFullnameFromAccount account} is not following anyone."
+          dataSource        : (selector, options, callback)=>
+            options.groupId or= KD.getSingleton('groupsController').getCurrentGroup().getId()
+            account.fetchFollowingWithRelationship selector, options, callback
+
+            account.countFollowingWithRelationship selector, (err, count)=>
+              @setCurrentViewNumber 'following', count
+        likes               :
+          loggedInOnly      : yes
+          noItemFoundText   : "#{KD.utils.getFullnameFromAccount account} has not liked any posts yet."
+          dataSource        : (selector, options, callback)->
+            selector = {sourceName: $in: ['JNewStatusUpdate']}
+            account.fetchLikedContents options, selector, callback
+      sort                  :
+        'modifiedAt'        :
+          title             : "Latest activity"
+          direction         : -1
+        'counts.followers'  :
+          title             : "Most followers"
+          direction         : -1
+        'counts.following'  :
+          title             : "Most following"
+          direction         : -1
+        'timestamp|new'     :
+          title             : 'Latest activity'
+          direction         : -1
+        'timestamp|old'     :
+          title             : 'Most activity'
+          direction         : 1
+    }, (controller)=>
+      @feedController = controller
+      view.addSubView controller.getView()
+      view.setCss minHeight : windowController.winHeight
+      @emit 'ready'
+
+  addProfileView:(view, member)->
+    options      =
+      cssClass   : "profilearea clearfix"
+      delegate   : view
+
+    if KD.isMine member
+      options.cssClass = KD.utils.curry "own-profile", options.cssClass
+    else
+      options.bind = "mouseenter" unless KD.isMine member
+
+    return view.addSubView memberProfile = new ProfileView options, member

   createFeed:(view, loadFeed = no)->
     @appManager.tell 'Feeder', 'createContentFeedController', {
@@ -57,35 +150,9 @@
                 count = 0 if err
                 @setCurrentViewNumber 'all', count

-        followed            :
-          loggedInOnly      : yes
-          title             : "Followers "
-          noItemFoundText   : "No one is following you yet."
-          dataSource        : (selector, options, callback)=>
-            options.groupId or= KD.getSingleton('groupsController').getCurrentGroup().getId()
-            KD.whoami().fetchMyFollowersFromGraph options, callback
-
-            KD.whoami().countFollowersWithRelationship selector, (err, count)=>
-              @setCurrentViewNumber 'followers', count
-        followings          :
-          loggedInOnly      : yes
-          title             : "Following "
-          noItemFoundText   : "You are not following anyone."
-          dataSource        : (selector, options, callback)=>
-            options.groupId or= KD.getSingleton('groupsController').getCurrentGroup().getId()
-            KD.whoami().fetchMyFollowingsFromGraph options, callback
-
-            KD.whoami().countFollowingWithRelationship selector, (err, count)=>
-              @setCurrentViewNumber 'following', count
       sort                  :
         'meta.modifiedAt'   :
           title             : "Latest activity"
-          direction         : -1
-        'counts.followers'  :
-          title             : "Most followers"
-          direction         : -1
-        'counts.following'  :
-          title             : "Most following"
           direction         : -1
     }, (controller)=>
       @feedController = controller
@@ -97,121 +164,6 @@
           @setCurrentViewHeader count

       KD.mixpanel "Load member list, success"
-
-  createFeedForContentDisplay:(view, account, followersOrFollowing, callback)->
-
-    @appManager.tell 'Feeder', 'createContentFeedController', {
-      # domId                 : 'members-feeder-split-view'
-      feedId                : "members.#{account.profile.username}"
-      itemClass             : MembersListItemView
-      listControllerClass   : MembersListViewController
-      limitPerPage          : 10
-      noItemFoundText       : "There is no member."
-      useHeaderNav          : yes
-      delegate              : this
-      # singleDataSource      : (selector, options, callback)=>
-        # filterFunc selector, options, callback
-      help                  :
-        subtitle            : "Learn About Members"
-        bookIndex           : 11
-        tooltip             :
-          title             : "
These people are all members of koding.com. Learn more about them and their interests, activity and coding prowess here.
"
-          placement         : "above"
-      filter                :
-        everything          :
-          title             : "All"
-          dataSource        : (selector, options, callback)=>
-            if followersOrFollowing is "followers"
-              account.fetchFollowersWithRelationship selector, options, callback
-            else
-              account.fetchFollowingWithRelationship selector, options, callback
-      sort                  :
-        'meta.modifiedAt'   :
-          title             : "Latest activity"
-          direction         : -1
-        'counts.followers'  :
-          title             : "Most followers"
-          direction         : -1
-        'counts.following'  :
-          title             : "Most following"
-          direction         : -1
-    }, (controller)=>
-      view.addSubView controller.getView()
-      # KD.singleton('display').emit "ContentDisplayWantsToBeShown", view
-      callback view, controller
-      if controller.facetsController?.filterController?
-        controller.emit 'ready'
-      else
-        controller.getView().on 'viewAppended', -> controller.emit 'ready'
-
-  createFolloweeContentDisplay:(account, filter, callback)->
-    # log "I need to create followee for", account, filter
-    newView = new MembersContentDisplayView
-      cssClass : "content-display #{filter}"
-      # domId    : 'members-feeder-split-view'
-    newView.createCommons(account, filter)
-    @createFeedForContentDisplay newView, account, filter, callback
-
-  createLikedFeedForContentDisplay:(view, account, callback)->
-
-    @appManager.tell 'Feeder', 'createContentFeedController', {
-      # domId                 : 'members-feeder-split-view'
-      itemClass             : ActivityListItemView
-      listControllerClass   : ActivityListController
-      listCssClass          : "activity-related"
-      noItemFoundText       : "There is no liked activity."
-      limitPerPage          : 8
-      delegate              : this
-      help                  :
-        subtitle            : "Learn Personal feed"
-        tooltip             :
-          title             : "
This is the liked feed of a single Koding user.
"
-          placement         : "above"
-      filter                :
-        everything          :
-          title             : "Everything"
-          dataSource        : (selector, options, callback)=>
-            account.fetchLikedContents options, callback
-        statusupdates       :
-          title             : 'Status Updates'
-          dataSource        : (selector, options, callback)->
-            selector = {sourceName: $in: ['JNewStatusUpdate']}
-            account.fetchLikedContents options, selector, callback
-        codesnippets        :
-          title             : 'Code Snippets'
-          dataSource        : (selector, options, callback)->
-            selector = {sourceName: $in: ['JCodeSnip']}
-            account.fetchLikedContents options, selector, callback
-        # Discussions Disabled
-        # discussions         :
-        #   title             : 'Discussions'
-        #   dataSource        : (selector, options, callback)->
-        #     selector = {sourceName: $in: ['JDiscussion']}
-        #     account.fetchLikedContents options, selector, callback
-      sort                :
-        'timestamp|new'   :
-          title           : 'Latest activity'
-          direction       : -1
-        'timestamp|old'   :
-          title           : 'Most activity'
-          direction       : 1
-    }, (controller)=>
-      view.addSubView controller.getView()
-      # KD.singleton('display').emit "ContentDisplayWantsToBeShown", view
-      callback view, controller
-
-      if controller.facetsController?.filterController?
-        controller.emit 'ready'
-      else
-        controller.getView().on 'viewAppended', -> controller.emit 'ready'
-
-  createLikedContentDisplay:(account, callback)->
-    newView = new MembersLikedContentDisplayView
-      cssClass : "content-display likes"
-      # domId    : 'members-feeder-split-view'
-
-    newView.createCommons account
-    @createLikedFeedForContentDisplay newView, account, callback

   loadView:(mainView, firstRun = yes, loadFeed = no)->
     if firstRun
@@ -223,38 +175,6 @@
       mainView.createCommons()
     @createFeed mainView, loadFeed

-
-  createContentDisplay:(account, callback)->
-
-    KD.singletons.appManager.setFrontApp this
-    controller     = new ContentDisplayControllerMember {delegate:this}, account
-    contentDisplay = controller.getView()
-    contentDisplay.on 'handleQuery', (query)=>
-      controller.ready -> controller.feedController?.handleQuery? query
-    @showContentDisplay contentDisplay
-    @utils.defer -> callback contentDisplay
-
-
-  createContentDisplayWithOptions:(options, callback)->
-    {model, route, query} = options
-    kallback = (contentDisplay, controller)=>
-      # needed to remove the member display which comes as HTML
-      unless KD.getSingleton('router').openRoutes[KD.config.entryPoint?.slug]
-        memberDisplay = document.getElementById('member-contentdisplay')
-        memberDisplay?.parentNode.removeChild(memberDisplay)
-
-      contentDisplay.on 'handleQuery', (query)->
-        controller.ready -> controller.handleQuery? query
-      callback contentDisplay
-
-    switch route.split('/')[2]
-      when 'Followers'
-        @createFolloweeContentDisplay model, 'followers', kallback
-      when 'Following'
-        @createFolloweeContentDisplay model, 'following', kallback
-      when 'Likes'
-        @createLikedContentDisplay model, kallback
-
   showContentDisplay:(contentDisplay)->

     KD.singleton('display').emit "ContentDisplayWantsToBeShown", contentDisplay
@@ -303,3 +223,9 @@

     whitelist = Object.keys(externalProfiles).slice().map (a)-> "ext|profile|#{a}"
     account.fetchStorages  whitelist, callback
+
+class MemberActivityListController extends ActivityListController
+  # used for filtering received live updates
+  addItem: (activity, index, animation)->
+    if activity.originId is @getOptions().creator.getId()
+      super activity, index, animation


:memo: Fix broken json-schema.org link

:memo: Fix broken json-schema.org link

--- src/config.coffee
+++ src/config.coffee
@@ -83,7 +83,7 @@
 #
 # ## Config Schemas
 #
-# We use [json schema](json-schema.org) which allows you to define your value's
+# We use [json schema](http://json-schema.org) which allows you to define your value's
 # default, the type it should be, etc. A simple example:
 #
 # ```coffee


Deleted file with typo in the name

Deleted file with typo in the name

--- test/unit/contoller.spec.coffee
+++ test/unit/contoller.spec.coffee
@@ -1,20 +0,0 @@
-'use strict'
-
-# jasmine specs for controllers go here
-
-# TODO figure out how to test Controllers that use modules
-describe "controllers", ->
-
-  beforeEach(module "app.controllers")
-
-  describe "MyCtrl1", ->
-
-    it "should make scope testable", inject ($rootScope, $controller) ->
-      scope = $rootScope.$new()
-      ctrl = $controller "MyCtrl1",
-        $scope: scope,
-      expect(scope.onePlusOne).toEqual(2)
-
-  describe "MyCtrl2", ->
-
-    it "should..."


Remove Math.max corrections to range

Remove Math.max corrections to range

Editor::getTextInBufferRange will clip the specified range instead.

--- src/cursor.coffee
+++ src/cursor.coffee
@@ -144,7 +144,7 @@
   # Returns a {Boolean}.
   isSurroundedByWhitespace: ->
     {row, column} = @getBufferPosition()
-    range = [[row, Math.max(0, column - 1)], [row, Math.max(0, column + 1)]]
+    range = [[row, column - 1], [row, column + 1]]
     /^\s+$/.test @editor.getTextInBufferRange(range)

   # Public: Returns whether the cursor is currently between a word and non-word


diffrender prototyping

diffrender prototyping

--- src/docpad.coffee
+++ src/docpad.coffee
@@ -1009,7 +1009,7 @@
 				if result
 					return result.get('contentRendered') or result.get('content')
 				else
-					warn = "The file #{relativeBase} was not found..."
+					warn = "The file #{subRelativePath} was not found..."
 					docpad.warn(warn)
 					return warn

@@ -1719,8 +1719,7 @@

 		# Extract document
 		if opts.filename
-			document = @createDocument()
-			document.set(
+			document = @createDocument(
 				filename: opts.filename
 				fullPath: opts.filename
 				data: opts.content
@@ -1812,32 +1811,38 @@

 		# Change event handler
 		changeHandler = (eventName,filePath,fileCurrentStat,filePreviousStat) ->
-			###
-			# Differential Rendering?
-			if config.differentialRendering
-
-				# Handle the action
+			# Handle
+			tasks = new balUtil.Group (err) ->
+				docpad.error(err)  if err
+				logger.log 'Regenerated due to file watch at '+(new Date()).toLocaleString()
+
+			# Handle the action
+			if eventName is 'new'
+				# Create a file for it
+				# todo, need to check if it is dir, or whether a file or document
+				changedFile = docpad.createDocument(fullPath: filePath)
+				# Load and render this file
+				tasks.push (complete) ->
+					changedFile.action('load contextualize render write', complete)
+			else
+				# Fetch
+				changedFile = database.findOne(fullPath: filePath)
 				if eventName is 'unlink'
+					# Destroy this file
 					changedFile.destroy()
 				else if eventName is 'change'
-					# Re-render just this file
-					changedFile = database.findOne(fullPath: filePath)
-					docpad.prepareAndRender changedFile, docpad.getTemplateData(), ->
-						# Re-Render anything that references the changes
-						pendingFiles = database.findAll(references: $has: changedFile).render()
-						docpad.prepareAndRender pend
-
-				else if eventName is 'new'
-
-				# Re-Render anything that should always re-render
-				database.findAll(referencesOthers: true).render()
-
-			# Re-Render everything
-			else
-			###
-			docpad.action 'generate', (err) ->
-				docpad.error(err)  if err
-				logger.log 'Regenerated due to file watch at '+(new Date()).toLocaleString()
+					# Re load and render this file
+					tasks.push (complete) ->
+						changedFile.action('load contextualize render write', complete)
+
+			# Afterwards, re-render anything that should always re-render
+			database.findAll(referencesOthers:true).each (document) -> tasks.push (complete) ->
+				document.action('render write',complete)
+			# todo, do a second pass here, first pass should just be renders, second pass should be writes, second pass can be async
+
+			# Execute the tasks synchronously
+			tasks.sync()
+

 		# A fatal error occured
 		fatal = (err) ->


Add a couple events for test purposes

Add a couple events for test purposes

--- src/editor.coffee
+++ src/editor.coffee
@@ -50,6 +50,23 @@
 #
 # **When in doubt, just default to buffer coordinates**, then experiment with
 # soft wraps and folds to ensure your code interacts with them correctly.
+#
+# ## Events
+#
+# ### will-insert-text
+#
+# Emit before the text has been inserted.
+#
+# * `event` event {Object}
+#   * `text` {String} text to be inserted
+#   * `cancel` {Function} Call to prevent the text from being inserted
+#
+# ### did-insert-text
+#
+# Emit after the text has been inserted.
+#
+# * `event` event {Object}
+#   * `text` {String} text to be inserted
 module.exports =
 class Editor extends Model
   Serializable.includeInto(this)


Check error ports on tests

Check error ports on tests

--- spec/SCDDetect.coffee
+++ spec/SCDDetect.coffee
@@ -64,7 +64,10 @@
           grps.push grp
         out.once "data", (data) ->
           results = data
+          console.log 'results', data
           done()
+        error.on 'data', (data) ->
+          done data
         ins.beginGroup 'foo'
         ins.send canvas

@@ -125,6 +128,8 @@
           out.once "data", (data) ->
             results = data
             done()
+          error.on 'data', (data) ->
+            done data
           ins.beginGroup 'foo'
           ins.send canvas


caching the path.normalize path.resolve stuff; speeds up processing a template significantly

--- src/engine.coffee
+++ src/engine.coffee
@@ -27,6 +27,8 @@
     @viewCache              = {} # filename -> view
     @fsErrorCache           = {} # filename -> timestamp last failed

+    @filenameCache = {} # caches dir -> filename -> path.normalize path.resolve dir, filename
+
   _log: (o) ->
     if @verbose
       if (typeof o) in ["string","number","boolean"]
@@ -34,6 +36,19 @@
       else
         console.log "toffee: #{util.inspect o}"

+  # basically returns `path.normalize path.resolve dir, filename`, but caches it to speed up multiple inclusions
+  normalizeFilename: (dir, filename) ->
+    cache = @filenameCache[dir]
+    if not cache?
+      @filenameCache[dir] = {}
+      cache = {}
+    normalized = cache[filename]
+    if not normalized?
+      normalized = path.normalize path.resolve dir, filename
+      @filenameCache[dir][filename] = normalized
+    return normalized
+
+
   render: (filename, options, cb) => @run filename, options, cb

   run: (filename, options, cb) =>
@@ -80,8 +95,7 @@
     options              = options or {}
     options.__toffee     = options.__toffee or {}
     options.__toffee.dir = options.__toffee.dir or process.cwd()
-    realpath             = path.normalize path.resolve options.__toffee.dir, filename
-    pwd                  = path.dirname realpath
+    realpath = @normalizeFilename options.__toffee.dir, filename

     v = (@_viewCacheGet realpath) or (@_loadCacheAndMonitor realpath, options)


Unsubscribe from native change events in afterEach

Instead of calling .off, which no longer has the desired effect. We
could also dispose every subscription, but this is easier.

--- spec/file-spec.coffee
+++ spec/file-spec.coffee
@@ -14,7 +14,7 @@
     file = new File(filePath)

   afterEach ->
-    file.off()
+    file.unsubscribeFromNativeChangeEvents()
     fs.removeSync(filePath)
     PathWatcher.closeAllWatchers()


use basic boot mode more in specs

use basic boot mode more in specs

--- spec/juno-spec.coffee
+++ spec/juno-spec.coffee
@@ -42,7 +42,7 @@
   testClient()

 describe "before use", ->
-  beforeEach cyclerSetup
+  beforeEach basicSetup
   it 'boots the client', ->
     waitsFor 5*60*1000, (done) ->
       juno.connection.boot().then -> done()
@@ -50,12 +50,12 @@
       conn = client.conn

 describe "in an editor", ->
-  beforeEach cyclerSetup
+  beforeEach basicSetup
   withClient()
   testEval()

 describe "after use", ->
-  beforeEach cyclerSetup
+  beforeEach basicSetup
   withClient()
   it "kills the client", ->
     client.kill()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -59,8 +59,7 @@

   enemy.I.velocity = enemy.I.direction.scale(4)
   # now the enemy has a velocity, pointing toward player
-  # with 4 times the magnitude
-  #of its direction
+  # with 4 times the magnitude of its direction

   </pre></code>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/follow.coffee
+++ src/follow.coffee
@@ -1,6 +1,6 @@
 ###*
 The Follow module provides a simple method to set an object's
-so that it will approach another
+so that it will approach another object.

 # Health regeneration


remove lists/bullets definition

remove lists/bullets definition

--- src/format.coffee
+++ src/format.coffee
@@ -53,14 +53,6 @@
       type: Format.types.LINE
       style: 'textAlign'

-    list:
-      tag: 'OL'
-      type: Format.types.LINE
-
-    bullet:
-      tag: 'UL'
-      type: Format.types.LINE
-

   constructor: (@config) ->


Add Emojis.toUnicode function

Add Emojis.toUnicode function

--- emojis.coffee
+++ emojis.coffee
@@ -8,17 +8,17 @@
 toCodePoints = (str) ->
     chars = []
     i = 0
-
+
     while i < str.length
         c1 = str.charCodeAt(i)
-
+
         if c1 >= 0xD800 and c1 < 0xDC00 and i+1 < str.length
             c2 = str.charCodeAt(i+1)
             if c2 >= 0xDC00 && c2 < 0xE000
                 chars.push(0x10000 + ((c1-0xD800)<<10) + (c2-0xDC00))
                 i += 2
                 continue
-
+
         chars.push(c1)
         i++

@@ -42,11 +42,11 @@

 Emojis = new Mongo.Collection 'emojis',
   transform: (emoji) ->
-
+
     # Shortcut helpers

     emoji.path = Emojis.basePath() + '/' + imagePath(emoji)
-
+
     emoji.toHTML = ->
       Emojis.template(this)

@@ -59,6 +59,36 @@
   Emojis._ensureIndex(alias: 1)
   Emojis._ensureIndex(ascii: 1)

+
+# General parse function.
+#
+# Parses `text` and returns emoji objects from :shortnames: and ASCII
+# smileys (:D).
+#
+# Optionally takes a function which can transform the return value.
+parse = (text, fn) ->
+  check text, String
+  check fn, Match.Optional(Function)
+
+  text
+  .replace smileyRegexp, (match, smiley, nose) ->
+    smiley = smiley.toUpperCase()
+    smiley = if nose then smiley.replace(/:-/g, ':') else smiley
+
+    emoji = Emojis.findOne(ascii: smiley)
+
+    if emoji
+      return if fn then fn(emoji) else emoji
+    else
+      return match
+
+  .replace shortnameRegexp, (match, alias) ->
+    emoji = Emojis.findOne(alias: alias)
+
+    if emoji
+      return if fn then fn(emoji) else emoji
+    else
+      return match

 Emojis._basePath = '/images/emojis'

@@ -74,24 +104,11 @@
 Emojis.template = (emoji) ->
   "<img src='#{Emojis.basePath()}/#{imagePath(emoji)}' title='#{emoji.alias}' alt='#{emoji.emoji or emoji.alias}' class='emoji'>"

+# Replace emoji shortnames with their unicode version.
+Emojis.toUnicode = (text) ->
+  parse text, (emoji) ->
+    emoji.emoji
+
+# Replace emoji shortnames with images.
 Emojis.parse = (text) ->
-  if typeof text isnt 'string'
-    return ''
-
-  text = text.toString()
-
-  text
-  .replace smileyRegexp, (match, smiley, nose) ->
-    smiley = smiley.toUpperCase()
-    smiley = if nose then smiley.replace(/:-/g, ':') else smiley
-
-    if emoji = Emojis.findOne(ascii: smiley)
-      return Emojis.template(emoji)
-    else
-      return match
-
-  .replace shortnameRegexp, (match, alias) ->
-    if emoji = Emojis.findOne(alias: alias)
-      return Emojis.template(emoji)
-    else
-      return match
+  parse(text, Emojis.template)


workerstats: Use median for scaleup

workerstats: Use median for scaleup

Seems there are some outliers

--- src/heroku.coffee
+++ src/heroku.coffee
@@ -63,6 +63,7 @@
 eventsFromLog = (logdata, started) ->
   events = []

+  # TODO: allow to output a cleaned/minimized logfile. Especially for tests
   # timestamp, target (app|heroku), action
   re = /^(.*?) (\w+)\[(.*)\]: (.*)$/mg
   matches = matchAll re, logdata
@@ -234,7 +235,7 @@
     stops = state.shutdowns.map (s) -> s.duration/1000
     scaleups = state.scaleups.map (s) -> s.duration/1000
     results =
-      scaleup: statistics.mean scaleups
+      scaleup: statistics.median scaleups
       scaleup_stddev: statistics.standard_deviation scaleups
       scaleup_length: scaleups.length
       startup: statistics.mean starts


Update app.coffee

Update app.coffee

--- js/app.coffee
+++ js/app.coffee
@@ -27,11 +27,11 @@
   $scope.distance = 0.1
   $scope.getRelativeElevation = ->
     totalPoints = 8
-    $scope.point = new LatLon($scope.lat, $scope.lon)
+    $scope.point = new LatLon(Number($scope.lat), Number($scope.lon))
     $scope.points = _.range(totalPoints).map (t) -> $scope.point.destinationPoint(t*360/totalPoints, $scope.distance)
     updateRelativeElevation = ->
       averageElevation = _.reduce($scope.points, ((memo, num) -> memo + num.height), 0) / totalPoints
       $scope.relativeElevation = $scope.point.height - averageElevation
     getElevation = (p) -> NED.async(p).then (e) -> p.height=e; updateRelativeElevation()
     getElevation $scope.point
-    _.each $scope.points, getElevation+    _.each $scope.points, getElevation


adding in google adwords tracking

adding in google adwords tracking

--- js/app.coffee
+++ js/app.coffee
@@ -153,6 +153,9 @@
     @current_user().loadOrganizations()
     @current_user().loadCollaboratorAccounts()
     display "add_projects", {}
+    if @current_user().repos().length == 0
+      track_signup_conversion()
+

   loadProject: (username, project, branch, refresh) =>


Improving test

Improving test

--- tests/unit/polvo/general.coffee
+++ tests/unit/polvo/general.coffee
@@ -31,7 +31,8 @@
         help.indexOf('Options').should.not.equal -1
         help.indexOf('Examples').should.not.equal -1

-    help = polvo null, stdio
+    help = polvo options, stdio
+    help = polvo {}, stdio

-    outs.should.equal 1
+    outs.should.equal 2
     errors.should.equal 0


Improve close on Esc behavior

Improve close on Esc behavior

--- dropdown.coffee
+++ dropdown.coffee
@@ -238,19 +238,21 @@

   # Isn't dropdown nor child of
   isDropdown = (el) ->
-    childOf(el, DROPDOWN) or el.is(DROPDOWN_TRIGGER)
+    childOf(el, DROPDOWN) or el.is(DROPDOWN) or el.is(DROPDOWN_TRIGGER)

   isInput = (el) ->
     node = el.get(0)
     node.tagName is 'INPUT' or node.tagName is 'TEXTAREA'

-  $(document).on 'keyup', (evt) ->
+  $(document).on 'keydown', (evt) ->
     # Close on Esc
     if evt.keyCode is 27
       el = $(evt.target)

-      # Don't close dropdowns if we're writing something.
-      if not isInput(el) and not isDropdown(el)
+      # Don't close dropdowns if we're writing something with content.
+      if isDropdown(el)
+        return if isInput(el) and el.val() isnt ''
+
         Dropdowns.hideAll()

   $(document).on 'click', (evt) ->


Always try to unwatch current user keymap

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -126,16 +126,17 @@
     CSON.resolve(path.join(@configDirPath, 'keymap'))

   unwatchUserKeymap: ->
-    return unless keymapPath = @userKeymapPath()
-    @remove(keymapPath)
+    keymapPath = @userKeymapPath()
     @userKeymapFile?.off()
+    @remove(keymapPath) if keymapPath

   loadUserKeymap: ->
-    return unless keymapPath = @userKeymapPath()
+    keymapPath = @userKeymapPath()
     @unwatchUserKeymap()
-    @load(keymapPath)
-    @userKeymapFile = new File(keymapPath)
-    @userKeymapFile.on 'contents-changed', => @loadUserKeymap()
+    if keymapPath
+      @load(keymapPath)
+      @userKeymapFile = new File(keymapPath)
+      @userKeymapFile.on 'contents-changed', => @loadUserKeymap()

   loadDirectory: (directoryPath) ->
     @load(filePath) for filePath in fs.listSync(directoryPath, ['.cson', '.json'])


minor code clean up

minor code clean up

--- main/q.coffee
+++ main/q.coffee
@@ -24,7 +24,7 @@

   size: => @_heap.size()

-  clear: => @_heap = this._initHeap([])
+  clear: => this._initHeap([])

   _initHeap: (jobs) =>
     @_heap = new queffee.Heap(jobs, queffee.Q._compareJob)


fixed populating sso endpoint in form

fixed populating sso endpoint in form


Remove -dev from version

Remove -dev from version

--- build/tasks/install-spark-dev.coffee
+++ build/tasks/install-spark-dev.coffee
@@ -14,7 +14,7 @@
     done = @async()
     workDir = grunt.config.get('workDir')
     particleDevPath = path.join(workDir, 'node_modules', 'spark-dev')
-    particleDevVersion = grunt.config.get('particleDevVersion')
+    particleDevVersion = grunt.config.get('particleDevVersion').replace('-dev', '')

     installDependencies = (done) ->
       # Build serialport


parameters as functions

parameters as functions

--- editor.coffee
+++ editor.coffee
@@ -1,6 +1,7 @@
 lead._finished = new Object

 default_options = {}
+define_parameters = true

 previously_run = null
 lead.graphite.load_docs()
@@ -161,6 +162,13 @@
     for k, op of lead.ops
       ops[k] = bind_op op

+    if define_parameters
+      for k of lead.graphite.parameter_docs
+        do (k) ->
+          ops[k] = bind_op
+            name: k
+            fn: (value) -> @current_options[k] = value
+
     context.cli = ops

     functions = {}


:memo: fix setHead & setTail docs in Marker

Change range -> position

--- src/marker.coffee
+++ src/marker.coffee
@@ -268,7 +268,7 @@

   # Extended: Sets the buffer position of the marker's head.
   #
-  # * `screenRange` The new {Point} to use
+  # * `bufferPosition` The new {Point} to use
   # * `properties` (optional) {Object} properties to associate with the marker.
   setHeadBufferPosition: (bufferPosition, properties) ->
     @bufferMarker.setHeadPosition(bufferPosition, properties)
@@ -281,7 +281,7 @@

   # Extended: Sets the screen position of the marker's head.
   #
-  # * `screenRange` The new {Point} to use
+  # * `screenPosition` The new {Point} to use
   # * `properties` (optional) {Object} properties to associate with the marker.
   setHeadScreenPosition: (screenPosition, properties) ->
     screenPosition = @displayBuffer.clipScreenPosition(screenPosition, properties)
@@ -295,7 +295,7 @@

   # Extended: Sets the buffer position of the marker's tail.
   #
-  # * `screenRange` The new {Point} to use
+  # * `bufferPosition` The new {Point} to use
   # * `properties` (optional) {Object} properties to associate with the marker.
   setTailBufferPosition: (bufferPosition) ->
     @bufferMarker.setTailPosition(bufferPosition)
@@ -308,7 +308,7 @@

   # Extended: Sets the screen position of the marker's tail.
   #
-  # * `screenRange` The new {Point} to use
+  # * `screenPosition` The new {Point} to use
   # * `properties` (optional) {Object} properties to associate with the marker.
   setTailScreenPosition: (screenPosition, options) ->
     screenPosition = @displayBuffer.clipScreenPosition(screenPosition, options)


Require jquert

Require jquert

--- client/cart_page.coffee
+++ client/cart_page.coffee
@@ -1,3 +1,5 @@
+$ = require("jquery")
+
 waitInterval = null
 uploadInterval = null
 cart = null


row is not used anymore

row is not used anymore

--- client/app/views/row.coffee
+++ client/app/views/row.coffee
@@ -1,13 +0,0 @@
-# Base class for row displayed inside my cloud gateway.
-class exports.BaseRow extends Backbone.View
-
-    tagName: "div"
-
-    constructor: (@model) ->
-        super()
-
-        @id = @model.slug
-        @model.view = @
-
-    remove: ->
-        @$el.remove()


:white_check_mark: Add tests for multiple root folders projects

--- spec/color-project-spec.coffee
+++ spec/color-project-spec.coffee
@@ -469,6 +469,23 @@
           expect(project.getPaths().length).toEqual(0)
           expect(project.getVariables().length).toEqual(0)

+    describe 'when the project has multiple root directory', ->
+      beforeEach ->
+        atom.config.set 'pigments.sourceNames', ['**/*.sass', '**/*.styl']
+
+        [fixturesPath] = atom.project.getPaths()
+        atom.project.setPaths([
+          "#{fixturesPath}"
+          "#{fixturesPath}-with-recursion"
+        ])
+
+        project = new ColorProject({})
+
+        waitsForPromise -> project.initialize()
+
+      it 'finds the variables from the two directories', ->
+        expect(project.getVariables().length).toEqual(16)
+
     ##     ######  ######## ######## ######## #### ##    ##  ######    ######
     ##    ##    ## ##          ##       ##     ##  ###   ## ##    ##  ##    ##
     ##    ##       ##          ##       ##     ##  ####  ## ##        ##


a few more dom helper methods

a few more dom helper methods

--- client/lanes/lib/dom.coffee
+++ client/lanes/lib/dom.coffee
@@ -22,6 +22,16 @@
         @el.select?()
         @el.focus?()

+    setStyle: (style) ->
+        _.extend(@el.style, style)
+
+    on: (evname, fn, useCapture = false) ->
+        if @el.addEventListener
+            @el.addEventListener(evname, fn, useCapture)
+        else if @el.attachEvent
+            @el.attachEvent('on' + evname, fn)
+
+
     closest: (selector) ->
         # Traverse the DOM up with a while loop
         el = @el
@@ -42,7 +52,7 @@

 for name, func of Lanes.Vendor.dom
     Lanes.lib.Dom::[name] = if name.match(/^has/)
-        wrapArg
+        wrapArg(func)
     else
         chain(func)


Fix correct layout conditions. Fixes #11

--- emails.coffee
+++ emails.coffee
@@ -74,7 +74,7 @@
     # Take optional template helpers.
     helpers: Match.Optional Object
     # Take an optional layoute template object.
-    layout: Match.Optional Object
+    layout: Match.Optional Match.OneOf(Object, Boolean)
   )

   settings = _.extend({}, Mailer.settings, options.settings)
@@ -137,7 +137,7 @@
     if template.scss
       content = addCSS Utils.toCSS(template.scss), content

-    if options.layout? and template.layout isnt false
+    if options.layout and template.layout isnt false
       layout = options.layout
       layoutContent = Utils.readFile(layout.path)


Reorder stuff

Reorder stuff

--- src/parser.coffee
+++ src/parser.coffee
@@ -63,108 +63,6 @@
 parseExamples = (tokens, doc) ->

 parseEvents = (tokens, doc) ->
-
-generateDescription = (tokens) ->
-  description = []
-  while token = _.first(tokens)
-    if token.type in ['paragraph', 'text']
-      description.push generateParagraph(tokens)
-
-    else if token.type is 'blockquote_start'
-      description.push generateBlockquote(tokens)
-
-    else if token.type is 'code'
-      description.push generateCode(tokens)
-
-    else if token.type is 'heading'
-      if token.depth == SpecialHeadingDepth and token.text in SpecialHeadings
-        break
-      else
-        description.push generateHeading(tokens)
-
-    else if token.type is 'list_start'
-      listToken = null
-      for listToken in tokens
-        break if listToken.type == 'text'
-
-      # Check if list is an arguments list. If it starts with `someVar`, it is.
-      if listToken? and /^\s*`([\w\.-]+)`/.test(listToken.text)
-        break
-      else
-        description.push generateList(tokens)
-
-    else break
-
-  description.join '\n\n'
-
-generateParagraph = (tokens) ->
-  tokens.shift().text
-
-generateHeading = (tokens) ->
-  token = tokens.shift()
-  "#{multiplyString('#', token.depth)} #{token.text}"
-
-generateBlockquote = (tokens) ->
-  lines = []
-
-  while token = tokens.shift()
-    break if token.type is 'blockquote_end'
-    if token.text?
-      for line in token.text.split('\n')
-        lines.push "> #{line}"
-
-  lines.join '\n'
-
-generateCode = (tokens) ->
-  token = tokens.shift()
-  lines = []
-  lines.push if token.lang? then "```#{token.lang}" else '```'
-  lines.push token.text
-  lines.push '```'
-  lines.join '\n'
-
-generateList = (tokens) ->
-  depth = -1
-  lines = []
-  linePrefix = null
-
-  ordered = null
-  orderedStack = []
-
-  indent = ->
-    multiplyString('  ', depth)
-
-  while token = _.first(tokens)
-    switch token.type
-      when 'list_start'
-        depth++
-        orderedStack.push ordered
-        ordered = token.ordered
-
-      when 'list_item_start', 'loose_item_start'
-        linePrefix = if ordered then "#{indent()}1. " else "#{indent()}* "
-
-      when 'text', 'code', 'blockquote_start'
-        if token.type == 'code'
-          textLines = generateCode(tokens).split('\n')
-        else if token.type == 'blockquote_start'
-          textLines = generateBlockquote(tokens).split('\n')
-        else
-          textLines = token.text.split('\n')
-
-        for line in textLines
-          prefix = linePrefix ? "#{indent()}  "
-          lines.push prefix + line
-          linePrefix = null # we used the bullet!
-
-      when 'list_end'
-        depth--
-        ordered = orderedStack.pop()
-
-    token = tokens.shift()
-    break if depth < 0
-
-  lines.join '\n'

 parseArgumentList = (tokens) ->
   ArgumentListTokenTypes = [
@@ -226,3 +124,113 @@
   {name, description, type}

 module.exports = {parse}
+
+###
+Section: Generation
+
+These methods will consume tokens and return a markdown representation of the
+tokens. Yeah, it generates markdown from the lexed markdown tokens.
+###
+
+# Will read / consume tokens down to a special section (args, events, examples)
+generateDescription = (tokens) ->
+  description = []
+  while token = _.first(tokens)
+    if token.type in ['paragraph', 'text']
+      description.push generateParagraph(tokens)
+
+    else if token.type is 'blockquote_start'
+      description.push generateBlockquote(tokens)
+
+    else if token.type is 'code'
+      description.push generateCode(tokens)
+
+    else if token.type is 'heading'
+      if token.depth == SpecialHeadingDepth and token.text in SpecialHeadings
+        break
+      else
+        description.push generateHeading(tokens)
+
+    else if token.type is 'list_start'
+      listToken = null
+      for listToken in tokens
+        break if listToken.type == 'text'
+
+      # Check if list is an arguments list. If it starts with `someVar`, it is.
+      if listToken? and /^\s*`([\w\.-]+)`/.test(listToken.text)
+        break
+      else
+        description.push generateList(tokens)
+
+    else break
+
+  description.join '\n\n'
+
+generateParagraph = (tokens) ->
+  tokens.shift().text
+
+generateHeading = (tokens) ->
+  token = tokens.shift()
+  "#{multiplyString('#', token.depth)} #{token.text}"
+
+generateBlockquote = (tokens) ->
+  lines = []
+
+  while token = tokens.shift()
+    break if token.type is 'blockquote_end'
+    if token.text?
+      for line in token.text.split('\n')
+        lines.push "> #{line}"
+
+  lines.join '\n'
+
+generateCode = (tokens) ->
+  token = tokens.shift()
+  lines = []
+  lines.push if token.lang? then "```#{token.lang}" else '```'
+  lines.push token.text
+  lines.push '```'
+  lines.join '\n'
+
+generateList = (tokens) ->
+  depth = -1
+  lines = []
+  linePrefix = null
+
+  ordered = null
+  orderedStack = []
+
+  indent = ->
+    multiplyString('  ', depth)
+
+  while token = _.first(tokens)
+    switch token.type
+      when 'list_start'
+        depth++
+        orderedStack.push ordered
+        ordered = token.ordered
+
+      when 'list_item_start', 'loose_item_start'
+        linePrefix = if ordered then "#{indent()}1. " else "#{indent()}* "
+
+      when 'text', 'code', 'blockquote_start'
+        if token.type == 'code'
+          textLines = generateCode(tokens).split('\n')
+        else if token.type == 'blockquote_start'
+          textLines = generateBlockquote(tokens).split('\n')
+        else
+          textLines = token.text.split('\n')
+
+        for line in textLines
+          prefix = linePrefix ? "#{indent()}  "
+          lines.push prefix + line
+          linePrefix = null # we used the bullet!
+
+      when 'list_end'
+        depth--
+        ordered = orderedStack.pop()
+
+    token = tokens.shift()
+    break if depth < 0
+
+  lines.join '\n'


fixed post route

fixed post route

--- src/routes.coffee
+++ src/routes.coffee
@@ -28,7 +28,7 @@
     col = db.collection(req.params.modelName)
     col.insert req.body, safe:true, (err, result) ->
         return next(err) if err?
-        res.send(200, result)
+        res.send(200, result[0])

 show = (req, res) ->
     col = db.collection(req.params.modelName)


Store current browserID in session and serve that by default if it still exists.

--- src/server.coffee
+++ src/server.coffee
@@ -72,13 +72,15 @@
         # TODO: check session for current Browser ID, and re-use that if found
         #       session[currentBrowser]
         server.get '/', (req, res) =>
-            # Load a Browser instance with the configured app.
-            browser = @browsers.create
-                app : @appPath
-                shared : @sharedState
-            console.log("id: #{browser.id}")
+            id = req.session.browserID
+            if !id? || !@browsers.find(id)
+                # Load a Browser instance with the configured app.
+                browser = @browsers.create
+                    app : @appPath
+                    shared : @sharedState
+                id = req.session.browserID = browser.id
             # TODO use browser.urlFor()
-            res.writeHead(301, {'Location' : "/browsers/#{browser.id}/index.html"})
+            res.writeHead(301, {'Location' : "/browsers/#{id}/index.html"})
             res.end()

         server.get '/browsers/:browserid/index.html', (req, res) ->


Key bindings; handle overlapping bindings

With:

   map g something
   map gg somethingElse

The mapping for "g" always takes priority, regardless of the order in
which they're encountered in `@keyToCommandRegistry`.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -106,7 +106,9 @@
       while 0 < keys.length
         [key, rest] = if 0 == keys.search @namedKeyRegex then [RegExp.$1, RegExp.$2] else [keys[0], keys[1..]]
         if 0 < rest.length
-          currentMapping = currentMapping[key] ?= {}
+          # Do not overwrite existing command bindings, they take priority.
+          break if (currentMapping[key] ?= {}).command
+          currentMapping = currentMapping[key]
         else
           currentMapping[key] = registryEntry
         keys = rest


Sprite receives from run loop and sends propose

--- src/layout/sprites.coffee
+++ src/layout/sprites.coffee
@@ -10,6 +10,7 @@

 {my} = require '../my'
 {vector} = require('../god/vector')
+{law} = require '../god/law'

 transform = (world) ->
   center = world.get('cell_size') / 2
@@ -27,14 +28,17 @@
   paths = shapes[shape]
   my.assert paths, "No paths for #{shape} of #{sprite_dict}"
   sprite_dict.paths = paths
-
+
 combine = (a, b, dir) ->
   if dir > 0 then vector.add(a, b) else vector.subtract(a, b)
+
+get_location_for_move = (world, dir) ->
+  combine world.get_plain('position'), world.get_plain('direction'), dir

 exports.sprites = {
   _LABEL: 'sprites'
   _KIND: 'sprite'
-  _EXPORTS: ['inspect', 'step', 'reset']
+  _EXPORTS: ['inspect', 'reset']
   _SETUP: (world) ->
     shapes = world.get 'shapes'
     sprites = world.get 'sprites'
@@ -42,7 +46,8 @@
       set_shape(sprite_dict, shapes)
       child = world.add_child sprite_dict
       child.handle_event 'apply'
-      # world.send 'inspect', child
+      child.handle_event 'prepare'
+      world.send 'inspect', child
   inspect: (world, sprite) -> world.put 'inspected', sprite
   selected: (world) -> world == world.get('inspected')

@@ -58,42 +63,52 @@
     rotate = "rotate(#{world.get('angle')} #{center} #{center})"
     "#{translate} #{rotate}"

+  running: (world, args) ->
+    'repeat'
+
   name_style: (world) ->
     cell_size = world.get 'cell_size'
     {x: 0.5 * cell_size, y: 0.5 * cell_size, fill: "white", stroke: "white"}

-  # TODO: Make less order-dependent
-
-  propose: (world, proposal) ->
-    others = world.send 'at_position', proposal
-    results = others.map (other) -> other.call 'bumps', world
-    world.put 'position', proposal unless results
-
+  prepare: (world, args) ->
+    word = world.get('language')[args]
+    console.log args
+    # TODO: use language.coffee
+    # this is bad. it should be getting this from language.coffee
+    signal = word.split " "
+    signal[2] = parseInt signal[2]
+    world.call 'perform', signal
+
+  perform: (world, action) ->
+    [method, key, value] = action
+    my.assert world[method], "#{world.label()}: no '#{method}' property"
+    world[method](key, value)
+
+  #propose: (world, proposal) ->
+    #others = world.send 'at_position', proposal
+    #results = others.map (other) -> other.call 'bumps', world
+    #world.put 'position', proposal unless results
+
   go: (world, dir) ->
     cell_count = world.get_plain('cell_count')
     my.assert dir?, "expects dir"
-    sum = combine world.get_plain('position'), world.get_plain('direction'), dir
-    if vector.inside(sum, cell_count)
-      world.put 'position', sum
-      # world.call 'propose', sum
-    else
-      world.send 'error', "#{world} attempted to move out of bounds"
+    sum = get_location_for_move world, dir
+    world.send 'propose', [world, sum]
+    #if vector.inside(sum, cell_count)
+      #world.call 'propose', sum
+    #else
+      #world.send 'error', "#{world} attempted to move out of bounds"

   turn: (world, dir) ->
     my.assert dir?, "expects dir"
     world.put 'direction', vector.turn(world.get_plain('direction'), dir)
     true # always a valid move
-
-  perform: (world, action) ->
-    [method, key, value] = action
-    my.assert world[method], "#{world.label()}: no '#{method}' property"
-    world[method](key, value)

   apply: (world, args) ->
     {target, action} = args
 #    console.log "apply world #{world}, target #{target}"
     world.call('perform', action) if world == target
-
+
   step: (world, args) ->
     local = world.get('programs')
     return unless world.is_world local
@@ -102,4 +117,4 @@
   reset: (world, args) ->
     ['position', 'direction'].map (key) -> world.put key, undefined
   click: (world, args) -> world.send 'inspect', world
-}+}


cleanup

cleanup

--- lib/ui.coffee
+++ lib/ui.coffee
@@ -341,12 +341,10 @@

   excludeFile: ->
     return if @provider.boundToEditor
-
-    if item = @getSelectedItem()
-      {filePath} = item
-      @excludedFiles.push(filePath) unless filePath in @excludedFiles
+    return unless selectedItem = @getSelectedItem()
+    unless selectedItem.filePath in @excludedFiles
+      @excludedFiles.push(filePath)
       nextFileItem = @findDifferentFileItem('next')
-
       @refresh().then =>
         if nextFileItem
           @selectItem(nextFileItem)


Fix: classList関連でこけていたのを修正

Fix: classList関連でこけていたのを修正

--- src-chrome/DOMExtention.coffee
+++ src-chrome/DOMExtention.coffee
@@ -32,7 +32,8 @@
 ele::rmvAttr = ele::removeAttr
 ele::attr = (a, b) ->
   return if b? then @setAttr(a, b) else @getAttr(a)
-ele::getClass = ele::classList
+ele::getClass = ->
+  return @classList
 ele::setClass = (a) ->
   if Array.isArray(a)
     @className = a.join(" ")
@@ -44,7 +45,11 @@
 ele::class = (a) ->
   return if a? then @setClass(a) else @getClass()
 # extention
-ele::addClass = ele::classList.add
-ele::removeClass = ele::classList.remove
-ele::toggleClass = ele::classList.toggle
-ele::hasClass = ele::classList.contains
+ele::addClass = (a) ->
+  return @classList.add(a)
+ele::removeClass = (a) ->
+  return @classList.remove(a)
+ele::toggleClass = (a) ->
+  return @classList.toggle(a)
+ele::hasClass = (a) ->
+  return @classList.contains(a)


Add toggle menu item for panels

Add toggle menu item for panels

Refs #164

--- menus/find-and-replace.cson
+++ menus/find-and-replace.cson
@@ -5,8 +5,10 @@
     { 'label': 'Replace in Buffer', 'command': 'find-and-replace:show-replace'}
     { 'label': 'Select Next', 'command': 'find-and-replace:select-next'}
     { 'label': 'Select All', 'command': 'find-and-replace:select-all'}
+    { 'label': 'Toggle Find in Buffer', 'command': 'find-and-replace:toggle'}
     { 'type': 'separator' }
     { 'label': 'Find in Project', 'command': 'project-find:show'}
+    { 'label': 'Toggle Find in Project', 'command': 'project-find:toggle'}
     { 'type': 'separator' }
     { 'label': 'Find Next', 'command': 'find-and-replace:find-next'}
     { 'label': 'Find Previous', 'command': 'find-and-replace:find-previous'}


Drop created SQL login if not used

Drop created SQL login if not used

--- src/add/perform.steps.coffee
+++ src/add/perform.steps.coffee
@@ -65,8 +65,7 @@
   Knt.Save()

 step 'SQL', 'Генерация пользователя SQL', (u)->
-  login = "#{ad.dc}\\#{u.UserLogin}"
-  mssql.h.sp_grantlogin login if sqllogins
+  mssql.h.sp_grantlogin login = "#{ad.dc}\\#{u.UserLogin}"

   cmd = mssql.command """
     Select Count(*) as N
@@ -76,10 +75,15 @@
     """
   assign.l cmd, u.UserLogin, login

-  return if mssql.execute cmd
+  unless mssql.execute cmd
   .pop().N
+    # mssql.h.sp_adduser login, u.UserLogin
+    cmd = mssql.command "Exec sp_adduser ?, ?"
+    assign.l cmd, login, u.UserLogin
+    .Execute()

-  # mssql.h.sp_adduser login, u.UserLogin
-  cmd = mssql.command "Exec sp_adduser ?, ?"
-  assign.l cmd, login, u.UserLogin
-  .Execute()
+  unless sqllogins
+    # mssql.h.sp_revokelogin login
+    cmd = mssql.command "Exec sp_revokelogin ?"
+    assign.l cmd, login
+    .Execute()


only need filtering by thread starter

only need filtering by thread starter

--- resources/assets/coffee/react/beatmap-discussions/discussions.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussions.coffee
@@ -173,16 +173,7 @@
   filterDiscussions: (discussions) =>
     return discussions unless @props.selectedUserId?

-    filtered = []
-    for discussion in discussions
-      posts = @filterPosts(discussion.posts, @props.selectedUserId)
-      filtered.push(discussion) if posts.length
-
-    filtered
-
-
-  filterPosts: (posts, userId) ->
-    _.filter(posts, user_id: userId)
+    _.filter(discussions, user_id: @props.selectedUserId)

   hidden: (discussion) =>


Fix event emitter error

Fix event emitter error

Introduced in 9cc4c2e5de623c323545074a85d49fbc7fee90d4 because
of botched merge conflict resolution

--- src/app/event-emitter.coffee
+++ src/app/event-emitter.coffee
@@ -78,8 +78,9 @@
                 @off eventName, handler
         else
           subscriptionCountBefore = @subscriptionCount()
-          if eventHandlers = @eventHandlersByEventName[eventName]
-            _.remove(eventHandlers, handler)
+          if handler
+            eventHandlers = @eventHandlersByEventName[eventName]
+            _.remove(eventHandlers, handler) if eventHandlers
           else
             delete @eventHandlersByEventName?[eventName]
           @afterUnsubscribe?() if @subscriptionCount() < subscriptionCountBefore


typo for @compile_bitwise_or

typo for @compile_bitwise_or


Put the group title box in a new row.

Put the group title box in a new row.

--- sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
+++ sli/admin-tools/admin-rails/app/assets/javascripts/custom_roles.js.coffee
@@ -20,7 +20,7 @@

   #Wire up Add Role button
   $("#addGroupButton").click ->
-    newRow = $("

")
+    newRow = $("

")
     $("#custom_roles tbody").append(newRow)

     newRow.find("td:eq(2)").append($("#rowEditTool").clone().children())
@@ -179,7 +179,7 @@

   #Replace editable group name with normal div
   input = tr.find("td:eq(0) input")
-  div = $("
").text(input.val())
+  div = $("

").text(input.val())
   input.replaceWith(div)
   saveData(getJsonData())
   editRowIndex = -1
@@ -245,7 +245,7 @@

 populateTable = (data) ->
   $("#custom_roles tbody").children().remove()
-  for role in data
+  for role in data
     newRow = $("

")
     $("#custom_roles tbody").append(newRow)


Added allowed actions

Added allowed actions

--- snippets/php.cson
+++ snippets/php.cson
@@ -206,6 +206,17 @@
      */
     private static \\$default_sort = \'${1} ${2:ASC}\';${0}
     """
+  'allowed_actions':
+    'prefix': 'allowed_actions '
+    'body': """
+    /**
+     * Defines methods that can be called directly
+     * @var array
+     */
+    private static \\$allowed_actions = array(
+    \t\'${1}\' => \'${2}\'
+    );
+    """
   'hide_ancestor':
     'prefix': 'hide_ancestor'
     'body': """