Update keymap-spec.coffee paths for windows

--- spec/keymap-spec.coffee
+++ spec/keymap-spec.coffee
@@ -1,3 +1,5 @@
+path = require 'path'
+
 Keymap = require '../src/keymap'
 {$, $$, RootView} = require 'atom'

@@ -351,7 +353,7 @@

   describe ".getAllKeyMappings", ->
     it "returns the all bindings", ->
-      keymap.bindKeys '~/.atom/packages/dummy/keymaps/a.cson', '.command-mode', 'k': 'c'
+      keymap.bindKeys path.join('~', '.atom', 'packages', 'dummy', 'keymaps', 'a.cson'), '.command-mode', 'k': 'c'

       mappings = keymap.getAllKeyMappings()
       expect(mappings.length).toBe 1
@@ -363,20 +365,20 @@
   describe ".determineSource", ->
     describe "for a package", ->
       it "returns '<package-name>'", ->
-        expect(keymap.determineSource('~/.atom/packages/dummy/keymaps/a.cson')).toEqual 'dummy'
+        expect(keymap.determineSource(path.join('~', '.atom', 'packages', 'dummy', 'keymaps', 'a.cson'))).toEqual 'dummy'

     describe "for a linked package", ->
       it "returns '<package-name>'", ->
-        expect(keymap.determineSource('/Users/john/github/dummy/keymaps/a.cson')).toEqual 'dummy'
+        expect(keymap.determineSource(path.join('Users', 'john', 'github', 'dummy', 'keymaps', 'a.cson'))).toEqual 'dummy'

     describe "for a user defined keymap", ->
       it "returns 'User'", ->
-        expect(keymap.determineSource('~/.atom/keymaps/a.cson')).toEqual 'User'
+        expect(keymap.determineSource(path.join('~', '.atom', 'keymaps', 'a.cson'))).toEqual 'User'

     describe "for a core keymap", ->
       it "returns 'Core'", ->
-        expect(keymap.determineSource('/Applications/Atom.app/.../node_modules/dummy/keymaps/a.cson')).toEqual 'Core'
+        expect(keymap.determineSource(path.join('Applications', 'Atom.app', '..', 'node_modules', 'dummy', 'keymaps', 'a.cson'))).toEqual 'Core'

     describe "for a linked core keymap", ->
       it "returns 'Core'", ->
-        expect(keymap.determineSource('/Users/john/github/atom/keymaps/a.cson')).toEqual 'Core'
+        expect(keymap.determineSource(path.join('Users', 'john', 'github', 'atom', 'keymaps', 'a.cson'))).toEqual 'Core'


make Nothing more null-y

make Nothing more null-y

--- src/monad.coffee
+++ src/monad.coffee
@@ -20,9 +20,9 @@

 # Not a value.
 Nothing = Trait.create null, (Trait.override (Trait {
-  inspect: () -> "<Nothing>"
   unit : () -> Nothing
-  bind : (fn) -> Nothing })
+  bind : (fn) -> Nothing
+  valueOf : () -> null })
   , (TMonad null))

 # Possibly a value or possibly, quietly nothing at all...


Link fixes

Link fixes

--- packages/shrub-grunt/dox/dynamic.coffee
+++ packages/shrub-grunt/dox/dynamic.coffee
@@ -371,7 +371,7 @@
         fileStats.file
       }:#{
         todo.index
-      }](../source/#{
+      }](source/#{
         filename
       }##{
         id
@@ -474,9 +474,9 @@
       parts = fileStats.pkg.split '/'
       parentPkg = parts.shift()
       subPkg = parts.join '/'
-      render += "## [#{parentPkg}/#{subPkg}](../source/#{sourcePath})"
+      render += "## [#{parentPkg}/#{subPkg}](source/#{sourcePath})"
     else
-      render += "## [#{fileStats.pkg}](../source/#{sourcePath})"
+      render += "## [#{fileStats.pkg}](source/#{sourcePath})"

     # Naively parse out the file description. It must be wrapped in asterisks,
     # i.e. italicized in markdown.


When promise resolution throws an exception, waitsForPromise is halted

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -99,13 +99,14 @@
     promise = fn()
     if shouldReject
       promise.fail(moveOn)
-      promise.done -> throw new Error("Expected promise to be rejected, but it was resolved")
+      promise.done ->
+        jasmine.getEnv().currentSpec.fail("Expected promise to be rejected, but it was resolved")
+        moveOn()
     else
       promise.done(moveOn)
       promise.fail ->
-        debugger
-        throw new Error("Expected promise to be resolved, but it was rejected")
-
+        jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected")
+        moveOn()

 window.resetTimeouts = ->
   window.now = 0


move constructor options in collectionview too

--- src/chaplin/views/collection_view.coffee
+++ src/chaplin/views/collection_view.coffee
@@ -81,6 +81,16 @@
     # Track a list of the visible views
     visibleItems: null

+    # Constructor
+    # -----------
+
+    constructor: (options) ->
+      # Apply options to view instance
+      if (options)
+        _(this).extend _.pick options, ['renderItems', 'itemView']
+
+      super
+
     # Initialization
     # --------------

@@ -92,9 +102,6 @@

       # Start observing the collection
       @addCollectionListeners()
-
-      # Apply options to view instance
-      _(this).extend _.pick options, ['renderItems', 'itemView']

       # Apply a filter if one provided
       @filter options.filterer if options.filterer?


add more urls to tests

add more urls to tests

--- test/unit/get-id-from-url.coffee
+++ test/unit/get-id-from-url.coffee
@@ -22,3 +22,13 @@
         url = 'https://www.youtube.com/watch?v=VbNF9X1waSc&feature=youtu.be'
         id = 'VbNF9X1waSc'
         expect(getIdFromURL(url)).toBe id
+
+    it 'should handle http://youtu.be', ->
+        url = 'https://youtu.be/3FY4MRdQOdE'
+        id = '3FY4MRdQOdE'
+        expect(getIdFromURL(url)).toBe id
+
+    it 'should handle "edit" links from video manager page', ->
+        url = 'https://www.youtube.com/edit?o=U&video_id=3k2ZBu3kuiE'
+        id = '3k2ZBu3kuiE'
+        expect(getIdFromURL(url)).toBe id


Don't save the currentPane in GitCommitAmend

--- spec/models/git-commit-amend-spec.coffee
+++ spec/models/git-commit-amend-spec.coffee
@@ -5,7 +5,7 @@
 GitCommitAmend = require '../../lib/models/git-commit-amend'
 {
   repo,
-  pathToRepoFile,
+  pathToRepoFile
 } = require '../fixtures'
 lastCommitMessage = "commit message"
 lastCommitStatus = "M   some-file.txt"
@@ -19,7 +19,6 @@
 describe "GitCommitAmend", ->
   beforeEach ->
     spyOn(fs, 'readFileSync').andReturn ''
-
     spyOn(git, 'stagedFiles').andCallFake ->
       args = git.stagedFiles.mostRecentCall.args
       if args[0].getWorkingDirectory() is repo.getWorkingDirectory()


Fix timeline queries

Fix timeline queries

--- lib/assets/javascripts/views/posts_feed.js.coffee
+++ lib/assets/javascripts/views/posts_feed.js.coffee
@@ -15,8 +15,8 @@
     @feed_headers = options.headers || {}

     default_queries = [
-      { profiles: 'entity,refs', max_refs: 1 },
-      { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], mentions: 'subscribing', profiles: 'entity,refs' },
+      { profiles: 'entity,refs', max_refs: 1, entities: false },
+      { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], mentions: 'subscribing', profiles: 'entity,refs', entities: false },
       { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], entities: @entity, profiles: 'entity' }
     ]
     @feed_queries = _.map options.feed_queries || default_queries, (q) => q.types ?= @types; q


Use an existing component connection if provided in config

Will be used for testing by using a fake connection mocking an XMPP server. But
maybe it can be useful in the real world too.

--- src/xmpp/connection.coffee
+++ src/xmpp/connection.coffee
@@ -33,7 +33,10 @@
         @jid = config.jid
         # Anyone wants reconnecting, regardless of the config file:
         config.reconnect = true
-        @conn = new xmpp.Component(config)
+        if config.conn?
+            @conn = config.conn
+        else
+            @conn = new xmpp.Component(config)
         @conn.on "error", (e) ->
             logger.error e
         @conn.on "online", =>


Imgur: fix null error caused by nonexistant albums

--- bots/Imgur.coffee
+++ bots/Imgur.coffee
@@ -30,25 +30,27 @@
 if not ARGS[1].match(new RegExp(".+\\#{sep}$"))
   ARGS[1] += sep # add the path separator if it is missing from the end

