Fixed type classes

Fixed type classes

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -999,7 +999,7 @@
           ctx.allBoundTypeVariables(),
           (findFree currentType),
           (substituteList ctx.substitution, matched.tea.constraints)
-        # log "assign type", name, printType currentType
+        # log "assign type", name, (printType currentType), retainedConstraints
         ctx.assignType name,
           if ctx.isAtDeferrableDefinition()
             quantifyUnbound ctx, (addConstraints currentType, retainedConstraints)
@@ -1228,7 +1228,6 @@
         dataType
       paramLabels = (_labeled _terms params or []).map(_fst).map(_labelName)
       # log "Adding constructor #{constr.symbol}", constrType
-      log printType constrType
       ctx.declare constr.symbol,
         type: quantifyUnbound ctx, toConstrained constrType
         arity: (paramLabels if params)
@@ -1267,22 +1266,25 @@
   # TODO: I need to figure out the error handling, we should bail out
   #       if an undeclared type var is used
   fieldTypes = for typeArgs in typeArgLists
-    if isRecord typeArgs
-      for type in _snd unzip _labeled _terms typeArgs
-        type = typeCompile ctx, type
-        for name, kind of values findFree type
-          if not inSet varNameSet, name
-            malformed type, "Type variable #{name} not declared"
-            throw new Error "Type variable #{name} not declared"
-          else
-            if foundKind = lookupInMap kinds, name
-              if not kindsEq foundKind, kind
-                malformed type, "Type variable #{name} must have the same kind"
+    if typeArgs
+      if isRecord typeArgs
+        for type in _snd unzip _labeled _terms typeArgs
+          type = typeCompile ctx, type
+          for name, kind of values findFree type
+            if not inSet varNameSet, name
+              malformed type, "Type variable #{name} not declared"
+              throw new Error "Type variable #{name} not declared"
             else
-              addToMap kinds, name, kind
-        type
-    else
-      malformed typeArgs, 'Required a record of types'
+              if foundKind = lookupInMap kinds, name
+                if not kindsEq foundKind, kind
+                  malformed type, "Type variable #{name} must have the same kind"
+              else
+                addToMap kinds, name, kind
+          type
+      else
+        malformed typeArgs, 'Required a record of types'
+        null
+    else
       null

   for typeParam in typeParams
@@ -1359,15 +1361,15 @@
       if ctx.isClassDefined name
         malformed 'class already defined', ctx.definitionPattern()
       else
-        classConstraint = findClassType name, paramNames, declarations
-        ctx.addClass name, classConstraint, superClasses, declarations
-        declareMethods ctx, classConstraint, declarations
-
-        translateDict name, (keysOfMap declarations), superClasses
+        {classConstraint, freshedDeclarations} = findClassType ctx, name, paramNames, declarations
+        ctx.addClass name, classConstraint, superClasses, freshedDeclarations
+        declareMethods ctx, classConstraint, freshedDeclarations
+
+        translateDict name, (keysOfMap freshedDeclarations), superClasses
     else
       'malformed'

-findClassType = (className, paramNames, methods) ->
+findClassType = (ctx, className, paramNames, methods) ->
   kinds = mapMap (-> undefined), (arrayToSet paramNames)
   for name, {arity, type, def} of values methods
     for param in paramNames
@@ -1382,9 +1384,13 @@
         # TODO: better error message
         malformed def, 'All methods must use the class paramater of the same kind'
       replaceInMap kinds, param, foundKind
+  freshingSub = mapToSubstitution mapMap ((kind) -> ctx.freshTypeVariable kind), kinds
   classParam = (param) ->
-    new TypeVariable param, (lookupInMap kinds, param)
-  new ClassConstraint className, new Types (map classParam, paramNames)
+    substitute freshingSub, new TypeVariable param, (lookupInMap kinds, param)
+  method = ({arity, type, def}) ->
+    {arity, def, type: (substitute freshingSub, type)}
+  classConstraint: (new ClassConstraint className, new Types (map classParam, paramNames))
+  freshedDeclarations: (mapMap method, methods)

 declareMethods = (ctx, classConstraint, methodDeclarations) ->
   for name, {arity, type} of values methodDeclarations
@@ -1419,8 +1425,9 @@
       instanceName = ctx.definitionName()

       ctx.newScope()
-      freshConstraints = assignMethodTypes ctx, instanceName,
+      freshInstanceType = assignMethodTypes ctx, instanceName,
         classDefinition, instanceType, constraints
+      freshConstraints = freshInstanceType.constraints
       definitions = pairs wheres
       methodsDeclarations = definitionList ctx,
         (prefixWithInstanceName definitions, instanceName)
@@ -1433,7 +1440,8 @@
       # TODO: defer for class declaration if not defined
       ## if not ctx.isClassDefined className    ...

-      instance = (new Constrained constraints, instanceType)
+      instance = (new Constrained freshConstraints,
+        (new ClassConstraint instanceType.className, freshInstanceType.type))
       ## if overlaps ctx, instance
       ##   malformed 'instance overlaps with another', instance
       ## else
@@ -1454,7 +1462,7 @@
     (quantifyUnbound ctx,
       (new Constrained instanceConstraints, instanceType.types))

-  # log "mguing", classDeclaration.constraint.type, freshInstanceType.type
+  # log "mguing", classDeclaration.constraint.types, freshInstanceType.type
   sub = mostGeneralUnifier classDeclaration.constraint.types, freshInstanceType.type
   # log sub

@@ -1466,7 +1474,7 @@
     prefixedName = instancePrefix instanceName, name
     ctx.declareArity prefixedName, arity
     ctx.assignType prefixedName, quantifiedType
-  freshInstanceType.constraints
+  freshInstanceType

 prefixWithInstanceName = (definitionPairs, instanceName) ->
   for [lhs, rhs] in definitionPairs
@@ -2329,8 +2337,8 @@

 unzip = (pairs) ->
   [
-    filter _is, map _fst, pairs
-    filter _is, map _snd, pairs
+    map _fst, pairs
+    map _snd, pairs
   ]

 zip = (list1, list2) ->
@@ -3600,7 +3608,7 @@
   polymorphicVars = filterMap ((name) -> inSet vars, name), findFree type
   kinds = mapToArray polymorphicVars
   varIndex = 0
-  quantifiedVars = mapMap (-> new QuantifiedVar varIndex++), polymorphicVars
+  quantifiedVars = mapToSubstitution mapMap (-> new QuantifiedVar varIndex++), polymorphicVars
   new ForAll kinds, (substitute quantifiedVars, type)

 star = '*'
@@ -3977,7 +3985,7 @@
   compiledExpression = compileCtxAstToJs topLevelExpression, ctx, expression
   (attachPrintedTypes ctx, expression)
   types: ctx._scope()
-  # subs: filterMap ((name) -> name is 'could not unify'), ctx.substitution
+  subs: ctx.substitution.fails
   ast: ast
   compiled: library + immutable + compiledDefinitions.js + compiledExpression.js

@@ -4092,7 +4100,7 @@
     return
   try
     log (collapse toHtml compiled.ast)
-    if not isMapEmpty compiled.subs
+    if _notEmpty compiled.subs
       log compiled.subs
     if result isnt (got = eval compiled.compiled)
       log "'#{testName}' expected", result, "got", got


Use super class constraints in finding class param kinds

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1844,7 +1844,7 @@
         malformed ctx, 'class already defined', ctx.definitionPattern()
       else
         {classConstraint, freshedDeclarations} = findClassType ctx, params,
-          name, paramNames, declarations
+          name, paramNames, declarations, constraints
         if classConstraint
           ctx.addClass name, classConstraint, superClasses, freshedDeclarations
           declareMethods ctx, classConstraint, freshedDeclarations
@@ -1868,10 +1868,12 @@
       else
         type)

-findClassType = (ctx, params, className, paramNames, methods) ->
+findClassType = (ctx, params, className, paramNames, methods, constraints) ->
   kinds = mapMap (-> undefined), (arrayToSet paramNames)
-  for name, {arity, type, def} of values methods
-    vars = findFree type.type
+  types = join (map ((c) -> type: c), constraints),
+    (map (({type, def}) -> type: type.type, def: def), (mapToArray methods))
+  for {type, def} in types
+    vars = findFree type
     for param in paramNames
       kindSoFar = lookupInMap kinds, param
       foundKind = lookupInMap vars, param


add test legend to plots

add test legend to plots

--- bokehjs/demo/coffee/glyphs.coffee
+++ bokehjs/demo/coffee/glyphs.coffee
@@ -33,7 +33,7 @@
     xaxes: "min"
     yaxes: "min"
     tools: false
-    legend: false
+    legend: "test"
   }

@@ -487,4 +487,4 @@
   options.title = "X"
   plot = make_plot(x, data, options)
   show(plot)
-)+)


'Updating for version 0.12.5dev9'

'Updating for version 0.12.5dev9'

--- bokehjs/src/coffee/version.coffee
+++ bokehjs/src/coffee/version.coffee
@@ -1 +1 @@
-export version = '0.12.5dev8'
+export version = '0.12.5dev9'


Added test for highlightMatchingLineNumber false

--- spec/bracket-matcher-spec.coffee
+++ spec/bracket-matcher-spec.coffee
@@ -194,6 +194,14 @@

         editor.getLastCursor().destroy()
         expectHighlights([0, 28], [12, 0])
+
+    describe "when highlightMatchingLineNumber config is disabled", ->
+      it "does not highlight the gutter", ->
+        atom.config.set('bracket-matcher.highlightMatchingLineNumber', false)
+        editor.moveToEndOfLine()
+        editor.moveLeft()
+        gutterDecorations = editor.getLineNumberDecorations().filter (gutterDecoration) -> gutterDecoration.properties.class is 'bracket-matcher'
+        expect(gutterDecorations.length).toBe 0

     describe "when the cursor moves off (clears) a selection next to a starting or ending pair", ->
       it "highlights the starting pair and ending pair", ->


Refactor syntax

Refactor syntax

--- spec/command_service_spec.coffee
+++ spec/command_service_spec.coffee
@@ -1,13 +1,14 @@
 describe 'CommandService', ->

-  sinon = require 'sinon'
-  expect = require 'expect'
+  sinon    = require 'sinon'
+  expect   = require 'expect'
+  eventric = require 'eventric'

-  AggregateRoot      = require('eventric')('AggregateRoot')
-  DomainEventService = require('eventric')('DomainEventService')
+  AggregateRoot      = eventric 'AggregateRoot'
+  DomainEventService = eventric 'DomainEventService'
+  CommandService     = eventric 'CommandService'

   Repository         = require('sixsteps-client')('SixStepsRepository')
-  CommandService     = require('eventric')('CommandService')

   sandbox = null
   beforeEach ->


fake published more accurately in spec for testing published states

--- test/components/course-calendar/plan.spec.cjsx
+++ test/components/course-calendar/plan.spec.cjsx
@@ -12,6 +12,7 @@

 {TimeActions, TimeStore} = require '../../../src/flux/time'
 {PlanPublishStore, PlanPublishActions} = require '../../../src/flux/plan-publish'
+{JobStore, JobActions} = require '../../../src/flux/job'

 Plan = require '../../../src/components/course-calendar/plan'

@@ -51,12 +52,17 @@
   publishingPlan.publish_last_requested_at = moment(TimeStore.getNow())
   publishingPlan.publish_job = {id: JOB_UUID, status: 'queued'}
   publishingPlan.is_publishing = true
-  if publishingPlan.published_at and moment(publishingPlan.published_at).isAfter(publishingPlan.publish_last_requested_at)
-    publishingPlan.published_at = moment(publishingPlan.publish_last_requested_at).subtract(2, 'days')
-    publishingPlan.is_published = true
-  else

   publishingPlan
+
+fakePublished = (plan) ->
+  succeededProgress =
+    for: plan.id
+    id: JOB_UUID
+    status: 'succeeded'
+
+  JobActions.loaded(succeededProgress, succeededProgress.id)
+  JobStore.emit("job.#{succeededProgress.id}.final", succeededProgress)

 checkChildrenComponents = (planComponent, item, checks) ->
   {plan, displays} = item
@@ -296,12 +302,6 @@
   it 'should show as published when plan is done publishing', ->
     item = _.clone(ITEM_DRAFT_ONE_DAY)
     item.plan = fakePublishing(item.plan)
-    item.plan.is_publishing = false
-    item.plan.is_published = true
-    succeededProgress =
-      for: item.plan.id
-      id: JOB_UUID
-      status: 'succeeded'

     Testing
       .renderComponent( ContainedPlan, props: {courseId: PLAN_COURSE_ID, item} )
@@ -315,14 +315,15 @@

         checkChildrenComponents(element.refs.plan, item, checks)

-        PlanPublishStore.emit("progress.#{plan.id}.#{succeededProgress.status}", succeededProgress)
-
         checksIsPublished =
           display: (components, {display}) ->
             displayNode = components.displayComponent.getDOMNode()
             expect(displayNode.classList.contains('is-published')).to.be.true

