rewrote the whole Input Method

rewrote the whole Input Method

--- lib/input-method.coffee
+++ lib/input-method.coffee
@@ -4,95 +4,98 @@
 {View, Point, $} = require 'atom'
 {log, warn, error} = require './logger'

+# Input Method Singleton (initialized only once per editor, activaed or not)
 class InputMethod extends EventEmitter

     activated: false
-    input: ''
+    muteEvent: false
+
+    # raw characters
+    inputBuffer: ''
+    # synchonize with the text buffer
+    outputBuffer: ''
+    # mark the starting position of the input buffer when activated
+    startPosition: null
+

     @trie: require './input-method/keymap.js'

     constructor: (@core) ->
-        @decorator = new InputMethodDecorator @core
+
+        # passes InputMethod to the Decorator
+        # the Decorator listens to 'resize' of InputMethod
+        @decorator = new InputMethodDecorator @core, @
+
+        @on 'deactivate', (range) =>
+            log 'IM', "deactivate #{range}"
+            @deactivate()
+
+        @on 'insert', (range, char) =>
+            log 'IM', "insert '#{char}'"
+            {output, further, candidateKeys, candidateSymbols} = @translate @inputBuffer
+            log 'IM', "@inputBuffer: '#{@inputBuffer}' translates to '#{output}'"
+            @updateOutputBuffer output
+            if further
+                @core.panel.activateIM @inputBuffer, candidateKeys, candidateSymbols
+            else
+                @deactivate()
+
+        @on 'delete', (range, textBuffer) =>
+            {output, further, candidateKeys, candidateSymbols} = @translate @inputBuffer
+            log 'IM', "delete #{range} #{textBuffer} #{@inputBuffer}"
+            if further
+                @core.panel.activateIM @inputBuffer, candidateKeys, candidateSymbols
+

     activate: ->
         if not @activated

+
+            @startPosition = @core.editor.getCursorBufferPosition()
             log 'IM', 'activated'
-
+            @inputBuffer = ''
+            @outputBuffer = ''
             @activated = true
-
-            # range & marker
-            start = @core.editor.getCursorBufferPosition()
-            @marker = @core.editor.markBufferRange(new Range start, start)
-
-            # insert '\' to the buffer
-            @core.editor.getBuffer().insert start, '\\'
-
-            # kick off the decorator view
-            @decorator.resize @marker.bufferMarker.range
+            @decorator.show()
+
+            # monitors raw text buffer and figures out what happend
+            @textBufferMarker = @core.editor.markBufferRange(new Range @startPosition, @startPosition)
+            @textBufferMarker.on 'changed', @analysisEvent
+
+            # insert '\' at the cursor
+            @updateOutputBuffer '\\'

             # initial input suggestion
-            candidateKeys = Object.keys(_.omit InputMethod.trie, '>>')
-            @core.panel.activateIM '', candidateKeys, []
-
-
-            # triggered then new characters are typed in
-            @marker.on 'changed', (ev) =>
-
-                # range & decorator
-                range = new Range ev.newTailBufferPosition, ev.newHeadBufferPosition
-
-                # log 'IM', "input buffer: #{@input}"
-                # log 'IM', "text buffer: #{@core.editor.getBuffer().getTextInRange(range)}"
-
-                # update input content incrementally,
-                # append only with the last character,
-                # since the former characters may could have been replaced with a preview symbol
-                newCharacter = @core.editor.getBuffer().getTextInRange(range).substr(1).substr(-1)
-
-                # update @input with the newly inserted character
-                @input += newCharacter
-
-                # see if the input is in the keymap
-                {valid, result} = @validate()
-
-                candidateKeys = Object.keys(_.omit result, '>>')
-                candidateSymbols = result[">>"]
-
-                if valid
-                    if Object.keys(result).length is 1
-                        # no further possible key combinations
-                        # replace with symbol right away
-                        @deactivate()
-                        symbol = result['>>'][0]
-                        @core.editor.getBuffer().setTextInRange range, symbol
-                        @deactivate()
-                    else
-                        # further key combinations are possible
-                        if result['>>'].length > 0
-                            symbol = result['>>'][0]
-                            @core.editor.getBuffer().setTextInRange range, symbol
-                            @decorator.resize(new Range range.start, range.start.translate(new Point 0, 1))
-                        else
-                            @decorator.resize range
-
-                        @core.panel.activateIM @input, candidateKeys, candidateSymbols
-
-                else
-                    # key combination out of keymap
-                    # replace with closest the symbol possible
-                    symbol = result['>>']
-                    if symbol.length > 0
-                        lastInput = @input.substr -1
-                        refill = symbol[0] + lastInput
-                        @core.editor.getBuffer().setTextInRange range, refill
-                    @deactivate()
+            @core.panel.activateIM '', @getCandidateKeys InputMethod.trie, []

         else
             # input method already activated
-            # this will happen only 2 consecutive backslash '\' was punched in
-            # and we shall leave 1 backslash in the buffer, then deactivate
+            # this will happen when the 2nd backslash '\' got punched in
+            # we shall leave 1 backslash in the buffer, then deactivate
             @deactivate()
+
+    analysisEvent: (ev) =>
+
+        # see if the text buffer's resized
+        range = new Range ev.newTailBufferPosition, ev.newHeadBufferPosition
+        textBuffer = @core.editor.getBuffer().getTextInRange range
+        if @getRange().compare range isnt 0
+            @emit 'resize', range
+
+        unless @muteEvent
+            if textBuffer.length is 0
+                # got wiped out
+                @emit 'deactivate', range
+            else
+                if textBuffer.length > @outputBuffer.length
+                    @inputBuffer += textBuffer.substr -1
+                    @outputBuffer = textBuffer
+                    @emit 'insert', range, textBuffer.substr -1
+                if textBuffer.length < @outputBuffer.length
+                    @inputBuffer = @inputBuffer.substr(0, @inputBuffer.length - 1)
+                    @outputBuffer = textBuffer
+                    @emit 'delete', range, textBuffer
+

     deactivate: ->
@@ -101,37 +104,96 @@
             log 'IM', 'deactivated'
             @core.panel.deactivateIM()
             @decorator.hide()
-            @marker.destroy()
+            @textBufferMarker.destroy()
             @activated = false
-            @input = ''
-
-    # if the input is in the keymap
-    validate: ->
+
+    getRange: ->
+        new Range(@startPosition, @startPosition.translate(new Point 0, @outputBuffer.length))
+
+    updateOutputBuffer: (text) ->
+
+        # update text buffer
+        @muteEvent = true
+        @core.editor.getBuffer().delete @getRange()
+        @core.editor.getBuffer().insert @startPosition, text
+        @muteEvent = false
+
+        # update output buffer
+        @outputBuffer = text
+
+    ##################
+    ###   Keymap   ###
+    ##################
+
+    getCandidateKeys: (trie) -> Object.keys(_.omit trie, '>>')
+    getCandidateSymbols: (trie) -> trie['>>']
+
+    # see if input is in the keymap
+    validate: (input) ->
         valid = true
-        cursor = InputMethod.trie
-        for i in [0 .. @input.length - 1]
-            char = @input.charAt i
-            next = cursor[char]
+        trie = InputMethod.trie
+        for i in [0 .. input.length - 1]
+            char = input.charAt i
+            next = trie[char]
             if next
-                cursor = next
+                trie = next
             else
                 valid = false
                 break

         return {
             valid: valid
-            result: cursor
+            trie: trie
         }

+    # converts characters to symbol, and tells if there's any further possible combinations
+    translate: (input) ->
+        {valid, trie} = @validate input
+        candidateKeys    = @getCandidateKeys trie
+        candidateSymbols = @getCandidateSymbols trie
+        if valid
+            if candidateKeys.length is 0
+                if candidateSymbols.length is 0
+                    output = '\\' + input
+                else
+                    output = candidateSymbols[0]
+                return {
+                    output: output
+                    further: false
+                }
+            else
+                if candidateSymbols.length is 0
+                    output = @outputBuffer
+                else
+                    output = candidateSymbols[0]
+                return {
+                    output: output
+                    further: true
+                    candidateKeys: candidateKeys
+                    candidateSymbols: candidateSymbols
+                }
+
+        else
+            # key combination out of keymap
+            # replace with closest the symbol possible
+            # log 'IM!', "input buffer: #{@inputBuffer} \n output buffer #{@outputBuffer} \n input #{input}"
+            return {
+                output: @outputBuffer
+                further: false
+            }
+

 class InputMethodDecorator extends View

     @content: -> @div outlet: 'decorator'

-    initialize: (@core) ->
+    initialize: (@core, inputMethod) ->

         @core.editor.editorView.overlayer.append @
         @addClass 'agda-input-method'
+
+        inputMethod.on 'resize', (range) =>
+            @resize range

     resize: (range) ->


Add settings view package description

Add settings view package description

--- package.cson
+++ package.cson
@@ -1,2 +1,3 @@
 'main': './lib/settings'
 'deferredDeserializers': ['SettingsView']
+'description': 'GUI pane for Atom settings'


Removed debug code

Removed debug code

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -610,7 +610,6 @@
 			@config.env = @instanceConfig.env or @websiteConfig.env or @websitePackageConfig.env or @initialConfig.env

 			# Merge configuration
-			debugger
 			balUtil.deepExtendPlainObjects(
 				@config
 				@initialConfig
@@ -2605,7 +2604,6 @@
 							# Prepare
 							pageUrl = req.url.replace(/\?.*/,'')
 							document = database.findOne(urls: $has: pageUrl)
-							console.log('\n\n=========================================\n', pageUrl, document.id, document.get('urls'))
 							return next()  unless document

 							# Check if we are the desired url


Utilisation préférée de la variable t de game

--- js/game/scenes/LoadingScene.coffee
+++ js/game/scenes/LoadingScene.coffee
@@ -1,9 +1,6 @@
 # Classe abstraite représentant une scène
 define ['cs!game/scenes/Scene'], (Scene) ->
     class LoadingScene extends Scene
-
-        constructor: (@game) ->
-            @t = 0

         # Progression du chargement
         handleProgress: (e) ->
@@ -23,7 +20,7 @@

             ctx.fillStyle = '#db7d39'
             for i in [0 .. 3]
-                if i < Math.floor(@t * 3) % 4
+                if i < Math.floor(@game.t * 3) % 4
                     ctx.fillRect(12 + 24 * i, @game.canvas.height - 24, 12, 12)

         # Chargement terminé


:fire: Inline LinterViews::renderPanelMarkers

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -34,9 +34,11 @@
   render: ({added, removed, messages}) ->
     @messages = @classifyMessages(messages)
     @panel.setMessages({added, removed})
-    @renderPanelMarkers({added, removed})
     @renderBubble()
     @renderCount()
+    @linter.eachEditorLinter((editorLinter) =>
+      editorLinter.updateMarkers({added, removed})
+    )

   renderLineMessages: (render = false) ->
     @classifyMessagesByLine(@messages)
@@ -92,11 +94,6 @@
   renderCount: ->
     @bottomContainer.setCount(@count)

-  renderPanelMarkers: ({added, removed}) ->
-    @linter.eachEditorLinter((editorLinter) =>
-        editorLinter.updateMarkers({added, removed})
-    )
-
   attachBottom: (statusBar) ->
     @subscriptions.add atom.config.observe('linter.statusIconPosition', (statusIconPosition) =>
       @bottomBar?.destroy()


fix small bug

fix small bug

--- src/api/vm.coffee
+++ src/api/vm.coffee
@@ -547,7 +547,7 @@
   # effort" meaning "try to restart this VM if possible but don't consider the
   # Pool to be overcommitted if this is not possible"; "restart" meaning "this
   # VM should be restarted"; "" meaning "do not try to restart this VM"
-  high_availability: { type: 'string', optional: true }
+  high_availability: { type: 'boolean', optional: true }

   # Number of virtual CPUs to allocate.
   CPUs: { type: 'integer', optional: true }


:fire: Remove on/off usage for resize:end event

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -135,8 +135,8 @@
     # The resize:end event is dispatched at the end of an animated resize
     # to not flood the cpu with updates.
     @subscriptions.add new Disposable =>
-      $(window).off 'resize:end', @onScrollViewResized
-    $(window).on 'resize:end', @onScrollViewResized
+      window.removeEventListener 'resize:end', @onScrollViewResized
+    window.addEventListener 'resize:end', @onScrollViewResized

     @miniScrollVisible = atom.config.get('minimap.minimapScrollIndicator')
     @miniScroller.toggleClass 'visible', @miniScrollVisible


Better error message

Better error message

--- lib/pg-hoff-util.coffee
+++ lib/pg-hoff-util.coffee
@@ -22,9 +22,7 @@
     )
         .then (data) ->
             resolved = true
-            if not /Running on/.test(data)
-                throw 'Spawn, wrong output'
-
+            throw 'Spawn, wrong output' unless /Running on/.test(data)
             return data

 timeout = (ms) ->
@@ -41,7 +39,7 @@
         .then ->
             tries = tries - 1
             if tries <= 0
-                throw('to many')
+                throw('Could not find open port (5000).')
             return cmd 'lsof -i :5000'
                 .catch (err) ->
                     return checkPort(interval, tries)


Changing intercom tracking

Changing intercom tracking

--- coffee/controllers.coffee
+++ coffee/controllers.coffee
@@ -114,10 +114,6 @@
             #   category: 'interesting'
             #   label: label
             # )
-            $intercom.trackEvent('analyse',
-              event_count: $scope.user.eventIds.length
-              total_facebook_users: _.size(UserStore.users)
-            )
             $location.url('/')
             $scope.user.eventsReady = true
           , 500)