+matches = ARGS[0].match /^(?:(?:(?:(?:(?:(?:https?:\/\/)?(?:www\.)?imgur\.com)?\/)?a)?\/)?(\w+)(?:[\/?#](?:.+)?)?)$/i
+if matches is null
+  casper.echo "argErr: album id can't be found in `#{ARGS[0]}`"; casper.exit 1
+ARGS[0] = matches[1]
 folder = "#{ARGS[1]}#{ARGS[0]}#{sep}"
-matches = ARGS[0].match /^(?:(?:(?:(?:https?:\/\/)?imgur\.com)?(?:\/?a\/))?(\w+)(?:(?:\?|#|\/)(?:.+)?)?)$/i
-ARGS[0] = matches[1] ?= matches[2]

 casper.echo "Fetching album #{ARGS[0]} from Imgur."
-casper.start "http://imgur.com/a/#{ARGS[0]}/layout/blog", ->
-  @reload -> # reload the page to ensure that the page is in the blog layout
-    [imgs, albumDesc] = @evaluate ->
-      images = []
-      for img in document.querySelectorAll('.image')
-        images.push(
-          link: img.querySelector('.album-view-image-link a').href # link to image
-          desc: img.querySelector('.description')?.innerText # description if it exists
-        )
-      return [images, document.querySelector('.panel .description h1').innerText];
-
-    if imgs.length is 0
-      @echo 'Album either empty or not found.'; @exit 2
-    @echo "Found #{imgs.length} images in the album."
+casper.start "http://imgur.com/a/#{ARGS[0]}/layout/blog", (data) ->
+  if data['status'] is 200
+    @reload -> # reload the page to ensure that the page is in the blog layout
+      [imgs, albumDesc] = @evaluate ->
+        images = []
+        for img in document.querySelectorAll('.image')
+          images.push(
+            link: img.querySelector('.album-view-image-link a').href # link to image
+            desc: img.querySelector('.description')?.innerText # description if it exists
+          )
+        return [images, document.querySelector('.panel .description h1').innerText];
+      @echo "Found #{imgs.length} images in the album."
+  else
+    @echo 'Album either empty or not found.'; @exit 2

 casper.then ->
   infoFile = "#{folder}0 - #{ARGS[0]}.txt"


minor refactor

minor refactor

--- app/router.coffee
+++ app/router.coffee
@@ -14,7 +14,7 @@
     @route "index", path: '/'
     @route "settings"
     @route "pricing"
-    @route 'payment', path:'/payment/:pricingId/:duration'
+    @route 'payment', path:'/payment/:pricingId/:paymentDuration'
     @route "project", path: '/project/:projectId', ->
       @route 'settings'
       @route 'files'


parameter to hide UI in pivotUI()

parameter to hide UI in pivotUI()

--- src/pivot.coffee
+++ src/pivot.coffee
@@ -673,6 +673,7 @@
             unusedAttrsVertical: 85
             autoSortUnusedAttrs: false
             onRefresh: null
+            showUI: true
             filter: -> true
             sorters: {}

@@ -711,7 +712,7 @@
             uiTable = $("<table>", "class": "pvtUi").attr("cellpadding", 5)

             #renderer control
-            rendererControl = $("<td>")
+            rendererControl = $("<td>").addClass("pvtUiCell")

             renderer = $("<select>")
                 .addClass('pvtRenderer')
@@ -722,7 +723,7 @@

             #axis list, including the double-click menu
-            unused = $("<td>").addClass('pvtAxisContainer pvtUnused')
+            unused = $("<td>").addClass('pvtAxisContainer pvtUnused pvtUiCell')
             shownAttributes = (a for a of attrValues when a not in opts.hiddenAttributes)
             shownInAggregators = (c for c in shownAttributes when c not in opts.hiddenFromAggregators)
             shownInDragDrop = (c for c in shownAttributes when c not in opts.hiddenFromDragDrop)
@@ -885,7 +886,7 @@
                     $(this).html(ordering[$(this).data("order")].colSymbol)
                     refresh()

-            $("<td>").addClass('pvtVals')
+            $("<td>").addClass('pvtVals pvtUiCell')
               .appendTo(tr1)
               .append(aggregator)
               .append(rowOrderArrow)
@@ -893,12 +894,12 @@
               .append($("<br>"))

             #column axes
-            $("<td>").addClass('pvtAxisContainer pvtHorizList pvtCols').appendTo(tr1)
+            $("<td>").addClass('pvtAxisContainer pvtHorizList pvtCols pvtUiCell').appendTo(tr1)

             tr2 = $("<tr>").appendTo(uiTable)

             #row axes
-            tr2.append $("<td>").addClass('pvtAxisContainer pvtRows').attr("valign", "top")
+            tr2.append $("<td>").addClass('pvtAxisContainer pvtRows pvtUiCell').attr("valign", "top")

             #the actual pivot table container
             pivotTable = $("<td>")
@@ -926,6 +927,8 @@
                 @find(".pvtAggregator").val opts.aggregatorName
             if opts.rendererName?
                 @find(".pvtRenderer").val opts.rendererName
+
+            @find(".pvtUiCell").hide() unless opts.showUI

             initialRender = true


experimenting with destructive methods

experimenting with destructive methods

--- src/point.coffee
+++ src/point.coffee
@@ -24,6 +24,14 @@
     }, Point.instanceMethods

   Point.instanceMethods =
+    ###*
+    Creates a copy of this point.
+
+    @name copy
+    @methodOf Point#
+    @returns A new point with the same x and y value as this point.
+    @type Point
+    ###
     copy: ->
       Point(this.x, this.y)


Fixes bug where tools would reload regardless if they were previously loaded or not.

--- app/app_view.coffee
+++ app/app_view.coffee
@@ -50,9 +50,9 @@
           Also not really the location I want to put this in the end.
           ###
           isScriptNotLoaded = (script) ->
-            if _.isUndefined window[script]
+            if _.isUndefined window[script.name]
               # Not on window. Maybe an Ubret script.
-              if _.isUndefined Ubret[script]
+              if _.isUndefined Ubret[script.name]
                 return true
             return false


Now saving days array along with a containing empty shifts array when a tournament is created.

--- client/setup.coffee
+++ client/setup.coffee
@@ -17,10 +17,16 @@
   'click #saveTournament': (evnt, template) ->
     # unless saveTournamentIsValid template
     #   return
+    firstDay = moment(template.find('#firstDate').value, 'DD MMM YYYY')
+    lastDay = moment(template.find('#lastDate').value, 'DD MMM YYYY')
+    length = lastDay.diff(firstDay, 'days')
     options =
       tournamentName: template.find('#tournamentName').value
-      firstDay: moment(template.find('#firstDate').value, 'DD MMM YYYY').toDate()
-      lastDay: moment(template.find('#lastDate').value, 'DD MMM YYYY').toDate()
+      firstDay: firstDay.toDate()
+      lastDay: lastDay.toDate()
+      days: for count in [1..length + 1]
+        date: moment(firstDay).add('days', count - 1).toDate(), shifts: []
+
     Meteor.call 'saveTournament', options, (err, id) ->
       unless err
         # show sucess alert
@@ -29,7 +35,6 @@
         # show error alert
   'click [data-action=delete]': (evnt, template) ->
     id = $(evnt.currentTarget).data 'tournament-id'
-    console.log "id is " + id
     Tournaments.remove id

 # savetournamentisvalid = (tmp) ->


Add send/reply functions to Robot

Add send/reply functions to Robot

--- src/robot.coffee
+++ src/robot.coffee
@@ -167,6 +167,22 @@
         continue if !line.match('-')
         @commands.push line[2..line.length]

+  # Public: A helper send function which delegates to the adapter's send
+  # function.
+  #
+  # user    - A User instance.
+  # strings - One or more Strings for each message to send.
+  send: (user, strings...) ->
+    @adapter.send user, strings...
+
+  # Public: A helper reply function which delegates to the adapter's reply
+  # function.
+  #
+  # user    - A User instance.
+  # strings - One or more Strings for each message to send.
+  reply: (user, strings...) ->
+    @adapter.reply user, strings...
+
   # Public: Get an Array of User objects stored in the brain.
   users: ->
     @brain.data.users


Added entry for LD29

Added entry for LD29

--- src/js/sampleSpec.coffee
+++ src/js/sampleSpec.coffee
@@ -841,7 +841,7 @@
 rat eye -> rat ; message "Sleeping rat"
 pickaxe eye -> pickaxe ; message "Pickaxe"
 crowbar eye -> crowbar ; message "Crowbar"
-jane eye -> jane ; message "Jane"
+jane eye -> jane ; message "So you have heard my distress signals"
 mr-white1 eye -> mr-white1 ; message "Mr White looks      thirsty"
 mr-white2 eye -> mr-white2 ; message "Mr White"
 radx-unpowered eye -> radx-unpowered ; message "Unpowered RADX"


Don't return a mapping from traverseToBufferRow if we fall off the end

--- src/app/row-map.coffee
+++ src/app/row-map.coffee
@@ -15,11 +15,7 @@

   mapBufferRowRange: (startBufferRow, endBufferRow, screenRows) ->
     { mapping, index, bufferRow } = @traverseToBufferRow(startBufferRow)
-    if mapping
-      if mapping.bufferRows != mapping.screenRows and index < @mappings.length - 1
-        throw new Error("Invalid mapping insertion")
-    else
-      index = 0
+    throw new Error("Invalid mapping insertion") if mapping and mapping.bufferRows != mapping.screenRows

     padBefore = startBufferRow - bufferRow
     padAfter = (bufferRow + mapping?.bufferRows) - endBufferRow
@@ -34,7 +30,11 @@
     bufferRow = 0
     screenRow = 0
     for mapping, index in @mappings
-      break if (bufferRow + mapping.bufferRows) > targetBufferRow
+      if (bufferRow + mapping.bufferRows) > targetBufferRow
+        return { mapping, index, screenRow, bufferRow }
+      bufferRow += mapping.bufferRows
+      screenRow += mapping.screenRows
+    { index, screenRow, bufferRow }
       bufferRow += mapping.bufferRows
       screenRow += mapping.screenRows
     { mapping, index, screenRow, bufferRow }


Also mount the host dbus on host_run/dbus

--- src/application.coffee
+++ src/application.coffee
@@ -214,6 +214,7 @@
 					'/lib/modules:/lib/modules'
 					'/lib/firmware:/lib/firmware'
 					'/run/dbus:/run/dbus'
+					'/run/dbus:/host_run/dbus'
 					'/var/run/docker.sock:/run/docker.sock'
 					'/etc/resolv.conf:/etc/resolv.conf:rw'
 				]


clean up vessel methods

clean up vessel methods

--- imports/api/vessels/methods.coffee
+++ imports/api/vessels/methods.coffee
@@ -109,9 +109,6 @@
 		run: (newVessel) -> if Meteor.isServer
 			{ Vessels } = require '/imports/api/vessels/vessels.coffee'

-			console.log 'newVessel'
-			console.log newVessel
-
 			project = Projects.findOne(newVessel.projectId, fields: vesselModule: 1, harborGroup: 1)

 			newVessel.harborGroup = project.harborGroup
@@ -136,17 +133,11 @@
 					for key in Object.keys(newVessel) when key in ['name', 'flag', 'type', 'callsign', 'eni', 'imo', 'mmsi', 'lastVisit', 'nextVisit', 'languages']
 						set[key] = newVessel[key]

-					console.log newVessel
-					console.log set
-
-					console.log Vessels.update newVessel._id, $set: set
+					Vessels.update newVessel._id, $set: set

 Meteor.methods

 	validateVesselInput: (args) ->
-
-		console.log 'args'
-		console.log args

 		{ Vessels } = require '/imports/api/vessels/vessels.coffee'


Two minor optimizations and (some) better documentation.

--- src/coffee/utils/telepathic-chain.coffee
+++ src/coffee/utils/telepathic-chain.coffee
@@ -17,16 +17,31 @@
     d.yay = _.once d.resolve
     d.nay = _.once d.reject
     return d
-
+###*
+* This is a simple AmpersandState wrapper around a simple object
+* @class InstructionState
+###
 InstructionState = AmpState.extend {
     props: {
+        ###*
+        * This is the command that the instruction represents
+        * @property fn
+        ###
         fn: ['function', true]
+        ###*
+        * This is the time that the instruction was requested
+        * @property timestamp
+        ###
         timestamp: {
             type: 'number'
             required: true
             default: ()->
                 return Date.now()
         }
+        ###*
+        * This is the name of the instruction
+        * @property name
+        ###
         name: {
             type: 'string'
             required: true
@@ -35,11 +50,27 @@
         }
     }
     session: {
+        ###*
+        * This is a singleton copy of the `fn` property, assigned
+        * during initialize
+        * @property single
+        ###
         single: ['function', true]
     }
+    ###*
+    * This method takes a function and wraps it around its own function
+    * @method wrap
+    * @param {Function} wrapper
+    * @return {Function} wrapped fn
+    ###
     wrap: (wrapper)->
         if _.isFunction wrapper
             return _.wrap @fn, wrapper
+    ###*
+    * The default constructor for ampersand objects
+    * Auto-generates a name if required, and also generates the single session property
+    * @method initialize
+    ###
     initialize: (attrs, opts)->
         if attrs.name?
             attrs.name = _.uniqueId attrs.name
@@ -49,12 +80,22 @@
         return @
 }

+###*
+* It's a collection of instructions. Pretty cool.
+* @class InstructionCollection
+###
 InstructionCollection = AmpCollection.extend {
     model: InstructionState
 }

+###*
+* This represents the internal instruction chain for each telepathic chain (defined below)
+* @class ChainState
+###
 ChainState = AmpState.extend {
+    # we use name as an identifer
     idAttribute: 'name'
+    # and we'll make one up if you don't give us one
     props: {
         name: {
             type: 'string'
@@ -63,9 +104,16 @@
                 return _.uniqueId "chain"
         }
     }
+    # It's our collection of instructions, named instructions. Pretty cool.
     collections: {
         instructions: InstructionCollection
     }
+    ###*
+    * Add a named instruction to our list of instructions
+    * @method addInstruction
+    * @param {String} name - the name of the instruction
+    * @param {Function} fn - the command to add to the new InstructionState
+    ###
     addInstruction: (name, fn)->
         unless _.isString name
             throw new TypeError "Expected name to be a string."
@@ -77,8 +125,13 @@
         }
         newState = new InstructionState settings
         return @instructions.add newState
+
     derived: {
-        # re-wraps the instruction set so it builds out a hashmap of promises
+        ###*
+        * Returns a list of singleton functions
+        * re-wraps the instruction set so we only call the singleton function
+        * @property commands
+        ###
         commands: {
             deps: [
                 'instructions'
@@ -87,22 +140,22 @@
             fn: ()->
                 return _.map @instructions.models, (instruction)->
                     if instruction.fn?
-                        # return _.wrap instruction.fn, (fn, container)->
-                        #     outcome = fn()
-                        #     if container? and _.isArray container
-                        #         container.push outcome
-                        #     return outcome
                         return instruction.single
                     return null
         }
     }
+    ###*
+    * Wrap all of the instructions with another function
+    * @method wrap
+    ###
     wrap: (wrapper)->
         unless _.isFunction wrapper
             throw new TypeError "Expected wrapper to be function."
         return _.map @instructions.models, (instruction)->
-            if instruction.fn?
-                return _.wrap instruction.fn, wrapper
+            if instruction.wrap?
+                return instruction.wrap wrapper
             return null
+
     initialize: (attrs, opts)->
         if attrs.name?
             attrs.name = _.uniqueId attrs.name
@@ -112,7 +165,6 @@

 module.exports = (stateName)->
     "use strict"
-    scopeThis = @
     stateSettings = {}
     if stateName?
         stateSettings.name = _.uniqueId stateName
@@ -160,7 +212,7 @@
     # our template list, which contains templates
     ___.guarded '_templates', {}

-# our template list, which contains templates
+    # our template list, which contains templates
     ___.guarded '_posts', {}

     # set sugar mode
@@ -175,12 +227,6 @@
     ___.readable 'sugar', ()->
         @_sugar = true
         return @
-
-    # we may need to generate random ids, this does that
-    ___.guarded 'generateId', (content=null)->
-        unless content?
-            content = "tpChain"
-        return _.uniqueId content

     # set the mode as promise
     ___.readable "promise", ()->
@@ -259,7 +305,7 @@
         # state.counter += 1
         return state.addInstruction.call state, name, fn, kind

-    ___.readable 'lookup', (location, id)->
+    ___.readable 'lookup', _.memoize (location, id)->
         self = @
         out = self[location][id]
         return out
@@ -373,11 +419,13 @@
                 subRoute.resolve out
             bad = (e)->
                 subRoute.reject e
-            return _.wrap struct.fn, (fn)->
+            return _.wrap struct.single, (fn)->
                 fn().then good, bad
                 return subRoute
         return sequentAll

+    # used by resolvePostsAndTemplates, this adds the .locals' properties
+    # to the post content before it goes out
     ___.guarded 'addLocalsToContent', (content)->
         self = TelepathicChain
         localized = content
@@ -413,6 +461,8 @@
                     localized[key] = loc
         return localized

+    # used internally during execute, this allows us to group together the
+    # correct post to template
     ___.guarded 'resolvePostsAndTemplates', (posts, templates, group)->
         self = TelepathicChain
         return _(posts).map((postContent, key)->


Added Quotes

Added Quotes

--- help/troubleshoot/apache.coffee
+++ help/troubleshoot/apache.coffee
@@ -33,7 +33,7 @@
     else if stdout[0...18] is "Apache2 is running"
       out += """
       Apache Responsed with an unknown status, there may be a problem.
-      Apache Status: #{stdout}
+      Apache Status: "#{stdout}"
       """

     done()


fix the isPublished flag

fix the isPublished flag

--- src/components/course-calendar/plan.cjsx
+++ src/components/course-calendar/plan.cjsx
@@ -64,7 +64,7 @@
     publishStatus: publishStatus
     isPublishing: PlanPublishStore.isPublishing(plan.id)
     isHovered: false
-    isPublished: plan.is_published and not plan.is_publishing
+    isPublished: plan.is_published

   # utility functions for functions called in lifecycle methods
   _doesPlanMatchesRoute: ->
@@ -104,12 +104,13 @@
     @setState({isViewingStats})

   checkPublishingStatus: (published) ->
-    planId = @props.item.plan.id
+    {plan} = @props.item
+    planId = plan.id
     if published.for is planId
       planStatus =
         publishStatus: published.status
         isPublishing: PlanPublishStore.isPublishing(planId)
-        isPublished: @state.isPublished
+        isPublished: plan.is_published or PlanPublishStore.isSucceeded(planId)

       @setState(planStatus)
       PlanPublishStore.removeAllListeners("progress.#{planId}.*", @checkPublishingStatus) if PlanPublishStore.isDone(planId)


add brush

add brush

--- coffee/graph.coffee
+++ coffee/graph.coffee
@@ -1,4 +1,20 @@
+fixtures =
+  a: 10
+
+#utils
 log = -> window.debug != false && console.log.apply(console, arguments)
+
+dist = (a, b) ->
+  xd = a.x - b.x
+  yd = a.y - b.y
+  Math.sqrt(xd * xd + yd * yd)
+
+mirror = (f) ->
+  if "function" != typeof f then f = d3.ease.apply(d3, arguments)
+  (t) -> if t < .5 then f(2 * t) else f(2 - 2 * t)
+
+rand_c = ->
+  '#' + (0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6)

 urls =
   search: (q) ->
@@ -20,13 +36,6 @@
   window.__cb__ = callback or -> log(arguments)
   d3.select('head').append('script').attr('src', urls.search(query))

-mirror = (f) ->
-  if "function" != typeof f then f = d3.ease.apply(d3, arguments)
-  (t) -> if t < .5 then f(2 * t) else f(2 - 2 * t)
-
-rand_c = ->
-  '#' + (0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6)
-
 drag = d3.behavior.drag().on 'drag', ->
   dx = d3.event.dx
   dy = d3.event.dy
@@ -43,88 +52,88 @@
   d3.selectAll('text').attr
     x: (d) -> d.x
     y: (d) -> d.y
-
-distance = (x1, y1, x2, y2) ->
-  xd = x1 - x2
-  yd = y1 - y2
-  Math.sqrt(xd * xd + yd * yd)
-
-dist = (a, b) ->
-  xd = a.x - b.x
-  yd = a.y - b.y
-  Math.sqrt(xd * xd + yd * yd)

 scale = d3.scale.linear()
   .domain([0,9])
-  .range([0,Math.PI * 2])
+  .range([0, Math.PI * 2])

 links = []
 count = 0
 create = (wiki) ->
+  return if (d3.selectAll('circle')[0].length > 40)
   console.log wiki
   h = (window.innerHeight / 2)
   w = (window.innerWidth / 2)
   count++
   wiki.query.search.forEach (obj, index) ->
-    obj.x = w + 100 * count * Math.cos(scale(index))
-    obj.y = h + 100 * count * Math.sin(scale(index))
+    obj.count = count
+    obj.i = index
+    obj.x = 75 * count * Math.cos(scale(index)) + w
+    obj.y = 75 * count * Math.sin(scale(index)) + h
+    obj.fill = rand_c()
+    obj.r = 25
     x = -> jsonp(obj.title, create)
     setTimeout x, 2000 if index < 1

-  nodes = d3.select('svg').selectAll('.node').data(wiki.query.search)
+  nodes = d3.select('.graph').selectAll('.node').data(wiki.query.search)
     .enter().append('circle')
+    .on('mouseover', -> d3.select(@).attr opacity: 1)
+    .on('mouseout', -> d3.select(@).attr opacity: .5)
+    .attr
+      opacity: .5
+      cx: (d) -> d.x
+      cy: (d) -> d.y
+      fill: (d) -> d.fill
     .call(drag)
     .transition()
+    .duration(1000)
+    .ease(d3.ease('cubic-in-out'))
     .delay((d, i) -> i * 50)
     .attr
-      cx: (d) -> d.x
-      cy: (d) -> d.y
-      fill: rand_c
-      r: 25
+      r: (d) -> d.r
+
   nodes.each (d, i) ->
-    d3.select('svg').append('text').datum(d)
+    return 10;
+    d3.select('.graph').append('text').datum(d)
       .text(d.title)
       .transition()
+      .ease(d3.ease('cubic-in-out'))
       .attr
-        x: (d) -> d.x
-        y: (d) -> d.y
-        fill: rand_c
-        'font-family': 'Deja Vu Sans Mono'
+        x: (d) -> d.x - 5
+        y: (d) -> d.y + 30
+        fill: d.fill
+        'font-family': 'deja vu sans mono'

-  q = d3.geom.quadtree(wiki.query.search, innerWidth, innerHeight)
-  i = 0
-  # d3.selectAll('circle').data().forEach (d, i) ->
-  #   q.visit (node, x1, y1, x2, y2) ->
-  #     if 200 > distance(x1, y1, x2, y2) and a != b
-  #       links.push
-  #         from: a
-  #         to: b
-
+  #TODO
+  # convert lines to path
+  # give links access to nodes
   d3.selectAll('circle').data().forEach (a) ->
     d3.selectAll('circle').data().forEach (b) ->
-      if 200 > dist(a, b) and a != b
+      if 250 > dist(a, b) and a != b
         links.push
           from: a
           to: b

-  d3.select('svg').selectAll('line').data(links)
+  d3.select('.graph').selectAll('line').data(links)
     .enter().insert('line', '*')
     .attr
       'stroke-width': 2
+      'stroke-opacity': .01
       x1: (d) -> d.from.x
       y1: (d) -> d.from.y
       x2: (d) -> d.from.x
       y2: (d) -> d.from.y
-      stroke: rand_c
+      stroke: (d) -> d.from.fill
     .transition()
     .duration(5000)
-    .ease(d3.ease('poly', 3))
+    .ease(d3.ease('cubic'))
     .attr
+      'stroke-opacity': .3
       x2: (d) -> d.to.x
       y2: (d) -> d.to.y

 init = ->
-  body = d3.select('body').style background: '#333'
+  body = d3.select('body')
   body.append('input')
     .attr('type','text')
     .style
@@ -134,16 +143,29 @@
       if d3.event.which == 13
         jsonp(d3.event.target.value, create)
         d3.event.target.value = ''
-
   svg = body.append('svg')
+  graph = svg.append('g').attr('class','graph')
+  brush = svg.append('g').attr('class','brush')
+    .attr
+      transform: "translate(0,#{innerHeight * .8})"
+      stroke: 'pink'
+      fill: 'red'
+      'stroke-width': '10'
+      'stroke-opacity': .3
+      'fill-opacity': .5
+    .call(d3.svg.brush().x(d3.scale.identity().domain([0, innerWidth])))
+    .on('brushstart', -> console.log 'strart')
+    .on('brush', -> console.log 'brush')
+    .on('brushend', -> console.log 'end')
+    .selectAll('rect')
+    .attr
+      rx: 15
+      ry: 15
+      height: '100px'
+
   test = "ocean"
   test && jsonp(test, create)
-
-x = 0
-rotate = ->
-  d3.selectAll('text').attr('rotate', -> x++)

 d3.select(window)
   .on('keydown', -> setInterval rotate, 10 if d3.event.which == 27)
   .on('load', init)
-


不要なconsole.logを廃止

不要なconsole.logを廃止

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -77,7 +77,6 @@
       # 自分以外からメッセージが来た場合だけmarkdownを更新する
       if data.from != myGuid
         markdown = if data.markdown == "" then defaulMarkdown else data.markdown
-        console.log markdown
         @setState
           markdown: markdown,
           # 暫定版キャレット位置維持機能。


Fix regexp to extract the listen json

Fix regexp to extract the listen json

--- index.coffee
+++ index.coffee
@@ -88,7 +88,7 @@
     socket.on 'data', (buffer) ->
       util = require 'util'
       json = buffer.toString()
-      json = json.match(/{.*}/)[0]
+      json = json.match(/\{".*\}/)[0]
       json = JSON.parse json
       filenames = _.union json.modified, json.added, json.removed

@@ -98,7 +98,7 @@

     socket.on 'end', ->
       # End is not working at the moment...
-      console.log chalk.red 'Connection to Listen at #{options.host}:#{options.port} lost'
+      console.log chalk.red "Connection to Listen at #{options.host}:#{options.port} lost"

   # Execute config with onLoad is true
   for config in options.configs
@@ -112,4 +112,4 @@
   process.on 'SIGINT', ->
     socket.destroy()
     console.log chalk.green "Successfully disconnected from Listen at #{options.host}:#{options.port}"
-    process.exit 0+    process.exit 0


fix(tooltip): doesn't show on archive either

--- internal_packages/tooltip/lib/tooltip.cjsx
+++ internal_packages/tooltip/lib/tooltip.cjsx
@@ -88,7 +88,6 @@

   _onTooltipLeave: =>
     return unless @_enteredTooltip
-    @_mutationObserver?.disconnect()
     @_enteredTooltip = false
     clearTimeout(@_showTimeout)
     @_hideTooltip()
@@ -117,9 +116,12 @@
       top = dim.top - TOOLTIP_HEIGHT

     # If for some reason the element was removed from underneath us, we
-    # won't know until we get here. The element's dimensions will return 0
-    # ,0, which we can use to filter out bad displays
-    if left < 5 and top < 5
+    # won't know until we get here. The element's dimensions will return
+    # (0, 14), which we can use to filter out bad displays. This can
+    # happen if our mutation observer misses the event. In some cases
+    # (like the multi-select toolbar), the button's great-grandparent is
+    # removed from the DOM and no mutation observer event is fired.
+    if left < 20 and top < 20
       @_hideTooltip()
       return

@@ -159,6 +161,7 @@
     document.body.offsetHeight

   _hideTooltip: =>
+    @_mutationObserver?.disconnect()
     @_lastTarget = null
     @setState
       top: 0


Fix ionic history

Fix ionic history

--- www/app/app.coffee
+++ www/app/app.coffee
@@ -12,7 +12,12 @@
   'monospaced.elastic'
 ])

-.run ($ionicPlatform, amMoment) ->
+.run (
+  amMoment
+  $rootScope
+  $ionicPlatform
+  $ionicHistory
+) ->
   # TODO reload user

   amMoment.changeLocale('zh-cn')
@@ -27,3 +32,9 @@
       # org.apache.cordova.statusbar required
       StatusBar.styleDefault()

+  # FIX 浏览器刷新/后退 nav icon 异常
+  $rootScope.$on '$stateChangeStart', (event, toState) ->
+    # 如何是回到Home页面，设置historyRoot
+    if toState.name is 'app.topics'
+      $ionicHistory.nextViewOptions
+        historyRoot: true


Code organization

Code organization

--- src/scripts/modules/media/nav/nav.coffee
+++ src/scripts/modules/media/nav/nav.coffee
@@ -1,7 +1,7 @@
 define (require) ->
   $ = require('jquery')
+  router = require('cs!router')
   linksHelper = require('cs!helpers/links')
-  router = require('cs!router')
   analytics = require('cs!helpers/handlers/analytics')
   BaseView = require('cs!helpers/backbone/views/base')
   template = require('hbs!./nav-template')


default bot name

default bot name

--- src/slack.coffee
+++ src/slack.coffee
@@ -22,7 +22,7 @@
       options =
          token:   process.env.HUBOT_SLACK_TOKEN
          team:    process.env.HUBOT_SLACK_TEAM
-         name:    process.env.HUBOT_SLACK_BOTNAME
+         name:    process.env.HUBOT_SLACK_BOTNAME or 'slackbot'

       # Listen to incoming webhooks from slack
       self.robot.router.post "/hubot/slack-webhook", (req, res) ->


Removing stopwords

Removing stopwords

--- classifier.coffee
+++ classifier.coffee
@@ -1,3 +1,6 @@
+# list from http://norm.al/2009/04/14/list-of-english-stop-words/
+STOP_WORDS = `"a,able,about,across,after,all,almost,also,am,among,an,and,any,are,as,at,be,because,been,but,by,can,cannot,could,dear,did,do,does,either,else,ever,every,for,from,get,got,had,has,have,he,her,hers,him,his,how,however,i,if,in,into,is,it,its,just,least,let,like,likely,may,me,might,most,must,my,neither,no,nor,not,of,off,often,on,only,or,other,our,own,rather,said,say,says,she,should,since,so,some,than,that,the,their,them,then,there,these,they,this,tis,to,too,twas,us,wants,was,we,were,what,when,where,which,while,who,whom,why,will,with,would,yet,you,your".split(',')
+
 class Classifier
     constructor: (@store) ->
         if @store?
@@ -5,13 +8,22 @@
         else
            @feature_count = {}
            @klass_count = {}
+        stop_words = STOP_WORDS.replace(/,/g, "\\b|\\b")
+        stop_words = "\\b"+stop_words+"\\b"
+        @stop_words = RegExp(stop_words, "ig")
+

      # Assumes document is a string
-     get_features:(document) ->
-         document = document.toLowerCase()
-         document = document.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()]/g," ")
+     get_features:(dokument) ->
+         dokument = dokument.toLowerCase()
          # remove common words here
-         features = document.split(" ")
+         dokument = dokument.replace(@stop_words, "")
+         # remove punctuation
+         dokument = dokument.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()]/g," ")
+
+         # TODO, might want to split on boundary
+
+         features = dokument.split(" ")
          stemmed = (stemmer(feature) for feature in features)
          return stemmed


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera_fade.coffee
+++ src/camera_fade.coffee
@@ -17,7 +17,7 @@
     color: 'black'

   fadeOutDefaults =
-    duration:
+    color:

   configureFade = (duration, color, alpha) ->
     I.flashDuration = duration


Passing undefined metaFunc to registerListener in livemp3 causes a crash

--- src/streammachine/outputs/livemp3.coffee
+++ src/streammachine/outputs/livemp3.coffee
@@ -24,7 +24,7 @@

         if @stream.preroll
             @stream.log.debug "making preroll request", stream:@stream.key
-            @stream.preroll.pump @res, => @stream.registerListener @, metadata:@metaFunc, data:@dataFunc
+            @stream.preroll.pump @res, => @stream.registerListener @, data:@dataFunc
         else
             @stream.registerListener @, data:@dataFunc


fix for UTF8 strings that use more than 1 byte, check UTF8 specs

--- src/stomp.coffee
+++ src/stomp.coffee
@@ -367,6 +367,27 @@
     # Get the actual Websocket (or a similar object)
     @ws= @ws_fn()

+    UTF8ArrayToStr = (array) =>
+      out = ""
+      len = array.length
+      i = 0
+      while(i < len)
+        c = array[i++]
+        switch(c >> 4)
+          when 0,1,2,3,4,5,6,7
+            # 0xxxxxxx
+            out += String.fromCharCode(c)
+          when 12,13
+            # 110x xxxx   10xx xxxx
+            char2 = array[i++]
+            out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F))
+          when 14
+            # 1110 xxxx  10xx xxxx  10xx xxxx
+            char2 = array[i++]
+            char3 = array[i++]
+            out += String.fromCharCode(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0))
+      return out
+
     @ws.onmessage = (evt) =>
       data = if typeof(ArrayBuffer) != 'undefined' and evt.data instanceof ArrayBuffer
         # the data is stored inside an ArrayBuffer, we decode it to get the
@@ -374,7 +395,8 @@
         arr = new Uint8Array(evt.data)
         @debug? "--- got data length: #{arr.length}"
         # Return a string formed by all the char codes stored in the Uint8array
-        (String.fromCharCode(c) for c in arr).join('')
+        #(String.fromCharCode(c) for c in arr).join('')
+        UTF8ArrayToStr(arr)
       else
         # take the data directly from the WebSocket `data` field
         evt.data


Add parent verification email toggle

Add parent verification email toggle

--- picoCTF-web/web/coffee/management-settings.coffee
+++ picoCTF-web/web/coffee/management-settings.coffee
@@ -142,11 +142,17 @@
       $set:
         email_verification: !@state.email_verification

+  toggleSendParentVerificationEmail: ->
+    @setState update @state,
+      $set:
+        parent_verification_email: !@state.parent_verification_email
+
   pushUpdates: (makeChange) ->
     pushData =
       email:
         enable_email: @state.enable_email
         email_verification: @state.email_verification
+        parent_verification_email: @state.parent_verification_email
         smtp_url: @state.smtp_url
         smtp_port: @state.smtp_port
         email_username: @state.email_username
@@ -170,6 +176,7 @@
   render: ->
     emailDescription = "Emails must be sent in order for users to reset their passwords."
     emailVerificationDescription = "Mandate email verification for new users"
+    parentEmailVerificationDescription = "Send an email to the parent's email address (if provided) upon registration"
     SMTPDescription = "The URL of the STMP server you are using"
     SMTPPortDescription = "The port of the running SMTP server"
     usernameDescription = "The username of the email account"
@@ -216,6 +223,7 @@
           <TextEntry name="From Address" value={@state.from_addr} type="text" onChange=@updateFromAddr description={fromAddressDescription}/>
           <TextEntry name="From Name" value={@state.from_name} type="text" onChange=@updateFromName description={fromNameDescription}/>
           <BooleanEntry name="Email Verification" value={@state.email_verification} onChange=@toggleEmailVerification description={emailVerificationDescription}/>
+          <BooleanEntry name="Parent Verification Email" value={@state.parent_verification_email} onChange=@toggleSendParentVerificationEmail description={parentEmailVerificationDescription}/>
           {SMTPSecuritySelect}
           <Row>
             <div className="text-center">
@@ -336,6 +344,7 @@
       enable_feedback: true
       email:
         email_verification: false
+        parent_verification_email: false
         enable_email: false
         from_addr: ""
         smtp_url: ""


Remove unnecessary named function in log handler


Fixed Saved/unsaved status of the document disappears after rename. [#110256880]

--- src/code/client.coffee
+++ src/code/client.coffee
@@ -254,10 +254,11 @@

   rename: (metadata, newName, callback) ->
     if newName isnt @state.metadata.name
+      dirty = @state.dirty
       @state.metadata.provider.rename @state.metadata, newName, (err, metadata) =>
         return @_error(err) if err
         @state.currentContent?.addMetadata docName: metadata.name
-        @_fileChanged 'renamedFile', @state.currentContent, metadata
+        @_fileChanged 'renamedFile', @state.currentContent, metadata, {dirty: dirty}
         callback? newName

   renameDialog: (callback = null) ->


return our own url for getLearnerUrl

return our own url for getLearnerUrl

this way when this mock interactive is embedded in LARA
it is possible to test reloading the page.

--- src/code/iframe.coffee
+++ src/code/iframe.coffee
@@ -10,7 +10,7 @@
   @MessageResponses =
     "getLearnerUrl":
       "message": 'setLearnerUrl'
-      "data": 'http://blahblah.com'
+      "data": window.location.href

     "getInteractiveState":
       "message": 'interactiveState'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/tween.coffee
+++ src/tween.coffee
@@ -18,4 +18,5 @@

   tween: (duration, properties) ->
-    for property, tar+    for property, target of properties
+


- [grunt] moved order of processhtml task

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -122,7 +122,6 @@
     'clean'
     'copy:build'
     'nunjucks:build'
-    'processhtml:build'
     'sprite:build'
     'webfont:build'
     'sass:build'
@@ -130,6 +129,7 @@
     'uncss:build'
     'csso:build'
     'clean:styles'
+    'processhtml:build'
     'shell:install'
     'shell:build'
     'uglify:build'


misc(gruntfile.coffee): added a task that copies any js file still present in the source


Fix object type bug introduced in core/kd


HUB-318 : Catch undefined callback

HUB-318 : Catch undefined callback

--- lib/actor/hactor.coffee
+++ lib/actor/hactor.coffee
@@ -787,10 +787,12 @@
         if status isnt undefined
           if typeof cb is "function"
             cb status, result
-            return
+          return
         else
           if typeof cb is "function"
             return cb codes.hResultStatus.NOT_AUTHORIZED, "already subscribed to channel " + hChannel
+          else
+            return

     @send @buildCommand(hChannel, "hSubscribe", {}, {timeout: 3000}), (hResult) =>
       if hResult.payload.status is codes.hResultStatus.OK and hResult.payload.result
@@ -846,6 +848,8 @@
                 else
                   if typeof cb is "function"
                     return cb codes.hResultStatus.OK, "QuickFilter removed"
+                  else
+                    return
             else
               inbound.stop()
               @inboundAdapters.splice(index, 1)


fix var name

fix var name

checking if the path module equals '/' wouldn't make sense in this context

--- lib/adapter_base.coffee
+++ lib/adapter_base.coffee
@@ -172,7 +172,7 @@
 resolvePath = (name) ->
   filepath = require.resolve(name)
   loop
-    if path is '/'
+    if filepath is '/'
       throw new Error("cannot resolve root of node module #{name}")
     filepath = path.dirname(filepath) # cut off the last part of the path
     if fs.existsSync(path.join filepath, 'package.json')


update docs in adapter_base

update docs in adapter_base

--- lib/adapter_base.coffee
+++ lib/adapter_base.coffee
@@ -15,9 +15,35 @@
   supportedEngines: undefined

   ###*
-   * @param {String} engine The engine to use. Defaults to
-     Adapter.supportedEngines[0]
-   * @return {[type]} [description]
+   * The name of the engine in-use. Generally this is the name of the package on
+     npm.
+   * @type {String}
+  ###
+  engineName: ''
+
+  ###*
+   * The actual engine, no adapter wrapper. Defaults to the engine that we
+     recommend for compiling that particular language (if it is installed).
+     Otherwise, whatever engine we support that is installed.
+  ###
+  engine: undefined
+
+  ###*
+   * Array of all file extensions the compiler should match
+   * @type {String[]}
+  ###
+  extensions: undefined
+
+  ###*
+   * Expected output extension
+   * @type {String}
+  ###
+  output: ''
+
+  ###*
+   * @param {String} [engine=Adapter.supportedEngines[0]] If you need to use a
+     particular engine to compile/render with, then specify it here. Otherwise
+     we use whatever engine you have installed.
   ###
   constructor: (@engineName, customPath) ->
     if not @supportedEngines or @supportedEngines.length is 0
@@ -40,36 +66,79 @@
         None found. Make sure one has been installed!
       """)

+  ###*
+   * Render a string to a compiled string
+   * @param {String} str
+   * @param {Object} [opts = {}]
+   * @return {Promise}
+  ###
   render: (str, opts = {}) ->
     if not @_render
       return W.reject new Error('render not supported')
     @_render(str, opts)

+  ###*
+   * Render a file to a compiled string
+   * @param {String} file The path to the file
+   * @param {Object} [opts = {}]
+   * @return {Promise}
+  ###
   renderFile: (file, opts = {}) ->
     opts = _.clone(opts, true)
     (new File(file))
       .read(encoding: 'utf8')
       .then _.partialRight(@render, _.extend(opts, {filename: file})).bind(@)

+  ###*
+   * Compile a string to a function
+   * @param {String} str
+   * @param {Object} [opts = {}]
+   * @return {Promise}
+  ###
   compile: (str, opts = {}) ->
     if not @_compile
       return W.reject new Error('compile not supported')
     @_compile(str, opts)

+  ###*
+   * Compile a file to a function
+   * @param {String} file The path to the file
+   * @param {Object} [opts = {}]
+   * @return {Promise}
+  ###
   compileFile: (file, opts = {}) ->
     (new File(file))
       .read(encoding: 'utf8')
       .then _.partialRight(@compile, _.extend(opts, {filename: file})).bind(@)

+  ###*
+   * Compile a string to a client-side-ready function
+   * @param {String} str
+   * @param {Object} [opts = {}]
+   * @return {Promise}
+  ###
   compileClient: (str, opts = {}) ->
     if not @_compileClient
       return W.reject new Error('client-side compile not supported')
     @_compileClient(str, opts)

+  ###*
+   * Compile a file to a client-side-ready function
+   * @param {String} file The path to the file
+   * @param {Object} [opts = {}]
+   * @return {Promise}
+  ###
   compileFileClient: (file, opts = {}) ->
     (new File(file))
       .read(encoding: 'utf8')
       .then _.partialRight(@compileClient, _.extend(opts, {filename: file})).bind(@)
+
+  ###*
+   * Some adapters that compile for client also need helpers, this method
+     returns a string of minfied JavaScript with all of them
+   * @return {Promise} A promise for the client-side helpers.
+  ###
+  clientHelpers: undefined

 requireEngine = (engineName, customPath) ->


metric: added un-bounce rate

metric: added un-bounce rate

--- src/models/metric.coffee
+++ src/models/metric.coffee
@@ -66,6 +66,22 @@
       from: 'nps'
       where: (day) ->
         "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms"
+  }
+  {
+    name: 'un-bounce rate'
+    isPercent: true
+    numerator:
+      select: 'count(distinct(sessionId))'
+      from: 'session'
+      where: (day) ->
+        "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms AND " +
+        "sessionEvents = '2'"
+    denominator:
+      select: 'count(distinct(sessionId))'
+      from: 'session'
+      where: (day) ->
+        "time >= #{dayToMS day}ms AND time < #{dayToMS day + 1}ms AND " +
+        "sessionEvents = '1'"
   }
   # {
   #   name: 'egp / view'
@@ -148,16 +164,6 @@
   #     select: 'count(distinct(sessionId))'
   #     from: 'pageview'
   # }
-  # {
-  #   name: 'un-bounce rate'
-  #   numerator:
-  #     select: 'count(distinct(sessionId))'
-  #     from: 'session'
-  #     where: -> 'sessionEvents=\'1\''
-  #   denominator:
-  #     select: 'count(distinct(sessionId))'
-  #     from: 'session'
-  # }
 ]

 module.exports = class Metric


CamelCase collection names

CamelCase collection names

--- src/mongodb_store.coffee
+++ src/mongodb_store.coffee
@@ -10,8 +10,8 @@

   initialize: (@_contextName, [options]..., callback=->) ->
     @_defaults (options ?= {}), @_optionDefaults
-    @_domainEventsCollectionName = "#{@_contextName}.domain_events"
-    @_projectionCollectionName   = "#{@_contextName}.projections"
+    @_domainEventsCollectionName = "#{@_contextName}.DomainEvents"
+    @_projectionCollectionName   = "#{@_contextName}.Projections"

     if options.dbInstance
       @db = options.dbInstance


Fixed breaking out of list with content, inaccurate smartlist triggers and faulty span handling

--- app/assets/javascripts/dante/editor.js.coffee
+++ app/assets/javascripts/dante/editor.js.coffee
@@ -762,6 +762,10 @@

     @handleTextSelection(anchor_node)

+    if (_.contains([8, 32, 13], e.which))
+      if $(anchor_node).hasClass("graf--li")
+        @removeSpanTag($(anchor_node));
+
     if (e.which == 8)

       #if detect all text deleted , re render
@@ -802,8 +806,6 @@
       #  @setupFirstAndLast()
       #  @displayTooltipAt($(@el).find(".is-selected"))

-      if $(anchor_node).hasClass("graf--li")
-        @handleListSpanTag($(anchor_node), e);

     #arrows key
     if _.contains([37,38,39,40], e.which)
@@ -1062,6 +1064,8 @@

     utils.log "LISTIFY PARAGRAPH"

+    @removeSpanTag($paragraph);
+
     content = $paragraph.html().replace(/ /g, " ").replace(regex, "")

     switch(listType)
@@ -1161,10 +1165,9 @@

       @setupFirstAndLast()

-  handleListSpanTag: ($li, e)->
+  removeSpanTag: ($item)->

-    $list = $li.parent("ol, ul")
-    $spans = $list.find("span")
+    $spans = $item.find("span")
     $(span).replaceWith($(span).html()) for span in $spans when not $(span).hasClass("defaultValue")
-    $li
+    $item


Removed unnecessary console.log statements.

--- src/main/coffeescript/scxml/SCXML.coffee
+++ src/main/coffeescript/scxml/SCXML.coffee
@@ -248,12 +248,11 @@
 					@_eval action,datamodelForNextStep,eventSet,true
 				when "log"
 					log = @_eval action,datamodelForNextStep,eventSet
-					if @opts.printTrace then logger.info(log)	#the one place where we use straight logger.info
+					logger.info(log)	#the one place where we use straight logger.info
 				when "send"
 					#data = if action.contentexpr then @_eval(action,datamodelForNextStep,eventSet) else null
 					#TODO: handle namelist,content,params
 					#TODO: deep copy
-					console.log action
 					if @_send then @_send(
 						{
 							target : if action.targetexpr then @_eval action.targetexpr,datamodelForNextStep,eventSet else action.target
@@ -453,7 +452,6 @@
 			#these may be passed in as options if, e.g., we 're using an external communication layer
 			#these are the defaults if an external communication layer is not being used.
 			@_send = opts.send or (event,options) ->
-				console.log event,options
 				if @opts.setTimeout
 					if @opts.printTrace then logger.trace "sending event",event.name,"with content",event.data,"after delay",options.delay


ensure correct order of views

ensure correct order of views

--- client/code/app.coffee
+++ client/code/app.coffee
@@ -1,6 +1,8 @@
 #= require namespace
 # Must come before any model that uses the mixin
 #= require model/boxable
+#= require model/tool
+#= require model/view
 #= require_tree model
 #= require_tree router
 #= require_tree view


Remove a console.log

Remove a console.log

--- src/controllers.coffee
+++ src/controllers.coffee
@@ -90,8 +90,6 @@
     key = key.replace ':page', @_currentPage
     key = key.replace ':take', @options.take

-    console.log(key)
-
     cached = @_findCache(key)

     if cached? then () => @_local(cached, pagingVector) else () => @_ajax(key, pagingVector)


Bugfix: markers weren't remembered with new map logic.

--- src/views.coffee
+++ src/views.coffee
@@ -141,6 +141,10 @@
                         @details_marker = marker
                     marker.on 'mouseover', (event) ->
                         event.target.openPopup()
+
+            _.each @current_markers, (markers, key, list) =>
+                _.each markers, (marker) =>
+                    @all_markers.addLayer marker

             @all_markers.addTo @map
             bounds = L.latLngBounds (m.getLatLng() for m in markers)


Adding promise to fix one-off runs

Adding promise to fix one-off runs

--- cli.coffee
+++ cli.coffee
@@ -91,6 +91,7 @@
       currentRecords

   updateDesignateRecords = do () ->
+    deferred = null
     fn = () ->
       designate.list()
         .then sitrep("pre-update")
@@ -102,14 +103,17 @@
           designate.list()
         .then sitrep("post-update")
     queuedFn = () ->
-      executing = null
-      semaphoredFn = () ->
-        Q.try(() -> executing = fn()).then(() -> executing = null)
-      if executing
-        executing.then semaphoredFn
+      if deferred
+        deferred.promise
       else
-        executing = semaphoredFn()
-    _.throttle(queuedFn, 1000, leading: false)
+        deferred = Q.defer()
+        Q.delay(1000)
+          .then fn
+          .done () ->
+            deferred.resolve()
+            deferred = null
+        deferred.promise
+    queuedFn

   resolveAndUpdateMonitors = () ->
     resolveAll(servers)
@@ -124,12 +128,11 @@
             else
               Q.all recordsWithoutMonitors.map (r) ->
                 monitor = Monitor(r.addr, tcpPort)
-                monitor.onenterup = () ->
-                  cli.info(r.addr+" is up")
+                enterFn = () ->
+                  cli.info(r.addr+" is "+monitor.current)
                   updateDesignateRecords()
-                monitor.onenterdown = () ->
-                  cli.info(r.addr+" is down")
-                  updateDesignateRecords()
+                monitor.onenterup = enterFn
+                monitor.onenterdown = enterFn
                 monitors.push(_.extend(r, { monitor: monitor }))
                 monitor.start()
                 null
@@ -169,6 +172,8 @@
       .then () ->
         async.until(finished, () -> Q.delay(500))
       .then () ->
+        updateDesignateRecords()
+      .then () ->
         if _.isEmpty(monitors)
           Q()
         else


support https in menu DSL

support https in menu DSL

--- client/ide.coffee
+++ client/ide.coffee
@@ -177,7 +177,7 @@
       for s in md.split '\n' when !/^\s*$/.test s = s.replace /#.*/, ''
         cond = ''; s = s.replace /\{(.*)\}/, (_, x) -> cond = x; ''
         cmd = ''; s = s.replace /\=([A-Z][A-Z0-9]{1,2})\b/, (_, x) -> cmd = x; ''
-        url = ''; s = s.replace /\=(http:\/\/\S+)/, (_, x) -> url = x; ''
+        url = ''; s = s.replace /\=(https?:\/\/\S+)/, (_, x) -> url = x; ''
         h = ind: s.replace(/\S.*/, '').length, '': s.replace /^\s*|\s*$/g, ''
         while h.ind <= stack[stack.length - 1].ind then stack.pop()
         if !cond || do new Function "var browser=!#{D.nwjs},mac=#{D.mac};return(#{cond})"


Fix individual level verifier test view

Fix individual level verifier test view

--- app/views/editor/verifier/VerifierView.coffee
+++ app/views/editor/verifier/VerifierView.coffee
@@ -36,6 +36,7 @@
     if @levelID
       @levelIDs = [@levelID]
       @testLanguages = ['python', 'javascript', 'java', 'cpp', 'lua', 'coffeescript']
+      @codeLanguages = ({id: c, checked: true} for c in @testLanguages)
       @cores = 1
       @startTestingLevels()
     else


Nerf jumping height to about one voxel. Ref GH-5

--- index.coffee
+++ index.coffee
@@ -34,7 +34,8 @@
     controls:
       discreteFire: false
       fireRate: 100 # ms between firing
-      jumpTimer: 50
+      jumpSpeed: 0.001
+      jumpTimer: 12.5

   opts = extend {}, defaults, opts || {}


Also reset NODE_PATH and GOOGLE_API_KEY on Linux.

--- index.coffee
+++ index.coffee
@@ -41,7 +41,7 @@
     defaultCommand = 'start /D "%cd%" cmd' +
       ' /K "set NODE_PATH=& set NODE_ENV=& set GOOGLE_API_KEY="'
   else
-    defaultCommand = 'NODE_ENV= x-terminal-emulator'
+    defaultCommand = 'NODE_PATH= NODE_ENV= GOOGLE_API_KEY= x-terminal-emulator'

 module.exports =
   config: {


better logging EVERYWHERE

better logging EVERYWHERE

--- index.coffee
+++ index.coffee
@@ -124,7 +124,7 @@
  	, (err, resp, body) ->
  		clefResponse = JSON.parse body
  		if err or not clefResponse.access_token?
- 			console.log "Error getting CLEF access token", err
+ 			console.log "Error getting CLEF access token", err, clefResponse
  			v0.responses.notAuth res
  		else
  			req.$session.clefAccessToken = JSON.parse(body)['access_token']


Fix #104

Fix #104

--- lib/editor-utils.coffee
+++ lib/editor-utils.coffee
@@ -167,6 +167,9 @@
         c = ""+result.match[0]
         if ["(","{","["].indexOf(c) >= 0
           if braceOpened == 0
+            # Start the range at the beginning of the line
+            # Accounts for clojure special chars like # and ' etc
+            result.range.start.column = 0;
             ranges.push([result.range.start])
           braceOpened++
         else if [")","}","]"].indexOf(c) >= 0
@@ -182,7 +185,17 @@
   getCursorInClojureTopBlockRange: (editor)->
     pos = editor.getCursorBufferPosition()
     topLevelRanges = @getTopLevelRanges(editor)
-    topLevelRanges.find (range) -> range.containsPoint(pos)
+    range = topLevelRanges.find (range) -> range.containsPoint(pos)
+
+    # If no range was found but the char under the cursor isn't whitespace,
+    # send the current row to the repl
+    if !range
+      charUnderCursor = editor.getTextInBufferRange([pos, [pos.row, pos.column + 1]])
+      if /\S/.test(charUnderCursor)
+        txt = editor.lineTextForBufferRow(pos.row)
+        range = [[pos.row, 0], [pos.row, txt.length]]
+
+    range

   # Searches all open text editors for the given string. Returns a tuple of the
   # editor found and the range within the editor for the first location that


Reverse focus too.

Reverse focus too.

Fixes #112.

--- keymaps/git.cson
+++ keymaps/git.cson
@@ -8,12 +8,13 @@
   'shift-up': 'git:expand-selection-up'
   'shift-down': 'git:expand-selection-down'
   'escape': 'git:close-all'
-  'right': 'git:view-and-commit-changes'
+  'right': 'git:focus-file-list'
   'tab': 'git:focus-commit-message'

 '.git-Panel:focus':
   'escape': 'git:close-all'
   'tab': 'git:focus-commit-message'
+  'shift-tab': 'git:focus-commit-button'
   'o': 'git:open-file'
   'd': 'git:open-diff'
   'left': 'git:open-diff'
@@ -23,7 +24,9 @@
   'cmd-enter': 'git:commit'
   'ctrl-enter': 'git:commit'
   'tab': 'git:focus-commit-button'
+  'shift-tab': 'git:focus-file-list'

 '.git-CommitBox-button':
   'tab': 'git:focus-file-list'
+  'shift-tab': 'git:focus-commit-message'
   'enter': 'git:commit'


display docs

display docs

--- lib/atom-ternjs-config-view.coffee
+++ lib/atom-ternjs-config-view.coffee
@@ -19,13 +19,16 @@
   buildOptionsMarkup: ->
     config = @getModel().config
     @content.appendChild(@buildBoolean(config.libs))
-    @content.appendChild(@buildStringArray(config.loadEagerly))
+    @content.appendChild(@buildStringArray(config.loadEagerly, 'loadEagerly'))

-  buildStringArray: (obj) ->
+  buildStringArray: (obj, section) ->
     wrapper = document.createElement('div')
     header = document.createElement('h2')
-    header.innerHTML = 'loadEagerly:'
+    header.innerHTML = section
+    doc = document.createElement('p')
+    doc.innerHTML = @getModel().config.docs[section].doc
     wrapper.appendChild(header)
+    wrapper.appendChild(doc)
     for path in obj
       item = document.createElement('input')
       item.type = 'text'


Made @include work with require'd modules [imzshh] (solve #16)

Applied aa34da7df8dee5fb5602d89cfe5268e207356fb0

--- src/zappa.coffee
+++ src/zappa.coffee
@@ -250,7 +250,7 @@
       v.apply(context, [context])

   context.include = (p) ->
-    sub = require path.join(context.root, p)
+    sub = if typeof p is 'string' then require path.join(context.root, p) else p
     sub.include.apply(context, [context])

   # Register a route with express.


ファイルの書き込み時にログを出力する処理を追加

ファイルの書き込み時にログを出力する処理を追加

--- bin/lamjet.coffee
+++ bin/lamjet.coffee
@@ -33,6 +33,8 @@
               resolve()
       .then (result)->
         return new Promise (resolve, reject)->
+          # TODO: ファイルが存在する場合、上書きの有無を確認する。
+          console.log("write #{filePath}...")
           fs.writeFile filePath, body, {encoding: "utf8", flag: "w"}, (error)->
             if error?
               reject(filePath: filePath, error: error)
@@ -40,8 +42,6 @@
               resolve(filePath: filePath, body: body)

   defaultFunctionName = path.basename(path.resolve())
-
-  # TODO: 「package.json」が存在する場合、上書きの有無を確認する。

   makePackageJson = (options)->
     functionName = options?.functionName ? throw new Error("functionName")


:bug: Fix minimap style persisting after view destruction

Closes #154

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -184,6 +184,7 @@

   # Destroys this view and release all its subobjects.
   destroy: ->
+    @resetMinimapWidthWithWrap()
     @paneView.removeClass('with-minimap')
     @off()
     @obsPane.dispose()


Fix clicking on the minimap

Fix clicking on the minimap

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -186,7 +186,7 @@
     top = Math.max(top, 0)
     top = Math.min(top, @miniScrollView.outerHeight() - miniOverLayerHeight)
     # @note: currently, no animation.
-    @editorView.updateScroll()
+    @editorView.scrollTop(top)
     # Fix trigger `mousewheel` event.
     setTimeout =>
       @isClicked = false


Fixing issue with binding the queryState function.

--- src/plugins/block.coffee
+++ src/plugins/block.coffee
@@ -53,6 +53,9 @@
             el.addClass 'selected'
             return
           el.removeClass 'selected'
+
+
+        @options.editable.element.bind 'keyup paste change mouseup', queryState

         @options.editable.element.bind 'halloenabled', =>
           @options.editable.element.bind 'keyup paste change mouseup', queryState


Remove paths array

Remove paths array

This is a HUGE speedup as we don't keep a giant array

--- lib/path-scanner.coffee
+++ lib/path-scanner.coffee
@@ -10,9 +10,6 @@
 class PathScanner extends EventEmitter

   constructor: (@rootPath, @options={}) ->
-    @paths = []
-    @stats = {}
-    @structure = {}
     @asyncCallsInProgress = 0
     @pathFilter = new PathFilter(@rootPath, @options)

@@ -38,14 +35,11 @@
     return unless stat

     if stat.isFile() and @pathFilter.isFileAccepted(path.relative(@rootPath, filePath))
-      @stats[filePath] = stat
-      @paths.push(filePath) unless _.contains(@paths, filePath)
       @emit('path-found', filePath)
     else if stat.isDirectory() and @pathFilter.isDirectoryAccepted(path.relative(@rootPath, filePath))
       @readDir(filePath)

   stat: (filePath) ->
-    return @stats[filePath] if @stats[filePath]?

     # lstat is SLOW, but what other way to determine if something is a directory or file ?
     # also, sync is about 200ms faster than async...


Upgrade to TextEditorView from EditorView.

See
https://github.com/atom/atom/commits/master/src/text-editor-view.coffee

--- lib/preview-view.coffee
+++ lib/preview-view.coffee
@@ -18,12 +18,20 @@
 catch e
   # Catch error
   # It will error on Atom versions v0.124.0 and later
-EditorView = ReactEditorView ? require path.resolve resourcePath, 'src', 'editor-view'
+try
+  EditorView = ReactEditorView ? require path.resolve resourcePath, 'src', 'editor-view'
+catch e
+  # Catch error
+TextEditorView = EditorView ? require path.resolve resourcePath, 'src', 'text-editor-view'
 TextBuffer = require path.resolve resourcePath, 'node_modules', 'text-buffer'
-Editor = require path.resolve resourcePath, 'src', 'editor'
+try
+  Editor = require path.resolve resourcePath, 'src', 'editor'
+catch e
+  # Catch error
+Editor = Editor ? require path.resolve resourcePath, 'src', 'text-editor'

 module.exports =
-class PreviewView extends EditorView
+class PreviewView extends TextEditorView
   @content: (params) ->
     params = params ? params || {}
     super(params)


Add missing require for fs dependency

Add missing require for fs dependency

--- lib/recent-files.coffee
+++ lib/recent-files.coffee
@@ -1,4 +1,5 @@
 _ = require 'underscore-plus'
+fs = require 'fs-plus'

 module.exports =
 class RecentFiles


:fire: Remove jQuery from ::isPlaceholder

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -430,7 +430,7 @@
     target = $(event.target)
     tabBar = @getTabBar(event.target)

-    return if @isPlaceholder(target)
+    return if @isPlaceholder(target[0])

     sortables = tabBar.find('.sortable')
     element = target.closest('.sortable')
@@ -455,7 +455,7 @@
     @placeholderEl = null

   isPlaceholder: (element) ->
-    element.is('.placeholder')
+    element.classList.contains('placeholder')

   getTabBar: (target) ->
     target = $(target)


Check that event is from correct pane

Check that event is from correct pane

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -188,9 +188,11 @@
       @getPlaceholderElement().insertAfter(el)

   onDropOnOtherWindow: (fromItemIndex, fromPaneIndex) =>
-    if fromPane = @paneContainer.paneAtIndex(fromPaneIndex)
-      if item = fromPane.itemAtIndex(fromItemIndex)
-        fromPane.removeItem(item)
+    fromPane = @paneContainer.paneAtIndex(fromPaneIndex)
+    return unless @pane.is(fromPane)
+
+    if itemToRemove = fromPane.itemAtIndex(fromItemIndex)
+      fromPane.removeItem(itemToRemove)

     @clearDropTarget()


Support native keybndings in info text in Themes panel

--- lib/themes-panel.coffee
+++ lib/themes-panel.coffee
@@ -15,7 +15,7 @@
       @div class: 'section packages', =>
         @div class: 'section-heading icon icon-device-desktop', 'Choose a Theme'

-        @div class: 'text padded', =>
+        @div class: 'text padded native-key-bindings', tabindex: -1, =>
           @span class: 'icon icon-question', 'You can also style Atom by editing '
           @a class: 'link', outlet: 'openUserStysheet', 'your stylesheet'

@@ -35,7 +35,7 @@
       @div class: 'section packages', =>
         @div class: 'section-heading icon icon-cloud-download', 'Install Themes'

-        @div class: 'text padded', =>
+        @div class: 'text padded native-key-bindings', tabindex: -1, =>
           @span class: 'icon icon-question'
           @span 'Themes are published to  '
           @a class: 'link', outlet: "openAtomIo", "atom.io"


Remove deprecated use of `_.nextTick`

Remove deprecated use of `_.nextTick`

--- lib/timecop-view.coffee
+++ lib/timecop-view.coffee
@@ -1,4 +1,4 @@
-{_, ScrollView} = require 'atom'
+{ScrollView} = require 'atom'
 PackagePanelView = require './package-panel-view'

 module.exports =
@@ -15,7 +15,7 @@
       @populateViews()
     else
       # Render on next tick so packages have been activated
-      _.nextTick => @populateViews()
+      setImmediate => @populateViews()

   populateViews: ->
     @showLoadedPackages()


Add JSDoc for #verifyPlaintext()

Add JSDoc for #verifyPlaintext()

--- src/dnt-connect.coffee
+++ src/dnt-connect.coffee
@@ -38,6 +38,14 @@
   hmac.digest 'base64'

 ###
+#
+# Verify hmac of plaintext is correct
+#
+# @param <string-utf8> plaintext - plaintext to encipher
+# @param <Buffer> iv - initialization vector
+# @param <string-base64> hmac - hmac of plaintext
+#
+# @return {@code true} if hmac is valid; otherwise {@code false}
 #
 ###
 Connect.prototype.verifyPlaintext = (plaintext, iv, hmac) ->


Wrap tests in closure

Wrap tests in closure

--- test/simple-machine.coffee
+++ test/simple-machine.coffee
@@ -1,16 +1,17 @@
 {SimpleMachine} = require '..'
 {expect} = require 'moodswing'

-machine = new SimpleMachine 'S',
-  A: {S: 'A', B: 'A', C: 'A'}
-  B: {S: 'B', C: 'B', A: 'B'}
-  C: {S: 'C', A: 'C', B: 'C'}
-,
-  A: (previous) -> expect(previous).to be: 'S'
-  B: (previous) -> expect(previous).to be: 'A'
-  C: (previous) -> expect(previous).to be: 'B'
+do ->
+  machine = new SimpleMachine 'S',
+    A: {S: 'A', B: 'A', C: 'A'}
+    B: {S: 'B', C: 'B', A: 'B'}
+    C: {S: 'C', A: 'C', B: 'C'}
+  ,
+    A: (previous) -> expect(previous).to be: 'S'
+    B: (previous) -> expect(previous).to be: 'A'
+    C: (previous) -> expect(previous).to be: 'B'

-expect(machine.trigger 'A').to be: true
-expect(machine.trigger 'B').to be: true
-expect(machine.trigger 'C').to be: true
-expect(machine.trigger 'all').to be: false+  expect(machine.trigger 'A').to be: true
+  expect(machine.trigger 'B').to be: true
+  expect(machine.trigger 'C').to be: true
+  expect(machine.trigger 'all').to be: false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/flickerable.coffee
+++ src/flickerable.coffee
@@ -6,7 +6,8 @@

     player.include 'Flickerable'

-
+    #
+    player.flicker()

 @name Flickerable
 @module


null check teachers

null check teachers

--- src/flux/roster.coffee
+++ src/flux/roster.coffee
@@ -49,7 +49,7 @@
     for courseId, roster of @_local
       teachers = roster.teachers
       teacherIndex = _.findIndex(teachers, id: teacherId)
-    roster.teachers.splice(teacherIndex, 1)
+    roster.teachers?.splice(teacherIndex, 1)
     if isCurrent
       CourseListingActions.delete(courseId)
     @emitChange()


Add some unit tests for "init"

Add some unit tests for "init"

--- test/unit/init.spec.coffee
+++ test/unit/init.spec.coffee
@@ -9,8 +9,67 @@
   beforeEach ->
     m = loadFile __dirname + '/../../lib/init.js', {glob: require 'glob'}

+
+  describe 'StateMachine', ->
+    machine = done = null
+
+    mockRli =
+      write: -> null
+      prompt: -> null
+      _deleteLineLeft: -> null
+      _deleteLineRight: -> null
+
+    beforeEach ->
+      machine = new m.StateMachine mockRli
+      done = jasmine.createSpy 'done'
+
+
+    it 'should go through all the questions', ->
+      questions = [
+        {id: 'framework', options: ['jasmine', 'mocha']}
+        {id: 'other'}
+      ]
+
+      done.andCallFake (answers) ->
+        expect(answers.framework).toBe 'jasmine'
+        expect(answers.other).toBe 'abc'
+
+      machine.process questions, done
+      machine.onLine 'jasmine'
+      machine.onLine 'abc'
+      expect(done).toHaveBeenCalled()
+
+
+    it 'should allow multiple answers', ->
+      questions = [
+        {id: 'browsers', multiple: true}
+      ]
+
+      done.andCallFake (answers) ->
+        expect(answers.browsers).toEqual ['Chrome', 'Safari']
+
+      machine.process questions, done
+      machine.onLine 'Chrome'
+      machine.onLine 'Safari'
+      machine.onLine ''
+      expect(done).toHaveBeenCalled()
+
+
+    it 'should validate answers', ->
+      validator = jasmine.createSpy 'validate'
+      questions = [
+        {id: 'validated', validate: validator}
+      ]
+
+      machine.process questions, done
+      machine.onLine 'something'
+
+      expect(done).toHaveBeenCalled()
+      expect(validator).toHaveBeenCalledWith 'something'
+
+
   describe 'getBasePath', ->
-
+
     # just for windows.
     replace = (p) -> p.replace(/\//g, path.sep)


WirePattern doesn't mark in ports as required anymore, it's up to a component designer if a non-required inport causes a deadlock. Closes #238.

--- src/lib/Helpers.coffee
+++ src/lib/Helpers.coffee
@@ -122,8 +122,6 @@
   for name in inPorts
     unless component.inPorts[name]
       throw new Error "no inPort named '#{name}'"
-    # Make the port required
-    component.inPorts[name].options.required = true
   for name in outPorts
     unless component.outPorts[name]
       throw new Error "no outPort named '#{name}'"


Provide postpone/resume functions that throw a more user-friendly error

--- src/lib/Helpers.coffee
+++ src/lib/Helpers.coffee
@@ -164,11 +164,16 @@

     debug "WirePattern Process API call with", data, groups, component.params, context.scope

+    postpone = ->
+      throw new Error 'noflo.helpers.WirePattern postpone is deprecated'
+    resume = ->
+      throw new Error 'noflo.helpers.WirePattern resume is deprecated'
+
     unless config.async
       # Set up custom error handlers
       errorHandler = setupErrorHandler component, config, output
       # Synchronous WirePattern, call done here
-      func.call component, data, groups, outProxy, null, null, input.scope
+      func.call component, data, groups, outProxy, postpone, resume, input.scope
       # No need to call done if component called fail
       return if output.result.__resolved
       # Let error handler send any remaining errors
@@ -182,7 +187,7 @@
     func.call component, data, groups, outProxy, (err) ->
       do errorHandler
       output.done err
-    , null, null, input.scope
+    , postpone, resume, input.scope

 # Provide deprecation warnings on certain more esoteric WirePattern features
 checkDeprecation = (config, func) ->


improved inline installation handling and styling #5

--- src/lib/install.coffee
+++ src/lib/install.coffee
@@ -1,19 +1,21 @@
-# [Undo Wikipedia Blackout](http://neocotic.com/UndoWikipediaBlackout)
-# (c) 2012 Alasdair Mercer
-# Freely distributable under the MIT license.
-# For all details and documentation:
-# <http://neocotic.com/UndoWikipediaBlackout>
-
-# Functionality
-# -------------
-
-# Wrap the functionality in a request for Undo Wikipedia Blackout's details in
-# order to get the ID in use.
-chrome.extension.sendRequest type: 'info', (data) ->
-  # Names of the classes to be removed from the targeted elements.
-  classes = ['chrome_install_button', 'btn-primary', 'primary']
-  # Disable all "Install" links on the homepage for Undo Wikipedia Blackout.
-  for link in document.querySelectorAll "a.#{classes[0]}[href$=#{data.id}]"
-    link.innerText  = 'Installed'
-    link.classList.add 'disabled'
-    link.classList.remove cls for cls in classes+# [Undo Wikipedia Blackout](http://neocotic.com/UndoWikipediaBlackout)
+# (c) 2012 Alasdair Mercer
+# Freely distributable under the MIT license.
+# For all details and documentation:
+# <http://neocotic.com/UndoWikipediaBlackout>
+
+# Functionality
+# -------------
+
+# Wrap the functionality in a request for Undo Wikipedia Blackout's details in
+# order to get the ID in use.
+chrome.extension.sendRequest type: 'info', (data) ->
+  # Names of the classes to be added to the targeted elements.
+  newClasses = ['disabled']
+  # Names of the classes to be removed from the targeted elements.
+  oldClasses = ['chrome_install_button']
+  # Disable all "Install" links on the homepage for Undo Wikipedia Blackout.
+  for link in document.querySelectorAll "a.#{oldClasses[0]}[href$=#{data.id}]"
+    link.innerText = 'Installed'
+    link.classList.add    cls for cls in newClasses
+    link.classList.remove cls for cls in oldClasses


Find matched alias from the table

Find matched alias from the table

--- src/scripts/alias.coffee
+++ src/scripts/alias.coffee
@@ -29,14 +29,19 @@
   robot.receive = (msg)->
     table = robot.brain.get(ALIAS_TABLE_KEY) || {}
     orgText = msg.text?.trim()
-    if new RegExp("(^[@]?(?:#{robot.name}|#{robot.alias})[:,]?)(\\s+)([^\\s]*)(.*)$").test orgText
+    if new RegExp("(^[@]?(?:#{robot.name}|#{robot.alias})[:,]?)(\\s+)(.*)$").test orgText
       name = RegExp.$1
       sp = RegExp.$2
       action = RegExp.$3
-      rest = RegExp.$4

-      if action != 'alias'
-        action = table[action] or table[action+rest] or action
+      if action && action != 'alias'
+        rest = ''
+        for k in Object.keys(table).sort().reverse()
+          v = table[k]
+          if new RegExp("^#{k}\\b").test(action)
+            rest = action.slice(k.length)
+            action = v
+            break
         msg.text = "#{name}#{sp}"
         msg.text += loadArgumentsInAction(rest, action)


generalizing the way spawn is run, and adding the remaining info commands

--- src/scripts/drush.coffee
+++ src/scripts/drush.coffee
@@ -29,7 +29,6 @@

 drush_interface = ->
   site_aliases = []
-  #allowed_commands = ["sa", "cc", "pml", "uinf", "pmi", "ws", "vget", "rq"]

   # helper method to propigate the site aliases in memory
   update_aliases = (msg) ->
@@ -50,6 +49,18 @@
   # run the update script upon construction
   update_aliases()

+  execute_drush = (msg, drush_args) ->
+    output = ''
+    msg.send "This may take a moment..."
+    clearcache = spawn("drush", drush_args)
+    clearcache.stdout.on "data", (data) ->
+      output += data
+    clearcache.stderr.on "data", (data) ->
+      output += data
+    clearcache.on "exit", (code) ->
+      output += "Command complete."
+      msg.send output
+
   allowed_commands =
     drush_sa: (msg, command) ->
       if command.args.indexOf('--update-aliases') is -1
@@ -60,52 +71,39 @@
         update_aliases(msg)

     drush_cc: (msg, command) ->
-      output = ''
-      msg.send "This may take a moment..."
-      clearcache = spawn("drush", [command.alias, "cc", "all"])
-      clearcache.stdout.on "data", (data) ->
-        output += data
-      clearcache.stderr.on "data", (data) ->
-        output += data
-      clearcache.on "exit", (code) ->
-        if code is 0
-          output += "Cache clear complete."
-        msg.send output
+      execute_drush(msg, [command.alias, "cc", "all"])

     drush_rq: (msg, command) ->
-      output = ''
-      msg.send "This may take a moment..."
-      clearcache = spawn("drush", [command.alias, "rq", "--severity=1"])
-      clearcache.stdout.on "data", (data) ->
-        output += data
-      clearcache.stderr.on "data", (data) ->
-        output += data
-      clearcache.on "exit", (code) ->
-        if code is 0
-          output += "Core Requirements complete."
-        msg.send output
+      execute_drush(msg, [command.alias, "rq", "--severity=1"])

     drush_pml: (msg, command) ->
-      output = ''
-      modules_state = "--status=enabled"
-      modules_core = "--no-core"
-      unless command.args.indexOf("--disabled") is -1
-        modules_state = "--status=disabled"
-      unless command.args.indexOf("--core") is -1
-        modules_core = "--core"
+      allowed_options = ["--status=enabled", "--status=disabled", "--no-core", "--core"]
+      filtered_options = command.args.filter((elem) ->
+        allowed_options.indexOf(elem) isnt -1
+      )
+      filtered_options.unshift(command.alias, "pml")
+      execute_drush(msg, filtered_options)

-      msg.send "This may take a moment..."
+    drush_uinf: (msg, command) ->
+      user_search = command.args.shift()
+      execute_drush(msg, [command.alias, "uinf", user_search])

-      spawn_options = [command.alias, "pml", modules_state, modules_core]
-      pml = spawn("drush", spawn_options)
-      pml.stdout.on "data", (data) ->
-        output += data
-      pml.stderr.on "data", (data) ->
-        output = "We've experienced and error fetching module list."
-      pml.on "exit", (code) ->
-        if code is 0
-          output += "Module list complete."
-        msg.send output
+    drush_pmi: (msg, command) ->
+      extension_search = command.args.shift()
+      execute_drush(msg, [command.alias, "pmi", extension_search])
+
+    drush_ws: (msg, command) ->
+      allowed_options = ["--full"]
+      filtered_options = command.args.filter((elem) ->
+        allowed_options.indexOf(elem) isnt -1
+      )
+      filtered_options.unshift(command.alias, "ws")
+      execute_drush(msg, filtered_options)
+
+    drush_vget: (msg, command) ->
+      variable_search = command.args.shift()
+      # forcing this to --exact to prevent channel flood from a huge search
+      execute_drush(msg, [command.alias, "vget", variable_search, "--exact"])

   # verify alias before firing the command, saves us time on waiting for an err from drush
   verify_alias = (check_alias) ->
@@ -124,6 +122,10 @@
         command_suff = extra_args.shift()
       else
         `undefined`
+    # Kinda gross but the site-alias command is the only one that does not need a site alias
+    # so lets check before we fire up drush to fail.
+    else unless site_alias is "sa"
+      `undefined`
     else
       command_suff = site_alias
       site_alias = ''


Fixing 'hubot ci build' command for branches with a '/' in their name

--- src/scripts/janky.coffee
+++ src/scripts/janky.coffee
@@ -62,7 +62,7 @@
       else
         msg.reply "can't help you right now."

-  robot.respond /ci build ([-_\.0-9a-zA-Z]+)(\/([-_\.a-zA-z0-9]+))?/i, (msg) ->
+  robot.respond /ci build ([-_\.0-9a-zA-Z]+)(\/([-_\.a-zA-z0-9\/]+))?/i, (msg) ->
     app     = msg.match[1]
     branch  = msg.match[3] || "master"
     room_id = msg.message.user.room


Avoid adding a loose ] onto the weblink inserted on Log - Janky script

--- src/scripts/janky.coffee
+++ src/scripts/janky.coffee
@@ -41,7 +41,7 @@
   response = ""
   response += "Build ##{build.number} (#{build.sha1}) of #{build.repo}/#{build.branch} #{build.status}"
   response += "(#{build.duration}s) #{build.compare}"
-  response += " [Log: #{build.web_url}]"
+  response += " [Log: #{build.web_url} ]"

 get = (path, params, cb) ->


less strict karma regex, whitespace fix

less strict karma regex, whitespace fix

Allows karma.coffee to take additional forms of <thing>

  <thing>: ++
  <thing> ++
  <thing>: --
  <thing> --

--- src/scripts/karma.coffee
+++ src/scripts/karma.coffee
@@ -19,26 +19,26 @@
 #   stuartf

 class Karma
-
+
   constructor: (@robot) ->
     @cache = {}
-
+
     @increment_responses = [
       "+1!", "gained a level!", "is on the rise!", "leveled up!"
     ]
-
+
     @decrement_responses = [
       "took a hit! Ouch.", "took a dive.", "lost a life.", "lost a level."
     ]
-
+
     @robot.brain.on 'loaded', =>
       if @robot.brain.data.karma
         @cache = @robot.brain.data.karma
-
+
   kill: (thing) ->
     delete @cache[thing]
     @robot.brain.data.karma = @cache
-
+
   increment: (thing) ->
     @cache[thing] ?= 0
     @cache[thing] += 1
@@ -48,10 +48,10 @@
     @cache[thing] ?= 0
     @cache[thing] -= 1
     @robot.brain.data.karma = @cache
-
+
   incrementResponse: ->
      @increment_responses[Math.floor(Math.random() * @increment_responses.length)]
-
+
   decrementResponse: ->
      @decrement_responses[Math.floor(Math.random() * @decrement_responses.length)]

@@ -64,46 +64,45 @@
     for key, val of @cache
       s.push({ name: key, karma: val })
     s.sort (a, b) -> b.karma - a.karma
-
+
   top: (n = 5) ->
     sorted = @sort()
     sorted.slice(0, n)
-
+
   bottom: (n = 5) ->
     sorted = @sort()
     sorted.slice(-n).reverse()
-
+
 module.exports = (robot) ->
   karma = new Karma robot
-  robot.hear /(\S+[^+\s])\+\+(\s|$)/, (msg) ->
+  robot.hear /(\S+[^+:\s])[: ]*\+\+(\s|$)/, (msg) ->
     subject = msg.match[1].toLowerCase()
     karma.increment subject
     msg.send "#{subject} #{karma.incrementResponse()} (Karma: #{karma.get(subject)})"
-
-  robot.hear /(\S+[^-\s])--(\s|$)/, (msg) ->
+
+  robot.hear /(\S+[^-:\s])[: ]*--(\s|$)/, (msg) ->
     subject = msg.match[1].toLowerCase()
     karma.decrement subject
     msg.send "#{subject} #{karma.decrementResponse()} (Karma: #{karma.get(subject)})"
-
+
   robot.respond /karma empty ?(\S+[^-\s])$/i, (msg) ->
     subject = msg.match[1].toLowerCase()
     karma.kill subject
     msg.send "#{subject} has had its karma scattered to the winds."
-
+
   robot.respond /karma( best)?$/i, (msg) ->
     verbiage = ["The Best"]
     for item, rank in karma.top()
       verbiage.push "#{rank + 1}. #{item.name} - #{item.karma}"
     msg.send verbiage.join("\n")
-
+
   robot.respond /karma worst$/i, (msg) ->
     verbiage = ["The Worst"]
     for item, rank in karma.bottom()
       verbiage.push "#{rank + 1}. #{item.name} - #{item.karma}"
     msg.send verbiage.join("\n")
-
+
   robot.respond /karma (\S+[^-\s])$/i, (msg) ->
     match = msg.match[1].toLowerCase()
     if match != "best" && match != "worst"
       msg.send "\"#{match}\" has #{karma.get(match)} karma."
-


Conversationalized MyWoT

Conversationalized MyWoT

--- src/scripts/mywot.coffee
+++ src/scripts/mywot.coffee
@@ -72,11 +72,9 @@
             mywot_childfriendliness = mywot_json[mywot_term][4]
             mywot_categories = mywot_json[mywot_term]["categories"]

-            mywot_profile = """MyWot Result for #{mywot_term}
-            ---------------------------
-            - Trustworthiness: #{reputation(mywot_trustworthiness[0])} (Confidence: #{mywot_trustworthiness[1]}%)
-            - Child Safety:    #{reputation(mywot_childfriendliness[0])} (Confidence: #{mywot_childfriendliness[1]}%)
-            - Categories: """
+            mywot_profile = """The MyWoT community trusts #{mywot_term} about #{reputation(mywot_trustworthiness[0])} (Confidence: #{mywot_trustworthiness[1]}%).
+            For kids, they say #{reputation(mywot_childfriendliness[0])} (Confidence: #{mywot_childfriendliness[1]}%).
+            If you really want to categorize it: """

             for key, value of mywot_categories
               mywot_profile += "\n  - #{mywot_category_mapping[key]} (Confidence: #{value}%)"


drag and drop tlog upload approximately works - needs progress bar

--- src/scripts/services/dapiServices.coffee
+++ src/scripts/services/dapiServices.coffee
@@ -57,7 +57,8 @@
   # Async read the specified ID
   getId: (id) ->
     @log.debug("Getting #{@endpoint}/#{id}")
-    @http.get(@urlId(id), @config)
+    c = angular.extend({}, @config)
+    @http.get(@urlId(id), c)
     .then (results) ->
       results.data

@@ -151,17 +152,20 @@
   endpoint: "mission"

   get_parameters: (id) ->
-    @http.get("#{@urlBase()}/#{id}/parameters.json", @config)
+    c = angular.extend({}, @config)
+    @http.get("#{@urlBase()}/#{id}/parameters.json", c)
     .success (results) ->
       results.data

   get_plotdata: (id) ->
-    @http.get("#{@urlBase()}/#{id}/dseries", @config)
+    c = angular.extend({}, @config)
+    @http.get("#{@urlBase()}/#{id}/dseries", c)
     .success (results) ->
       results.data

   get_geojson: (id) ->
-    @http.get("#{@urlBase()}/#{id}/messages.geo.json", @config)
+    c = angular.extend({}, @config)
+    @http.get("#{@urlBase()}/#{id}/messages.geo.json", c)
     .success (results) ->
       results.data


add a uuid for testing purchases

add a uuid for testing purchases

--- tutor/specs/courses-test-data.coffee
+++ tutor/specs/courses-test-data.coffee
@@ -19,7 +19,7 @@
     { "id": "1", "type": "student" }
   ],
   students: [
-    { role_id: "1", student_identifier: '1234' }
+    { role_id: "1", student_identifier: '1234', uuid: "06fc16fc-70f5-4db1-a61d-b0f496cf3cd4" }
   ]

 }


重载find，以便于使用limt和排序功能

重载find，以便于使用limt和排序功能

--- biz/commit.coffee
+++ biz/commit.coffee
@@ -96,6 +96,16 @@
       cb null, project_id
     )

+  #读取commit
+  find: (data, cb)->
+    limit = data.limit
+    delete data.limit
+    options =
+      beforeQuery: (query)->
+        query.limit limit || 20
+        query.orderBy 'timestamp', 'desc'
+
+    super data, options, cb
   ###
     #处理git commit
   ###


Update assumptions

Update assumptions

--- src/smc-util/misc.coffee
+++ src/smc-util/misc.coffee
@@ -1903,8 +1903,13 @@
 #   `sorted_path_array`:Array<String>
 #   `exclusions`:String | object | immutable.Collection
 #
-# Assumes
-#   sorted_path_array` has no duplicates and contains absolute paths from a common root
+# Assumes sorted_path_array
+#   has no duplicates
+#   contains absolute paths
+#   contains all parents of any path
+#     ie. if sorted_path_array contains "a/b/c", then it
+#     must also contain "a" and "a/b"
+#   is sorted alphabetically such that '/' comes before any other character
 #
 # Returns
 #   An new copy of `sorted_path_array` where the paths in `exclude` and


webapp/3d: noKeys deprecated -- see https://github.com/mrdoob/three.js/blob/master/examples/js/controls/OrbitControls.js#L962

--- src/smc-webapp/3d.coffee
+++ src/smc-webapp/3d.coffee
@@ -248,7 +248,7 @@
         # set up camera controls
         @controls = new THREE.OrbitControls(@camera, @renderer.domElement)
         @controls.damping = 2
-        @controls.noKeys = true
+        @controls.enableKeys = false # see https://github.com/mrdoob/three.js/blob/master/examples/js/controls/OrbitControls.js#L962
         @controls.zoomSpeed = 0.4
         if @_center?
             @controls.target = @_center


Merging #2033; fixes #1534

Merging #2033; fixes #1534

--- nodes.coffee
+++ nodes.coffee
@@ -386,6 +386,7 @@
   isComplex      : -> @hasProperties() or @base.isComplex()
   isAssignable   : -> @hasProperties() or @base.isAssignable()
   isSimpleNumber : -> @base instanceof Literal and SIMPLENUM.test @base.value
+  isString       : -> @base instanceof Literal and IS_STRING.test @base.value
   isAtomic       : ->
     for node in @properties.concat @base
       return no if node.soak or node instanceof Call
@@ -916,6 +917,16 @@
             exps[i] = @addProperties node, name, o
         child.expressions = exps = flatten exps

+  # `use strict` (and other directives) must be the first expression statement(s)
+  # of a function body. This method ensures the prologue is correctly positioned
+  # above the `constructor`.
+  hoistDirectivePrologue: ->
+    index = 0
+    {expressions} = @body
+    ++index while (node = expressions[index]) and node instanceof Comment or
+      node instanceof Value and node.isString()
+    @directives = expressions.splice 0, index
+
   # Make sure that a constructor is defined for the class, and properly
   # configured.
   ensureConstructor: (name) ->
@@ -938,6 +949,7 @@
     name = "_#{name}" if name.reserved
     lname = new Literal name

+    @hoistDirectivePrologue()
     @setContext name
     @walkBody name, o
     @ensureConstructor name
@@ -946,6 +958,7 @@
     if decl
       @body.expressions.unshift new Assign (new Value (new Literal name), [new Access new Literal 'name']), (new Literal "'#{name}'")
     @body.expressions.push lname
+    @body.expressions.unshift @directives...
     @addBoundFunctions o

     call  = Closure.wrap @body


Fix trying to delete supervisor images.

Fix trying to delete supervisor images.

--- src/docker-utils.coffee
+++ src/docker-utils.coffee
@@ -55,13 +55,13 @@
 		.finally ->
 			imagesBeingFetched--

+	supervisorImages = [ "#{config.localImage}:latest", "#{config.remoteImage}:latest" ]
 	exports.cleanupContainersAndImages = ->
 		knex('app').select()
 		.map (app) ->
 			app.imageId + ':latest'
 		.then (apps) ->
 			# Cleanup containers first, so that they don't block image removal.
-			supervisorImages = [ "#{config.localImage}:latest", "#{config.remoteImage}:latest" ]
 			docker.listContainersAsync(all: true)
 			.filter (containerInfo) ->
 				isUserApp = _.contains(apps, containerInfo.Image)
@@ -80,7 +80,7 @@
 				docker.listImagesAsync()
 				.filter (image) ->
 					!_.any image.RepoTags, (imageId) ->
-						_.contains(apps, imageId)
+						_.contains(apps, imageId) or _.contains(supervisorImages, imageId)
 				.map (image) ->
 					docker.getImage(image.Id).removeAsync()
 					.then ->


Destroy levelsWorkspaceManager when package gets deactivated

--- lib/levels-debugger-ruby.coffee
+++ lib/levels-debugger-ruby.coffee
@@ -37,6 +37,7 @@
     @debuggerPresenter.destroy()
     @socketChannel.destroy()
     @incomingMessageDispatcher.destroy()
+    levelsWorkspaceManager.destroy()
     return

   handleOpener: (uri) ->


Dispatch events through atom.emitter (for now).

--- lib/merge-conflicts-view.coffee
+++ lib/merge-conflicts-view.coffee
@@ -94,7 +94,7 @@
           @pathList.find("li:contains('#{p}') .stage-ready").eq(0).hide()

       if @state.isEmpty()
-        atom.emit 'merge-conflicts:done'
+        atom.emitter.emit 'merge-conflicts:done'
         @finish(SuccessView)

   finish: (viewClass) ->
@@ -115,7 +115,7 @@
         return if handleErr(err)

         full = path.join atom.project.getPaths()[0], p
-        atom.emit 'merge-conflicts:resolved', file: full, total: 1, resolved: 1
+        atom.emitter.emit 'merge-conflicts:resolved', file: full, total: 1, resolved: 1
         atom.workspace.open p

   stageFile: (event, element) ->
@@ -128,7 +128,7 @@
     GitBridge.add repoPath, (err) ->
       return if handleErr(err)

-      atom.emit 'merge-conflicts:staged', file: filePath
+      atom.emitter.emit 'merge-conflicts:staged', file: filePath

   @detect: ->
     return unless atom.project.getRepositories().length > 0


Implement copy instead of serialize

Implement copy instead of serialize

Implementing copy is all you need to do to be able to split the view
and this will remove the warning about unable to deserialize when Atom
is closed and reopened with the find results open.

--- lib/project/results-pane.coffee
+++ lib/project/results-pane.coffee
@@ -4,12 +4,7 @@

 module.exports =
 class ResultsPaneView extends ScrollView
-  atom.deserializers.add(this)
-
   @URI: "atom://find-and-replace/project-results"
-
-  @deserialize: (state) ->
-    new ResultsPaneView()

   @content: ->
     @div class: 'preview-pane pane-item', =>
@@ -38,11 +33,11 @@
   beforeRemove: ->
     @model.setActive(false)

+  copy: ->
+    new ResultsPaneView()
+
   getPane: ->
     @parent('.item-views').parent('.pane').view()
-
-  serialize: ->
-    deserializer: 'ResultsPaneView'

   getTitle: ->
     "Project Find Results"


Improve BufferedProcess option

Improve BufferedProcess option

--- lib/rubocop-auto-correct.coffee
+++ lib/rubocop-auto-correct.coffee
@@ -47,11 +47,22 @@
     args = ['-a', filePath]
     stdout = (output) ->
       if output.match("corrected")
-        if atom.config.get('rubocop-auto-correct.notification')
-          atom.notifications.addSuccess(output)
+        atom.notifications.addSuccess(output)
     stderr = (output) ->
-      if atom.config.get('rubocop-auto-correct.notification')
         atom.notifications.addError(output)
+
+    options = {
+      command: command,
+      args: args,
+      stdout: stdout,
+      stderr: stderr
+    }
+
+    unless atom.config.get('rubocop-auto-correct.notification')
+      options = {
+        command: command,
+        args: args,
+      }

     which command, (err) ->
       if (err)
@@ -63,7 +74,7 @@
           ''' }
         )

-      process = new BufferedProcess({command, args, stdout, stderr})
+      process = new BufferedProcess(options)
       process

   run: (editor) ->


New comment format

New comment format

--- src/tools/remotes.coffee
+++ src/tools/remotes.coffee
@@ -1,6 +1,6 @@
-#
-# Get available distributions list
-#
+###
+Get available distributions list
+###

 module.exports = (force)->
   r = []


changed bar chart tooltip to follow mouse

--- src/viz/types/bar.coffee
+++ src/viz/types/bar.coffee
@@ -89,6 +89,6 @@
     axis = if vars.time.value is vars.y.value then "y" else "x"
     vars.self[axis] scale: "discrete"
 bar.shapes       = ["square"]
-bar.tooltip      = "static"
+bar.tooltip      = "follow"

 module.exports = bar


Extend HTMLElement

Extend HTMLElement

--- lib/selection-count-view.coffee
+++ lib/selection-count-view.coffee
@@ -1,5 +1,5 @@
 module.exports =
-class SelectionCountView
+class SelectionCountView extends HTMLElement
   initialize: (@statusBar) ->
     @classList.add('selection-count', 'inline-block')


uff: made buffer geometry. tired.

uff: made buffer geometry. tired.


removed //-parsing from the status widget, // will be treated as regular text, only protocol schemas like http: will be converted to links. tooltip was adjusted for color and readability, content display updated to use tooltips/link expansion as well. buttons for the helper window relocated slightly and enhanced visually