-        checkChildrenComponents(element.refs.plan, item, checksIsPublished)
+        checker = _.partial(checkChildrenComponents, element.refs.plan, item, checksIsPublished)
+
+        PlanPublishStore.on("progress.#{plan.id}.*", checker)
+        fakePublished(plan)

   it 'should show as publishing when re-publishing', ->
     item = _.clone(ITEM_PUBLISHED_THREE_DAYS)
@@ -346,8 +347,6 @@
   it 'should show full modal if re-publishing', ->
     item = _.clone(ITEM_PUBLISHED_THREE_DAYS)
     item.plan = fakePublishing(item.plan)
-    item.plan.is_published = true
-    item.plan.is_publishing = false
     Testing
       .renderComponent( ContainedPlan, props: {courseId: PLAN_COURSE_ID, item} )
       .then ({dom, element, root}) ->
@@ -376,11 +375,6 @@
     item = _.clone(ITEM_DRAFT_ONE_DAY)
     item.plan = fakePublishing(item.plan)

-    succeededProgress =
-      for: item.plan.id
-      id: JOB_UUID
-      status: 'succeeded'
-
     Testing
       .renderComponent( ContainedPlan, props: {courseId: PLAN_COURSE_ID, item: ITEM_PUBLISHED_THREE_DAYS} )
       .then ({dom, element}) ->
@@ -404,25 +398,20 @@

         checkChildrenComponents(element.refs.plan, item, checksForIsPublishing)

-        PlanPublishStore.emit("progress.#{item.plan.id}.#{succeededProgress.status}", succeededProgress)
-
         checksIsPublished =
           display: (components, {display}) ->
             displayNode = components.displayComponent.getDOMNode()
             expect(displayNode.classList.contains('is-published')).to.be.true

-        checkChildrenComponents(element.refs.plan, item, checksIsPublished)
-
+        checker = _.partial(checkChildrenComponents, element.refs.plan, item, checksIsPublished)
+        PlanPublishStore.on("progress.#{item.plan.id}.*", checker)
+
+        fakePublished(item.plan)

   it 'should check for publishing subscribe if plan isPublishing props update', ->
     item = _.clone(ITEM_DRAFT_ONE_DAY)
     item.plan = fakePublishing(item.plan)
     item.plan.isPublishing = true
-
-    succeededProgress =
-      for: item.plan.id
-      id: JOB_UUID
-      status: 'succeeded'

     Testing
       .renderComponent( ContainedPlan, props: {courseId: PLAN_COURSE_ID, item: ITEM_DRAFT_ONE_DAY} )


Fix mocks path.

Fix mocks path.


Update feature_command_aggregate.spec.coffee

--- src/context/feature_command_aggregate.spec.coffee
+++ src/context/feature_command_aggregate.spec.coffee
@@ -4,8 +4,6 @@
     exampleContext = null
     beforeEach (done) ->
       exampleContext = eventric.context 'exampleContext'
-      exampleContext.addAggregate 'Example', class Example
-
       exampleContext.defineDomainEvent 'ExampleCreated', ->
       exampleContext.defineDomainEvent 'SomethingHappened', (params) ->
         @someId   = params.someId
@@ -21,9 +19,6 @@
           @$emitDomainEvent 'SomethingHappened',
             someId: someId
             someProp: 'foo'
-
-        handleExampleCreated: ->
-          @entities = []

         handleSomethingHappened: (domainEvent) ->
           @someId = domainEvent.payload.someId


remove errant print

remove errant print

--- src/hubot-confluence.coffee
+++ src/hubot-confluence.coffee
@@ -25,7 +25,6 @@
   url = make_url(suffix, true)
   headers = make_headers()

-  msg.send url
   msg.http(url).headers(headers).get() (e, res, body) ->
     if e
       msg.send "Error: #{e}"


Remove location junk from panel creation

--- spec/panel-container-spec.coffee
+++ spec/panel-container-spec.coffee
@@ -16,11 +16,11 @@
     it 'emits an onDidAddPanel event with the index the panel was inserted at', ->
       container.onDidAddPanel addPanelSpy = jasmine.createSpy()

-      panel1 = new Panel(item: new TestPanelItem(), location: 'left')
+      panel1 = new Panel(item: new TestPanelItem())
       container.addPanel(panel1)
       expect(addPanelSpy).toHaveBeenCalledWith({panel: panel1, index: 0})

-      panel2 = new Panel(item: new TestPanelItem(), location: 'left')
+      panel2 = new Panel(item: new TestPanelItem())
       container.addPanel(panel2)
       expect(addPanelSpy).toHaveBeenCalledWith({panel: panel2, index: 1})

@@ -28,9 +28,9 @@
     it 'emits an onDidRemovePanel event with the index of the removed item', ->
       container.onDidRemovePanel removePanelSpy = jasmine.createSpy()

-      panel1 = new Panel(item: new TestPanelItem(), location: 'left')
+      panel1 = new Panel(item: new TestPanelItem())
       container.addPanel(panel1)
-      panel2 = new Panel(item: new TestPanelItem(), location: 'left')
+      panel2 = new Panel(item: new TestPanelItem())
       container.addPanel(panel2)

       expect(removePanelSpy).not.toHaveBeenCalled()


fix failing spec

fix failing spec

--- spec/quick-highlight-spec.coffee
+++ spec/quick-highlight-spec.coffee
@@ -7,7 +7,7 @@

   decorations = []
   for id, decoration of editor.decorationsStateForScreenRowRange(0, editor.getLineCount())
-    if decoration.properties.class.match(pattern)
+    if decoration.properties.class?.match(pattern)
       decorations.push(decoration)
   decorations


Atoms are only in /posts and /status

Atoms are only in /posts and /status

Fixes #101.

--- src/local/operations.coffee
+++ src/local/operations.coffee
@@ -1266,11 +1266,14 @@
             async.forEach updates, (update, cb3) ->
                 switch update.type
                     when 'items'
-                        update.items = update.items.filter (item) ->
-                            res = validateItem item.el
-                            unless res
-                                logger.warn "Rejecting invalid Atom #{item.id} from #{update.node}"
-                            res
+                        # Validate Atoms in /posts and /status nodes
+                        nodeType = getNodeType update.node
+                        if nodeType in ['posts', 'status']
+                            update.items = update.items.filter (item) ->
+                                res = validateItem item.el
+                                unless res
+                                    logger.warn "Rejecting invalid Atom #{item.id} from #{update.node}"
+                                res
                         if update.items.length == 0
                             return cb3()


slightly less noisy

slightly less noisy

--- src/slack_connection.coffee
+++ src/slack_connection.coffee
@@ -22,9 +22,6 @@
   loggedIn: (self, team) =>
     console.log "Logged in as #{self.name} of #{team.name}, but not yet connected"

-    # store a copy of our own user data
-    @self = self
-
   open: =>
     console.log 'Slack client now connected'

@@ -35,7 +32,10 @@
   message: (msg) =>
     # Ignore our own messages
     return if msg.user == @self.id
-    console.log msg.text
+    if msg.type == 'message'
+      console.log "#{msg._client.team.name} / #{msg.channel} / #{msg.user} - #{msg.text}"
+    else
+      console.log "Unknown message type, #{msg.type}"

   userChange: (user) =>


Don't attempt to complete the visit if a snapshot wasn't restored

--- src/turbolinks/visit.coffee
+++ src/turbolinks/visit.coffee
@@ -55,8 +55,7 @@
       @saveSnapshot()
       if @snapshotRestored = @controller.restoreSnapshotForLocationWithAction(@location, @action)
         @adapter.visitSnapshotRestored?(this)
-      unless @shouldIssueRequest()
-        @complete()
+        @complete() unless @shouldIssueRequest()

   loadResponse: ->
     if @response?


:bug: Ignore lint requests until file is saved somewhere

--- lib/editor-linter.coffee
+++ lib/editor-linter.coffee
@@ -43,6 +43,7 @@

   lint: (wasTriggeredOnChange) ->
     return unless @editor is atom.workspace.getActiveTextEditor()
+    return unless @editor.getPath()
     return if @_lock(wasTriggeredOnChange)

     scopes = @editor.scopeDescriptorForBufferPosition(@editor.getCursorBufferPosition()).scopes


Removed unused fragment wrapper

Removed unused fragment wrapper

--- source/runtime.coffee
+++ source/runtime.coffee
@@ -25,11 +25,6 @@

   pop = ->
     append(stack.pop())
-
-  fragment = (fn) ->
-    push document.createDocumentFragment()
-    fn()
-    pop()

   observeAttribute = (element, name, value) ->
     value.observe? (newValue) ->


Update Tasker.coffee

Update Tasker.coffee

--- server/src/Core/Tasker.coffee
+++ server/src/Core/Tasker.coffee
@@ -21,7 +21,10 @@
     constructor: (@_app, @_serverLogger) ->
         @_coreElementManager = new ElementManager @_app
         @_timers =[]
+        @_tasks = []
         @tasks = 0
+        process.on 'SIGTERM', =>
+            @youAreNext()

     run: ->
         @_validateTasks @_app.components
@@ -84,9 +87,27 @@
                 @_logger?.log? "Task: [#{name}] was invoked. [#{task.__logTrigger}]"
                 task.__logTrigger = 0

-            unless task.__isLocked
+            unless task.__isLocked or task.__marked
                 task.__isLocked = true
                 task[run](task.__emiter)
         , interval, @_serverLogger
+
+        @_tasks.push task
+
+    youAreNext: ->
+        console.log 'Marking tasks...'
+        tasks = @_tasks.map (task) ->
+            task.__marked = true
+            task
+        console.log 'Marking done.'
+        setInterval ->
+            openTasks = tasks.filter (task) ->
+                task.__isLocked
+            console.log 'Open tasks:', openTasks.length
+            if openTasks.length is 0
+                console.log 'All tasks finished. Exiting...'
+                clearInterval setInterval
+                process.exit 0
+        , 1000

 module.exports = Tasker


test case for #436

test case for #436

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1309,6 +1309,14 @@
     expectStreamEvents(
       -> Bacon.fromArray([error(), 1])
       [error(), 1])
+  it "toString", ->
+    expect(Bacon.fromArray([1,2]).toString()).to.equal("Bacon.fromArray([1,2])")
+  it "doesn't mutate the given array, toString works after subscribe (bug fix)", ->
+    array = [1,2]
+    s = Bacon.fromArray(array)
+    s.onValue(->)
+    expect(s.toString()).to.equal("Bacon.fromArray([1,2])")
+    expect(array).to.deep.equal([1,2])

 describe "EventStream.concat", ->
   describe "provides values from streams in given order and ends when both are exhausted", ->


Partially revert last

Partially revert last

Too much overloading for init


Rename some rcb* variables.

Rename some rcb* variables.


Fix prefix generation

Fix prefix generation

--- lib/jedi-provider.coffee
+++ lib/jedi-provider.coffee
@@ -33,19 +33,8 @@

         success: (data) ->
           # get prefix
-          lines = text.split "\n"
-          line = lines[row]
-
-          # generate a list of potential prefixes
-          indexes = []
-          indexes.push line.substr(line.lastIndexOf(" ") + 1)
-          indexes.push line.substr(line.lastIndexOf("(") + 2)
-          indexes.push line.substr(line.lastIndexOf(".") + 1)
-
-          # sort array by string length - shortest element is the prefix
-          prefix = indexes.sort((a, b) ->
-            a.length - b.length
-          )[0]
+          regex = /[a-z|A-Z|_0-9-]+$/
+          prefix = options.prefix.match(regex)?[0] or ''

           # build suggestions
           for index of data
@@ -60,7 +49,7 @@
               label: label
             })

-            resolve(suggestions)
+          resolve(suggestions)

         error: (data) ->
           console.log "Error communicating with server"