@@ -128,6 +124,13 @@
 ev.controller('EventsHomeCtrl', [
   '$scope'
   ($scope) ->
+    $intercom.trackEvent('events_home',
+      total_facebook_users: _.size(UserStore.users)
+      event_count: $scope.user.eventIds.length
+      event_ids: _.pluck($scope.user.events, 'id').join(",")
+      event_names: _.pluck($scope.user.events, 'name').join(",")
+      event_invited_counts: _.pluck($scope.user.events, 'invitedCount').join(",")
+    )
 ])

 ev.controller('TableCtrl', [
@@ -137,6 +140,9 @@
   ($scope, $routeParams, UserStore) ->
     $scope.highlightId = $routeParams.highlight
     $scope.attendees = _.sortBy(_.values(UserStore.users), (user) -> -user.getScore())
+    $intercom.trackEvent('view_table',
+      highlight_id: $scope.highlightId
+    )
 ])

 ev.controller('VennCtrl', [
@@ -149,9 +155,7 @@
   'UserStore'
   '$intercom'
   ($scope, UserStore, $intercom) ->
-    $intercom.trackEvent('view_gender_ratio_index',
-      event_count: $scope.user.eventIds.length
-    )
+    $intercom.trackEvent('view_gender_ratio_index')
 ])

 ev.controller('GenderRatioShowCtrl', [
@@ -162,8 +166,9 @@
   ($scope, $routeParams, UserStore, $intercom) ->
     $scope.event = $scope.user.events[$routeParams.id]
     $intercom.trackEvent('view_gender_ratio_show',
-      female_invited_count: $scope.event.genderCounts.invited.f
-      male_invited_count: $scope.event.genderCounts.invited.m
-      neutral_invited_count: $scope.event.genderCounts.invited.n
+      event_id: $scope.event.id
+      female_attending_count: $scope.event.genderCounts.attending.f
+      male_attending_count: $scope.event.genderCounts.attending.m
+      neutral_attending_count: $scope.event.genderCounts.attending.n
     )
 ])


Actually include unqualified modules again...

--- lib/editors.coffee
+++ lib/editors.coffee
@@ -49,7 +49,7 @@
     lines = buffer.getLines()

     # lax module parsing
-    regex = /^import\s+(?:qualified\s+)?([a-zA-Z0-9.]+)\s*(?:hiding\s*)?(?:\([^)]*\))?\s*(?:as\s+([a-zA-Z0-9.]+))/
+    regex = /^import\s+(?:qualified\s+)?([a-zA-Z0-9.]+)\s*(?:hiding\s*)?(?:\([^)]*\))?\s*(?:as\s+([a-zA-Z0-9.]+))?/
     qmodules = []
     modules = []
     lines.forEach (line) =>


Updating `toaster.coffee` config file.

Updating `toaster.coffee` config file.