Pasting into a participant field now preserves any text already there (Issue #2410) (#2635)

This modifies the behavious when pasting into the to, cc or bcc fields of a new draft.
Any text already in the input field is not automatically deleted. Rather the pasted
text is added to the already existing text. If the new contents of the email field now
matches as an email address (based on RegExpUtils.emailRegex().test()), then the address
is immediately tokenized. If the new input text doesn't match as an email, just append
the new text and wait for further input.

--- src/components/tokenizing-text-field.cjsx
+++ src/components/tokenizing-text-field.cjsx
@@ -528,8 +528,13 @@

   _onPaste: (event) =>
     data = event.clipboardData.getData('text/plain')
-    @_addInputValue(data)
-    event.preventDefault()
+    newInputValue = @state.inputValue + data
+    if RegExpUtils.emailRegex().test(newInputValue)
+      @_addInputValue(newInputValue, skipNameLookup: true)
+      event.preventDefault()
+    else
+      @_refreshCompletions(newInputValue)
+

   # Managing Suggestions


Ensure login does not wait for the browser process to close

Unclear why, but for some reason this only actually blocked on the
browser on OSX.

Connects-To: #791
Change-Type: patch

--- lib/auth/index.coffee
+++ lib/auth/index.coffee
@@ -56,7 +56,7 @@
 		# Leave a bit of time for the
 		# server to get up and runing
 		setTimeout ->
-			open(loginUrl)
+			open(loginUrl, { wait: false })
 		, 1000

 		return server.awaitForToken(options)


remove debug

remove debug

--- lib/assets/javascripts/stark/stark.js.coffee
+++ lib/assets/javascripts/stark/stark.js.coffee
@@ -129,7 +129,6 @@
     @log "render_views "

     if @current_state?
-      @log @current_state
       @trigger("state:deactivate", @current_state)
       @current_state.deactivate()
       @current_state.dispose(state)


Check available when creates a new instance

--- source/core/attributes.coffee
+++ source/core/attributes.coffee
@@ -33,7 +33,7 @@
   chemistry: (children) ->
     children = @attributes.children or @default.children
     for item, index in children
-      for key of item when not @available or key in @available
+      for key of item when not @constructor.available or key in @constructor.available
         base = key.split(".")
         type = base[0]
         class_name = base[1]
@@ -43,6 +43,14 @@
             attributes = Atoms.Core.Helper.mix item[key], @default.children?[index]?[key]
           @children.push @instance type, class_name, attributes

-  instance: (type, class_name, attributes) ->
-    attributes.parent = @
-    new Atoms[type][class_name] attributes
+  instance: (type, class_name, attributes={}) ->
+    if @__available type, class_name
+      attributes.parent = attributes.parent or @
+      new Atoms[type][class_name] attributes
+    else
+      throw "Instance no available in current container."
+
+  __available: (type, class_name) ->
+    instance = Atoms[type][class_name]
+    instance = instance.base or instance.name
+    (not @constructor.available or "#{type}.#{instance}" in @constructor.available)


update.

update.

--- src/main/public/nz/SpriteCharacter.coffee
+++ src/main/public/nz/SpriteCharacter.coffee
@@ -60,6 +60,7 @@

   clearGhost: ->
     @ghost.remove() if @ghost?
+    @ghost = null
     return

   isGhost: -> @mapx != @character.mapx or @mapy != @character.mapy


problem.coffee : queue mathjax typesetting just once

--- lms/static/coffee/src/modules/problem.coffee
+++ lms/static/coffee/src/modules/problem.coffee
@@ -51,7 +51,6 @@
             @$("#answer_#{key}").html(value)	# needs to be html, not text, for complex solutions (eg coding)
             @$("#solution_#{key}").html(value)	# needs to be html, not text, for complex solutions (eg coding)
         MathJax.Hub.Queue ["Typeset", MathJax.Hub]
-        MathJax.Hub.Queue ["Typeset", MathJax.Hub]
         @$('.show').val 'Hide Answer'
         @element.addClass 'showed'
     else


Removed stale code.

Removed stale code.

--- manoseimas/widget/frontend/scripts/ui.coffee
+++ manoseimas/widget/frontend/scripts/ui.coffee
@@ -14,26 +14,7 @@
             track_event "Scroll", "MPs"
             mps_scroll_tracked = true

-        #check_mp_visibility()
         detect_mps_section()
-
-element_visible = (el) ->
-    rect = el.getBoundingCientRect()
-    return rect.top >= 0 && rect.left >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight)
-
-load_image = (el, callback) ->
-    img = new Image()
-    img.alt = ''
-    img.onload = () ->
-        el.parent.replaceChild img, el
-        callback()
-
-    img.src = el.getAttribute 'data-src'
-
-check_mp_visibility = () ->
-    for el in $(".lazy")
-        if element_visible el
-            load_image el

 # Automatically select section buttons based on context
 detect_mps_section = () ->
@@ -53,8 +34,3 @@
             $("#MSWidget-mps-#{sp}_button").style.backgroundColor = '#ccc'
         else
             $("#MSWidget-mps-#{sp}_button").style.backgroundColor = '#fff'
-
-
-
-#addEventListener('scroll',processScroll);
-#EX: https://gist.github.com/aliem/2171438


wider back button by default

wider back button by default

--- vnc-example01.framer/modules/ViewNavigationController.coffee
+++ vnc-example01.framer/modules/ViewNavigationController.coffee
@@ -8,7 +8,7 @@
 	BACK_BUTTON_FRAME =
 		x: 0
 		y: 40
-		width: 88
+		width: 88*2
 		height: 88
 	PUSH =
 		UP:     "pushUp"


minor

minor

--- spec/flow-spec.coffee
+++ spec/flow-spec.coffee
@@ -2,7 +2,7 @@
 Flow = require '../flow'

 test "Core methods of Flow cannot be changed", (t) ->
-  flow = Flow()
+  flow = Flow(commands: {}, commandHandlers: {})
   flow.dispatch = 'foo'

   t.false flow.dispatch is 'foo'


Ace: (dry) use KD.utils.isNavigatorApple

--- client/Ace/editormacroview.coffee
+++ client/Ace/editormacroview.coffee
@@ -109,7 +109,7 @@
     return  markdown

   getUserPlatformId:->
-    if navigator.platform in ["MacIntel", "MacPPC", "Mac68K", "Macintosh", "iPad"]
+    if KD.utils.isNavigatorApple()
       return  2
     return 1


rollbar: close around rollbar stuff

rollbar: close around rollbar stuff

--- client/app/MainApp/rollbar.coffee
+++ client/app/MainApp/rollbar.coffee
@@ -1,27 +1,28 @@
 # Wrapper for pushing events to Rollbar.
-logToExternal = KD.logToExternal = (args) ->
-  _rollbar.push args
+if _rollbar? then do ->
+  logToExternal = KD.logToExternal = (args) ->
+    _rollbar.push args

 # Push status events to Rollbar.
-connections     = []
-disconnections  = []
+  connections     = []
+  disconnections  = []

-KD.remote.on 'connected', ->
-  connections.push new Date
+  KD.remote.on 'connected', ->
+    connections.push new Date

-KD.remote.on 'disconnected', ->
-  disconnections.push new Date
+  KD.remote.on 'disconnected', ->
+    disconnections.push new Date

-KD.remote.on 'reconnected', ->
-  connections.push new Date
-  data =
-    connections:connections
-    disconnections: disconnections
-  logToExternal msg:"disconnected, then reconnected", data:data
+  KD.remote.on 'reconnected', ->
+    connections.push new Date
+    data =
+      connections:connections
+      disconnections: disconnections
+    logToExternal msg:"disconnected, then reconnected", data:data

-KD.getSingleton('mainController').on "AccountChanged", (account) ->
-  user = KD.whoami?().profile or KD.whoami()
-  _rollbarParams.person =
-    id: user.hash or user.nickname
-    username: user.nickname
-    name: user.firstName + " " + user.lastName
+  KD.getSingleton('mainController').on "AccountChanged", (account) ->
+    user = KD.whoami?().profile or KD.whoami()
+    _rollbarParams.person =
+      id: user.hash or user.nickname
+      username: user.nickname
+      name: user.firstName + " " + user.lastName


Set $.plot to a noop so the specs can run headless.

--- spec/javascripts/dot_ledger/views/accounts/show_spec.js.coffee
+++ spec/javascripts/dot_ledger/views/accounts/show_spec.js.coffee
@@ -1,3 +1,5 @@
+$.plot = $.noop
+
 describe "DotLedger.Views.Accounts.Show", ->
   createView = ->
     model =  new DotLedger.Models.Account


Restore checkins schedule

Restore checkins schedule

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -10,7 +10,7 @@
 import callApi from '../lib/callApi'

-SESSION_TIMES = ["15:00", "16:00"]
+SESSION_TIMES = ["14:00", "15:30"]
 #SESSION_TIMES = ["14:00"]

 export default class Checkins extends React.Component
@@ -60,11 +60,6 @@
             Не забудьте с собой паспорт (если паспорта еще нет, то свидетельство о рождении) -- его могут спросить охранники на входе!

-

-            22 сентября особое расписание занятий: в 14.00 занятие только для новичков, для всех остальных занятия в 15.00 и 16.00.
-

-
-
             {
             if @state.result?.error
                 <Alert bsStyle="danger">


Update Checkins.coffee

Update Checkins.coffee

--- client/components/Checkins.coffee
+++ client/components/Checkins.coffee
@@ -37,10 +37,6 @@
         wasme = [false, false]

Регистрация на занятие

-

             Новичкам (кто не решил в курсе ни одной задачи) приходить на занятие нельзя


Forget password might actually do something now

--- src-public/app.coffee
+++ src-public/app.coffee
@@ -56,6 +56,7 @@
     url: '/invites'
     controller: 'InvitesCtrl'
     templateUrl: 'invites.html'
+
   .state 'forgotmypassword',
     url: '/forgotmypassword'
     controller: 'ResetPasswordCtrl'


Update thank you url

Update thank you url

--- src/scripts/pages/donate/form/form.coffee
+++ src/scripts/pages/donate/form/form.coffee
@@ -973,7 +973,7 @@
       amount: () -> @amount
       thanks: () ->
         url = "#{location.protocol}//#{location.host}/donate/thankyou"
-        url += "?uuid=#{@uuid}&type=#{@type}" if @uuid and @type
+        url += "/#{@uuid}/#{@type}" if @uuid and @type
         return url

     events:


[backport] Broadcasting auth information

--- app/coffee/modules/auth.coffee
+++ app/coffee/modules/auth.coffee
@@ -161,6 +161,7 @@
             user = @model.make_model("users", user)

             @.setUser(user)
+            @rootscope.broadcast("auth:refresh", user)
             return user

     login: (data, type) ->
@@ -175,6 +176,7 @@
             user = @model.make_model("users", data.data)
             @.setToken(user.auth_token)
             @.setUser(user)
+            @rootscope.broadcast("auth:login", user)
             return user

     logout: ->
@@ -184,6 +186,7 @@

         @._setTheme()
         @._setLocales()
+        @rootscope.broadcast("auth:logout")
         @analytics.setUserId()

     register: (data, type, existing) ->
@@ -200,6 +203,7 @@
             user = @model.make_model("users", response.data)
             @.setToken(user.auth_token)
             @.setUser(user)
+            @rootscope.broadcast("auth:register", user)
             return user

     getInvitation: (token) ->


make scroll position be preserved on split.

--- src/smc-webapp/code-editor/actions.coffee
+++ src/smc-webapp/code-editor/actions.coffee
@@ -171,6 +171,7 @@
         @_tree_op('split_leaf', id ? @store.getIn(['local_view_state', 'active_id']), direction)
         for i,_ of @_get_leaf_ids()
             if not ids0[i]
+                @copy_scroll_position(id, i)
                 id = i  # this is a new id
                 break
         @set_active_id(id)
@@ -196,6 +197,11 @@
             scroll = scroll.set(id, immutable.fromJS(info))
         @setState(local_view_state : local.set('scroll', scroll))
         @_save_local_view_state()
+
+    copy_scroll_position: (id1, id2) =>
+        info = @store.getIn(['local_view_state', 'scroll', id1])
+        if info?
+            @save_scroll_position(id2, info)

     enable_key_handler: =>
         if @_state == 'closed'


fixing story section

fixing story section

Former-commit-id: e29ac7b5d1895d05a334757ca14f54698634e0e1 [formerly 9d6700a2ef0cf04efa3cda156ea50232e702a9d7] [formerly ee6a65774217a2da4a04f75155bfe60a3aa315a5 [formerly 3ce92acab62cca46f911afa804bcaa91147b8aec]] [formerly 607f02b68c74597cbc07ef03b9707e1b9ddf99ae [formerly 084bb633daa39735461f414aa0c18c4095ce38f1] [formerly 8193c2acf28cd2a48904a7f57d736959e23d88cb [formerly 8193c2acf28cd2a48904a7f57d736959e23d88cb [formerly c40d5d6a6e25b78c4634e4a76365e2e94fd09d8e]]]]
Former-commit-id: c68473616e307fdb4887f37bd61ef2626de41d0c [formerly 952856d1ee626c713b2b2cd23527d2f20d9fe8c6] [formerly b91c9475e529e4ec24e034113c96e42c56236990 [formerly 33b406c70b08d8ec9af2580ef224ef101ec12de1]]
Former-commit-id: 729756a899bea0e5cf3431ad66da2de3c4bd17ba [formerly 6d9cd6941dc6f3fb1b1f0b475ad7993a14d2c95b]
Former-commit-id: ca6690e6c6cc95812d56d9dac4b8ee032b8151f6
Former-commit-id: 6db282d62ca78ef938d28ed7991e03deb03c3665

--- @client/saas_landing_page/story.coffee
+++ @client/saas_landing_page/story.coffee
@@ -6,8 +6,8 @@
     DIV
       id: 'story'
       style: _.extend {}, base_text,
-        marginTop: 60
-        marginBottom: 20
+        width: SAAS_PAGE_WIDTH
+        margin: '60px auto 20px auto'

       H1
@@ -90,7 +90,7 @@
           textAlign: 'center'

-        "Fictional story starring Benjamin Franklin, coming soon."
+        "Coming soon: a fictional founding myth starring Benjamin Franklin."

   truth : ->
     caption_text =


Added missing keybind in linux.cson. `'ctrl-,': 'application:show-settings'` exists in `win32.cson` and `darwin32.cson`

--- keymaps/linux.cson
+++ keymaps/linux.cson
@@ -14,6 +14,7 @@
   'F11': 'window:toggle-full-screen'

   # Sublime Parity
+  'ctrl-,': 'application:show-settings'
   'ctrl-N': 'application:new-window'
   'ctrl-W': 'window:close'
   'ctrl-o': 'application:open-file'


use extend view in media form

use extend view in media form

--- MediaAdminBundle/Resources/public/coffee/MediaFormView.coffee
+++ MediaAdminBundle/Resources/public/coffee/MediaFormView.coffee
@@ -1,6 +1,6 @@
 MediaFormView = OrchestraView.extend(
-  events:
-    'submit': 'addEventOnForm'
+
+  extendView : [ 'submitAdmin' ]

   initialize: (options) ->
     @options = @reduceOption(options, [
@@ -11,6 +11,8 @@
     @loadTemplates [
       'OpenOrchestraBackofficeBundle:BackOffice:Underscore/fullPageFormView'
     ]
+    @options.formView = 'showMediaForm'
+    @options.entityType = 'media'
     return

   render: ->
@@ -22,18 +24,4 @@
       PO.formPrototypes.addPrototype $(this)
       return
     return
-
-  addEventOnForm: (event) ->
-    event.preventDefault()
-    viewContext = @
-    $('form', @options.domContainer).ajaxSubmit
-      context:
-        button: $(".submit_form",event.currentTarget).parent()
-      success: (response) ->
-        formChannel.trigger 'formSubmit'
-        viewClass = appConfigurationView.getConfiguration('media', 'showMediaForm')
-        new viewClass(viewContext.addOption(
-          html: response
-        ))
-    return
 )


Clean up GET /api/confirm route

Clean up GET /api/confirm route

--- src/server/routes/get/confirm.coffee
+++ src/server/routes/get/confirm.coffee
@@ -6,17 +6,12 @@
 	fn: [
 		xsrf.set
 		(req, res) ->
-			xsrf = req.getXsrf()
-			id = req.query?.id
-			client = req.query?.client
-			name = req.user.name
-			email = req.user.email
 			json =
-				xsrf: xsrf
-				id: id
-				client: client
-				name: name
-				email: email
+				xsrf: req.getXsrf()
+				id: req.query?.id
+				client: req.query?.client
+				name: req.user.displayName
+				email: req.user.email
 			res.render "confirm", json
 	]


add selection reticle

add selection reticle

--- lib/assets/javascripts/attachinary.js.coffee
+++ lib/assets/javascripts/attachinary.js.coffee
@@ -13,9 +13,12 @@
               <% if(files[i].resource_type == "raw") { %>

               <% } else { %>
+

                 <img
-                  src="<%= $.cloudinary.url(files[i].public_id, { "version": files[i].version, "format": 'jpg', "crop": 'fill', "width": 75, "height": 75 }) %>"
-                  alt="" width="75" height="75" />
+                  src="<%= $.cloudinary.url(files[i].public_id, { "version": files[i].version, "format": 'jpg'}) %>"
+                  alt=""/>
+
+

               <% } %>
               <a href="#" data-remove="<%= files[i].public_id %>">Remove


Put file name at beginning of markdown preview title

Previously the 'Markdown Preview - ' prefix dominated the tab text
making it hard to disambiguate which preview was for which editor.

Now the title leads with the file name and follows with a "Preview" suffix.

--- src/packages/markdown-preview/lib/markdown-preview-view.coffee
+++ src/packages/markdown-preview/lib/markdown-preview-view.coffee
@@ -52,7 +52,7 @@
     path: @buffer.getPath()

   getTitle: ->
-    "Markdown Preview – #{@buffer.getBaseName()}"
+    "#{@buffer.getBaseName()} Preview"

   getUri: ->
     "markdown-preview:#{@buffer.getPath()}"


Fix pending spec

Fix pending spec

--- spec/chart-d3-bar-spec.coffee
+++ spec/chart-d3-bar-spec.coffee
@@ -107,12 +107,9 @@

   describe '::renderBars', ->
     it 'should return an array', ->
-      actual = instance.renderBars([], x0Scale, args.options)
+      actual = instance.renderBars(args.generatedData, x0Scale, args.options)
       expect(actual).to.be.an(Array)
-
-    xit 'should return an array', ->
-      actual = instance.renderBars(args.generatedData, x0Scale, args.options)
-      expect(actual[0][0].outerHTML).to.contain('translate(7,0)')
+      expect(actual[0]).to.have.length(7)

   describe '::renderBar', ->
     it 'should return an array', ->


:art: Reorganize buffer specs

:art: Reorganize buffer specs

--- spec/color-buffer-spec.coffee
+++ spec/color-buffer-spec.coffee
@@ -52,6 +52,20 @@
     it 'creates the corresponding markers in the text editor', ->
       expect(editor.findMarkers(type: 'pigments-color').length).toEqual(4)

+    describe 'when the editor is destroyed', ->
+      it 'destroys the color buffer at the same time', ->
+        editor.destroy()
+
+        expect(project.colorBuffersByEditorId[editor.id]).toBeUndefined()
+
+    ##    ##     ##    ###    ########   ######
+    ##    ##     ##   ## ##   ##     ## ##    ##
+    ##    ##     ##  ##   ##  ##     ## ##
+    ##    ##     ## ##     ## ########   ######
+    ##     ##   ##  ######### ##   ##         ##
+    ##      ## ##   ##     ## ##    ##  ##    ##
+    ##       ###    ##     ## ##     ##  ######
+
     describe 'when the project variables becomes available', ->
       [updateSpy] = []
       beforeEach ->
@@ -149,6 +163,14 @@
           }
           expect(colorBuffer.serialize()).toEqual(expected)

+    ##     ######   #######  ##        #######  ########   ######
+    ##    ##    ## ##     ## ##       ##     ## ##     ## ##    ##
+    ##    ##       ##     ## ##       ##     ## ##     ## ##
+    ##    ##       ##     ## ##       ##     ## ########   ######
+    ##    ##       ##     ## ##       ##     ## ##   ##         ##
+    ##    ##    ## ##     ## ##       ##     ## ##    ##  ##    ##
+    ##     ######   #######  ########  #######  ##     ##  ######
+
     describe 'with a buffer with only colors', ->
       beforeEach ->
         waitsForPromise ->
@@ -234,9 +256,3 @@

         it 'removes the previous editor markers', ->
           expect(editor.findMarkers(type: 'pigments-color').length).toEqual(2)
-
-  describe 'when the editor is destroyed', ->
-    it 'destroys the color buffer at the same time', ->
-      editor.destroy()
-
-      expect(project.colorBuffersByEditorId[editor.id]).toBeUndefined()


updated platform.coffee

updated platform.coffee

--- src/tasks/build/base/platform.coffee
+++ src/tasks/build/base/platform.coffee
@@ -27,7 +27,6 @@
       if fn then fn()

   buildPlatform = (platform, fn) ->
-    helpers.exec("cordova platform add #{platform}")  if grunt.config.get('phonegap.config.cli') is 'cordova'
     if helpers.isRemote(platform)
       remote platform, ->
         runAfter 'remote', platform, fn
@@ -35,7 +34,17 @@
       local platform, ->
         runAfter 'local', platform, fn

+  addPlatform = (platform, fn) ->
+    if grunt.config.get('phonegap.config.cli') is 'cordova'
+      helpers.exec "cordova platform add #{platform}", (err) ->
+        if err
+          grunt.fatal err
+        else
+          buildPlatform platform, fn
+    else
+      buildPlatform platform, fn
+
   build: (platforms, fn) ->
     grunt.log.writeln 'Building platforms'
-    async.eachSeries platforms, buildPlatform, (err) ->
+    async.eachSeries platforms, addPlatform, (err) ->
       if fn then fn err


Log more info about callbacks

Log more info about callbacks

--- packages/rocketchat-lib/lib/callbacks.coffee
+++ packages/rocketchat-lib/lib/callbacks.coffee
@@ -73,8 +73,7 @@
 			callbackResult = callback result, constant

 			if RocketChat.callbacks.showTime is true
-				console.log hook, Date.now() - time
-				console.log callback.stack.split('\n')[2]
+				console.log Date.now() - time, hook, callback.id, callback.stack.split('\n')[2]?.match(/\(.+\)/)?[0]

 			return callbackResult
 		, item


Add spec for resolving compatible module paths

--- spec/module-cache-spec.coffee
+++ spec/module-cache-spec.coffee
@@ -1,5 +1,7 @@
 path = require 'path'
 Module = require 'module'
+fs = require 'fs-plus'
+temp = require 'temp'
 ModuleCache = require '../src/module-cache'

 describe 'ModuleCache', ->
@@ -20,3 +22,34 @@
     }
     expect(require('./fixtures/module-cache/file.json').foo).toBe 'bar'
     expect(Module._findPath.callCount).toBe 0
+
+  it 'resolves module paths to a compatible version provided by core', ->
+    packagePath = fs.realpathSync(temp.mkdirSync('atom-package'))
+    ModuleCache.add packagePath, {
+      _atomModuleCache:
+        folders: [{
+          paths: [
+            ""
+          ]
+          dependencies:
+            'underscore-plus': '*'
+        }]
+    }
+    ModuleCache.add atom.getLoadSettings().resourcePath, {
+      _atomModuleCache:
+        dependencies: [{
+          name: 'underscore-plus'
+          version: require('underscore-plus/package.json').version
+          path: path.join('node_modules', 'underscore-plus', 'lib', 'underscore-plus.js')
+        }]
+    }
+
+    indexPath = path.join(packagePath, 'index.js')
+    fs.writeFileSync indexPath, """
+      exports.load = function() {require('underscore-plus');};
+    """
+
+    packageMain = require(indexPath)
+    Module._findPath.reset()
+    packageMain.load()
+    expect(Module._findPath.callCount).toBe 0


It's "isnt" not "is not"

It's "isnt" not "is not"

--- spec/spellchecker-spec.coffee
+++ spec/spellchecker-spec.coffee
@@ -115,7 +115,7 @@

       @fixture.checkSpellingAsync(string).then (r) -> ranges = r

-      waitsFor -> ranges is not null
+      waitsFor -> ranges isnt null

       runs ->
         expect(ranges).toEqual [


Repo detection can accept an explicit filepath

--- lib/git-bridge.coffee
+++ lib/git-bridge.coffee
@@ -73,8 +73,8 @@
   @_getActivePath: ->
     atom.workspace.getActivePaneItem()?.getPath?()

-  @_getActiveRepo: ->
-    [rootDir] = atom.project.relativizePath @_getActivePath()
+  @getActiveRepo: (filepath) ->
+    [rootDir] = atom.project.relativizePath(filepath or @_getActivePath())
     if rootDir?
       rootDirIndex = atom.project.getPaths().indexOf(rootDir)
       repo = atom.project.getRepositories()[rootDirIndex]
@@ -82,9 +82,9 @@
       repo = atom.project.getRepositories()[0]
     return repo

-  @_repoWorkDir: -> @_getActiveRepo().getWorkingDirectory()
-
-  @_repoGitDir: -> @_getActiveRepo().getPath()
+  @_repoWorkDir: (filepath) -> @getActiveRepo(filepath).getWorkingDirectory()
+
+  @_repoGitDir: (filepath) -> @getActiveRepo(filepath).getPath()

   @_statusCodesFrom: (chunk, handler) ->
     for line in chunk.split("\n")


first pass at splitting apart a generic slider component from the opinion slider

The API for Slider still needs work.

Former-commit-id: 963b4a965d2d18829d1a5e9c0b54611a0bb2d925 [formerly 5a0419f3751caa9843ee7936c62bbe81ad2708ba] [formerly a4c57e7015f08b68f601d68a6a00b6e6daee54d2 [formerly 7588d378f91ce0a065b2ce201832555617fcb711]] [formerly a1ef71cad7a29a80d8c7fbbd1d4857247ac6c731 [formerly 7078d359c073f573695e3213b8395f4bb979ebe9] [formerly b7b66a60eae861ad0dc064db026bbcaba98ddf61 [formerly b7b66a60eae861ad0dc064db026bbcaba98ddf61 [formerly 813de24b3c8aa584a4a9dc232c81e90cbbfe6060]]]]
Former-commit-id: 736608bbe031e17f134340b1543a0186a61cd8aa [formerly a0e39951fc1fe61056cc7759706d08a9d2b3c532] [formerly 6737606da592899044b7742ed0665c4c3e3dc73c [formerly 7d821e7f72b609800ef396196ddd731ae89ff3b2]]
Former-commit-id: 0ab9826d86b86a995c58c7bde085f8d9a9fd45e6 [formerly 12877b8cc4636ab859d2a0b6c52f20f7b571f366]
Former-commit-id: 08aebf2974b115b1e060519c2f4ce3c28180693e
Former-commit-id: 7af4831e84aa3b0403f8233bd6d8bf688c43df29

--- @client/slider.coffee
+++ @client/slider.coffee
@@ -12,303 +12,6 @@

 require './customizations'
 require './shared'
-
-window.Slider = ReactiveComponent
-  displayName: 'Slider'
-
-  render : ->
-
-    slider = fetch @props.key
-    your_opinion = fetch @props.your_opinion
-
-    # initialize
-    if !slider.initialized
-      _.defaults slider,
-        initialized: true
-        has_moved : false
-        is_moving : false
-        stance : null
-      save slider
-
-    # Update the slider position when the server gets back to us
-    if slider.stance != your_opinion.stance and !slider.is_moving
-      slider.stance = your_opinion.stance
-      slider.clientX = @props.width * (slider.stance / 2 + .5)
-      if your_opinion.stance
-        slider.has_moved = true
-      save(slider)
-
-    ####
-    # Define slider layout
-    slider_style =
-      position: 'relative'
-      left: - (@props.width - BODY_WIDTH) / 2
-      width: @props.width
-      height: SLIDER_HANDLE_SIZE
-
-    if @props.backgrounded
-      css.grayscale slider_style
-
-    DIV
-      className: 'slider'
-      style : slider_style
-
-      # Draw the pole labels of the slider
-      @drawPoleLabels()
-
-      # Draw the base of the slider
-      @drawSliderBase()
-
-      if @props.focused && @props.enabled
-        @drawFeedback()
-
-      if @props.enabled
-        @drawSliderHandle()
-
-  drawPoleLabels: ->
-    slider = fetch @props.key
-
-    if !slider.docked
-      for pole_label, idx in @props.pole_labels
-        [main_text, sub_text] = pole_label
-
-        w = Math.max( widthWhenRendered(main_text, {fontSize: 30}), \
-                      widthWhenRendered(sub_text, {fontSize: 14}))
-        DIV
-          key: main_text
-          style:
-            position: 'absolute'
-            fontSize: 30
-            top: -20
-            pointerEvents: 'none'
-            left: if idx == 0 then -(w + 55)
-            right: if idx == 1 then -(w + 55)
-
-          main_text
-
-          DIV
-            key: "pole_#{sub_text}_sub"
-            style:
-              fontSize: 14
-              textAlign: 'center'
-
-            sub_text
-
-    else
-      for pole_label, idx in @props.pole_labels
-        DIV
-          key: "small-#{pole_label[0]}"
-          style:
-            position: 'absolute'
-            fontSize: 20
-            top: -12
-            pointerEvents: 'none'
-            left: if idx == 0 then -15
-            right: if idx == 1 then -20
-
-          if idx == 0 then '–' else '+'
-
-  drawSliderBase: ->
-    slider = fetch @props.key
-
-    slider_base_style =
-      width: @props.width
-      height: 6
-      backgroundColor: '#BBBFC7'
-      position: 'absolute'
-
-    DIV
-      style : slider_base_style
-
-      if !slider.docked
-
-        for support in [true, false]
-          DIV
-            key: "slider-base-#{support}"
-            style:
-              position: 'absolute'
-              left: if support then -5
-              right: if !support then -5
-              width: 5
-              height: slider_base_style.height
-              backgroundColor: slider_base_style.backgroundColor
-
-            DIV
-              style: cssTriangle \
-                       (if support then 'left' else 'right'), \
-                       slider_base_style.backgroundColor, 12, 6,
-                          position: 'absolute'
-                          left: if support then -12
-                          right: if !support then -12
-
-  drawFeedback: ->
-    slider = fetch @props.key
-
-    slider_feedback =
-      if !slider.has_moved
-        'Slide Your Overall Opinion'
-      else if isNeutralOpinion(slider.stance)
-        "You are Undecided"
-      else
-        degree = Math.abs(slider.stance)
-        strength_of_opinion = if degree > .999
-                                "Fully "
-                              else if degree > .5
-                                "Firmly "
-                              else
-                                "Slightly "
-
-        valence = customization "slider_pole_labels.individual." + \
-                                if slider.stance > 0 then 'support' else 'oppose'
-
-        "You #{strength_of_opinion} #{valence}"
-
-    feedback_style =
-      pointerEvents: 'none'
-      fontSize: 30
-      fontWeight: 700
-      color: focus_blue
-      visibility: if @props.backgrounded then 'hidden'
-
-    # Keep feedback centered over handle, but keep within the bounds of
-    # the slider region when the slider is in an extreme position.
-    feedback_left = @props.width * (slider.stance/2 + .5)
-    feedback_width = widthWhenRendered(slider_feedback, feedback_style) + 10
-
-    if slider.docked
-      if slider.stance > 0
-        feedback_left = Math.min(@props.width - feedback_width/2, feedback_left)
-      else
-        feedback_left = Math.max(feedback_width/2, feedback_left)
-
-    _.extend feedback_style,
-      position: 'absolute'
-      top: if slider.docked then -57 else -80
-      left: feedback_left
-      marginLeft: -feedback_width / 2
-      width: feedback_width
-
-    DIV
-      style: feedback_style
-      slider_feedback
-
-  drawSliderHandle: ->
-    slider = fetch @props.key
-    DIV
-      className: 'the_handle'
-      onMouseUp: @handleMouseUp
-      onTouchEnd: @handleMouseUp
-      onTouchCancel: @handleMouseUp
-
-      onMouseDown: @handleMouseDown
-      onTouchStart: @handleMouseDown
-
-      onTouchMove: @handleMouseMove
-      style: css.crossbrowserify
-        width: SLIDER_HANDLE_SIZE
-        height: SLIDER_HANDLE_SIZE
-        transition: "transform #{TRANSITION_SPEED}ms"
-        transform: "scale(#{if !@props.focused || slider.docked then 1 else 2.5})"
-        visibility: if @props.backgrounded then 'hidden'
-        top: -9
-        position: 'relative'
-        marginLeft: -SLIDER_HANDLE_SIZE / 2
-        zIndex: 10
-        left: slider.clientX
-
-
-      customization('slider_handle')
-        value: (slider.stance + 1) / 2
-        detail: @props.focused
-
-      if @props.focused && !slider.has_moved
-        for support in [true, false]
-          DIV
-            style:
-              right: if support then -15
-              left: if !support then -15
-              position: 'absolute'
-              top: 3
-              color: focus_blue
-              fontSize: 12
-            if support then '>' else '<'
-
-
-
-  # Kick off sliding
-  handleMouseDown: (e) ->
-    el = @getDOMNode()
-    # Dragging has to start by dragging the slider handle
-    return if !$(e.target).is('.the_handle')
-
-    e.preventDefault()
-
-    # Initiate dragging
-    slider = fetch(@props.key)
-    slider.is_moving = true
-    slider.offsetX = e.clientX or e.touches[0].clientX
-
-    slider.startX = parseInt($(e.target)[0].style.left, 10) || 0
-    save slider
-
-    $(window).on "mousemove.slider", @handleMouseMove
-    $(window).on "mouseup.slider", @handleMouseUp
-
-  # Stop sliding
-  handleMouseUp: (e) ->
-    # Don't do anything if we're not actually dragging. We only hit this logic
-    # if there is some delay in removing the event handlers.
-    slider = fetch @props.key
-
-    return if !slider.is_moving
-
-    e.preventDefault()
-
-    your_opinion = fetch @props.your_opinion
-
-    # Clicking on the slider handle should transition us between
-    # crafting <=> results. We should also transition to crafting
-    # when we've been dragging on the results page.
-    if @props.additionalOnMouseUp
-      @props.additionalOnMouseUp e
-
-    # We save the slider's position to the server only on mouse-up.
-    # This way you can drag it with good performance.
-    if your_opinion.stance != slider.stance
-      your_opinion.stance = slider.stance
-      save your_opinion
-      window.writeToLog
-        what: 'move slider'
-        details: {stance: slider.stance}
-
-    # Turn off dragging
-    slider.is_moving = false
-    save slider
-
-    $(window).off ".slider" # Remove event handlers
-
-  # While sliding
-  handleMouseMove: (e) ->
-    e.preventDefault() # prevents text selection of surrounding elements
-
-    slider = fetch(@props.key)
-
-    clientX = if e.clientX?
-                e.clientX
-              else
-                e.touches[0].clientX
-
-    # Update position
-    slider.clientX = slider.startX + clientX - slider.offsetX
-    slider.clientX = 0 if slider.clientX < 0
-    slider.clientX = @props.width if slider.clientX > @props.width
-    slider.has_moved = true
-
-    # convert position of handle to a slider value on [1, -1]
-    slider.stance = translatePixelXToStance(slider.clientX, @props.width)
-
-    save slider
-

 ####
@@ -333,11 +36,13 @@
 slider_handles.face = (props) ->

   SVG
-    height: SLIDER_HANDLE_SIZE
-    width: SLIDER_HANDLE_SIZE
+    height: props.height
+    width: props.width
     viewBox: "-2 -1 104 104"
     style:
       pointerEvents: 'none'
+      position: 'absolute'
+      top: 0

     DEFS null,
       svg.innerbevel
@@ -346,8 +51,8 @@
                   {color: 'white', opacity:  .25, dx:  0, dy: 3, stdDeviation: 3}]

     CIRCLE
-      fill: focus_blue
-      stroke: focus_blue
+      fill: props.color
+      stroke: props.color
       cx: 50
       cy: 50
       r: 50
@@ -356,7 +61,7 @@
       G null,

         CIRCLE
-          fill: focus_blue
+          fill: props.color
           filter: "url(#handle-innerbevel)"

           cx: 50
@@ -416,13 +121,16 @@

 slider_handles.flat = (props) ->
-
-  SVG
-    height: SLIDER_HANDLE_SIZE
-    width: SLIDER_HANDLE_SIZE
+  svg_props =
+    height: props.handle_height
+    width: props.handle_width
     viewBox: "-2 -1 104 104"
     style:
       pointerEvents: 'none'
+      position: 'absolute'
+      top: 0
+
+  SVG svg_props,

     DEFS null,

@@ -439,29 +147,409 @@
                   {color: 'white', opacity:  .25, dx:  0, dy: 3, stdDeviation: 3}]

     CIRCLE