--- toaster.coffee
+++ toaster.coffee
@@ -1,13 +1,50 @@
-toast 'www/src'
-  alias: 'theoricus'
-  expose: 'window'
-  vendors: [
-    "www/vendors/history.js/scripts/bundled/html4+html5/native.history.js"
-    "www/vendors/jade/runtime.js"
-    "www/vendors/inflection.js"
-    "www/vendors/jquery.js"
-    "www/vendors/JSON-js/json2.js"
+# a single build's config
+#  ~> many can be specified repeating everything bellow this line
+toast
+
+  name: 'theoricus'
+
+  # src folders (relative to this!)
+  dirs:[
+    'www/src'
   ]
+
+  # main module
+  main: 'theoricus/theoricus'
+
+  # release folder
+  release_dir: 'lib/js'
+
+  # excluded items (default=[])
+  exclude: []
+
+  # removes coffee safety wrapper (default=true - duplicates the AMD wrapper)
+  bare: true
+
+  # minifies release files (default=true)
   minify: false
-  release: 'lib/theoricus.js'
-  debug: 'lib/theoricus.debug.js'+
+  # infos for simple static server (with -s option)
+  webroot: 'lib'
+  port: 3000
+
+  # optimization configs (when specified, means toaster's loader will be used)
+  # the resulting will probably not be usefull with another AMD loader
+  optimize:
+
+    # base url to reach your release folder through http
+    base_url: 'js'
+
+    # vendors to consider (can be local or remote)
+    vendors:
+      'history': 'www/vendors/history.js/scripts/bundled/html4+html5/native.history.js'
+      'jade': 'www/vendors/jade/runtime.js'
+      'inflection': 'www/vendors/inflection.js'
+      'jquery': 'www/vendors/jquery.js'
+      'json': 'www/vendors/JSON-js/json2.js'
+
+    # each layer wil hold all the definitions needed (all levels) in order to
+    # work. if layers share dpendencies, they will not repeat acros layers.
+    # layers:
+    #   'main': ['app/app']
+    #   'users': ['app/controllers/users']


Configure sass to use libsass

Configure sass to use libsass

--- brunch-config.coffee
+++ brunch-config.coffee
@@ -10,3 +10,7 @@
     stylesheets:
       joinTo:
         'ss/app.css': /^app\/styles/
+
+  plugins:
+    sass:
+      mode: 'native'


Remove quadratic interpolation

Remove quadratic interpolation

Between the lack of symmetry, the fact that periodic functions can't be
guaranteed to join smoothly, and the overhead of precalculating the
tangents, this just isn't worth it.

--- Smooth.coffee
+++ Smooth.coffee
@@ -14,7 +14,6 @@
 	###Interpolation methods###
 	METHOD_NEAREST: 0 #Rounds to nearest whole index
 	METHOD_LINEAR: 1
-	METHOD_QUADRATIC: 2
 	METHOD_CUBIC: 3 # Default: cubic interpolation

 	###Input clipping modes###
@@ -134,39 +133,6 @@
 		return (2*t3 - 3*t2 + 1)*p[0] + (t3 - 2*t2 + t)*m[0] + (-2*t3 + 3*t2)*p[1] + (t3 - t2)*m[1]

-class QuadraticInterpolator extends AbstractInterpolator
-	constructor: (array, config) ->
-		super
-		###Precalculate tangents###
-		tangents = @calculateTangents array[1] - array[0]
-		if config.clip is Enum.CLIP_PERIODIC
-			#For periodic clipping, use a second pass to smooth the seam between cycles
-			tangents = @calculateTangents tangents[tangents.length-1]
-
-		@tangents = Smooth tangents, clip:config.clip, method:Enum.METHOD_NEAREST
-
-
-	calculateTangents: (initialTangent) ->
-		tangents = [initialTangent]
-		for i in [1...@length]
-			a = @array[i - 1]
-			b = @array[i]
-			m = tangents[i-1]
-			tangents[i] = 2*(b - a) - m
-		return tangents
-
-
-	interpolate: (t) ->
-		k = Math.floor t
-		m = @tangents k
-		start = @getClippedInput k
-		end = @getClippedInput k + 1
-		a = end - start - m
-		b = m
-		c = start
-		t -= k
-		return a*t*t + b*t + c
-

@@ -192,7 +158,6 @@
 		when Enum.METHOD_NEAREST then NearestInterpolator
 		when Enum.METHOD_LINEAR then LinearInterpolator
 		when Enum.METHOD_CUBIC then CubicInterpolator
-		when Enum.METHOD_QUADRATIC then QuadraticInterpolator
 		else
 			err = new Error
 			err.message = "The interpolation method #{config.method} is invalid."


Bug fix \pdftooltip

Bug fix \pdftooltip

The refactoring of \pdftooltip into latex2htmlCommandsAlpha meant we lost
the `math` argument.  Re-added, so \pdftooltip (the sole user) no longer
crashes.

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -149,7 +149,7 @@
 ## Process all commands starting with \ followed by a letter a-z.
 ## This is not a valid escape sequence in Markdown, so can be safely supported
 ## in Markdown too.
-latex2htmlCommandsAlpha = (tex) ->
+latex2htmlCommandsAlpha = (tex, math) ->
   tex = tex
   .replace /\\(BY|YEAR)\s*{([^{}]*)}/g, '$2'
   .replace /\\protect\b\s*/g, ''
@@ -247,7 +247,7 @@
   tex = latex2htmlDef tex
   ## After \def expansion and verbatim processing, protect math
   [tex, math] = preprocessKaTeX tex
-  tex = latex2htmlCommandsAlpha tex
+  tex = latex2htmlCommandsAlpha tex, math
   [tex, math]

 ## Full LaTeX support, including all supported commands and symbols
@@ -264,7 +264,7 @@
   tex = '
' + tex
   ## Commands
   tex = tex.replace /\\\\/g, '[DOUBLEBACKSLASH]'
-  tex = latex2htmlCommandsAlpha tex
+  tex = latex2htmlCommandsAlpha tex, math
   .replace /\\c\s*{s}/g, 'ş'
   .replace /\\c\s*{z}/g, 'z̧'
   .replace /\\v\s*{C}/g, 'Č'


Search highlights all instances, not just one

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -759,8 +759,9 @@
      (search = Router.current()?.params?.search)?
     recurse = (query) ->
       if (query.title and isTitle) or (query.body and not isTitle)
-        pattern = (query.title ? query.body)
+        pattern = query.title ? query.body
         unless pattern.$not
+          pattern = new RegExp pattern, 'g' if pattern instanceof RegExp
           text = text.replace pattern, '$&'
       for list in [query.$and, query.$or]
         continue unless list


* change deprecated properties of custom-factory

--- src/cache-factory.coffee
+++ src/cache-factory.coffee
@@ -28,10 +28,10 @@
   extend Factory,
     _cache: instanceCache = Cache(cacheSettings)

-  customFactory(Factory, aOptions) unless Factory._objects
+  customFactory(Factory, aOptions) unless Factory::_objects

-  registeredObjects = Factory._objects
-  aliases = Factory._aliases
+  registeredObjects = Factory::_objects
+  aliases = Factory::_aliases

   ###
   # result:


Add panels for theme loading and activation

--- lib/timecop-view.coffee
+++ lib/timecop-view.coffee
@@ -1,7 +1,7 @@
-{_, $$, View} = require 'atom'
+{_, $$, ScrollView} = require 'atom'

 module.exports =
-class TimecopView extends View
+class TimecopView extends ScrollView
   @content: ->
     @div class: 'timecop pane-item', tabindex: -1, =>
       @div class: 'tool-panel padded', =>
@@ -16,59 +16,90 @@
           @div class: 'inset-panel', =>
             @div class: 'panel-heading', 'Package Activation'
             @div class: 'panel-body padded', =>
-              @div class: 'text-info', outlet: 'activateSummary'
-              @ul class: 'list-group', outlet: 'activateArea'
+              @div class: 'text-info', outlet: 'packageActivateSummary'
+              @ul class: 'list-group', outlet: 'packageActivateArea'
+
+      @div class: 'tool-panel padded', =>
+        @div class: 'panel', =>
+          @div class: 'inset-panel', =>
+            @div class: 'panel-heading', 'Theme Loading'
+            @div class: 'panel-body padded', =>
+              @div class: 'text-info', outlet: 'themeLoadingSummary'
+              @ul class: 'list-group', outlet: 'themeLoadingArea'
+
+      @div class: 'tool-panel padded', =>
+        @div class: 'panel', =>
+          @div class: 'inset-panel', =>
+            @div class: 'panel-heading', 'Theme Activation'
+            @div class: 'panel-body padded', =>
+              @div class: 'text-info', outlet: 'themeActivateSummary'
+              @ul class: 'list-group', outlet: 'themeActivateArea'

   initialize: ({@uri}) ->
     if atom.getActivePackages().length > 0
-      @showLoadedPackages()
-      @showActivePackages()
+      @populateViews()
     else
       # Render on next tick so packages have been activated
-      _.nextTick =>
-        @showLoadedPackages()
-        @showActivePackages()
+      _.nextTick => @populateViews()
+
+  populateViews: ->
+    @showLoadedPackages()
+    @showActivePackages()
+    @showLoadedThemes()
+    @showActiveThemes()
+
+  getSlowPackages: (packages, timeKey) ->
+    time = 0
+    count = 0
+    packages = packages.filter (pack) ->
+      time += pack[timeKey]
+      count++
+      pack[timeKey] > 5
+    packages.sort (pack1, pack2) -> pack2[timeKey] - pack1[timeKey]
+    {time, count, packages}
+
+  createPackageView: (pack, timeKey) ->
+    $$ ->
+      @li class: 'list-item', =>
+        @span class: 'inline-block', pack.name
+        highlightClass = 'highlight-warning'
+        highlightClass = 'highlight-error' if pack[timeKey] > 25
+        @span class: "inline-block #{highlightClass}", "#{pack[timeKey]}ms"

   showLoadedPackages: ->
-    totalLoadTime = 0
-    packageCount = 0
-    loadedPackages = atom.getLoadedPackages().filter ({loadTime}) ->
-      totalLoadTime += loadTime
-      packageCount++
-      loadTime > 5
-
-    loadedPackages.sort (pack1, pack2) -> pack2.loadTime - pack1.loadTime
-    for pack in loadedPackages
-      @loadingArea.append $$ ->
-        @li class: 'list-item', =>
-          @span class: 'inline-block', pack.name
-          highlightClass = 'highlight-warning'
-          highlightClass = 'highlight-error' if pack.loadTime > 25
-          @span class: "inline-block #{highlightClass}", "#{pack.loadTime}ms"
+    {time, count, packages} = @getSlowPackages(atom.getLoadedPackages(), 'loadTime')
+    for pack in packages
+      @loadingArea.append(@createPackageView(pack, 'loadTime'))
     @loadingSummary.text """
-      Loaded #{packageCount} packages in #{totalLoadTime}ms.
-      #{loadedPackages.length} packages took longer than 5ms to load.
+      Loaded #{count} packages in #{time}ms.
+      #{packages.length} packages took longer than 5ms to load.
     """

   showActivePackages: ->
-    totalActivateTime = 0
-    packageCount = 0
-    activePackages = atom.getActivePackages().filter ({activateTime}) ->
-      totalActivateTime += activateTime
-      packageCount++
-      activateTime > 5
+    {time, count, packages} = @getSlowPackages(atom.getActivePackages(), 'activateTime')
+    for pack in packages
+      @packageActivateArea.append(@createPackageView(pack, 'activateTime'))
+    @packageActivateSummary.text """
+      Activated #{count} packages in #{time}ms.
+      #{packages.length} packages took longer than 5ms to activate.
+    """

-    activePackages.sort (pack1, pack2) -> pack2.activateTime - pack1.activateTime
-    for pack in activePackages
-      @activateArea.append $$ ->
-        @li class: 'list-item', =>
-          @span class: 'inline-block', pack.name
-          highlightClass = 'highlight-warning'
-          highlightClass = 'highlight-error' if pack.activateTime > 25
-          @span class: "inline-block #{highlightClass}", "#{pack.activateTime}ms"
-    @activateSummary.text """
-      Activated #{packageCount} packages in #{totalActivateTime}ms.
-      #{activePackages.length} packages took longer than 5ms to activate.
+  showLoadedThemes: ->
+    {time, count, packages} = @getSlowPackages(atom.themes.getLoadedThemes(), 'loadTime')
+    for pack in packages
+      @themeLoadingArea.append(@createPackageView(pack, 'loadTime'))
+    @themeLoadingSummary.text """
+      Loaded #{count} themes in #{time}ms.
+      #{packages.length} themes took longer than 5ms to load.
+    """
+
+  showActiveThemes: ->
+    {time, count, packages} = @getSlowPackages(atom.themes.getActiveThemes(), 'activateTime')
+    for pack in packages
+      @themeActivateArea.append(@createPackageView(pack, 'activateTime'))
+    @themeActivateSummary.text """
+      Activated #{count} packages in #{time}ms.
+      #{packages.length} packages took longer than 5ms to activate.
     """

   serialize: ->


Updated zoom levels for admin map

Updated zoom levels for admin map

--- app/assets/javascripts/areas.js.coffee
+++ app/assets/javascripts/areas.js.coffee
@@ -6,12 +6,12 @@
   initializeMap('map') if $('#map').length > 0

 initializeMap = (map_id) ->
-  baseMap = L.tileLayer('http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png', {maxZoom: 18})
-  baseSatellite = L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png', {maxZoom: 18})
+  baseMap = L.tileLayer('http://tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png', {maxZoom: 17})
+  baseSatellite = L.tileLayer('http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png', {maxZoom: 17})

   map = L.map map_id,
     center: [24.5, 54]
-    zoom: 9
+    zoom: 8
     layers: [baseSatellite]

   # Layers


dynamic forms working now pre refresh

dynamic forms working now pre refresh

--- app/assets/javascripts/articles.coffee
+++ app/assets/javascripts/articles.coffee
@@ -3,4 +3,10 @@
 # You can use CoffeeScript in this file: http://coffeescript.org/
 jQuery ->
   agencies = $('#article_agency_id').html()
-  console.log(agencies)+  $('#article_state_id').change ->
+    state = $('#article_state_id :selected').text()
+    options = $(agencies).filter("optgroup[label='#{state}']").html()
+    if options
+      $('#article_agency_id').html(options)
+    else
+      $('#article_agency_id').empty()


Update application.coffee syntax

Update application.coffee syntax

--- app/assets/javascripts/domready.coffee
+++ app/assets/javascripts/domready.coffee
@@ -1,9 +1,8 @@
-#-------------------------------------
-#  DOM Ready
-#  - Runs globally when the document
-#    is ready or when a new page is
-#    requested via Turbolinks
-#-------------------------------------
+# *************************************
+#
+#   Document Ready
+#
+# *************************************

 jQuery ($) ->

@@ -16,13 +15,13 @@
     #
     #---------------------------------

-    $('.js-dropdown-btn').on 'click', (e) ->
-      e.stopPropagation() # NOTE: Needed to prevent trigger click event on document
-      $(@).closest('.js-dropdown').toggleClass('is-active')
+    $('.js-dropdown-btn').on 'click', ( event ) ->
+      event.stopPropagation()
+      $(@).closest( '.js-dropdown' ).toggleClass( 'is-active' )

     # TODO: Modularize clickout behavior
-    $(document).on 'click', ->
-      $('.js-dropdown').removeClass('is-active')
+    $( document ).on 'click', ->
+      $( '.js-dropdown' ).removeClass( 'is-active' )

     #---------------------------------
     #
@@ -32,10 +31,10 @@
     #---------------------------------

     # Collapse accordion content on DOM Ready
-    $('.js-accordion-content').hide()
+    $( '.js-accordion-content' ).hide()

-    $('.js-accordion-btn').on 'click', (e) ->
-      $(@).closest('.js-accordion').find('.js-accordion-content').slideToggle()
+    $( '.js-accordion-btn' ).on 'click', ( event ) ->
+      $(@).closest( '.js-accordion' ).find( '.js-accordion-content' ).slideToggle()

     # -------------------------------------
     #
@@ -43,8 +42,8 @@
     #
     # -------------------------------------

-    $('.js-autoSubmit').on 'change', ->
-      $(@).closest('form').trigger('submit')
+    $( '.js-autoSubmit' ).on 'change', ->
+      $(@).closest( 'form' ).trigger( 'submit' )

     #---------------------------------
     #
@@ -61,9 +60,9 @@
     #---------------------------------

     Orientation.tableBank
-      context: $('.markdown')
-      element: $('table')
-      gutter: '20'
+      context : $('.markdown')
+      element : $('table')
+      gutter  : '20'

     # -------------------------------------
     #   Fullscreen Editor
@@ -73,23 +72,25 @@
       $(@).each ->
         redraw = this.offsetHeight

-    $('.js-editor-open').on 'click', (event) ->
+    $( '.js-editor-open' ).on 'click', ( event ) ->
       event.preventDefault()

-      $('.js-editor-textarea')
-        .addClass('is-editing')
-        .parent('.js-editor-overlay')
-        .addClass('is-active')
+      $( '.js-editor-textarea' )
+        .addClass( 'is-editing' )
+        .parent( '.js-editor-overlay' )
+        .addClass( 'is-active' )

-      setTimeout do $('.js-editor-textarea').focus(), 50
+      setTimeout $('.js-editor-textarea').focus(), 50

-    $(document).on 'click', '.js-editor-close', (event) ->
+    $(document).on 'click', '.js-editor-close', ( event ) ->
       event.preventDefault()

-      $('.js-editor-overlay').removeClass('is-active')
-      $('.js-editor-textarea').removeClass('is-editing').focus()
+      $( '.js-editor-overlay' ).removeClass( 'is-active' )
+      $( '.js-editor-textarea' ).removeClass( 'is-editing' ).focus()

-  # Bind behavior to document load/reload
-  # NOTE: Needed for Turbolinks compatibility
-  $(document).ready domready
-  $(document).on 'page:load', domready
+  # -------------------------------------
+  #   Turbolinks & DOM Ready Handlers
+  # -------------------------------------
+
+  $( document ).ready( domready )
+  $( document ).on( 'page:load', domready )


Add loading / empty state to talk boards page

--- app/talk/init.cjsx
+++ app/talk/init.cjsx
@@ -11,12 +11,14 @@
 ROLES = require './lib/roles'
 auth = require '../api/auth'
 {Link} = require 'react-router'
+Loading = require '../components/loading-indicator'

 module?.exports = React.createClass
   displayName: 'TalkInit'

   getInitialState: ->
     boards: []
+    loading: true

   propTypes:
     section: React.PropTypes.string # 'zooniverse' for main-talk, 'project_id' for projects
@@ -31,7 +33,7 @@
     auth.checkCurrent().then =>
       talkClient.type('boards').get(section: @props.section)
         .then (boards) =>
-          @setState {boards}
+          @setState {boards, loading: false}

   onSubmitBoard: (e) ->
     e.preventDefault()
@@ -90,10 +92,12 @@

-          {if @state.boards.length
-            @state.boards.map(@boardPreview)
-           else
-
There are currently no boards.
}
+          {if @state.loading
+            <Loading />
+           else if @state.boards?.length is 0
+
There are currently no boards.

+           else if @state.boards?.length
+             @state.boards.map(@boardPreview)}


Added options to recover at the end of execution of a target.

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -27,30 +27,58 @@

 exports.Target = class Target
 	constructor: () ->
-		@tasks = []
+		@steps = []
+		@recoveries = []

 	step: (task, callback) ->
-		@tasks.push task, callback
+		@steps.push [task, callback]
+
+	recover: (task, callback) ->
+		@recoveries.push [task, callback]

 	ignite: (dry, callback) ->
 		if dry
-			for task in @tasks
+			for entry in @steps
+				task = entry[0]
+				task_callback = entry[1]
+
 				logsmight.info task.desc if task.desc?
+
+			for entry in @recoveries
+				task = entry[0]
+				task_callback = entry[1]
+
+				logsmith.info task.desc if task.desc?

 			do callback
 		else
-			queue = async.queue (task, callback) ->
+			steps_queue = async.queue (task, callback) ->
 				logsmight.info task.desc if task.desc?

 				return task.run callback if task.run?
 				return task callback if task

-			queue.drain = callback
-
-			for task in @tasks
-				queue.push task if task
+			steps_queue.drain = () =>
+				recoveries_queue = async.queue (task, callback) ->
+					logsmight.info task.desc if task.desc?

-	copy: () -> throw new Error "not implemented"
+					return task.run callback if task.run?
+					return task callback if task
+
+				recoveries_queue.drain = callback
+
+				for entry in @recoveries
+					task = entry[0]
+					task_callback = entry[1]
+
+					recoveries_queue.push task, task_callback
+
+			for entry in @steps
+				task = entry[0]
+				task_callback = entry[1]
+
+				steps_queue.push task, task_callback
+
 	exec: () -> throw new Error "not implemented"
 	spawn: () -> throw new Error "not implemented"


fix request closure settings

fix request closure settings

--- noddy/service/oabutton/request.coffee
+++ noddy/service/oabutton/request.coffee
@@ -158,15 +158,15 @@
         req.status = 'closed'
         req.closed_on_create = true
         req.closed_on_create_reason = 'gt5'
-  if fast and not req.doi?
+  if fast and not req.doi? and status isnt 'closed'
     req.status = 'closed'
     req.closed_on_create = true
     req.closed_on_create_reason = 'nodoi'
-  if fast and req.crossref_type? and req.crossref_type isnt 'journal-article'
+  if fast and req.crossref_type? and req.crossref_type isnt 'journal-article' and status isnt 'closed'
     req.status = 'closed'
     req.closed_on_create = true
     req.closed_on_create_reason = 'notarticle'
-  if req.sherpa?.color? and typeof req.sherpa.color is 'string' and req.sherpa.color.toLowerCase() is 'white'
+  if req.sherpa?.color? and typeof req.sherpa.color is 'string' and req.sherpa.color.toLowerCase() is 'white' and status isnt 'closed'
     req.status = 'closed'
     req.closed_on_create = true
     req.closed_on_create_reason = 'sherpawhite'


remove trailing code

remove trailing code

--- app/assets/javascripts/patients.coffee
+++ app/assets/javascripts/patients.coffee
@@ -50,8 +50,6 @@

 ready = ->
-
-  markFulfilledWhenFieldsChecked()
   $(document).on "click", "#toggle-call-log", ->
     $(".old-calls").toggleClass("hidden")
     html = if $(".old-calls").hasClass("hidden") then "View all calls" else "Limit list"


fires insert events one at a time now

fires insert events one at a time now

--- src/class_methods.coffee
+++ src/class_methods.coffee
@@ -1,5 +1,6 @@
 requestCallbackNonce = 0
-define ["underscore", "lib/inflection", "lib/socket"], (_, inflect, socket) ->
+define ["underscore", "lib/inflection", "lib/socket", "lib/events"], (_, inflect, socket, events) ->
+  {EventEmitter} = events

   _.mixin(inflect)
   classes = {}
@@ -26,7 +27,11 @@

     sendReadMessage: (name, query, callback) ->
       socket.emit "LiveDocumentRead", name, query, requestCallbackNonce
-      socket.on "LiveDocument" + requestCallbackNonce, callback
+      socket.on "LiveDocument" + requestCallbackNonce, (docs) ->
+        if _.isArray(docs)
+          _.each(docs, callback)
+        else
+          callback docs
       requestCallbackNonce += 1

     # **sendCreateMessage** *private*
@@ -58,13 +63,12 @@
     # _callback_: Function to call once read is complete, first argument is a
     # list of documents matching query.

-    read: (query, callback) ->
-      if typeof query == "function"
-        callback = query
-        query = {}
+    read: (query) ->
+      emitter = new EventEmitter()
+      @sendReadMessage _(_(@name).pluralize()).uncapitalize(), query, (document) =>
+        emitter.emit "insert", document
+      return emitter

-      @sendReadMessage _(_(@name).pluralize()).uncapitalize(), query, (documents) =>
-        callback documents

     # **create** *public*
     #


stuped map wont center properly

stuped map wont center properly

--- app/assets/javascripts/progress.coffee
+++ app/assets/javascripts/progress.coffee
@@ -5,7 +5,7 @@
 			zoomControl: false
 			animate: true
 			attributionControl: false
-			minZoom: 13
+			minZoom: 8
 			maxZoom: 20
 			dragging: true
 		)


Other autocomplete strategies now also submit form on selection

--- app/assets/javascripts/subjects.coffee
+++ app/assets/javascripts/subjects.coffee
@@ -23,6 +23,7 @@
     $this.textcomplete(
       [
         {
+          name: 'submit-on-click'
           match: /(^|\s)has\:([\w\-]*)$/
           search: (term, callback) ->
             words = ['adverse-events', 'comments', 'files']
@@ -36,6 +37,7 @@
           replace: (value) -> return "$1has:#{value}"
         },
         {
+          name: 'submit-on-click'
           match: /(^|\s)is\:([\w\-]*)$/
           search: (term, callback) ->
             words = ['randomized']
@@ -49,6 +51,7 @@
           replace: (value) -> return "$1is:#{value}"
         },
         {
+          name: 'submit-on-click'
           match: /(^|\s)not\:([\w\-]*)$/
           search: (term, callback) ->
             words = ['randomized']
@@ -62,6 +65,7 @@
           replace: (value) -> return "$1not:#{value}"
         },
         {
+          name: 'submit-on-click'
           match: /(^|\s)adverse-events\:([\w\-]*)$/
           search: (term, callback) ->
             words = ['open', 'closed']
@@ -90,6 +94,8 @@
     ).on('textComplete:select': (e, value, strategy) ->
       if strategy.name == 'search' and value not in ['adverse-events', 'has', 'is', 'not']
         $(this).closest('form').submit()
+      else if strategy.name == 'submit-on-click'
+        $(this).closest('form').submit()
     )
   )


Change out live() usage (removed in jQuery 1.9)

Fixes #2629

--- core/app/assets/javascripts/store/checkout.js.coffee
+++ core/app/assets/javascripts/store/checkout.js.coffee
@@ -75,7 +75,7 @@
       ($ '#payment_method_' + @value).show() if @checked
     )

-    ($ '#cvv_link').live('click', (event) ->
+    ($ document).on('click', '#cvv_link', (event) ->
       window_name = 'cvv_info'
       window_options = 'left=20,top=20,width=500,height=500,toolbar=0,resizable=0,scrollbars=1'
       window.open(($ this).attr('href'), window_name, window_options)


fixed small bug

fixed small bug

--- core/app/backbone/factlink/tooltip_definition.coffee
+++ core/app/backbone/factlink/tooltip_definition.coffee
@@ -46,10 +46,10 @@
     @_options.removeTooltip @_options.$container, @$target, @_$tooltip
     delete @_$tooltip

-  start_tooltip: (options, $target) ->
-    @_$tooltip = options.makeTooltip options.$container, $target
+  start_tooltip: ->
+    @_$tooltip = @_options.makeTooltip @_options.$container, @$target
     @_$tooltip.hoverIntent
-      timeout: options.closingtimeout
+      timeout: @_options.closingtimeout
       over: => @state.set inTooltip: true
       out:  => @state.set inTooltip: false

@@ -57,6 +57,6 @@
     if @_$tooltip
       @removeTooltip() unless @state.hovered()
     else
-      start_tooltip if @state.hovered()
+      @start_tooltip() if @state.hovered()


use backbone model to save state

use backbone model to save state

--- core/app/backbone/factlink/tooltip_definition.coffee
+++ core/app/backbone/factlink/tooltip_definition.coffee
@@ -15,50 +15,50 @@

   constructor: (options) ->
     @_options = _.defaults options, @defaults
+    @state = new Backbone.Model
+      inTooltip: false
+      inTarget: false

   render: ->
     @_options.$container.hoverIntent
       timeout: @_options.closingtimeout
       selector: @_options.selector
-      over: @_hoverTarget true
-      out: @_hoverTarget false
+      over:(e) => @_hoverTarget e, true
+      out: (e) => @_hoverTarget e, false

   close: ->
     @removeTooltip()
     @_options.$container.off(".hoverIntent") #unfortunately, we can't do better than this.

-  _openInstance: ($target) ->
-    @$target = $target
-    @start_tooltip @_options, @$target
-
   removeTooltip: =>
     @_options.removeTooltip @_options.$container, @$target, @_$tooltip
-    delete @we_have_tooltip
+    delete @_$tooltip

-  _hoverTarget: (target_is_hovered) -> (e) =>
-    if @we_have_tooltip
+  _hoverTarget: (e, target_is_hovered) ->
+    if @_$tooltip
       @setTargetHover target_is_hovered
     else if target_is_hovered
-      @_openInstance $(e.currentTarget)
+     @$target = $(e.currentTarget)
+     @start_tooltip @_options, @$target

   start_tooltip: (options, $target) ->
-    @we_have_tooltip = true
-    @_inTooltip = false
-    @_inTarget = true #opened on hover over target
-
+    @state.set('inTarget', true)
     @_$tooltip = options.makeTooltip options.$container, $target
     @_$tooltip.hoverIntent
       timeout: options.closingtimeout
       over: => @setTooltipHover(true)
       out: => @setTooltipHover(false)

-  _check: -> @removeTooltip() if !(@_inTarget || @_inTooltip)
+  _check: -> @removeTooltip() unless @hovered()
+
+  hovered: ->
+    @state.get('inTarget') || @state.get('inTooltip')

   setTooltipHover: (state) ->
-    @_inTooltip = state
+    @state.set('inTooltip',state)
     @_check()

   setTargetHover: (state) ->
-    @_inTarget = state
+    @state.set('inTarget', state)
     @_check()


fix : attribute name autocomplete doesn't work properly in the middle of other attributes (on xml)

--- lib/viewProvider.coffee
+++ lib/viewProvider.coffee
@@ -64,12 +64,18 @@
     return false unless trailingWhitespace.test(prefix)
     @hasTagScope(scopeDescriptor.getScopesArray())

-  isAttributeStartWithPrefix: ({prefix, scopeDescriptor}) ->
+  isAttributeStartWithPrefix: ({prefix, scopeDescriptor, bufferPosition, editor}) ->
     return false unless prefix
     return false if trailingWhitespace.test(prefix)

     scopes = scopeDescriptor.getScopesArray()
     return true if scopes.indexOf('entity.other.attribute-name.html') isnt -1
+
+    previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)]
+    previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition)
+    previousScopesArray = previousScopes.getScopesArray()
+    return true if previousScopesArray.indexOf('entity.other.attribute-name.html') isnt -1
+
     return false unless @hasTagScope(scopes)

     scopes.indexOf('punctuation.definition.tag.html') isnt -1 or


Simplified the creation of the deletebutton

--- core/app/backbone/views/evidence/comment_view.coffee
+++ core/app/backbone/views/evidence/comment_view.coffee
@@ -34,12 +34,10 @@
     @headingRegion.show new EvidenceishHeadingView model: @model.creator()

     if @model.can_destroy()
-      @_deleteButtonView = new ReactView
+      @deleteRegion.show new ReactView
         component: ReactDeleteButton
           model: @model
           onDelete: -> @model.destroy wait: true
-
-      @deleteRegion.show @_deleteButtonView

     @subCommentsLinkRegion.show new ReactView
       component: ReactCommentReply


Add challenge description within a paragraph so it has margins

--- core/app/backbone/views/react_feed_activities.coffee
+++ core/app/backbone/views/react_feed_activities.coffee
@@ -81,8 +81,8 @@
             fact.get('site_title')
         ]
       },
-      fact.get('displaystring')
-
+      _p [],
+        fact.get('displaystring'),

 ReactCreatedCommentActivity = React.createBackboneClass
   displayName: 'ReactCreatedCommentActivity'


Refactoring for reusability

Refactoring for reusability

--- tubular.coffee
+++ tubular.coffee
@@ -60,28 +60,30 @@

     createGetter (-> target), 0

-  createBoundVariableScope = (parentFinder, varName, boundValue, notify) ->
+  rootVariableFinder = ((varName) -> throw 'unknown variable ' + varName)
+
+  createBoundVariableFinder = (parentFinder, varName, boundValue, notify) ->
     createGetter = (subPath) ->
       createPathGetter boundValue, subPath

-    createScopeWithFinder (name, callback) ->
+    (name, callback) ->
       if name is varName
         callback createGetter, notify
       else
         parentFinder name, callback

-  createMutableVariableScope = (parentFinder, varName, varValue) ->
+  createMutableVariableFinder = (parentFinder, varName, varValue) ->
     varNotify = createNotifier()
     createGetter = (subPath) ->
       createPathGetter varValue, subPath

-    createScopeWithFinder (name, callback) ->
+    (name, callback) ->
       if name is varName
         callback createGetter, varNotify
       else
         parentFinder name, callback

-  createScopeWithFinder = (variableFinder) ->
+  createScope = (variableFinder) ->
     {
       bind: (subName, path, subTemplate) ->
         viewInstance = this
@@ -98,9 +100,9 @@
             newValue = getter()
             if newValue isnt value
               value = newValue
-              runTemplate viewInstance, createBoundVariableScope(variableFinder, subName, value, notify), subTemplate
+              runTemplate viewInstance, createScope(createBoundVariableFinder(variableFinder, subName, value, notify)), subTemplate

-          runTemplate viewInstance, createBoundVariableScope(variableFinder, subName, value, notify), subTemplate
+          runTemplate viewInstance, createScope(createBoundVariableFinder(variableFinder, subName, value, notify)), subTemplate

           { clear: clear }

@@ -122,7 +124,7 @@
           result

       set: (varName, initialValue, subTemplate) ->
-        runTemplate this, createMutableVariableScope(variableFinder, varName, initialValue), subTemplate
+        runTemplate this, createScope(createMutableVariableFinder(variableFinder, varName, initialValue)), subTemplate
     }

   # @todo mask a top-level property and also keep track of which scope notifier it is
@@ -144,6 +146,6 @@

     undefined # prevent stray output

-  initialScope = createBoundVariableScope ((varName) -> throw 'unknown variable ' + varName), '_', rootModel, modelNotify
+  initialScope = createScope createBoundVariableFinder(rootVariableFinder, '_', rootModel, modelNotify)

   runTemplate Object.prototype, initialScope, rootTemplate


some improvements from the review

some improvements from the review

--- src/coffee/mapper.coffee
+++ src/coffee/mapper.coffee
@@ -14,21 +14,21 @@
 class ConstantTransformer extends ValueTransformer
   init: (options) ->
     @_value = options.value
-    Q(@)
+    Q(this)

   transform: (value) ->
     @_value

 class UpperCaseTransformer extends ValueTransformer
   init: (options) ->
-    Q(@)
+    Q(this)

   transform: (value) ->
     value.toUpperCase()

 class LowerCaseTransformer extends ValueTransformer
   init: (options) ->
-    Q(@)
+    Q(this)

   transform: (value) ->
     value.toLowerCase()
@@ -37,7 +37,7 @@
   init: (options) ->
     @_size = options.size
     @_chars = options.chars
-    Q(@)
+    Q(this)

   transform: (value) ->
     rndChars = _.map _.range(@_size), (idx) =>
@@ -49,7 +49,7 @@
   init: (options) ->
     @_find = new RegExp(options.find, 'g')
     @_replace = options.replace
-    Q(@)
+    Q(this)

   transform: (value) ->
     value.replace @_find, @_replace
@@ -72,9 +72,9 @@
       .then (values) =>
         @_headers = values.headers
         @_values = values.data
-        @
+        this
     else
-      Q(@)
+      Q(this)

   _parseCsv: (csvText) ->
     d = Q.defer()
@@ -91,18 +91,20 @@
     d.promise

   transform: (value) ->
-    keyIdx = if typeof @_keyCol == "string" then @_headers.indexOf(@_keyCol) else @_keyCol
-    valueIdx = if typeof @_valueCol == "string" then @_headers.indexOf(@_valueCol) else @_valueCol
+    keyIdx = if _.isString @_keyCol then @_headers.indexOf(@_keyCol) else @_keyCol
+    valueIdx = if _.isString @_valueCol then @_headers.indexOf(@_valueCol) else @_valueCol

     if keyIdx < 0 or valueIdx < 0
       throw new Error("Something is wrong in lookup config: key '#{@_keyCol}' or value '#{@_valueCol}' column not found by name!.")

-    found = _.find @_values, (row) -> row[keyIdx] == value
+    found = _.find @_values, (row) -> row[keyIdx] is value

     if found
       found[valueIdx]
     else
-      throw new Error("Unfortunately, lookup transformation filed for value '#{value}'.#{if @_file then "File: #{@_file}." else ""} Values: #{@_values.join "; "}")
+      fileMessage = if @_file then "File: #{@_file}." else ""
+      valuesMessage = @_values.join "; "
+      throw new Error("Unfortunately, lookup transformation failed for value '#{value}'.#{fileMessage} Values: #{valuesMessage}")

 class ColumnMapping
   init: (options) -> util.abstractMethod() # promise with this
@@ -149,7 +151,7 @@
     @_initValueTransformers options.valueTransformers
     .then (vt) =>
       @_valueTransformers = vt
-      @
+      this

   map: (origRow, accRow) ->
     value = if accRow[@_fromCol] then accRow[@_fromCol] else origRow[@_fromCol]
@@ -165,7 +167,7 @@
     @_toCol

   priority: () ->
-    if @_priority then @_priority else 500
+    @_priority or 2000

 class ColumnGenerator extends ColumnMapping
   init: (options) ->
@@ -183,14 +185,11 @@

     Q.all promises
     .then (parts) =>
-      @
+      this

   map: (origRow, accRow) ->
     partialValues = _.map @_parts, (part, idx) =>
-      size = part.size
-      pad = part.pad
-      fromCol = part.fromCol
-      valueTransformers = part.valueTransformers
+      {size, pad, fromCol, valueTransformers} = part

       value = if accRow[fromCol] then accRow[fromCol] else origRow[fromCol]

@@ -201,7 +200,7 @@

       if transformed.length < size and pad
         _s.pad(transformed, size, pad)
-      else if transformed.length = size
+      else if transformed.length is size
         transformed
       else
         throw new Error("Generated column part size (#{transformed.length} - '#{transformed}') is smaller than expected size (#{size}) and no padding is defined for this column. Source column '#{fromCol}', generated column '#{@_toCol}' (part #{idx}) with current value '#{value}'.")
@@ -218,7 +217,7 @@
     @_toCol

   priority: () ->
-    if @_priority then @_priority else 1000
+    @_priority or 3000

 ###
   Transforms one object into another object accoring to the mapping configuration
@@ -235,7 +234,7 @@
       @_constructMapping(JSON.parse(contents))
     .then (mapping) =>
       @_columnMapping = mapping
-      @
+      this

   _constructMapping: (mappingJson) ->
     columnPromises = _.map mappingJson.columnMapping, (elem) ->
@@ -284,14 +283,13 @@

     c = csv().from.stream(csvIn).to.stream(csvOut)
     .transform (row, idx, done) =>
-      if idx == 0 and @_options.includesHeaderRow
+      if idx is 0 and @_options.includesHeaderRow
         headers = row
         newHeaders = @_options.mapping.transformHeader row
         done null, newHeaders
       else
-        if idx == 0
-          headers = _.map _.range(row.length), (idx) ->
-            "#{idx}"
+        if idx is 0
+          headers = _.map _.range(row.length), (idx) -> "#{idx}"

         @_options.mapping.transformRow @_convertToObject(headers, row)
         .then (converted) =>
@@ -315,9 +313,7 @@
     _.reduce _.map(properties, ((prop, idx) -> [prop, idx])), reduceFn, {}

   _convertFromObject: (properties, obj) ->
-    foo = _.map properties, (name) ->
-      obj[name]
-    foo
+    _.map properties, (name) -> obj[name]

   run: ->
     # TODO: introduce concept for the the second (additional) CSV file that stores retailer update info


Clean up leftover  refs.

Clean up leftover  refs.

--- src/coffee/window.coffee
+++ src/coffee/window.coffee
@@ -96,7 +96,7 @@

 Scribe.Window::_getTop = ->
   height = OSX.NSScreen.mainScreen.frame.size.height
-  height - this.nativeObject.frame.origin.y
+  height - @nativeObject.frame.origin.y

 Scribe.Window::_setTop = (y) ->
   height = OSX.NSScreen.mainScreen.frame.size.height
@@ -109,7 +109,7 @@
   @nativeObject.frame.size.width

 Scribe.Window::_setWidth = (width) ->
-  frame = this.nativeObject.frame
+  frame = @nativeObject.frame
   rect = OSX.NSMakeRect(frame.origin.x, frame.origin.y, width, frame.size.height)
   @nativeObject['setFrame:display:'](rect, true)

@@ -117,7 +117,7 @@
   @nativeObject.frame.size.height

 Scribe.Window::_setHeight = (height) ->
-  frame = this.nativeObject.frame
+  frame = @nativeObject.frame
   rect = OSX.NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width, height)
   @nativeObject['setFrame:display:'](rect, true)


Fix gruntfile less task

Fix gruntfile less task

--- mrfogg-front/Gruntfile.coffee
+++ mrfogg-front/Gruntfile.coffee
@@ -16,7 +16,7 @@
                     paths: ['app/less']
                 },
                 files: {
-                    "app/less/style.css": "app/less/*.less"
+                    "app/styles/main.css": "app/styles/less/*.less"
                 }
             }
         },


relation_id is available as st_id and store_id

--- lib/orpheus.coffee
+++ lib/orpheus.coffee
@@ -93,7 +93,7 @@
 				# create a relation function,
 				# e.g. user(12).book(15)
 				@[rel] = (id) ->
-					@["#{qualifier}_id"] = id
+					@["#{qualifier}_id"] = @["#{rel}_id"]= id
 					return @

 			# Add a validation function to a field


restructure parser output

restructure parser output

--- lib/package.coffee
+++ lib/package.coffee
@@ -22,13 +22,15 @@
   initialize: (@packagepath) ->

     #@name = @path.replace(/^.*[\\\/]/, '')
+
+    @initialPackageName = "parsing package..."
     @classList.add('entry',  'list-nested-item')#,  'collapsed')
     @header = document.createElement('div')
     @packageNameElem = document.createElement('span')
     @packageNameElem.classList.add('name', 'icon', 'icon-file-directory')
-    @packageNameElem.title=@packageName
-    packageNameTextNode = document.createTextNode(@packageName)
-    @packageNameElem.appendChild(packageNameTextNode)
+    @packageNameElem.title= @initialPackageName
+    @packageNameTextNode = document.createTextNode(@initialPackageName)
+    @packageNameElem.appendChild(@packageNameTextNode)
     @header.appendChild(@packageNameElem)

     @appendChild(@header)
@@ -86,21 +88,54 @@
       continue if !stat.isFile?()
       @reparseFile(fullPath)

-  reparseFile: (filepath) ->
+  reparseFile: (filePath)->
     out = []
     promise = new Promise((resolve, reject) =>
       new BufferedProcess({
         command: '/home/franz/work/outline/outline-parser/outline-parser',
-        args: ['-f', filepath],
+        args: ['-f', filePath],
         stdout: (data) =>
           out.push(data)

         exit: (code) =>
           resolve(code)
       })
-    ).then (code) ->
+    ).then (code) =>
       console.log "parser finished", code
-      console.log "output:", out.join("\n")
+      outlineTree = @makeOutlineTree(out.join("\n"))
+
+  makeOutlineTree: (parserOutput) ->
+    console.log "making outline from", parserOutput
+    parsed = JSON.parse parserOutput
+    file = parsed.Filename
+
+    if @packageNameTextNode.nodeValue == @initialPackageName
+      @packageNameTextNode.nodeValue = parsed.Packagename
+      @packageNameElem.title = parsed.Packagename
+    else
+      @packageNameTextNode.nodeValue ?= parsed.Packagename
+      @packageNameElem.title ?= parsed.Packagename
+
+
+    outlineTree = {}
+
+    for name, symbol of parsed.Entries
+
+      if symbol?.Receiver
+        parsed.Entries[symbol.Receiver].Children ?= {}
+        parsed.Entries[symbol.Receiver].Children[name] = symbol
+
+    for name, symbol of parsed.Entries
+      if !symbol?.Receiver
+        outlineTree[name] = symbol
+
+    console.log "transformed tree"
+    console.log outlineTree
+
+
+    # set package name if not set yet
+
+

   updateFileEntries: (fileName, entries) ->
     for entryName, entryValues of entries


Added more parameters

Added more parameters


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -108,7 +108,7 @@

       objects.invoke "trigger", "overlay", canvas

-  self.include Mova
+  self.include Movable

   self.include(Camera.ZSort)
   self.include(Camera.Zoom)


add tls settings

add tls settings

--- services/document-updater/config/settings.defaults.coffee
+++ services/document-updater/config/settings.defaults.coffee
@@ -1,6 +1,7 @@
 Path = require('path')
 http = require('http')
 http.globalAgent.maxSockets = 300
+fs = require('fs')

 module.exports =
 	internal:
@@ -44,6 +45,12 @@
 			key_schema:
 				projectHistoryOps: ({project_id}) -> "ProjectHistory:Ops:{#{project_id}}"
 				projectHistoryFirstOpTimestamp: ({project_id}) -> "ProjectHistory:FirstOpTimestamp:{#{project_id}}"
+			tls: if process.env['REDIS_CA_CERT'] && process.env['REDIS_CLIENT_CERT'] && process.env['REDIS_CLIENT_KEY']
+				ca: fs.readFileSync(process.env['REDIS_CA_CERT']),
+				cert: fs.readFileSync(
+					process.env['REDIS_CLIENT_CERT']
+				),
+				key: fs.readFileSync(process.env['REDIS_CLIENT_KEY'])

 		new_project_history:
 			port: process.env["NEW_HISTORY_REDIS_PORT"] or "6379"
@@ -54,6 +61,12 @@
 				projectHistoryFirstOpTimestamp: ({project_id}) -> "ProjectHistory:FirstOpTimestamp:{#{project_id}}"
 				projectHistoryMigrationKey: ({project_id}) -> "ProjectHistory:MigrationKey:{#{project_id}}"
 			migration_phase: "prepare"
+			tls: if process.env['NEW_HISTORY_REDIS_CA_CERT'] && process.env['NEW_HISTORY_REDIS_CLIENT_CERT'] && process.env['NEW_HISTORY_REDIS_CLIENT_KEY']
+				ca: fs.readFileSync(process.env['NEW_HISTORY_REDIS_CA_CERT']),
+				cert: fs.readFileSync(
+					process.env['NEW_HISTORY_REDIS_CLIENT_CERT']
+				),
+				key: fs.readFileSync(process.env['NEW_HISTORY_REDIS_CLIENT_KEY'])
 			redisOptions:
 				keepAlive: 100


Talker adapter, fix double post event submit

--- src/hubot/talker.coffee
+++ src/hubot/talker.coffee
@@ -76,7 +76,11 @@
     @socket.addListener 'data', (data) ->
       for line in data.split '\n'
         console.log line
-        message = JSON.parse(line) unless line is ''
+
+        message = unless line is ''
+          message = JSON.parse(line)
+        else
+          message = null

         if message
           if message.type == "connected"


Added validation fields

Added validation fields

--- client/app/Applications/Inbox.kdapplication/AppController.coffee
+++ client/app/Applications/Inbox.kdapplication/AppController.coffee
@@ -55,11 +55,21 @@
                 label         : "Subject:"
                 placeholder   : 'Enter a subject'
                 name          : "subject"
+                validate      :
+                  rules       :
+                    required  : yes
+                  messages    :
+                    required  : "Subject field is required!"
               message         :
                 label         : "Message:"
                 type          : "textarea"
                 name          : "body"
                 placeholder   : 'Enter your message'
+                validate      :
+                  rules       :
+                    required  : yes
+                  messages    :
+                    required  : "Message field is required!"
             buttons           :
               Send            :
                 title         : "Send"
@@ -211,15 +221,6 @@
       return new KDNotificationView
         title : "Sending private message for guests not allowed."

-    {subject, body} = messageDetails
-    if subject.trim() is ''
-      return new KDNotificationView
-        title : "Please enter a subject."
-
-    if body.trim() is ''
-      return new KDNotificationView
-        title : "Please enter a message."
-
     KD.remote.api.JPrivateMessage.create messageDetails, callback

   prepareMessage:(formOutput, callback, newMessageBar)->


reflection of change of current color now visible after undo redo

--- Skuler.coffee
+++ Skuler.coffee
@@ -245,10 +245,12 @@

 	handleUndo: (event) =>
 		@drawing.undo()
+		@updateViewCurrColor()

 	handleRedo: (event) =>
 		@drawing.redo()
+		@updateViewCurrColor()

 	handleKeyShortcut: (event) =>
@@ -286,11 +288,13 @@
 			when 89 # z
 				if event.ctrlKey
 					@drawing.redo()
+					@updateViewCurrColor()
 					true

 			when 90 # z
 				if event.ctrlKey
 					@drawing.undo()
+					@updateViewCurrColor()
 					true

 			when 77 # m


Minor CoffeeScript cleanup in PlayState.

--- src/state-play.coffee
+++ src/state-play.coffee
@@ -86,7 +86,7 @@
       @camera.updateShake()
       if @_shakeOnPlayerFall()
         @camera.unfollow()
-      else unless (@camera.target? or @camera.isShaking())
+      else unless @camera.target? or @camera.isShaking()
         @camera.follow @player.cameraFocus

       @camera.updatePositionWithCursors @cursors if @detachedCamera
@@ -202,8 +202,8 @@
       tween = @fadeIn text, Timer.SECOND

     _isPlayerReadyToEnd: ->
-      @player.state is 'still' and @player.control is on and
-      @player.sprite.y <= @endingPoint.y
+      (@player.state is 'still' and @player.control is on and
+       @player.sprite.y <= @endingPoint.y)

     _renderDebugDisplay: ->
       @resetDebugDisplayLayout()
@@ -226,9 +226,8 @@
           @ended = yes

     _shakeOnPlayerFall: ->
-      if @player.nextState is 'landing' and @player.distanceFallen() > shakeFallH
-        @camera.shake()
-      else no
+      return no unless @player.nextState is 'landing' and @player.distanceFallen() > shakeFallH
+      @camera.shake()

     _toggleCameraAttachment: (attached) ->
       attached ?= not @detachedCamera


Add wsClient listener by default in browser. More robust way to enable chalk in browser

--- src/storyboard.coffee
+++ src/storyboard.coffee
@@ -5,20 +5,22 @@
 ###
 k = require './constants'

-# Enable chalk colors in the browser (we'll handle the conversion)
+# Chalk is disabled by default in the browser. Override
+# this default (we'll handle ANSI code conversion ourselves
+# when needed)
+chalk = require 'chalk'
+chalk.enabled = true
+
+mainStory = require './stories'
+hub = require './hub'
+hub.init {mainStory}
+
+hub.addListener require './listeners/console'
 if k.IS_BROWSER
-  process.env.COLORTERM = true
+  hub.addListener require './listeners/wsClient'

-hub = require './hub'
-stories = require './stories'
-consoleListener = require './listeners/console'
-
-mainStory = stories.createStory []
-hub.init {mainStory}
-hub.addListener consoleListener
-
-# Make sure a record is created
-mainStory.changeTitle 'ROOT STORY'
+# Make sure a record is created for the root story
+mainStory.logStory 'CREATED'

 module.exports = {
   mainStory,


Implement font color

Implement font color

--- src/style/font.coffee
+++ src/style/font.coffee
@@ -1,4 +1,6 @@
 Utils = require('../utils')
+
+COLOR_TABLE = [null, "000000", "FFFFFF", "0000FF", "00FF00", "FF0000", "00FFFF", "FF00FF", "FFFF00", "000080", "008000", "800000", "008080", "800080", "808000", "C0C0C0", "808080", "FF9999", "663399", "CCFFFF", "FFFFCC", "660066", "8080FF", "CC6600", "FFCCCC", "800000", "FF00FF", "00FFFF", "FFFF00", "800080", "000080", "808000", "FF0000", "FFCC00", "FFFFCC", "CCFFCC", "99FFFF", "FFCC99", "CC99FF", "FF99CC", "99CCFF", "FF6633", "CCCC33", "00CC99", "00CCFF", "0099FF", "0066FF", "996666", "969696", "663300", "669933", "003300", "003333", "003399", "663399", "993333", "333333"]

 class Font
 	constructor:(id, sm, xmlobj)->
@@ -7,17 +9,25 @@
 		@_.id = id
 		@_.sm = sm
 		@size = parseInt xmlobj.sz?[0].$.val
-		@_.color = xmlobj.color?[0].$
+		colorAttrs = xmlobj.color?[0].$ || {}
+		color = null
+		if colorAttrs.rgb
+			color = colorAttrs.rgb
+		else if colorAttrs.indexed
+			color = COLOR_TABLE[colorAttrs.indexed]
+		else if colorAttrs.theme
+			@_.color_theme = colorAttrs.theme
+			color = @_.sm._.theme.getColor(colorAttrs.theme)
+
+		@color = color
 		@name = xmlobj.name?[0].$.val
-		@_.charset = parseInt xmlobj.charset?[0].$.val
+		#@_.charset = parseInt xmlobj.charset?[0].$.val
 		#@_.family = parseInt xmlobj.family?[0].$.val
-		@_.scheme = xmlobj.scheme?[0].$.val
+		#@_.scheme = xmlobj.scheme?[0].$.val

 	Object.defineProperties @prototype,
 		"id":
 			get: -> @_.id
-		"color":
-			get: -> @_.color
 		"charset":
 			get: -> @_.charset
 		"scheme":
@@ -29,7 +39,7 @@

 	toXmlObj: ->
-		obj = {}
+		obj = @_.xmlobj

 		if @size
 			obj.sz = [{$:{val:@size}}]
@@ -37,6 +47,9 @@
 		if @name
 			obj.name = [{$:{val:@name}}]

+		if @color
+			obj.color = [{$:{rgb:@color}}]
+
 		obj

 module.exports = Font


Add MarkerLayer::onDidDestroy

Add MarkerLayer::onDidDestroy

--- src/marker-layer.coffee
+++ src/marker-layer.coffee
@@ -45,6 +45,11 @@
   destroy: ->
     @destroyed = true
     @delegate.markerLayerDestroyed(this)
+    @emitter.emit 'did-destroy'
+    @emitter.dispose()
+
+  onDidDestroy: (callback) ->
+    @emitter.on 'did-destroy', callback

   isAlive: ->
     not @destroyed


Fix require path in example

Fix require path in example

--- examples/test.coffee
+++ examples/test.coffee
@@ -1,4 +1,4 @@
-Testify = require "./testify"
+Testify = require "../testify"
 assert = require "assert"

 Testify.test "gnome", (suite) ->


DRY-ing out Gruntfile: removing redundant script list

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -1,7 +1,7 @@
 module.exports = (grunt) ->
+  # Declare all non-minified scripts in load order here:
   scripts =
   [
-
     'app/_bower_components/webmaker-auth-client/dist/webmaker-auth-client.min.js'
     'app/_bower_components/makeapi-client/src/make-api.js'
     'app/_bower_components/jquery/dist/jquery.js'
@@ -32,8 +32,7 @@
     script = script.replace('app/', 'user/')
     devScript += "<script src='/" + script + "'></script>"

-  prodScript = '<script src="/user/_compiled/dependencies.min.js"></script>
-                <script src="/user/_compiled/app.min.js"></script>'
+  prodScript = '<script src="/user/_compiled/app.min.js"></script>'

   grunt.initConfig
     less:
@@ -178,36 +177,12 @@
             }
           ]
     uglify:
-      dependencies:
+      app:
         options:
           sourceMap: true
           mangle: false
         files:
-          'app/_compiled/dependencies.min.js': [
-
-            'app/_bower_components/webmaker-auth-client/dist/webmaker-auth-client.min.js'
-            'app/_bower_components/makeapi-client/src/make-api.js'
-            'app/_bower_components/jquery/dist/jquery.js'
-            'app/_bower_components/selectize/dist/js/standalone/selectize.js'
-
-            'app/_bower_components/angular/angular.js'
-            'app/_bower_components/angular-route/angular-route.js'
-            'app/_bower_components/angular-resource/angular-resource.js'
-            'app/_bower_components/angular-bootstrap/ui-bootstrap.js'
-            'app/_bower_components/angular-bootstrap/ui-bootstrap-tpls.js'
-            'app/_bower_components/angular-sanitize/angular-sanitize.js'
-            'app/_bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js'
-
-            'app/_bower_components/locompleter/locompleter.js'
-
-            'app/_bower_components/webmaker-analytics/analytics.js'
-          ]
-      app:
-        options:
-          sourceMap: true
-          mangle: false
-        files:
-          'app/_compiled/app.min.js': 'app/_js/**/*.js'
+          'app/_compiled/app.min.js': scripts

   grunt.loadNpmTasks "grunt-shell-spawn"
   grunt.loadNpmTasks "grunt-jsbeautifier"


Tweak coffeelint settings

Tweak coffeelint settings

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -50,11 +50,11 @@
           interrupt: true

     coffeelint:
-      all:
-        files: 'src/**/*.coffee'
-        options:
-          no_tabs: false
-          no_empty_param_list: true
+      files: 'src/**/*.coffee'
+      options:
+        no_tabs             : {level: 'ignore'}
+        no_empty_param_list : {level: 'error'}
+        indentation         : {level: 'ignore'}  # Indentation linting is buggy https://github.com/clutchski/coffeelint/issues/4

   # Default task.
   grunt.registerTask 'default', ['coffeelint', 'coffee', 'concat', 'uglify']


optimized copy/uglify

optimized copy/uglify

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -74,7 +74,6 @@
           banner: '<%= meta.banner %>'
         files:
           'dist/<%= pkg.name %>-<%= pkg.version %>.min.js': ['dist/<%= pkg.name %>-<%= pkg.version %>.js']
-          'dist/<%= pkg.name %>-latest.min.js': ['dist/<%= pkg.name %>-<%= pkg.version %>.min.js']
           'dist/jquery.<%= pkg.name %>-<%= pkg.version %>.min.js': ['dist/jquery.<%= pkg.name %>-<%= pkg.version %>.js']

     # watch for changes
@@ -108,6 +107,6 @@

   # Default task(s).
-  grunt.registerTask 'build', ['notify_hooks','concat','test','uglify','copy']
+  grunt.registerTask 'build', ['notify_hooks','concat','uglify','copy','test']
   grunt.registerTask 'default', ['notify_hooks','connect','watch']
   grunt.registerTask 'test', ['jshint','qunit']


chore(Gruntfile): separate watch task for tests for speed

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -94,8 +94,11 @@
         files: '<%= coffeelint.gruntfile.files.src %>'
       demo:
         files: "#{MODULES_DIR}/**/*.html"
+      tests:
+        files: "#{MODULES_DIR}/*/*/test/*.coffee"
+        tasks: ['coffee:tests', 'karma:unit:run']
       coffee:
-        files: "#{MODULES_DIR}/**/*.coffee"
+        files: "#{MODULES_DIR}/*/*/*.coffee"
         tasks: ['coffee', 'karma:unit:run']
       less:
         files: "#{MODULES_DIR}/**/*.less"


Modify commit message.

Modify commit message.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -269,6 +269,7 @@
         file: 'package.json'
         npm: false
         tagName: 'v<%= version %>'
+        commitMessage: 'release v<%= version %>'
         additionalFiles: [
           'bower.json',
           '<%= config.app %>/manifest.json'


Updated names of s3 tasks. Added update-s3 to gh_pages task

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -112,9 +112,9 @@

   grunt.registerTask 'default', ['build']
   grunt.registerTask 'build', ['coffee', 'concat', 'uglify', 'cssmin']
-  grunt.registerTask 'merge', ['build', 'zip:chosen', 's3:master', 'clean:chosen_zip']
-  grunt.registerTask 'release', ['build', 'zip:chosen', 'package_jquery', 'dom_munger:download_links', 's3:master', 's3:latest_version', 'clean:chosen_zip']
-  grunt.registerTask 'gh_pages', ['copy:dist', 'build_gh_pages:gh_pages']
+  grunt.registerTask 'master-s3', ['build', 'zip:chosen', 's3:master', 'clean:chosen_zip']
+  grunt.registerTask 'update-s3', ['build', 'zip:chosen', 'package_jquery', 'dom_munger:download_links', 's3:master', 's3:latest_version', 'clean:chosen_zip']
+  grunt.registerTask 'gh_pages', ['copy:dist', 'build_gh_pages:gh_pages', 'update-s3']

   grunt.registerTask 'package_jquery', 'Generate a jquery.json manifest file from package.json', () ->
     src = "package.json"


grunt clean will rm all generated files & dir

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -98,7 +98,7 @@
 				tasks: ['stylus']

 		clean:
-			build: ['build/*']
+			files: ['build', 'tmp', 'dist']

 	grunt.registerTask 'manifest', [
 		'template:manifest'


dont use parens if no need

dont use parens if no need

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -5,7 +5,7 @@
     TESTS_DIR: 'test/'
     SPEC_DIR: '<%= TESTS_DIR %>spec/'
     DIST_FILE: 'dist/<%= PKG.name %>'
-    PKG: @file.readJSON('package.json')
+    PKG: @file.readJSON 'package.json'

     concat:
       dist:


Fix gruntfile so that tests pass

Fix gruntfile so that tests pass

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -383,6 +383,9 @@
       generic_core: Rule.typescriptSrcLenient 'compile-src/generic_core'
       generic_core_specs: Rule.typescriptSpecDeclLenient 'compile-src/generic_core'

+      logging: Rule.typescriptSrcLenient 'compile-src/logging'
+      webrtc: Rule.typescriptSrcLenient 'compile-src/webrtc'
+
       # TODO: Remove uistatic / make it the same as uipolymer once polymer is
       # fully integrated.
       uistatic: Rule.typescriptSrcLenient 'compile-src/uistatic'
@@ -429,7 +432,8 @@
         src: FILES.jasmine_helpers
             .concat [
               'build/compile-src/mocks/freedom-mocks.js'
-              'build/dev/chrome/app/scripts/uproxy-lib/logging/logging.js'
+              'build/compile-src/logging/logging.js'
+              'build/compile-src/webrtc/peerconnection.js'
               'build/compile-src/socks-to-rtc/socks-to-rtc.js'
               'build/compile-src/rtc-to-net/rtc-to-net.js'
               'build/compile-src/uproxy.js'
@@ -445,7 +449,6 @@
               'build/compile-src/generic_core/storage.js'
               'build/compile-src/generic_core/social.js'
               'build/compile-src/generic_core/core.js'
-              'build/dev/chrome/app/scripts/uproxy-lib/webrtc/peerconnection.js'
             ]
         options:
           specs: 'build/compile-src/generic_core/**/*.spec.js'
@@ -590,6 +593,8 @@
   # --- Testing tasks ---
   taskManager.add 'test_core', [
     'build_generic_core'
+    'ts:logging'
+    'ts:webrtc'
     'ts:generic_core_specs'
     'ts:mocks'
     'jasmine:generic_core'


Include deps' public directory

Include deps' public directory

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -174,20 +174,22 @@
     concat:
       js:
         src: [
+          # Bootloader needs to be built first
+          'components/pixbi-bootloader/public/*.js'
+          'components/**/public/*.js'
           'tmp/**/*.js'
-          'components/**/tmp/*.js'
         ]
         dest: 'public/index.js'
       css:
         src: [
+          'components/**/public/*.css'
           'tmp/index.css'
-          'components/**/tmp/*.css'
         ]
         dest: 'public/index.css'
       html:
         src: [
+          'components/**/public/index.html'
           'tmp/index.html'
-          'components/**/tmp/index.html'
         ]
         dest: 'tmp/index.html'


Add Grunt task for bumping to the next patch release

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -401,6 +401,28 @@
         for fname in ['package.json', 'bower.json']
             changeVersion(fname, pkg.version)

+    grunt.registerTask 'bump-patch', 'Updates the version to next patch-release', ->
+        svutil = require('semver-utils')
+        current = pkg.version
+        version = svutil.parse(pkg.version)
+
+        # Ensure it is able to be bumped
+        console.log(version.release)
+        if version.release
+            grunt.fatal("Version #{ current } not final. Should this be bumped to a pre-release?")
+
+        # Remove release and build strings
+        version.patch = '' + (parseInt(version.patch, 10) + 1)
+        version.release = 'beta'
+        version.build = ''
+
+        pkg.version = svutil.stringify(version)
+        for fname in ['package.json', 'bower.json']
+            changeVersion(fname, pkg.version)
+
+        run 'git add bower.json package.json'
+        run "git commit -m '#{ [version.major, version.minor, version.patch].join('.') } Bump'"
+
     grunt.registerTask 'tag-release', 'Create a release on master', ->
         run 'git add .'
         run "git commit -m '#{ pkg.version } Release'"
@@ -427,4 +449,5 @@
         'release-binaries'
         'tag-release'
         'release-help'
+        'bump-patch'
     ]


Add info to compiled summon file about editing/commiting it.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -6,6 +6,12 @@
   grunt.initConfig
     meta:
       shebang: '#!/usr/bin/env node'
+      hacking: """
+        // Please don't edit this file directly. It is compiled from the CoffeeScript
+        // in summon.coffee. If you edit the CoffeeScript source and compile its changes,
+        // please keep the commits to this file separate. See the README for instructions
+        // on setting up a pre-commit hook to help with that.
+      """

     coffee:
       default:
@@ -17,7 +23,7 @@
     # Prepend the node shebang line
     concat:
       options:
-        banner: '<%= meta.shebang %>\n'
+        banner: '<%= meta.shebang %>\n\n<%= meta.hacking %>\n\n'
       dist:
         src: 'summon'
         dest: 'summon'


Create "dev" grunt task and set "default" as alias

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -31,4 +31,5 @@
     grunt.loadNpmTasks 'grunt-contrib-watch'

     grunt.registerTask 'compile', ['coffee', 'uglify']
-    grunt.registerTask 'default', ['compile', 'watch']
+    grunt.registerTask 'dev', ['compile', 'watch']
+    grunt.registerTask 'default', ['default']


Continue rename

Continue rename

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -78,8 +78,8 @@
             'melt-coffee': 'coffee'
             'melt-parser': 'parser'
             'melt-helper': 'helper'
-            'ice': 'main'
-          name: 'ice'
+            'melt': 'main'
+          name: 'melt'
           optimize: 'none'
           out: 'dist/melt.js'


Use default WebRTC signalling server

Use default WebRTC signalling server

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -30,9 +30,6 @@
         options:
           debug: true
           ide: 'http://localhost:8000/index.html'
-          signalserver: 'http://flowhub-rtc.herokuapp.com' # FIXME: use
-#          signalserver: 'http://localhost:8888'
-#          signalserver: 'http://api.flowhub.io'
         files:
           "browser/<%=pkg.name%>.js": ['component.json']


uncomment

uncomment

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -69,7 +69,7 @@
       setUser:
         options:
           stdout: false
-        command: '' #'git config --global user.name "Travis Build" && git config --global user.email "$GH_TOKEN"'
+        command: 'git config --global user.name "Travis Build" && git config --global user.email "$GH_TOKEN"'
       cloneProject:
         options:
           stdout: false


added globals

added globals

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -38,7 +38,9 @@
                     define      : true
                     require     : true
                     describe    : true
+                    xdescribe   : true
                     it          : true
+                    xit         : true
                     beforeEach  : true
                     afterEach   : true
                     should      : true


Always run autoprefixer

Always run autoprefixer

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -106,12 +106,6 @@
   # Template-related
   templateTasks = [
     'jade'
-  ]
-
-  # Style-related
-  styleTasks = [
-    'autoprefixer'
-    'cssshrink'
   ]

   # Stylus files follow an order
@@ -305,34 +299,6 @@
   ## Custom tasks
   ####

-  grunt.registerTask 'compile', (type) ->
-    switch type
-      when 'js'
-        grunt.task.run('copy:js')
-
-        # Optimize
-        if NODE_ENV isnt 'dev'
-          grunt.task.run(scriptTasks)
-
-      when 'stylus'
-        # Compile with or without variable injection
-        if grunt.task.exists(styleVariableFile)
-          grunt.task.run('stylus:withVariables')
-        else
-          grunt.task.run('stylus:noVariables')
-
-        # Optimize only when we're in production and if there are stylesheets
-        if NODE_ENV isnt 'dev' and
-           grunt.file.expand('app/**/*.styl').length > 0
-          grunt.task.run(styleTasks)
-
-      when 'jade'
-        grunt.task.run(templateTasks)
-
-      # Compile dependencies
-      when 'deps'
-        runOnDeps('build')
-
   grunt.registerTask 'check', ['closure-compiler']

   grunt.registerTask 'dev', ['build', 'connect', 'watch']
@@ -349,3 +315,33 @@
         version = thisManifest.version
         task = "shell:writeVersion:#{appName}:#{version}"
         grunt.task.run(task)
+
+  grunt.registerTask 'compile', (type) ->
+    switch type
+      when 'js'
+        grunt.task.run('copy:js')
+
+        # Optimize
+        if NODE_ENV isnt 'dev'
+          grunt.task.run(scriptTasks)
+
+      when 'stylus'
+        # Compile with or without variable injection
+        if grunt.task.exists(styleVariableFile)
+          grunt.task.run('stylus:withVariables')
+        else
+          grunt.task.run('stylus:noVariables')
+
+        grunt.task.run('autoprefixer')
+
+        # Optimize only when we're in production and if there are stylesheets
+        if NODE_ENV isnt 'dev' and
+           grunt.file.expand('app/**/*.styl').length > 0
+          grunt.task.run('cssshrink')
+
+      when 'jade'
+        grunt.task.run(templateTasks)
+
+      # Compile dependencies
+      when 'deps'
+        runOnDeps('build')


Add brunch compilation target.

Add brunch compilation target.

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -97,15 +97,6 @@
           src: '**/*.js'
         ]

-        options:
-          processContent: (content, path) ->
-            name = ///temp/(.*)\.js///.exec(path)[1]
-            """
-            require.define({'#{name}': function(exports, require, module) {
-            #{content}
-            }});
-            """
-
       amd:
         files: [
           expand: true
@@ -173,6 +164,47 @@
           src: modules
         ]

+      brunch:
+        files: [
+          dest: 'build/brunch/<%= pkg.name %>.js'
+          src: modules
+        ]
+
+        options:
+          banner: '''
+          /*!
+           * Chaplin <%= pkg.version %>
+           *
+           * Chaplin may be freely distributed under the MIT license.
+           * For all details and documentation:
+           * http://chaplinjs.org
+           */
+
+          // Dirty hack for require-ing backbone and underscore.
+          (function() {
+            var deps = {
+              backbone: window.Backbone, underscore: window._
+            };
+
+            for (var name in deps) {
+              (function(name) {
+                var definition = {};
+                definition[name] = function(exports, require, module) {
+                  module.exports = deps[name];
+                };
+
+                try {
+                  require(item);
+                } catch(e) {
+                  require.define(definition);
+                }
+              })(name);
+            }
+          })();
+
+
+          '''
+
     # Lint
     # ----
     coffeelint:
@@ -229,6 +261,10 @@
         files:
           'build/commonjs/chaplin.min.js': 'build/commonjs/chaplin.js'

+      brunch:
+        files:
+          'build/brunch/chaplin.min.js': 'build/brunch/chaplin.js'
+
     # Compression
     # -----------
     compress:
@@ -247,6 +283,14 @@

         options:
           archive: 'build/commonjs/chaplin.min.js.gz'
+
+      brunch:
+        files: [
+          src: 'build/brunch/chaplin.min.js'
+        ]
+
+        options:
+          archive: 'build/brunch/chaplin.min.js.gz'

     # Watching for changes
     # --------------------
@@ -309,9 +353,19 @@
     'compress:amd'
   ]

+  grunt.registerTask 'build:brunch', [
+    'coffee:compile'
+    'urequire'
+    'copy:commonjs'
+    'concat:brunch'
+    'uglify:brunch'
+    'compress:brunch'
+  ]
+
   grunt.registerTask 'build', [
     'build:amd'
     'build:commonjs'
+    'build:brunch'
   ]

   # Lint


Grunt watch

Grunt watch

--- Gruntfile.coffee
+++ Gruntfile.coffee
@@ -20,6 +20,16 @@
       src: ['src/*.coffee']
       test: ['spec/*.coffee']

+    watch: {
+      scripts: {
+        files: ['src/*.coffee'],
+        tasks: ['default'],
+        options: {
+          spawn: false,
+        },
+      },
+    },
+
     shell:
       test:
         command: 'jasmine-focused --captureExceptions --coffee spec/'
@@ -31,6 +41,7 @@
   grunt.loadNpmTasks('grunt-contrib-coffee')
   grunt.loadNpmTasks('grunt-shell')
   grunt.loadNpmTasks('grunt-coffeelint')
+  grunt.loadNpmTasks('grunt-contrib-watch')

   grunt.registerTask 'clean', -> require('rimraf').sync('lib')
   grunt.registerTask('lint', ['coffeelint:src', 'coffeelint:test'])


updating footer to use correct async button component

--- src/components/task-plan/footer.cjsx
+++ src/components/task-plan/footer.cjsx
@@ -2,7 +2,7 @@
 React = require 'react'
 BS = require 'react-bootstrap'
 {TaskPlanStore, TaskPlanActions} = require '../../flux/task-plan'
-AsyncButton = require '../async-button'
+AsyncButton = require '../buttons/async-button'

 PlanFooter = React.createClass
   displayName: 'PlanFooter'


fix(account): notify if we have bad accounts

--- src/flux/stores/account-store.coffee
+++ src/flux/stores/account-store.coffee
@@ -43,6 +43,14 @@
       @_accounts.push((new Account).fromJSON(json))

     @_tokens = NylasEnv.config.get(saveTokensKey) || {}
+    @_trigger()
+
+  _trigger: ->
+    for account in @_accounts
+      if not account?.id
+        err = new Error("An invalid account was added to `this._accounts`")
+        NylasEnv.reportError(err)
+        @_accounts = _.compact(@_accounts)
     @trigger()

   _save: =>
@@ -60,7 +68,7 @@
     @_caches = {}
     @_accounts[idx] = account
     NylasEnv.config.set(saveObjectsKey, @_accounts)
-    @trigger()
+    @_trigger()

   _onRemoveAccount: (id) =>
     idx = _.findIndex @_accounts, (a) -> a.id is id
@@ -75,7 +83,7 @@
       ipc = require('electron').ipcRenderer
       ipc.send('command', 'application:reset-config-and-relaunch')
     else
-      @trigger()
+      @_trigger()
       Actions.focusDefaultMailboxPerspectiveForAccounts(@_accounts)

   _onReorderAccount: (id, newIdx) =>
@@ -86,7 +94,7 @@
     @_accounts.splice(existingIdx, 1)
     @_accounts.splice(newIdx, 0, account)
     @_save()
-    @trigger()
+    @_trigger()

   addAccountFromJSON: (json) =>
     if not json.email_address or not json.provider
@@ -102,7 +110,7 @@
     @_accounts.push(account)
     @_save()

-    @trigger()
+    @_trigger()
     Actions.focusDefaultMailboxPerspectiveForAccounts([account.id])

   # Exposed Data


Adding json to xml util method

Adding json to xml util method

--- src/main/webapp/lib/util/Util.coffee
+++ src/main/webapp/lib/util/Util.coffee
@@ -230,4 +230,62 @@
       newZoom = $a.Util.STROKE_WEIGHT_THIN
     else
       newZoom = $a.Util.STROKE_WEIGHT_THINNER
-    newZoom+    newZoom
+
+  # Function to convert XML to JSON
+  # Reference: http://goessner.net/download/prj/jsonxml/
+  json2xml: (json, tab) ->
+    # Recursive function to transveres through child JSON objects
+    toXml: (v, name, ind) ->
+      # define XML variable to be global
+      $a.xml = "";
+      # If JSON element is an Array
+      if v instanceof Array
+        for elm in v
+          $a.xml += ind + toXml(elm, name, ind+"\t") + "\n"
+
+      # If JSON element is an object
+      else if typeof(v) == "object"
+        hasChild = false
+        $a.xml += ind + "<" + name
+        for key in v
+          # If element has "@" character at beginning it is an attribute
+          if key.charAt(0) == "@"
+            $a.xml += " " + key.substr(1) + "=\"" + v[key].toString() + "\""
+          # Otherwise element is a tag
+          else
+            hasChild = true
+        # Close brace dependent if it has children
+        $a.xml += if hasChild then ">" else "/>"
+
+        # Continue on if JSON element has child elements
+        if hasChild
+          for key in v
+            # If element key is of type text (no XML tag created)
+            if key == "#text"
+              $a.xml += v[key]
+            else if key == "#cdata"
+              $a.xml += "<![CDATA[" + v[key] + "]]>"
+            # Else if element is not an XML attribute it has children
+            else if key.charAt(0) != "@"
+              $a.xml += toXml v[key], m, ind+"\t"
+
+          # Add indentation for new element if it is on a new line
+          if $a.xml.charAt($a.xml.length-1) == "\n"
+            $a.xml += ind + "</" + name + ">"
+          else
+            $a.xml += "</" + name + ">"
+
+      # If JSON element is just a string value
+      else
+         $a.xml += ind + "<" + name + ">" + v.toString() +  "</" + name + ">"
+      $a.xml
+
+    for elm in json
+      xml += toXml(json[elm], elm, "");
+    if tab?
+      xml.replace(/\t/g, tab)
+    else
+      xml.replace(/\t|\n/g, "")
+    # return xml
+    xml


Fix user update check for last admin

Fix user update check for last admin

--- packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee
+++ packages/rocketchat-lib/server/methods/insertOrUpdateUser.coffee
@@ -97,6 +97,11 @@

 			return _id
 		else
+			# prevent removing admin role of last admin
+			adminCount = Meteor.users.find({ _id: { $ne: userData._id }, roles: { $in: ['admin'] } }).count()
+			if adminCount is 0 and userData.role isnt 'admin'
+				throw new Meteor.Error 'error-action-not-allowed', 'Leaving the app without admins is not allowed', { method: 'insertOrUpdateUser', action: 'Remove_last_admin' }
+
 			#update user
 			updateUser = $set: {}


Track settings changes.

Track settings changes.

--- services/web/public/coffee/ide/settings/services/settings.coffee
+++ services/web/public/coffee/ide/settings/services/settings.coffee
@@ -1,19 +1,37 @@
 define [
 	"base"
 ], (App) ->
-	App.factory "settings", ["ide", (ide) ->
+	App.factory "settings", ["ide", "event_tracking", (ide, event_tracking) ->
 		return {
 			saveSettings: (data) ->
+				for key in Object.keys(data)
+					changedSetting = key
+					changedSettingVal = data[key]
+					event_tracking.send "setting-changed", { changedSetting, changedSettingVal }
+
 				data._csrf = window.csrfToken
 				ide.$http.post "/user/settings", data

+
 			saveProjectSettings: (data) ->
+				for key in Object.keys(data)
+					changedSetting = key
+					changedSettingVal = data[key]
+					event_tracking.send "project-setting-changed", { changedSetting, changedSettingVal}
+
 				data._csrf = window.csrfToken
 				ide.$http.post "/project/#{ide.project_id}/settings", data
+

 			saveProjectAdminSettings: (data) ->
+				for key in Object.keys(data)
+					changedSetting = key
+					changedSettingVal = data[key]
+					event_tracking.send "project-admin-setting-changed", { changedSetting, changedSettingVal }
+
 				data._csrf = window.csrfToken
 				ide.$http.post "/project/#{ide.project_id}/settings/admin", data
+

 		}
 	]


stops atom from filtering the results

stops atom from filtering the results

also increases debounce timer and sets max item number to 30

--- lib/pursuit.coffee
+++ lib/pursuit.coffee
@@ -10,7 +10,7 @@
     @panel ?= atom.workspace.addModalPanel(item: this, visible: false)
     editor = @[0].firstChild.getModel()
     buffer = editor.getBuffer()
-    buffer.stoppedChangingDelay = 200
+    buffer.stoppedChangingDelay = 1000
     buffer.onDidStopChanging (text) =>
       @getCompletions buffer.getText()

@@ -34,6 +34,8 @@
     super

   getFilterKey: -> "ident"
+
+  getFilterQuery: -> ""

   viewForItem: (item) ->
     "<li class='two-lines'>
@@ -75,6 +77,7 @@
     if not @selectView
       @selectView = new PursuitIdentifierListView(@getCompletions)
       @selectView.initialize()
+    @selectView.setMaxItems(30)
     @selectView.show()

 module.exports = Pursuit


runtime: Don't log on startup, breaks fbp-spec waiting

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -20,7 +20,6 @@
   #

 liveProgramming = process.env.MICROFLO_LIVE?
-console.log 'microflo: Live programming enabled' if liveProgramming

 commandstream = require("./commandstream")
 generate = require("./generate")


Fix Uri path

Fix Uri path

--- lib/welcome.coffee
+++ lib/welcome.coffee
@@ -1,6 +1,6 @@
 {CompositeDisposable} = require 'atom'
 WelcomeUri = 'atom://welcome'
-GuideUri = 'atom://welcome'
+GuideUri = 'atom://guide'

 createWelcomeView = (state) ->
   WelcomeView = require './welcome-view'
@@ -29,6 +29,7 @@
     @subscriptions = new CompositeDisposable
     @subscriptions.add atom.workspace.addOpener (filePath) ->
       createWelcomeView(uri: WelcomeUri) if filePath is WelcomeUri
+    @subscriptions.add atom.workspace.addOpener (filePath) ->
       createGuideView(uri: GuideUri) if filePath is GuideUri
     @subscriptions.add atom.commands.add 'atom-workspace', 'welcome:show', => @show()
     if atom.config.get('welcome.showOnStartup')


add /what'?s next/ command to play.coffee

--- src/scripts/play.coffee
+++ src/scripts/play.coffee
@@ -50,6 +50,15 @@
       str = "\"#{json.name}\" by #{json.artist}, from \"#{json.album}\"."
       message.send("#{URL}/images/art/#{json.id}.png?login=HOTFIX#.jpg")
       message.send("Now playing " + str)
+
+  robot.respond /what'?s next/i, (message) ->
+    authedRequest message, '/queue', 'get', {}, (err, res, body) ->
+      json = JSON.parse(body)
+      song = json.songs[1]
+      if song.name
+        message.send("We will play this awesome track \"#{song.name}\" by #{song.artist} in just a minute!")
+      else
+        message.send("The queue is empty :( Try adding some songs, eh?")

   robot.respond /say (.*)/i, (message) ->
     authedRequest message, '/say', 'post', {message: message.match[1]}, (err, res, body) ->


Change to changeColor to avoid property conflict

--- lib/layervault/nodes/folder.coffee
+++ lib/layervault/nodes/folder.coffee
@@ -7,4 +7,4 @@
   move: (to, cb) -> @api.post("#{@nodePath}/move", { to: to }, cb.bind(@))
   rename: @move

-  color: (color, cb) -> @api.put("#{@nodePath}/color", { color: color }, cb.bind(@))+  changeColor: (color, cb) -> @api.put("#{@nodePath}/color", { color: color }, cb.bind(@))


removed silly log and fixed error

removed silly log and fixed error

--- lib/move-to-mark-view-model.coffee
+++ lib/move-to-mark-view-model.coffee
@@ -21,6 +21,7 @@

   select: (requireEOL) ->
     markPosition = @vimState.getMark(@char)
+    return unless markPosition?
     currentPosition = @editor.getCursorBufferPosition()
     selectionRange = null
     if currentPosition.isGreaterThan(markPosition)
@@ -33,7 +34,6 @@
         markPosition = @editor.clipBufferPosition([markPosition.row, Infinity])
         currentPosition = new Point(currentPosition.row, 0)
       selectionRange = new Range(currentPosition, markPosition)
-    console.log(selectionRange)
     @editor.setSelectedBufferRange(selectionRange, requireEOL: requireEOL)

   execute: ->


Be defensive when setting the selected item

Closes #262

--- lib/suggestion-list-element.coffee
+++ lib/suggestion-list-element.coffee
@@ -199,12 +199,12 @@
     count

   renderSelectedItem: ->
-    return unless @selectedLi?
-    @selectedLi.classList.remove('selected')
+    @selectedLi?.classList.remove('selected')
     @selectedLi = @ol.childNodes[@selectedIndex]
-    @selectedLi.classList.add('selected')
-    @scrollSelectedItemIntoView()
-    @updateDescription()
+    if @selectedLi?
+      @selectedLi.classList.add('selected')
+      @scrollSelectedItemIntoView()
+      @updateDescription()

   # This is reading the DOM in the updateDOM cycle. If we dont, there is a flicker :/
   scrollSelectedItemIntoView: ->


change default cursor

change default cursor

--- src/core/Renderer.coffee
+++ src/core/Renderer.coffee
@@ -31,7 +31,7 @@
 			@dom.style.height = @height + 'px'
 			@dom.width = @width * @pixelRatio
 			@dom.height = @height * @pixelRatio
-		@dom.style.cursor = 'crosshair'
+		@dom.style.cursor = options.cursor or 'default'
 		@dom.style.boxSizing = 'content-box'
 		@dom.style.background = options.background
 		@dom.oncontextmenu = -> false


chore(grunt): Added option to run e2e tests locally

--- misc/grunt/tasks.coffee
+++ misc/grunt/tasks.coffee
@@ -216,6 +216,9 @@
       args.push "--browser=#{browser}"
       args.push "--params.browser=#{browser}"

+    if grunt.option("local")?
+      args.push "--seleniumAddress=http://localhost:4444/wd/hub"
+
     p = child.spawn "node", args
     p.stdout.pipe process.stdout
     p.stderr.pipe process.stderr