-      fill: focus_blue
-      stroke: focus_blue
+      fill: props.color
+      stroke: props.color
       filter: 'url(#handle-innerbevel)'
       cx: 50
       cy: 50
       r: 50

-    do =>
-      user = fetch(fetch('/current_user').user)
-
-      if user.avatar_file_name
-        IMAGE
-          'xlink:href': avatarUrl user, 'large'
-          x: 2
-          y: 2
-
-          width: 96
-          height: 96
-
-          clipPath: 'url(#slider-avatar-clip)'
-
-
-
+    if props.use_face
+      do =>
+        user = fetch(fetch('/current_user').user)
+
+        if user.avatar_file_name
+          IMAGE
+            'xlink:href': avatarUrl user, 'large'
+            x: 2
+            y: 2
+
+            width: 96
+            height: 96
+
+            clipPath: 'url(#slider-avatar-clip)'
+
+
+window.Slider = ReactiveComponent
+  displayName: 'Slider'
+
+  getDefaultProps : ->
+    return {
+      base_height: 6
+      base_color: 'rgb(160, 160, 160)'
+      two_way: false
+      draw_helpers: false
+      focused: true
+      enabled: true
+      handle: slider_handles.flat
+    }
+
+  render : ->
+
+    slider = fetch @props.key
+
+    # initialize
+    if !slider.position?
+      _.defaults slider,
+        position: -1.0 #TODO: make the base slider widget operate from 0 to 1.0
+        has_moved : false
+        is_moving : false
+      save slider
+
+    ####
+    # Define slider layout
+    slider_style = _.defaults {}, (@props.slider_style || {}),
+      width: @props.width
+      height: @props.height
+      position: 'relative'
+
+
+    DIV
+      className: 'slider'
+      style : slider_style
+
+      # Draw the base of the slider
+      @drawSliderBase()
+
+      if @props.enabled
+        @drawSliderHandle()
+
+
+  drawSliderBase: ->
+    slider = fetch @props.key
+
+    slider_base_style =
+      width: @props.width
+      height: @props.base_height
+      backgroundColor: @props.base_color
+      position: 'absolute'
+
+    DIV
+      style : slider_base_style
+
+      # Draw sharp edge to base
+      if !slider.docked && @props.two_way
+
+        for support in [true, false]
+          DIV
+            key: "slider-base-#{support}"
+            style:
+              position: 'absolute'
+              left: if support then -5
+              right: if !support then -5
+              width: 5
+              height: slider_base_style.height
+              backgroundColor: slider_base_style.backgroundColor
+
+            DIV
+              style: cssTriangle \
+                       (if support then 'left' else 'right'), \
+                       slider_base_style.backgroundColor, 12, 6,
+                          position: 'absolute'
+                          left: if support then -12
+                          right: if !support then -12
+
+
+  drawSliderHandle: ->
+    handle_width = handle_height = @props.height
+
+    slider = fetch @props.key
+
+    DIV
+      className: 'the_handle'
+      onMouseUp: @handleMouseUp
+      onTouchEnd: @handleMouseUp
+      onTouchCancel: @handleMouseUp
+
+      onMouseDown: @handleMouseDown
+      onTouchStart: @handleMouseDown
+
+      onTouchMove: @handleMouseMove
+
+      style: css.crossbrowserify _.extend (@props.handle_style || {}),
+        width: handle_width
+        height: handle_height
+        top: -(handle_height - @props.base_height) / 2
+        position: 'relative'
+        marginLeft: -handle_width / 2
+        zIndex: 10
+        left: slider.clientX
+
+
+      @props.handle _.extend (@props.handle_props || {}),
+        value: (slider.position + 1) / 2
+        detail: @props.focused
+        handle_height: handle_height
+        handle_width: handle_width
+
+
+      if @props.draw_helpers
+        for support in [true, false]
+          DIV
+            style:
+              right: if support then -15
+              left: if !support then -15
+              position: 'absolute'
+              top: 4
+              color: focus_blue
+              fontSize: 12
+              pointerEvents: 'none'
+            if support then '>' else '<'
+
+
+  # Kick off sliding
+  handleMouseDown: (e) ->
+    el = @getDOMNode()
+    # Dragging has to start by dragging the slider handle
+    return if !$(e.target).is('.the_handle')
+
+    e.preventDefault()
+
+    # Initiate dragging
+    slider = fetch(@props.key)
+    slider.is_moving = true
+    slider.offsetX = e.clientX or e.touches[0].clientX
+
+    slider.startX = parseInt($(e.target)[0].style.left, 10) || 0
+    save slider
+
+    @props.onMouseDownCallback(e) if @props.onMouseDownCallback
+
+    $(window).on "mousemove.slider", @handleMouseMove
+    $(window).on "mouseup.slider", @handleMouseUp
+
+  # Stop sliding
+  handleMouseUp: (e) ->
+    # Don't do anything if we're not actually dragging. We only hit this logic
+    # if there is some delay in removing the event handlers.
+    slider = fetch @props.key
+
+    return if !slider.is_moving
+
+    e.preventDefault()
+
+    # Turn off dragging
+    slider.is_moving = false
+    save slider
+
+    @props.onMouseUpCallback(e) if @props.onMouseUpCallback
+
+    $(window).off ".slider" # Remove event handlers
+
+  # While sliding
+  handleMouseMove: (e) ->
+    e.preventDefault() # prevents text selection of surrounding elements
+
+    slider = fetch(@props.key)
+
+    clientX = if e.clientX?
+                e.clientX
+              else
+                e.touches[0].clientX
+
+    # Update position
+    slider.clientX = slider.startX + clientX - slider.offsetX
+    slider.clientX = 0 if slider.clientX < 0
+    slider.clientX = @props.width if slider.clientX > @props.width
+    slider.has_moved = true
+
+    # convert position of handle to a slider value on [1, -1]
+    slider.position = translatePixelXToStance(slider.clientX, @props.width)
+
+    save slider
+
+    @props.onMouseMoveCallback(e) if @props.onMouseMoveCallback
+
+
+window.OpinionSlider = ReactiveComponent
+  displayName: 'OpinionSlider'
+
+  render : ->
+
+    slider = fetch @props.key
+    your_opinion = fetch @props.your_opinion
+
+    # initialize
+    if !slider.initialized
+      _.defaults slider,
+        initialized: true
+        stance : null
+      save slider
+
+    # Update the slider position when the server gets back to us
+    if slider.stance != your_opinion.stance and !slider.is_moving
+      slider.stance = your_opinion.stance
+      slider.clientX = @props.width * (slider.stance / 2 + .5)
+      if your_opinion.stance
+        slider.has_moved = true
+      save(slider)
+
+    ####
+    # Define slider layout
+    slider_style =
+      position: 'relative'
+      left: - (@props.width - BODY_WIDTH) / 2
+      width: @props.width
+      height: SLIDER_HANDLE_SIZE
+
+    if @props.backgrounded
+      css.grayscale slider_style
+
+    DIV
+      className: 'opinion_slider'
+      style : slider_style
+
+      # Draw the pole labels of the slider
+      @drawPoleLabels()
+
+      if @props.focused && @props.enabled
+        @drawFeedback()
+
+
+      Slider
+        key: @props.key
+        width: @props.width
+        height: SLIDER_HANDLE_SIZE
+        base_height: 6
+        base_color: if @props.focused then 'rgb(175, 215, 255)' else 'rgb(200, 200, 200)'
+        two_way: true
+        draw_helpers: @props.focused && !slider.has_moved
+        focused: @props.focused
+        enabled: @props.enabled
+        handle: customization('slider_handle')
+        handle_props:
+          color: focus_blue
+        handle_style:
+          transition: "transform #{TRANSITION_SPEED}ms"
+          transform: "scale(#{if !@props.focused || slider.docked then 1 else 2.5})"
+          visibility: if @props.backgrounded then 'hidden'
+
+
+        onMouseUpCallback: @handleMouseUp
+        onMouseMoveCallback: @handleMouseMove
+
+
+  drawPoleLabels: ->
+    slider = fetch @props.key
+
+    if !slider.docked
+      for pole_label, idx in @props.pole_labels
+        [main_text, sub_text] = pole_label
+
+        w = Math.max( widthWhenRendered(main_text, {fontSize: 30}), \
+                      widthWhenRendered(sub_text, {fontSize: 14}))
+        DIV
+          key: main_text
+          style:
+            position: 'absolute'
+            fontSize: 30
+            top: -20
+            pointerEvents: 'none'
+            left: if idx == 0 then -(w + 55)
+            right: if idx == 1 then -(w + 55)
+
+          main_text
+
+          DIV
+            key: "pole_#{sub_text}_sub"
+            style:
+              fontSize: 14
+              textAlign: 'center'
+
+            sub_text
+
+    else
+      for pole_label, idx in @props.pole_labels
+        DIV
+          key: "small-#{pole_label[0]}"
+          style:
+            position: 'absolute'
+            fontSize: 20
+            top: -12
+            pointerEvents: 'none'
+            left: if idx == 0 then -15
+            right: if idx == 1 then -20
+
+          if idx == 0 then '–' else '+'
+
+  drawFeedback: ->
+    slider = fetch @props.key
+
+    slider_feedback =
+      if !slider.has_moved
+        'Slide Your Overall Opinion'
+      else if isNeutralOpinion(slider.stance)
+        "You are Undecided"
+      else
+        degree = Math.abs(slider.stance)
+        strength_of_opinion = if degree > .999
+                                "Fully "
+                              else if degree > .5
+                                "Firmly "
+                              else
+                                "Slightly "
+
+        valence = customization "slider_pole_labels.individual." + \
+                                if slider.stance > 0 then 'support' else 'oppose'
+
+        "You #{strength_of_opinion} #{valence}"
+
+    feedback_style =
+      pointerEvents: 'none'
+      fontSize: 30
+      fontWeight: 700
+      color: focus_blue
+      visibility: if @props.backgrounded then 'hidden'
+
+    # Keep feedback centered over handle, but keep within the bounds of
+    # the slider region when the slider is in an extreme position.
+    feedback_left = @props.width * (slider.stance/2 + .5)
+    feedback_width = widthWhenRendered(slider_feedback, feedback_style) + 10
+
+    if slider.docked
+      if slider.stance > 0
+        feedback_left = Math.min(@props.width - feedback_width/2, feedback_left)
+      else
+        feedback_left = Math.max(feedback_width/2, feedback_left)
+
+    _.extend feedback_style,
+      position: 'absolute'
+      top: if slider.docked then -57 else -80
+      left: feedback_left
+      marginLeft: -feedback_width / 2
+      width: feedback_width
+
+    DIV
+      style: feedback_style
+      slider_feedback
+
+  # Stop sliding
+  handleMouseUp: (e) ->
+    slider = fetch @props.key
+    your_opinion = fetch @props.your_opinion
+
+    # Clicking on the slider handle should transition us between
+    # crafting <=> results. We should also transition to crafting
+    # when we've been dragging on the results page.
+    if @props.additionalOnMouseUp
+      @props.additionalOnMouseUp e
+
+    # We save the slider's position to the server only on mouse-up.
+    # This way you can drag it with good performance.
+    if your_opinion.stance != slider.stance
+      your_opinion.stance = slider.stance
+      save your_opinion
+      window.writeToLog
+        what: 'move slider'
+        details: {stance: slider.stance}
+
+
+  # While sliding
+  handleMouseMove: (e) ->
+    # convert position of handle to a slider value on [1, -1]
+    slider = fetch @props.key
+    slider.stance = slider.position
+    save slider

 styles += """


hdp_select: instantiate store

hdp_select: instantiate store

--- lib/hdp_select.coffee
+++ lib/hdp_select.coffee
@@ -15,6 +15,7 @@
 module.exports = (options, callback) ->
   options.name = options.argument if options.argument?
   options.version ?= 'latest'
+  options.store ?= {}
   if options.version and options.version not in ['latest', 'current']
     options.store['hdp_select.version.default'] = options.version
   @call (_, callback) ->


WebtermView: helper for screen's fit command

--- client/Terminal/webtermview.coffee
+++ client/Terminal/webtermview.coffee
@@ -266,6 +266,14 @@
       event.preventDefault()

+  triggerFitToWindow: ->
+
+    return  unless @terminal?.server?
+
+    @terminal.server.controlSequence String.fromCharCode 2
+    @terminal.server.input 'F'
+
+
   mainTabPaneShown: (pane) ->

     return  unless pane.hasClass('ide') and pane.getElement().contains @getElement()


mixpanel: register user to mixpanel when account changes

--- client/app/MainApp/mixpanel.coffee
+++ client/app/MainApp/mixpanel.coffee
@@ -5,12 +5,13 @@
     eventData = rest[1]
     $user   = KD.nick()

+    # TODO: are we even sending "Login" events to mixpanel?
     if eventName is "Login" or (eventName is "Groups" and eventData is "ChangeGroup")
       # identify user on mixpanel
-      @registerUser()
+      #@registerUser()
     else if eventName is "Connected to backend"
       @track eventName, KD.nick()
-      @registerUser()
+      #@registerUser()
     else if eventName is "New User Signed Up"
       @track eventName, KD.whoami().profile
     else if eventName is "User Opened Ace"
@@ -97,7 +98,7 @@
   #identifies user on mixpanel, by default username on koding, should be unique
   registerUser:->
     if KD.isLoggedIn()
-      user = KD.whoami()
+      user  = KD.whoami()
       email = user.fetchEmail (err, email)->
         mixpanel.identify user.profile.nickname
         mixpanel.people.set
@@ -109,3 +110,25 @@

   setOnce:(property, value, callback )->
     mixpanel.people.set_once property, value, callback
+
+# TODO: check if KD.config.logToExternal is true
+if mixpanel? then do ->
+  KD.getSingleton('mainController').on "AccountChanged", (account) ->
+    if KD.isLoggedIn()
+      user       = KD.whoami()
+      {nickname} = user.profile
+      email      = user.fetchEmail (err, email)->
+        {firstName, lastName} = user.profile
+        {createdAt}           = user.meta
+
+        console.log "registering user to mixpanel",
+          nickname, email, firstName, lastName, createdAt
+
+        # register user to mixpanel
+        mixpanel.identify nickname
+        mixpanel.people.set
+          "$username"   : nickname
+          "name"        : "#{firstName} #{lastName}"
+          "$joinDate"   : createdAt
+          "$email"      : email
+        mixpanel.name_tag "#{nickname}.kd.io"


Update: atom config

Update: atom config

--- Users/~/.atom/config.cson
+++ Users/~/.atom/config.cson
@@ -3,6 +3,9 @@
     makeNeverCloseSelfClosing: false
   core:
     openEmptyEditorOnStart: false
+    packagesWithKeymapsDisabled: [
+      "platformio-ide-terminal"
+    ]
     telemetryConsent: "no"
     themes: [
       "one-dark-ui"


tweak to footer

tweak to footer

Former-commit-id: 2408503f6a17c688593cea71fc0576677a787864 [formerly 302ab3752eedaf8f91f24f9fcab674bb97645d66] [formerly d1121add00507c7e93d6c5e3e6090fd9dc51b7da [formerly bb4fd7e0d30daea793ea916d91027a3ed5b29cc0]] [formerly 7cb3fcd8e51fdf4c1950c37ad763ac5b2434020e [formerly fb3bba60ed55f2420399515a4039df342134b323] [formerly f09a7397bcc5695548ba504c727a02efdc0ab995 [formerly f09a7397bcc5695548ba504c727a02efdc0ab995 [formerly c16a1c53dd2ffebd914535712acbc0d82633d280]]]]
Former-commit-id: b11aad176917520e32d6a2a0253d060de13cad7a [formerly 059ec1b2c2b5b572ed9981a9688cf20e0839bd7b] [formerly 64e7f13efc924f5bcc0eeba8d0ee92cb2deecd0c [formerly 71a56ced799e84d43d661d38d94debf079c67a7c]]
Former-commit-id: 7669c9858f838c6a2bad53c97115bbdf29526ef5 [formerly a3a6cd5e8f1310cfab7d715856785a77e3be3c1b]
Former-commit-id: 2fd1e847469c89d612c7df861b1f91beb5193f39
Former-commit-id: 67183f36e9570e9334d9d38cbc9553bc9a0ccc08

--- @client/footer.coffee
+++ @client/footer.coffee
@@ -20,20 +20,30 @@
         position: 'relative'
         padding: '2.5em 0 .5em 0'
         textAlign: 'center'
-        margin: 'auto'
-        marginLeft: if customization('lefty') then 20 + BODY_WIDTH / 2
-        width: BODY_WIDTH
         zIndex: 0

       # A href: "#{subdomain.external_project_url}", target: '_blank', style: {display: 'inline-block', margin: 'auto'},
       #   if subdomain.branding.logo
       #     IMG src: "#{subdomain.branding.logo}", style: {width: 300}

-      DIV style: {marginTop: 30},
+      DIV
+        style:
+          marginTop: 30
+
         TechnologyByConsiderit()
-        DIV style: {marginTop: 5},
-          'Bug to report? Want to use this technology in your organization? '
-          A style: {textDecoration: 'none', textDecoration: 'underline'}, href: "mailto:admin@consider.it", 'Email us'
+
+        DIV
+          style:
+            marginTop: 5
+
+          'Bug to report? Want to use this technology yourself? '
+          A
+            style:
+              textDecoration: 'underline'
+            href: "mailto:admin@consider.it"
+
+            'Email us'
+          ' at admin@consider.it'

 window.TechnologyByConsiderit = ReactiveComponent
   displayName: 'TechnologyByConsiderit'


Note that stderr is optional

Note that stderr is optional

--- src/buffered-node-process.coffee
+++ src/buffered-node-process.coffee
@@ -28,7 +28,7 @@
   #      standard error of the script. The callback is called as data is
   #      received but it's buffered to ensure only complete lines are passed
   #      until the source stream closes. After the source stream has closed
-  #      all remaining data is sent in a final call.
+  #      all remaining data is sent in a final call (optional).
   #    + exit:
   #      The callback which receives a single argument containing the exit
   #      status (optional).


Push published pages to master

Push published pages to master

--- docpad.coffee
+++ docpad.coffee
@@ -31,7 +31,7 @@
   plugins:
     ghpages:
       deployRemote: 'origin'
-      deployBranch: 'gh-pages'
+      deployBranch: 'master'
       outPath: '.'
 }


Change site title and add collection function.

--- docpad.coffee
+++ docpad.coffee
@@ -2,9 +2,12 @@
 docpadConfig = {
 	templateData:
 		site:
-			title: "Dorian Patterson"
+			title: "Curly Hair Coder"

 		getPreparedTitle: -> if @document.title then "#{@document.title} | #{@site.title}" else @site.title
+		collections:
+			pages: ->
+				@getCollection("html").findAllLive({isPage:true})
 }

 # Export the Configuration


Tests: Router: Add extra spy to prevent ide loading.

It was changing the TestRunner url to IDE route.

--- client/ide/test/routes.test.coffee
+++ client/ide/test/routes.test.coffee
@@ -77,9 +77,11 @@
         mock.envDataProvider.ensureDefaultWorkspace()

         expect.spyOn dataProvider, 'ensureDefaultWorkspace'
+        expect.spyOn dataProvider, 'fetchWorkspaceByMachineUId' # to prevent ide load and url change

         routes.routeHandler 'workspace', ROUTE_PARAMS.workspace
         expect(dataProvider.ensureDefaultWorkspace).toHaveBeenCalled()
+        expect(dataProvider.fetchWorkspaceByMachineUId).toHaveBeenCalled()

       it 'should routeToLatestWorkspace if there is no machine', ->


Fix tests following 029741ea2d93031f0c9d5e57ba9cf75546976798.

Following 029741ea2d93031f0c9d5e57ba9cf75546976798 (which seemed
trivial), I forgot to check the tests (which were in fact broken, and
correctly broken).

If there is no link-text key queue, then *all* links should match.

--- content_scripts/link_hints.coffee
+++ content_scripts/link_hints.coffee
@@ -479,9 +479,9 @@
     scoreFunction = @scoreLinkHint @linkTextKeystrokeQueue.join ""
     matchingHintMarkers =
       hintMarkers
-        .filter (linkMarker) ->
+        .filter (linkMarker) =>
           linkMarker.score = scoreFunction linkMarker
-          0 < linkMarker.score
+          0 < linkMarker.score or 0 == @linkTextKeystrokeQueue.length
         .sort (a, b) ->
           if b.score == a.score then b.stableSortCount - a.stableSortCount else b.score - a.score


env for showing notify modal when starting threads

--- client/shared/helpers/apply.coffee
+++ client/shared/helpers/apply.coffee
@@ -27,8 +27,13 @@
         options.afterSaveComplete(event) if typeof options.afterSaveComplete is 'function'

   applyDiscussionStartSequence: (scope, options = {}) ->
+    steps = if ENV['DONT_NOTIFY_NEW_THREAD']
+      ['save']
+    else
+      ['save', 'announce']
+
     applySequence scope,
-      steps: ['save', 'announce']
+      steps: steps
       emitter: options.emitter or scope
       saveComplete: (_, event) ->
         LmoUrlService.goTo LmoUrlService.discussion(event.model())


add has-error/success class to parent div

--- app/assets/javascripts/helpers/ui.js.coffee
+++ app/assets/javascripts/helpers/ui.js.coffee
@@ -46,12 +46,18 @@
 _.pass = (el, html) ->
   html = "" if not html?
   $(el).addClass("pass").removeClass("fail").html(html)
+  p = $(el).parent()
+  if p[0].nodeName == "DIV" && p.hasClass("has-feedback")
+    p.addClass("has-success").removeClass("has-error")

 # Add fail and remove pass, set the given html
 _.fail = (el, msg) ->
   if msg && msg.length > 0 && msg.charAt(0) != ' '
     msg = " " + msg
-  $(el).addClass("fail").removeClass("pass").html(_.icon("remove") + msg)
+  $(el).addClass("fail").removeClass("pass").html(_.icon("exclamation-circle") + msg)
+  p = $(el).parent()
+  if p[0].nodeName == "DIV" && p.hasClass("has-feedback")
+    p.addClass("has-error").removeClass("has-success")

 # Dim the given element
 _.dim = (el) ->


[multiline] Add placeholder text to blk description on adding

This isn't the greatest fix, because it requires the user to delete the
text that appears, but it's better than adding a ton of complexity to
handle what appears to be a Chrome bug, IMHO.

closes #535

--- app/assets/javascripts/components/common/text_area_input.cjsx
+++ app/assets/javascripts/components/common/text_area_input.cjsx
@@ -36,7 +36,7 @@
     if @props.wysiwyg
       input_element = (
         <TrixEditor
-          value={@state.value}
+          value={@state.value || 'Block description…'}
           onChange={@_handleChange}
         />
       )


graph shows 15 data points per day...

graph shows 15 data points per day...

up from original 5

--- app/assets/javascripts/pv_queries.js.coffee
+++ app/assets/javascripts/pv_queries.js.coffee
@@ -16,7 +16,7 @@
     dni_string = $("#mygraph").data("dni")
     dni_array = dni_string.split(" ").map(parseFloat)
     for month in [1..12]
-      for hour in [6..16] by 1
+      for hour in [6..20] by 1
         kW = dni_array.shift()
         row = [hour, month, kW]
         data.addRow(row)
@@ -24,14 +24,14 @@
     # specify options
     options =
       width:  "500px"
-      height: "400px"
+      height: "450px"
       style: "surface"
       showPerspective: true
       showGrid: true
       showShadow: false
       keepAspectRatio: true
-      verticalRatio: 0.5
-      cameraPosition: {"horizontal": 5.6, "vertical": 0.25, "distance": 1.7}
+      verticalRatio: 0.8
+      cameraPosition: {"horizontal": 5.6, "vertical": 0.2, "distance": 1.8}

     # Instantiate our graph object.
     graph = new links.Graph3d(document.getElementById('mygraph'))


Tests: Return all users.

Tests: Return all users.

--- client/test/lib/utils/utils.coffee
+++ client/test/lib/utils/utils.coffee
@@ -39,15 +39,15 @@

     if createNewUserData
       users = @generateUsers()
-      return users[index]
+      return if index is -1 then users else users[index]

     try
       usersFile = fs.readFileSync('users.json')
       users = JSON.parse(usersFile)

-      return users[index]
+      return if index is -1 then users else users[index]

     catch

       users = @generateUsers()
-      return users[index]
+      return if index is -1 then users else users[index]


Fixed parameters

Fixed parameters

--- app/assets/javascripts/views/clouds/settings_dialog.js.coffee
+++ app/assets/javascripts/views/clouds/settings_dialog.js.coffee
@@ -48,7 +48,7 @@
         .on "change", (e) =>
           ids = @cloud.get('moderator_ids')
           ids.push(e.val)
-          @saveCloud({ moderator_ids: ids },
+          @saveCloud({ x_moderator_ids: ids },
             success: => @.$('#cloud_moderators').select2("val", "")
           )


Add apm to public API

Add apm to public API

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -3,6 +3,7 @@

 module.exports =
   _: _
+  apm: require 'atom-package-manager'
   BufferedNodeProcess: require '../src/buffered-node-process'
   BufferedProcess: require '../src/buffered-process'
   Directory: require '../src/directory'


fixed retrieve stats from statsd

fixed retrieve stats from statsd

--- src/api/statsd.coffee
+++ src/api/statsd.coffee
@@ -79,13 +79,13 @@
       path = renderUrl + ".statuses." + statusType + ".count,'1week'))&format=json";
       var result = yield exports.fetchData(path);
       if (result && result.data) {
-        total[statusType] = result.data[1][0];
+        total[statusType] = (result.data[1] || result.data[0])[0];
       } else {
         total[statusType] = 0;
       }
     }

-    return this.body = [
+     this.body = [
       {
         _id: {"channelID": channelId},
         failed: total.Failed,
@@ -95,6 +95,7 @@
         completedWErrors: total['Completed with error(s)']
       }
     ];
+    return this.body
   } else {

     path = renderUrl + ".count,'1day'))&from=-7days&format=json";


remove auth from dependencies in group-page.coffee

--- app/components/group-page/group-page.coffee
+++ app/components/group-page/group-page.coffee
@@ -1,7 +1,7 @@
 module.exports =
   url: '/groups/:groupId'
   template: require('app/components/group-page/group-page.html')
-  controller: ($scope, $auth, Records, $stateParams, $location) ->
+  controller: ($scope, Records, $stateParams, $location) ->
     # console.log('groupId: ', $stateParams.groupId)
     # Records.groups.findOrFetchByKey($stateParams.groupId).then (group) =>
     #   console.log('found group: ', Records.groups.find($stateParams.groupId))


return undefined to prevent unnecessary collection of values into array

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -106,6 +106,7 @@
           spy(obs)
       finally
         delete registerObs.running
+  undefined

 withMethodCallSupport = (wrapped) ->
   (f, args...) ->
@@ -1038,6 +1039,7 @@
         sink event
     unsubAll = ->
       sub.unsub?() for sub in subscriptions
+      undefined
     subscribeInput = (subscription) ->
       subscription.unsub = (subscription.input.subscribeInternal(guardedSink(subscription.input)))
     unsubscribeInput = (input) ->
@@ -1364,6 +1366,7 @@
   flush = ->
     while waiterObs.length > 0
       flushWaiters(0)
+    undefined

   flushWaiters = (index) ->
     obs = waiterObs[index]
@@ -1374,6 +1377,7 @@
     flushDepsOf(obs)
     for f in obsWaiters
       f()
+    undefined

   flushDepsOf = (obs) ->
     deps = obs.internalDeps()
@@ -1382,6 +1386,7 @@
       if waiters[dep.id]
         index = _.indexOf(waiterObs, dep)
         flushWaiters(index)
+    undefined

   inTransaction = (event, context, f, args) ->
     if rootEvent


don't mess up indices in sequentially/take if an exception is thrown

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -13,6 +13,7 @@
       value = eventTransformer(args...)
       unless value instanceof Array and _.last(value) instanceof Event
         value = [value]
+
       for event in value
         reply = sink(event = toEvent(event))
         if reply == Bacon.noMore or event.isEnd()
@@ -72,7 +73,12 @@
   index = 0
   Bacon.fromPoll delay, ->
     value = values[index++]
-    if index < values.length then value else [value, end()]
+    if index < values.length
+      value
+    else if index == values.length
+      [value, end()]
+    else
+      end()

 Bacon.repeatedly = (delay, values) ->
   index = 0
@@ -360,8 +366,9 @@
         if count > 0
           @push event
         else
-          @push event
-          @push end()
+          if count == 0
+            @push event
+            @push end()
           Bacon.noMore

   map: (f, args...) ->


Optimise Bacon.later, Bacon.never and Bacon.once

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -77,7 +77,7 @@
 Bacon.more = ["<more>"]

 Bacon.later = (delay, value) ->
-  withDescription(Bacon, "later", delay, value, Bacon.sequentially(delay, [value]))
+  withDescription(Bacon, "later", delay, value, Bacon.fromPoll(delay, -> [value, end()]))

 Bacon.sequentially = (delay, values) ->
   index = 0
@@ -154,9 +154,17 @@
     sink (end())
     nop

-Bacon.never = -> withDescription(Bacon, "never", Bacon.fromArray([]))
-
-Bacon.once = (value) -> withDescription(Bacon, "once", value, Bacon.fromArray([value]))
+Bacon.never = ->
+  new EventStream describe(Bacon, "never"), (sink) ->
+    sink (end())
+    nop
+
+Bacon.once = (value) ->
+  new EventStream describe(Bacon, "once", value), (sink) ->
+    sink (toEvent(value))
+    sink (end())
+    nop
+

 Bacon.fromArray = (values) ->
   assertArray values


Update display synchronously when scrolling to prevent flicker

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -460,7 +460,7 @@
     return if scrollTop == @cachedScrollTop
     @cachedScrollTop = scrollTop

-    @requestDisplayUpdate() if @attached
+    @updateDisplay() if @attached

     @renderedLines.css('top', -scrollTop)
     @underlayer.css('top', -scrollTop)


Refer to instance cursorRow variable

Refer to instance cursorRow variable

--- src/app/gutter.coffee
+++ src/app/gutter.coffee
@@ -42,7 +42,7 @@
     return if @firstScreenRow < 0

     newCursorRow = @editor().getCursorBufferPosition().row - @firstScreenRow
-    if newCursorRow isnt cursorRow
-      cursorRow = newCursorRow
+    if newCursorRow isnt @cursorRow
+      @cursorRow = newCursorRow
       @find('.line-number.cursor-line-number').removeClass('cursor-line-number')
-      @find(".line-number:eq(#{cursorRow})").addClass('cursor-line-number')
+      @find(".line-number:eq(#{newCursorRow})").addClass('cursor-line-number')


Fixup space after synctex in texify (hack?)

--- lib/builder.coffee
+++ lib/builder.coffee
@@ -82,14 +82,17 @@
     filebase = parsed_fname.base  # base includes the extension but not the dir
     filename = parsed_fname.name  # name only includes the name (no dir, no ext)

-    # TODO also read from shebang line and "project" file
-    # TODO get program as well
+    # Get options and programs
     directives = parse_tex_directives fname,
       keyMaps: 'ts-program': 'program',
       multiValues: ['options']

     user_options = atom.config.get("latextools.builderSettings.options")
     user_options = user_options.concat directives.options
+
+    # Clean up options: I think this may bbe a deeper issue, but for now:
+    if user_options.length==1 && user_options[0] == undefined
+      user_options = []

     # white-list the selectable programs
     # on Windows / miktex, allow both pdftex, etc and pdflatex


Removed unnecessary isnew class, added debug logging function to prevent browser errors when testing

--- src/coffee/jquery.rondell.coffee
+++ src/coffee/jquery.rondell.coffee
@@ -27,7 +27,6 @@
         small: "rondell-item-small"
         hidden: "rondell-item-hidden"
         loading: "rondell-item-loading"
-        isNew: "rondell-item-new"
         hovered: "rondell-item-hovered"
         overlay: "rondell-item-overlay"
         focused: "rondell-item-focused"
@@ -201,6 +200,9 @@

         @scrollbar._instance = new RondellScrollbar(scrollbarContainer, @scrollbar)

+    log: (msg) ->
+      console?.log msg
+
     # Animation functions, can be different for each rondell
     funcLeft: (l, r, i) ->
       r.center.left - r.itemProperties.size.width / 2.0 + Math.sin(l) * r.radius.x
@@ -235,11 +237,14 @@

     _addItem: (idx, item) =>
       @items[idx - 1] = item
+      @_itemIndices[idx] = idx

       # Add some private variables
-      item.id = idx
-      item.animating = false
-      @_itemIndices[idx] = item.currentSlot = idx
+      $.extend item,
+        id: idx
+        animating: false
+        isNew: true
+        currentSlot: idx

       # If item is an img tag, wrap with div
       if item.object.is "img"
@@ -248,7 +253,8 @@

       # Init click events
       item.object
-      .addClass("#{@classes.isNew} #{@classes.item}")
+      .addClass("#{@classes.item}")
+      .data("item", item)
       .css
         opacity: 0
         width: item.sizeSmall.width
@@ -676,7 +682,7 @@

         # Move item to it's new target
         item.object.stop(true)
-        .removeClass("#{@classes.isNew} #{@classes.focused}")
+        .removeClass(@classes.focused)
         .css
           zIndex: newZ
           display: "block"


replaced custom drop menu with an HTML SELECT

I don’t know why I didn’t use a normal select element in the first
place.

--- @client/roles.coffee
+++ @client/roles.coffee
@@ -131,7 +131,7 @@
         wildcard: {label: 'Any registered user can post new proposals', default: true}},
       {
         name: 'visitor',
-        label: 'Users who can access forum',
+        label: 'People who can access forum',
         description: 'If set to private forum, invite specific people to join below.',
         #icon: 'fa-android',
         wildcard: {label: 'Public forum. Anyone with a link can see all proposals.', default: true}}
@@ -214,25 +214,6 @@
         save @local

-    trigger = (e, role) =>
-      @local.role = role
-      @local.roles_menu = false
-      save @local
-      e.stopPropagation()
-      e.preventDefault()
-
-    set_focus = (idx) =>
-      idx = 0 if !idx?
-      @local.focus = idx
-      @refs["menuitem-#{idx}"].getDOMNode().focus()
-      save @local
-
-
-    close_menu = =>
-      document.activeElement.blur()
-      @local.roles_menu = false
-      save @local
-
     other_roles = (r for r in @props.roles when r.name != @local.role.name)
     DIV
       style:
@@ -241,167 +222,28 @@
         padding: '18px 24px'

-
-      # Show (and optionally change) the role currently being modified
-      # by the invite component
-      DIV
-        ref: 'menu_wrap'
-        key: 'select_role_menu'
+      SELECT
+        id: 'select_role'
+        type: 'text'
+        name: 'select_role'
+        defaultValue: 0
+        onChange: (event) =>
+          role = other_roles[event.target.value]
+          @local.role = role
+          save @local
+
         style:
-          fontWeight: 500
           fontSize: 18
-          marginBottom: 6
+          marginBottom: 18
           display: 'inline-block'

-        onTouchEnd: =>
-          @local.roles_menu = !@local.roles_menu
-          save(@local)
-
-        onMouseEnter: => @local.roles_menu = true; save(@local)
-        onMouseLeave: close_menu
-
-        onFocus: =>
-          @local.roles_menu = true
-          save(@local)
-
-        onBlur: (e) =>
-          setTimeout =>
-            # if the focus isn't still on an element inside of this menu,
-            # then we should close the menu
-            if $(document.activeElement).closest(@refs.menu_wrap.getDOMNode()).length == 0
-              @local.roles_menu = false; save @local
-          , 0
-
-        onKeyDown: (e) =>
-          if e.which == 13 || e.which == 27 # ENTER or ESC
-            close_menu()
-            e.preventDefault()
-          else if e.which == 38 || e.which == 40 # UP / DOWN ARROW
-            @local.focs = -1 if !@local.focus?
-            if e.which == 38
-              @local.focus--
-              if @local.focus < 0
-                @local.focus = other_roles.length - 1
-            else
-              @local.focus++
-              if @local.focus > other_roles.length - 1
-                @local.focus = 0
-            set_focus(@local.focus)
-            e.preventDefault() # prevent window from scrolling too
-
-
-
-
-        if (r for r in other_roles when !r.hide).length > 0
-          BUTTON
-            id: 'select_new_role'
-            tabIndex: 0
-            'aria-haspopup': "true"
-            'aria-owns': "role_menu_popup"
-            style:
-              backgroundColor: '#ddd'
-              padding: '8px 12px'
-              borderRadius: 8
-              cursor: 'pointer'
-              border: 'none'
-              border: '1px solid #aaa'
-
-            I
-              className: "fa #{@local.role.icon}"
-              style:
-                displayName: 'inline-block'
-                margin: '0 8px 0 0'
-            "Add #{@local.role.label}"
-
-
-            I style: {marginLeft: 8}, className: "fa fa-caret-down"
-        else
-          DIV
-            id: 'select_new_role'
-            style:
-              backgroundColor: 'rgba(100,100,150,.1)'
-              padding: '8px 12px'
-              borderRadius: 8
-              border: 'none'
-
-            I
-              className: "fa #{@local.role.icon}"
-              style:
-                displayName: 'inline-block'
-                margin: '0 8px 0 0'
-            "Add #{@local.role.label}"
-
-
-
-        UL
-          id: 'role_menu_popup'
-          role: "menu"
-          'aria-hidden': !@local.roles_menu
-          hidden: !@local.roles_menu
-
-          style:
-            width: 500
-            position: 'absolute'
-            left: if @local.roles_menu then 24 else -9999
-            zIndex: 99
-            listStyle: 'none'
-            backgroundColor: '#fff'
-            border: '1px solid #eee'
-
-          for role,idx in other_roles when !role.hide
-            LI
-              role: "menuitem"
-              ref: "menuitem-#{idx}"
-              className: 'invite_menu_item'
-              tabIndex: 0
-              style:
-                padding: '2px 12px'
-                fontSize: 18
-                cursor: 'pointer'
-                borderBottom: '1px solid #fafafa'
-                backgroundColor: if @local.focus == idx then '#414141'
-                color: if @local.focus == idx then 'white'
-              key: "menuitem-#{idx}"
-
-              onClick: do(idx, role) => (e) =>
-                if @local.focus != idx
-                  set_focus idx
-                trigger(e, role)
-
-              onTouchEnd: do(idx, role) => (e) =>
-                if @local.focus != idx
-                  set_focus idx
-                trigger(e, role)
-
-              onKeyDown: do(role) => (e) =>
-                trigger(e, role) if e.which == 13 #ENTER
-
-              onFocus: do(idx) => (e) =>
-                if @local.focus != idx
-                  set_focus idx
-                e.stopPropagation()
-
-              onMouseEnter: do(idx) => =>
-                if @local.focus != idx
-                  set_focus idx
-
-              onBlur: do(idx) => (e) =>
-                @local.focus = null
-                save @local
-
-              onMouseExit: do(idx) => =>
-                @local.focus = null
-                save @local
-
-              I
-                className: "fa #{role.icon}"
-                style:
-                  displayName: 'inline-block'
-                  margin: '0 8px 0 0'
-              "Add #{role.label}"
-
-
-
+
+        for role,idx in other_roles
+          if !role.hide
+            do (role, idx) =>
+              OPTION
+                value: idx
+                "Add #{role.label}"

       # Show everyone queued for being added/invited to a role


Fix rising at high speed

Fix rising at high speed

--- app/scripts/lib/script_modules/level.coffee
+++ app/scripts/lib/script_modules/level.coffee
@@ -268,18 +268,18 @@
       @_verify(sequence)
       d = WhenJS.defer()

-      currentSpeed = @level._forcedSpeed?.x || @level._forcedSpeed
+      currentSpeed = @level._forcedSpeed?.x ? @level._forcedSpeed
       { duration } = options
       if @_skippingToCheckpoint() or duration is 0
         @level.setHeight -height
       else
         speedY = (height / duration) * 1000

-        @level.setForcedSpeed(x: currentSpeed, y: -speedY)
+        @level.setForcedSpeed({ x: currentSpeed, y: -speedY }, accellerate: no)
         level = @level
         Crafty.e('Delay').delay(
           ->
-            level.setForcedSpeed(currentSpeed)
+            level.setForcedSpeed(currentSpeed, accellerate: no)
             d.resolve()
           duration
         )


fix for color with empty string input

fix for color with empty string input

--- framer/Layer.coffee
+++ framer/Layer.coffee
@@ -32,7 +32,7 @@

 			# console.log "Layer.#{name}.set #{value} current:#{@[name]}"

-			if transformer and (value or value is null)
+			if transformer
 				value = transformer(value)

 			# Return unless we get a new value


Fix small bug

Fix small bug

--- framer/Utils.coffee
+++ framer/Utils.coffee
@@ -263,7 +263,7 @@
 	# Returns an Object
 	#

-	point = exports.extend {}, point
+	point = _.clone point

 	traverse = (view) ->