Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/approachable.coffee
+++ test/approachable.coffee
@@ -22,11 +22,11 @@

 test "should handle negative value", ->
   obj = GameObject
+    powerup: -70
     cooldowns:
       powerup:
         target: 0
         approachBy: 1
-        value: -70

   obj.include(Approachable)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/approachable.coffee
+++ test/approachable.coffee
@@ -91,7 +91,6 @@

   obj.include(Approachable)

-
   equals obj.I.age, 1, "should successfully update"

 module()


Fix test

Fix test

--- test/coffee/tests.coffee
+++ test/coffee/tests.coffee
@@ -596,6 +596,9 @@
     socketView.model.start.append new model.TextToken('\'hi\'')
       .append socketView.model.end

+    socketView.model.notifyChange()
+    documentView.layout()
+
     socketView = view_.getViewNodeFor document.getTokenAtLocation(4).container

     strictEqual socketView.dimensions[0].height, view_.opts.textHeight + 2 * view_.opts.textPadding, 'Final height O.K.'


test fix

test fix

--- test/common/setup.coffee
+++ test/common/setup.coffee
@@ -24,7 +24,7 @@
   if env.SAUCE
     desired.name = testInfo.name if testInfo?.name
     if env.TRAVIS_JOB_NUMBER
-      desired.name = env.TRAVIS_JOB_NUMBER + " " + desired.name
+      desired.name = "[" env.TRAVIS_JOB_NUMBER + "] " + desired.name
     desired.tags = _.union(desired.tags, testInfo.tags) if testInfo?.tags
   desired['tunnel-identifier'] = env.TRAVIS_JOB_NUMBER if env.TRAVIS_JOB_NUMBER
   desired
@@ -35,16 +35,18 @@

 env.TRAVIS_JOB_ID = process.env.TRAVIS_JOB_ID
 env.TRAVIS_JOB_NUMBER = process.env.TRAVIS_JOB_NUMBER
+env.TRAVIS_BUILD_NUMBER = process.env.TRAVIS_BUILD_NUMBER

 if env.TRAVIS_JOB_ID
   env.TRAVIS = true;
   console.log "Travis environment detected."
   console.log "TRAVIS_JOB_ID --> ", env.TRAVIS_JOB_ID
+  console.log "TRAVIS_BUILD_NUMBER --> ", env.TRAVIS_BUILD_NUMBER
   console.log "TRAVIS_JOB_NUMBER --> ", env.TRAVIS_JOB_NUMBER

 if env.SAUCE
   env.SAUCE_JOB_ID = \
-    env.TRAVIS_JOB_ID ||
+    env.TRAVIS_BUILD_NUMBER ||
     process.env.SAUCE_JOB_ID ||
     Math.round(new Date().getTime() / (1000*60));
   env.SAUCE_USERNAME = process.env.SAUCE_USERNAME


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -1,6 +1,6 @@
 module "Controllable"

-# sets up pla
+# sets up player object
 QUnit.testStart = ->
   window.animation = GameObject
     data: animationData


fix up broken test beforeEach hook

fix up broken test beforeEach hook

--- test/plotter_test.coffee
+++ test/plotter_test.coffee
@@ -105,7 +105,9 @@
         xMin: -4, yMin: -1.4, xMax: 6, yMax: 8.6
       }
     describe 'tool macros', ->
-      beforeEach -> p.command { macro: [ 'AMRECT1', '21,1,$1,$2,0,0,0' ] }
+      beforeEach ->
+        p.parser = { format: { places: [2, 4] } }
+        p.command { macro: [ 'AMRECT1', '21,1,$1,$2,0,0,0' ] }
       it 'should add the macro to the macros list', ->
         p.macros.RECT1.name.should.eql 'RECT1'
       it 'should add macro tools to the tools object', ->


add test case for Project.removeUrl()

add test case for Project.removeUrl()

--- test/project_test.coffee
+++ test/project_test.coffee
@@ -453,6 +453,45 @@
       expect(added[prj1[0].url][prj1[0].id].show).to.be.equal(prj1[0].show)
       expect(added[prj2[0].url]).to.be.empty  # removed
       expect(added[prj3[0].url].index).to.be.equal(prj3[0].urlIndex - 1) # update index
+      expect(added[prj3[0].url][prj3[0].id].text).to.be.equal(prj3[0].text)
+      expect(added[prj3[0].url][prj3[0].id].show).to.be.equal(prj3[0].show)
+
+
+  ###
+   test for removeUrl(url)
+  ###
+  describe 'removeUrl(url)', ->
+
+    it 'should be empty, when remove 1 project from 3 same url projects.', () ->
+      expect(Project.get()).to.be.empty
+
+      # execute
+      Project.add(prj1[0])
+      Project.add(prj1[1])
+      Project.add(prj1[2])
+      Project.removeUrl(prj1[0].url)
+
+      # assert
+      added = Project.get()
+      expect(added).to.be.empty
+
+
+    it 'should leaves 2 project, when remove 1 url from 3 different url projects.', () ->
+      expect(Project.get()).to.be.empty
+
+      # execute
+      Project.add(prj1[0])
+      Project.add(prj2[0])  # be removed
+      Project.add(prj3[0])
+      Project.removeUrl(prj2[0].url)
+
+      # assert
+      added = Project.get()
+      expect(added[prj1[0].url].index).to.be.equal(prj1[0].urlIndex)
+      expect(added[prj1[0].url][prj1[0].id].text).to.be.equal(prj1[0].text)
+      expect(added[prj1[0].url][prj1[0].id].show).to.be.equal(prj1[0].show)
+      expect(added[prj2[0].url]).to.be.empty  # removed
+      expect(added[prj3[0].url].index).to.be.equal(prj3[0].urlIndex-1) # updated
       expect(added[prj3[0].url][prj3[0].id].text).to.be.equal(prj3[0].text)
       expect(added[prj3[0].url][prj3[0].id].show).to.be.equal(prj3[0].show)


Reorder settings

Reorder settings

--- lib/init.coffee
+++ lib/init.coffee
@@ -18,7 +18,7 @@
       default: 'pylama'
       description: 'Excutable path for external Pylama.
       Example: /usr/local/bin/pylama'
-      order: 1
+      order: 2
     configFileLoad:
       type: 'string'
       default: 'Don\'t use pylama config'
@@ -26,48 +26,48 @@
         'Don\'t use pylama config',
         'Use pylama config']
       title: 'Use Pylama configuration file'
-      order: 2
+      order: 3
     configFileName:
       type: 'string'
       default: 'pylama.ini'
       title: 'Configuration file name'
-      order: 3
+      order: 4
     ignoreErrorsAndWarnings:
       type: 'string'
       default: 'D203,D212,D213,D404'
       description: 'Comma-separated list of errors and warnings.
       Example: ED203,D212,D213,D404,111,E114,D101,D102,DW0311'
-      order: 4
+      order: 5
     skipFiles:
       type: 'string'
       default: ''
       description: 'Skip files by masks.
       Comma-separated list of a file names.
       Example: */messages.py,*/__init__.py'
-      order: 5
+      order: 6
     lintOnFly:
       type: 'boolean'
       default: true
       description: "Enable linting on the fly. Need to restart Atom."
-      order: 6
+      order: 7
     usePep8:
       type: 'boolean'
       default: true
       title: 'Use pycodestyle/pep8'
       description: 'Use pycodestyle/pep8 style guide checker.'
-      order: 7
+      order: 8
     usePep257:
       type: 'boolean'
       default: true
       title: 'Use pydocstyle/pep257'
       description: 'Use pydocstyle/pep257 docstring conventions checker.'
-      order: 8
+      order: 9
     usePyflakes:
       type: 'boolean'
       default: true
       title: 'Use Pyflakes'
       description: 'Use Pyflakes checker.'
-      order: 9
+      order: 10
     usePylint:
       type: 'boolean'
       default: false
@@ -75,24 +75,24 @@
       description: 'Use PyLint linter. May be unstable for internal Pylama.
       For use with external Pylama you should install pylama_pylint module
       ("pip install pylama-pylint").'
-      order: 10
+      order: 11
     useMccabe:
       type: 'boolean'
       default: true
       title: 'Use McCabe'
       description: 'Use McCabe complexity checker.'
-      order: 11
+      order: 12
     useIsort:
       type: 'boolean'
       default: false
       title: 'Use isort'
       description: 'Use isort imports checker.'
-      order: 12
+      order: 13
     isortOnSave:
       type: 'boolean'
       default: false
       title: 'isort imports on save (experimental).'
-      order: 13
+      order: 14

   activate: ->


#update #2

#update #2

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,6 +10,9 @@
 {settings} = require '../package.json'

 {exec} = require 'child_process'
+
+// Report crashes to our server.
+require('crash-reporter').start();

 # Report crashes to our server.
 # require('crash-reporter').start()
@@ -37,7 +40,7 @@
     # fullscreen: true
     icon: "#{__dirname}/../gfx/Dashy.ico"
     'web-preferences':
-      'plugins': true
+      '''plugins': true
     'always-on-top' : false
   webContents = mainWindow.webContents
   webContents.enableDeviceEmulation fitToView: true


modify default settings

modify default settings

--- lib/main.coffee
+++ lib/main.coffee
@@ -32,9 +32,9 @@
       order: 3
     buildOnWine:
       title: "Build on Wine"
-      description: "When not on Windows, `ISCC.exe` will be launched in Wine. For better error detection, you might have to tweak `WINEDEBUG`."
+      description: "*Experimental* – When not on Windows, `ISCC.exe` will be launched in Wine. For better error detection, you might have to tweak `WINEDEBUG`."
       type: "boolean"
-      default: false
+      default: true
       order: 4
     manageDependencies:
       title: "Manage Dependencies"


Significantly reduce loading time

Significantly reduce loading time

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,10 +1,8 @@
-{ CompositeDisposable, Disposable } = require 'atom'
-path = require 'path'
-
 module.exports =
   config: require './config'

   activate: ->
+    { CompositeDisposable } = require 'atom'
     @disposables = new CompositeDisposable
     @activated = false
     global.atom_latex = this
@@ -15,10 +13,11 @@
             (grammar.scopeName.indexOf('text.tex.latex') > -1) or
             (grammar.name is 'LaTeX')
           promise = new Promise (resolve, reject) =>
-            @lazyLoad()
+            setTimeout(( => @lazyLoad()), 100)
             resolve()

   lazyLoad: ->
+    return if @activated
     @activated = true

     @latex = new AtomLaTeX
@@ -43,15 +42,17 @@
       'atom-latex:tools-backquote': () => @latex.provider.syntax.backquote()
       'atom-latex:tools-doublequote': () => @latex.provider.syntax.doublequote()

+    path = require 'path'
     @disposables.add atom.workspace.observeTextEditors (editor) =>
       @disposables.add editor.onDidSave () =>
         if atom.config.get('atom-latex.build_after_save') and \
             editor.buffer.file?.path and \
             path.extname(editor.buffer.file?.path) == '.tex'
-          @latex.builder.build()
+          @latex.builder.build

   deactivate: ->
-    return @disposables.dispose()
+    @latex?.dispose()
+    @disposables.dispose()

   provide: ->
     if !@provider?
@@ -66,10 +67,12 @@
       @status = new Status
       @disposables.add @status
     @status.attach statusBar
+    { Disposable } = require 'atom'
     return new Disposable( => @status.detach())

-class AtomLaTeX extends Disposable
+class AtomLaTeX
   constructor: ->
+    { CompositeDisposable } = require 'atom'
     @disposables = new CompositeDisposable
     Builder = require './builder'
     Cleaner = require './cleaner'


:bug: Fix a bug in Main::consumeLinter There's still a bug that mysteriously re-adds the removed linter when active pane is changed

--- lib/main.coffee
+++ lib/main.coffee
@@ -31,14 +31,15 @@
         @instance.linters.push linter
     new Disposable =>
       @instance.linters = @instance.linters.filter (item) =>
-        unless item in linters
+        if item in linters
           if item.scope is 'project'
             @instance.messagesProject.delete(item)
           else
-            @instance.eachLinter (editorLinter) -> editorLinter.messages.delete(item)
+            @instance.eachLinter (editorLinter) ->
+              editorLinter.messages.delete(item)
           false
         true
-      @instance.views.render()
+       @instance.views.render()
   consumeStatusBar: (statusBar) ->
     @instance.views.attachBottom(statusBar)


Allow opening file URIs with :line:col suffix

--- lib/main.coffee
+++ lib/main.coffee
@@ -10,7 +10,7 @@
   grammar.fileTypes ? []

 module.exports =
-  wordRegex: /[-\w/\.]+/
+  wordRegex: /[-\w/\.]+(:\d+){0,2}/

   activate: (state) ->
     atom.commands.add 'atom-text-editor',
@@ -67,12 +67,21 @@
     range = editor.getLastCursor().getCurrentWordBufferRange({@wordRegex})
     return unless fileName = editor.getTextInBufferRange(range)

+    [fileName, line, column] = fileName.split ":"
+    line = line || 1
+    column = column || 1
+
     return unless filePath = @getFilePath(editor, fileName)
     pane = atom.workspace.getActivePane()
     switch split
       when 'down' then pane.splitDown()
       when 'right' then pane.splitRight()

-    atom.workspace.open(filePath, searchAllPanes: false)
+    options = {
+        searchAllPanes: false,
+        initialLine: line-1,
+        initialColumn: column-1
+    }
+    atom.workspace.open(filePath, options)

   deactivate: ->


removed pointless semi-colon

removed pointless semi-colon

--- lib/syfi.coffee
+++ lib/syfi.coffee
@@ -1,5 +1,5 @@
 {CompositeDisposable} = require 'atom'
-exec = require('child_process').exec;
+exec = require('child_process').exec
 SymfonyFinder = require './symfonyFinder'

 module.exports = Syfi =


Some changes to device creation

Some changes to device creation

Set all device properties in createDevice function
Emit instead of return device creation error

--- lib/upnp.coffee
+++ lib/upnp.coffee
@@ -10,20 +10,27 @@

 exports.createDevice = (type, name, address) ->
     unless type of devices
-        return new Error "UPnP device of type #{type} is not yet implemented."
+        device = new EventEmitter()
+        device.emit 'error', new Error "UPnP device of type #{type} is not yet implemented."
+        return device

     device = Object.create devices[type],
-        name: { value: name, enumerable: true }
         type: { value: type, enumerable: true }

+    device.name = name or type
     device.address = address if address?

-    init device
+    init device, (err, res) ->
+        return device.emit 'error', err if err?
+        Object.defineProperty device, 'uuid', value: "uuid:#{res.uuid}"
+        device.address = res.address
+        device.httpPort = res.port
+        device.emit 'ready'

     device

-Device = Object.create new EventEmitter
+Device = new EventEmitter()

 Device.announce = (callback = ->) ->
     ssdp.start.call @
@@ -40,7 +47,7 @@
 # Need an object to reference a device by its type.
 devices = MediaServer: MediaServer

-# Initiate a device.
+# Asynchronous operations to get some device properties.
 init = (device, callback) ->
     async.parallel(
         uuid: (callback) -> helpers.getUuid device.type, device.name, callback
@@ -50,10 +57,5 @@
             else
                 helpers.getNetworkIP callback
         port: (callback) -> httpServer.start.call device, callback
-        (err, res) ->
-            return device.emit 'error', err if err?
-            Object.defineProperty device, 'uuid', value: "uuid:#{res.uuid}"
-            device.address = res.address
-            device.httpPort = res.port
-            device.emit 'ready'
+        callback
     )


Use an explicit prefix

Use an explicit prefix

--- timeAsyncMethod.coffee
+++ timeAsyncMethod.coffee
@@ -1,12 +1,12 @@

-module.exports = (obj, methodName, key, logger) ->
+module.exports = (obj, methodName, prefix, logger) ->
 	metrics = require('./metrics')

 	if typeof obj[methodName] != 'function'
 		throw new Error("[Metrics] expected object property '#{methodName}' to be a function")

 	realMethod = obj[methodName]
-	key = "methods.#{key}"
+	key = "#{prefix}.#{methodName}"

 	obj[methodName] = (originalArgs...) ->


Remove unnecesary trigger

Remove unnecesary trigger

--- source/class/entity.coffee
+++ source/class/entity.coffee
@@ -21,7 +21,6 @@
   @fields: (attributes...) ->
     @records    = {}
     @attributes = attributes or []
-    do @unbind
     @

   @create: (attributes) ->
@@ -106,7 +105,6 @@
       @trigger 'error', error
     else
       record = if @isNew() then @create() else @update()
-      @trigger 'save'
       record

   updateAttributes: (attributes) ->
@@ -132,13 +130,9 @@
     delete @constructor.records[@uid]
     @trigger 'destroy'
     @trigger 'change'
-    @unbind()

   clone: ->
     Object.create @
-
-  unbind: ->
-    @trigger 'unbind'

   trigger: (args...) ->
     args.splice(1, 0, @.clone())


Add list-copy test

Add list-copy test

--- spec/list-edit-spec.coffee
+++ spec/list-edit-spec.coffee
@@ -21,6 +21,21 @@
         activationPromise
         # TODO: Would be nice if we could do this just once, instead of for each spec, but waitsForPromise does
         #       not seem to work in describe (unlike what the documentation suggests.)
+
+  describe 'list-copy', ->
+    it 'copies the selected range to the clipboard', ->
+      #               01234567890123456789012345678901234567890
+      editor.setText '[   Blinky ,  Pinky ,  Inky ,  Clyde   ]'
+      editor.setSelectedBufferRange [[0, 15], [0,24]] # Pinky & Clyde
+      atom.commands.dispatch editorView, 'list-edit:copy'
+      clip = atom.clipboard.readWithMetadata()
+      expect(clip?.text).toBe('Pinky ,  Inky')
+      expect(clip?.metadata).toEqual(
+        { id : 'list-edit-clip-meta', openBracket : '['
+        , initialWhitespace : '   ', finalWhitespace : '   '
+        , separator : { leadingWhitespace: ' ', sepChar : ',', trailingWhitespace : '  ' }
+        , eltRanges : [ [0, 5], [9, 13] ]
+        })

   describe 'list-cut', ->
     it 'Cursor inside element in singleton list cuts element and whitespace', ->


Fix World/Screen coordinate conversions for Rect params.

--- src/scene/sceneView.coffee
+++ src/scene/sceneView.coffee
@@ -102,7 +102,7 @@
   # screen coordinates (pixels)
   worldToScreen: (value,scale=1) ->
     if value instanceof Rect
-      return new Rect(value).multiply (SceneView.UNIT * scale)
+      return new Rect(value).scale (SceneView.UNIT * scale)
     else if value instanceof Point
       return new Point(value).multiply (SceneView.UNIT * scale)
     value * (SceneView.UNIT * scale)
@@ -111,7 +111,7 @@
   # game world coordinates (game unit sizes)
   screenToWorld: (value,scale=1) ->
     if value instanceof Rect
-      return new Rect(value).multiply 1 / (SceneView.UNIT * scale)
+      return new Rect(value).scale 1 / (SceneView.UNIT * scale)
     else if value instanceof Point
       return new Point(value).multiply 1 / (SceneView.UNIT * scale)
     value * (1 / (SceneView.UNIT * scale))


Allow Hubot to suggest strategies to others, in multiple ways

--- src/scripts/oblique.coffee
+++ src/scripts/oblique.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Display an oblique strategy
+#   Suggests an oblique strategy
 #
 # Dependencies:
 #   None
@@ -8,13 +8,23 @@
 #   None
 #
 # Commands:
-#   hubot strategy - Returns an oblique strategy
+#   hubot strategy - Suggests a strategy
+#   hubot a strategy for <user> - Suggests a strategy to user
+#
+# Notes:
+#   You can be as verbose as you want as long as you address Hubot and mention
+#   the word strategy and, optionally, one or more users.
+#
+#   Thanks, Brian Eno.
 #
 # Author:
 #   hakanensari

 module.exports = (robot) ->
-  robot.respond /\W*(?:an? )?(?:oblique )?strategy\??$/i, (msg) ->
-    msg.http('http://oblique.io')
+  robot.respond /.*strategy/i, (msg) ->
+    mentions = msg.message.text.match(/(@\w+)/g)
+    robot.http('http://oblique.io')
       .get() (err, res, body) ->
-        msg.send JSON.parse body
+        strategy = JSON.parse body
+        strategy = "#{mentions.join(', ')}: #{strategy}" if mentions
+        msg.send strategy


Update octospy.coffee with case insensitive regex. Fixes #491

--- src/scripts/octospy.coffee
+++ src/scripts/octospy.coffee
@@ -188,11 +188,11 @@
     robot.brain.data.octospy ||= {}

   # Public: Announce the kinds of things octospy knows about
-  robot.respond /octospy events/, (msg) ->
+  robot.respond /octospy events/i, (msg) ->
     msg.reply "I know about " + ( event for event of views ).join(', ')

   # Public: Dump the watching hash
-  robot.respond /octospying/, (msg) ->
+  robot.respond /octospying/i, (msg) ->
     watching = []

     # Troll octospy's data for any possible listeners, then see if they're us
@@ -221,7 +221,7 @@
   # repo       - The repository name (ex. 'github/hubot'
   # event      - The event type to stop watching (default: 'push')
   # github_url - The base github URL (default: 'github.com'
-  robot.respond /octospy stop ([^ ]+\/[^ ]+) ?([^ ]*)? ?([^ ]*)?/, (msg) ->
+  robot.respond /octospy stop ([^ ]+\/[^ ]+) ?([^ ]*)? ?([^ ]*)?/i, (msg) ->
     repo = msg.match[1]
     event = msg.match[2] || 'push'
     github_url = msg.match[3] || 'github.com'
@@ -273,7 +273,7 @@
   # repo       - The repository name (ex. 'github/hubot'
   # event      - The event type to stop watching (default: 'push')
   # github_url - The base github URL (default: 'github.com'
-  robot.respond /octospy ([^ ]+\/[^ ]+) ?([^ ]*)? ?([^ ]*)?/, (msg) ->
+  robot.respond /octospy ([^ ]+\/[^ ]+) ?([^ ]*)? ?([^ ]*)?/i, (msg) ->
     repo = msg.match[1]
     event = msg.match[2] || 'push'
     github_url = msg.match[3] || 'github.com'


stupid whitespace

stupid whitespace

--- src/scripts/walmart.coffee
+++ src/scripts/walmart.coffee
@@ -25,5 +25,5 @@
         match = body.match /http:\/\/media.peopleofwalmart.com\/wp-content\/uploads\/\d\d\d\d\/\d\d\/.+?\.jpg/g
         if (match)
           total_pics = match.length
-		  random_pic = Math.floor(Math.random() * total_pics)
-		  msg.send match[random_pic]
+          random_pic = Math.floor(Math.random() * total_pics)
+          msg.send match[random_pic]


added count of on hold tickets

added count of on hold tickets

--- src/scripts/zendesk.coffee
+++ src/scripts/zendesk.coffee
@@ -10,6 +10,7 @@
 #   hubot (all) tickets - returns the total count of all unsolved tickets. The 'all' keyword is optional.
 #   hubot new tickets - returns the count of all new (unassigned) tickets
 #   hubot open tickets - returns the count of all open tickets
+#   hubot on hold tickets - returns the count of all on hold tickets
 #   hubot escalated tickets - returns a count of tickets with escalated tag that are open or pending
 #   hubot pending tickets - returns a count of tickets that are pending
 #   hubot list (all) tickets - returns a list of all unsolved tickets. The 'all' keyword is optional.
@@ -24,6 +25,7 @@
 queries =
   unsolved: "search.json?query=status<solved+type:ticket"
   open: "search.json?query=status:open+type:ticket"
+  hold: "search.json?query=status:hold+type:ticket"
   new: "search.json?query=status:new+type:ticket"
   escalated: "search.json?query=tags:escalated+status:open+status:pending+type:ticket"
   pending: "search.json?query=status:pending+type:ticket"
@@ -90,6 +92,11 @@
     zendesk_request msg, queries.open, (results) ->
       ticket_count = results.count
       msg.send "#{ticket_count} open tickets"
+
+  robot.hear /on hold tickets$/i, (msg) ->
+    zendesk_request msg, queries.hold, (results) ->
+      ticket_count = results.count
+      msg.send "#{ticket_count} on hold tickets"

   robot.respond /list (all )?tickets$/i, (msg) ->
     zendesk_request msg, queries.unsolved, (results) ->


destory -> destroy

destory -> destroy

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -624,7 +624,7 @@
           expect(pane.getItems()).toEqual [editor1, editor2]

     describe "when replacing a pending item which is the last item in a second pane", ->
-      it "does not destory the pane even if core.destroyEmptyPanes is on", ->
+      it "does not destroy the pane even if core.destroyEmptyPanes is on", ->
         atom.config.set('core.destroyEmptyPanes', true)
         editor1 = null
         editor2 = null


Pass TextEditorRegistry to Workspace constructor in spec

--- spec/workspace-spec.coffee
+++ spec/workspace-spec.coffee
@@ -33,6 +33,7 @@
         notificationManager: atom.notifications, clipboard: atom.clipboard,
         applicationDelegate: atom.applicationDelegate,
         viewRegistry: atom.views, assert: atom.assert.bind(atom),
+        textEditorRegistry: atom.textEditors
       })
       atom.workspace.deserialize(workspaceState, atom.deserializers)

@@ -843,7 +844,8 @@
         config: atom.config, project: atom.project, packageManager: atom.packages,
         notificationManager: atom.notifications, deserializerManager: atom.deserializers,
         clipboard: atom.clipboard, viewRegistry: atom.views, grammarRegistry: atom.grammars,
-        applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom)
+        applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom),
+        textEditorRegistry: atom.textEditors
       })
       workspace2.deserialize(state, atom.deserializers)
       expect(jsPackage.loadGrammarsSync.callCount).toBe 1
@@ -905,7 +907,8 @@
           config: atom.config, project: atom.project, packageManager: atom.packages,
           notificationManager: atom.notifications, deserializerManager: atom.deserializers,
           clipboard: atom.clipboard, viewRegistry: atom.views, grammarRegistry: atom.grammars,
-          applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom)
+          applicationDelegate: atom.applicationDelegate, assert: atom.assert.bind(atom),
+          textEditorRegistry: atom.textEditors
         })
         workspace2.deserialize(atom.workspace.serialize(), atom.deserializers)
         item = workspace2.getActivePaneItem()


fix some text

fix some text

--- specs/ValidatorSpec.coffee
+++ specs/ValidatorSpec.coffee
@@ -9,109 +9,109 @@
 			{asset : "Migdal_2013_3_414", rows : 27}
 		])

-	it "needs to have noyarot ereh shirim teudot hithayvot mimshaltiyot",->
+	it "validates noyarot ereh shirim teudot hithayvot mimshaltiyot",->
 		asyncSpecWait()
 		validateRows("ניירות ערך סחירים","תעודות התחייבות ממשלתיות",[
 			{asset : "Migdal_2013_3_414", rows : 26}
 		])

-	it "needs to have noyarot ereh shirim teudot hov mishariyot",->
+	it "validates noyarot ereh shirim teudot hov mishariyot",->
 		asyncSpecWait()
 		validateRows("ניירות ערך סחירים","תעודות חוב מסחריות",[

 		])

-	it "needs to have noyarot ereh shirim agah konzerni",->
+	it "validates noyarot ereh shirim agah konzerni",->
 		asyncSpecWait()
 		validateRows("ניירות ערך סחירים",'"אג""ח קונצרני"',[
 			{asset : "Migdal_2013_3_414", rows : 192}
 		])

-	it "needs to have noyarot ereh shirim menayot",->
+	it "validates noyarot ereh shirim menayot",->
 		asyncSpecWait()
 		validateRows("ניירות ערך סחירים",'מניות',[
 			{asset : "Migdal_2013_3_414", rows : 149}
 		])

-	it "needs to have noyarot ereh shirim teudot sal",->
+	it "validates noyarot ereh shirim teudot sal",->
 		asyncSpecWait()
 		validateRows("ניירות ערך סחירים",'תעודות סל',[
 			{asset : "Migdal_2013_3_414", rows : 72}
 		])

-	it "needs to have noyarot ereh shirim kranot neemanut",->
+	it "validates noyarot ereh shirim kranot neemanut",->
 		asyncSpecWait()
 		validateRows("ניירות ערך סחירים",'קרנות נאמנות',[
 			{asset : "Migdal_2013_3_414", rows : 13}
 		])

-	it "needs to have noyarot ereh shirim kitvei optziya",->
+	it "validates noyarot ereh shirim kitvei optziya",->
 		asyncSpecWait()
 		validateRows("ניירות ערך סחירים",'כתבי אופציה',[
 			{asset : "Migdal_2013_3_414", rows : 9}
 		])

-	it "needs to have noyarot ereh LO shirim teudot hithayvut mimshaltiyot",->
+	it "validates noyarot ereh LO shirim teudot hithayvut mimshaltiyot",->
 		asyncSpecWait()
 		validateRows("ניירות ערך לא סחירים",'תעודות התחייבות ממשלתיות',[
 			{asset : "Migdal_2013_3_414", rows : 126}
 		])

-	it "needs to have noyarot ereh LO shirim agah konzerni",->
+	it "noyarot ereh LO shirim agah konzerni",->
 		asyncSpecWait()
 		validateRows("ניירות ערך לא סחירים",'"אג""ח קונצרני"',[
 			{asset : "Migdal_2013_3_414", rows : 23}
 		])

-	it "needs to have noyarot ereh LO shirim menayot",->
+	it "noyarot ereh LO shirim menayot",->
 		asyncSpecWait()
 		validateRows("ניירות ערך לא סחירים",'מניות',[

 		])

-	it "needs to have noyarot ereh LO shirim kranot hashkaa",->
+	it "validates noyarot ereh LO shirim kranot hashkaa",->
 		asyncSpecWait()
 		validateRows("ניירות ערך לא סחירים",'קרנות השקעה',[
 			{asset : "Migdal_2013_3_414", rows : 1}
 		])

-	it "needs to have noyarot ereh LO shirim kitvei optziya",->
+	it "validates noyarot ereh LO shirim kitvei optziya",->
 		asyncSpecWait()
 		validateRows("ניירות ערך לא סחירים",'כתבי אופציה',[
 			{asset : "Migdal_2013_3_414", rows : 3}
 		])

-	it "needs to have noyarot ereh LO shirim optiyot",->
+	it "validates noyarot ereh LO shirim optiyot",->
 		asyncSpecWait()
 		validateRows("ניירות ערך לא סחירים",'אופציות',[

 		])

-	it "needs to have noyarot ereh LO shirim hozim atidiim",->
+	it "validates noyarot ereh LO shirim hozim atidiim",->
 		asyncSpecWait()
 		validateRows("ניירות ערך לא סחירים",'חוזים עתידיים',[
 			{asset : "Migdal_2013_3_414", rows : 19}
 		])

-	it "needs to have halvaot",->
+	it "validates halvaot",->
 		asyncSpecWait()
 		validateRows("הלוואות",'',[
 			{asset : "Migdal_2013_3_414", rows : 44}
 		])

-	it "needs to have pikdonot",->
+	it "validates pikdonot",->
 		asyncSpecWait()
 		validateRows("פקדונות",'',[

 		])

-	it "needs to have zhuyot mekarkeyin",->
+	it "validates zhuyot mekarkeyin",->
 		asyncSpecWait()
 		validateRows("זכויות מקרקעין",'',[
 			{asset : "Migdal_2013_3_414", rows : 1}
 		])

-	it "needs to have hashkaot aherot",->
+	it "validates hashkaot aherot",->
 		asyncSpecWait()
 		validateRows("השקעות אחרות",'',[


optimize no-op user_set_queries

optimize no-op user_set_queries

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -2853,6 +2853,21 @@
             return

         #dbg("verify all requested fields may be set by users, and also fill in generated values")
+
+        # mod_fields counts the fields in query that might actually get modified
+        # in the database when we do the query; e.g., account_id won't since it gets
+        # filled in with the user's account_id, and project_write won't since it must
+        # refer to an existing project.  We use mod_field **only** to skip doing
+        # no-op queries below.
+        mod_fields = 0
+        for field in misc.keys(opts.query)
+            if user_query.set.fields[field] not in ['account_id', 'project_write']
+                mod_fields += 1
+        if mod_fields == 0
+            # nothing to do
+            opts.cb()
+            return
+
         for field in misc.keys(user_query.set.fields)
             if user_query.set.fields[field] == undefined
                 opts.cb("user set query not allowed for #{opts.table}.#{field}")
@@ -2938,7 +2953,7 @@
                     on_change_hook(@, old_val, query, account_id, cb)
                 else
                     cb()
-        ], opts.cb)
+        ], (err) => opts.cb(err))

     # fill in the default values for obj in the given table
     _query_set_defaults: (obj, table, fields) =>


pr 877 rewrite. support links

pr 877 rewrite. support links

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -131,6 +131,10 @@

 SUPPORT_LINKS =
+    frequently_asked_questions :
+        icon : 'question-circle'
+        href : 'https://github.com/sagemathinc/smc/wiki/Portal'
+        link : 'SageMathCloud documentation'
     pricing :
         icon : 'money'
         href : PolicyPricingPageUrl
@@ -154,10 +158,6 @@
         icon : 'comments-o'
         href : 'https://gitter.im/sagemath/cloud'
         link : 'Realtime chat and help'
-    frequently_asked_questions :
-        icon : 'question-circle'
-        href : 'https://github.com/sagemathinc/smc/wiki/Portal'
-        link : 'Wiki portal for help and FAQ'
     # removed since none of us SMC devs use ask.sagemath these days
     #quick_question :
     #    icon : 'question-circle'
@@ -491,8 +491,6 @@
         <Row>
             <Col sm=10 smOffset=1 md=8 mdOffset=2 xs=12>

-                    See the SageMathCloud Wiki.
-

                     <Icon name='medkit'/><Space/><Space/>
                     In case of any questions or problems, do not hesitate to create a <ShowSupportLink />.

@@ -501,6 +499,8 @@
                     <Icon name='envelope'/><Space/><Space/> You can also send an email to <HelpEmailLink />.

                     In such an email, please include the URL link to the relevant project or file.
+

+                    The SageMathCloud Documentation


Create task shell that bootstraps worker

--- src/snippets-reader.coffee
+++ src/snippets-reader.coffee
@@ -1,43 +1,23 @@
-eval("window = {};")
-eval("console = {};")
-console.warn = ->
-  self.postMessage
-    type: 'warn'
-    details: arguments
-console.log = ->
-  self.postMessage
-    type: 'warn'
-    details: arguments
-eval("attachEvent = function(){};")
+module.exports =
+  loadTextmateSnippets: ({path}) ->
+    fs = require 'fs'
+    snippetsDirPath = fs.join(path, 'Snippets')
+    snippets = fs.list(snippetsDirPath).map (snippetPath) ->
+      fs.readPlist(snippetPath)
+    self.postMessage
+      type: 'loadSnippets'
+      snippets: snippets

-self.addEventListener 'message', (event) ->
-  switch event.data.type
-    when 'start'
-      window.resourcePath = event.data.resourcePath
-      importScripts(event.data.requirePath)
-      self.postMessage(type:'started')
-    else
-      self[event.data.type](event.data)
-
-self.loadTextmateSnippets = ({path}) ->
-  fs = require 'fs'
-  snippetsDirPath = fs.join(path, 'Snippets')
-  snippets = fs.list(snippetsDirPath).map (snippetPath) ->
-    fs.readPlist(snippetPath)
-  self.postMessage
-    type: 'loadSnippets'
-    snippets: snippets
-
-self.loadAtomSnippets = ({path}) ->
-  fs = require 'fs'
-  snippetsDirPath = fs.join(path, 'snippets')
-  snippets = []
-  for snippetsPath in fs.list(snippetsDirPath)
-    continue if fs.base(snippetsPath).indexOf('.') is 0
-    try
-      snippets.push(fs.readObject(snippetsPath))
-    catch e
-      console.warn "Error reading snippets file '#{snippetsPath}'"
-  self.postMessage
-    type: 'loadSnippets'
-    snippets: snippets
+  loadAtomSnippets: ({path}) ->
+    fs = require 'fs'
+    snippetsDirPath = fs.join(path, 'snippets')
+    snippets = []
+    for snippetsPath in fs.list(snippetsDirPath)
+      continue if fs.base(snippetsPath).indexOf('.') is 0
+      try
+        snippets.push(fs.readObject(snippetsPath))
+      catch e
+        console.warn "Error reading snippets file '#{snippetsPath}'"
+    self.postMessage
+      type: 'loadSnippets'
+      snippets: snippets


stub sub-worlds

stub sub-worlds

--- src/test/test_world.coffee
+++ src/test/test_world.coffee
@@ -17,13 +17,12 @@
     test_store("array", [1, "b"])
     t.end()

-  test 'world iterates over children', (t) ->
-    t.ok world.add_child, "add_child"
-    value = "Anjali"
-    world.add_child(value)
-
-    t.ok world.map_child, "map_child"
+  test 'world has children', (t) ->
+    world.add_child("Anjali")
+    result = world.map_child (child) -> "#{child} Prabhakar"
+    t.equal result[0], "Anjali Prabhakar", "add/map child"
+    t.end()

-    result = world.map_child (child) -> "#{child} Prabhakar"
-    t.equal result[0], "Anjali Prabhakar"
+  test 'world creates world for dictionaries', (t) ->
+    t.fail()
     t.end()


Edit welcome message.

Edit welcome message.

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -174,7 +174,30 @@
     autofocus: false
     animateScroll: true
     promptHistory: true
-    welcomeMessage: "Enter 'help' for a list of commands. Ctrl-D is EOF."
+    welcomeMessage: """
+      Welcome to Doppio! You may wish to try the following Java programs:
+        rhino
+        javac FileRead.java
+        javac Fib.java
+        java FileRead
+        java Fib <num>
+        java Chatterbot
+        java DiffPrint foo bar
+        java RegexTestHarness
+        java Lzw c foo foo_lzw (compress)
+        java Lzw d foo_lzw foo (decompress)
+
+      The .java files can be inspected by typing `edit [filename]`.
+
+      You can also upload your own files using the uploader above the top-right
+      corner of the console.
+
+      The provided Java compiler is for Java 4, so programs using modern Java
+      features (e.g. varargs) will not compile. However, you can still run them
+      by compiling them locally and uploading their classfiles.
+
+      Enter 'help' for full a list of commands. Ctrl-D is EOF.
+      """

   demo_files = ['special/DiffPrint.class', 'special/Chatterbot.java',
   'special/Chatterbot.class', 'special/Lzw.java', 'special/Lzw.class',
@@ -288,30 +311,6 @@
     if args[0] == '*' then localStorage.clear()
     else DoppioFile.delete args[0]
     true
-  list_demos: ->
-    controller.message """
-      We have loaded a bunch of source and class files -- the class files are
-      for Java programs using modern Java features that the provided Java 4
-      compiler does not support. You can inspect these files using the
-      commands 'ls', 'cat', and 'edit'.
-
-      You may wish to try the following commands:
-        javac FileRead.java
-        javac Fib.java
-        java FileRead
-        java Fib <num>
-        java Chatterbot
-        java DiffPrint foo bar
-        java RegexTestHarness
-        java Lzw c foo foo_lzw (compress)
-        java Lzw d foo_lzw foo (decompress)
-
-      After running these programs, use 'list_cache' to see the Java Class
-      Library files that they depended upon.
-
-      You can also upload your own files using the uploader above the top-right
-      corner of the console.
-    """
   emacs: -> "Try 'vim'."
   vim: -> "Try 'emacs'."
   time: (args) ->
@@ -345,8 +344,6 @@
     """
     Ctrl-D is EOF.

-    Lost? Try typing 'list_demos' to see what you can do.
-
     Java-related commands:
       javac <source file>    -- Invoke the Java 4 compiler.
       java <class> [args...] -- Run with command-line arguments.


Changing to an actual feature-detect.

Changing to an actual feature-detect.

Firefox throws an exception if you try to assign to location.origin, as it marks it read-only.

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -236,7 +236,7 @@
     row_list.push row.join('')
   return row_list.join('\n')

-location.origin = location.origin or "#{location.protocol}//#{location.host}"
+location.origin ?= location.origin or "#{location.protocol}//#{location.host}"

 commands =
   view_dump: ->


another null check bugfix

another null check bugfix

--- browser/frontend.coffee
+++ browser/frontend.coffee
@@ -235,7 +235,7 @@
     key = localStorage.key(i)
     continue unless key.substr(0, 6) is 'file::'
     file = DoppioFile.load key.substr(6) # hack
-    continue unless validExtension(file.name)
+    continue unless file? and validExtension(file.name)
     if (file.name.substr(0, lastArg.length).toLowerCase() is lastArg)
       potentialCompletions.push(
         if not keepExt() then file.name.split('.')[0]


Added some more tags (mostly video games)

--- src/app/common/tags.coffee
+++ src/app/common/tags.coffee
@@ -38,6 +38,35 @@
     {"name": "the last airbender", "regex": /(the ?last ?airbender)|avatar/},
     {"name": "steven universe", "regex": /steven ?universe/}
   ],
+  'movies' : [
+    {"name": "pacific rim", "regex" : /pacfic ?rim/},
+    {"name": "rushmore", "regex" : /rushmore/},
+    {"name": "the royal tenenbaums", "regex" : /(the)? ?royal ?tenenbaums/},
+    {"name": "the hunger games", "regex" : /(the)? ?hunger ?games/},
+    {"name": "pitch perfect", "regex" : /pitch ? perfect/},
+    {"name": "the dark knight", "regex" : /(the)? ?dark ?knight)/}
+  ],
+  'video games' : [
+    {"name": "harvest moon", "regex" : /harvest ?moon/},
+    {"name": "street fighter", "regex" : /street ?fighter/},
+    {"name": "final fantasy vii", "regex" : /final ?fantasy ?((vii)|(7))/},
+    {"name": "final fantasy vi", "regex" : /final ?fantasy ?((vi)|(6))/},
+    {"name": "final fantasy iv", "regex" : /final ?fantasy ?((iv)|(4))/},
+    {"name": "chrono trigger", "regex" : /chrono ?trigger/},
+    {"name": "super smash bros", "regex" : /(super)? ?smash ?bros/},
+    {"name": "super mario", "regex" : /(super)? ?mario/},
+    {"name": "fire emblem", "regex" : /fire ?emblem/},
+    {"name": "legend of zelda", "regex" : /(the)? ?(legend ?of)? ?zelda/},
+    {"name": "galaga", "regex" : /galaga/},
+    {"name": "persona", "regex" : /persona|(shin ?megami ?tensei)/},
+    {"name": "fire emblem", "regex" : /fire ?emblem/},
+    {"name": "metal gear solid", "regex" : /metal ?gear ?solid/},
+    {"name": "tomodachi life", "regex" : /tomodachi ?life/},
+    {"name": "mario kart", "regex" : /mario ?kart/},
+    # {"name": "love plus", "regex" : /love ?plus/},
+    {"name": "flappy bird", "regex" : /flappy ?bird/},
+    {"name": "halo", "regex" : /halo/}
+  ],
   'homestuck': [
     {"name": "dave strider", "regex": /dave ?strider/},
     {"name": "roxy lalonde", "regex": /roxy ?lalonde/},
@@ -75,6 +104,8 @@
     {"name": "space dandy", "regex": /space ?dandy/},
     {"name": "kill la kill", "regex": /kill ?la ?kill/},
     {"name": "sailor moon", "regex": /sailor ?moon/},
+    {"name": "naruto", "regex": /naruto/},
+    {"name": "one piece", "regex": /one ?piece/},
     {"name": "attack on titan", "regex": /(attack ?on ?titan)|(snk)|(shinkegi ?no ?kyojin)/}
   ]
     # template: {"name": "", "regex": //}


fix(cgFileUploadCtrl): handle onBeforeUpload promise rejection (MDC-910) (#19)

--- cgFileUploadCtrl.coffee
+++ cgFileUploadCtrl.coffee
@@ -212,7 +212,7 @@
             if @onBeforeUpload
                 promise = @onBeforeUpload(_ctrl)
                 if promise?.then?
-                    promise.then(_doUpload)
+                    promise.then(_doUpload).catch(@_errorHandler)
                 else _doUpload()
             else _doUpload()


feat(ctrl): add uploadMethod option

feat(ctrl): add uploadMethod option

--- cgFileUploadCtrl.coffee
+++ cgFileUploadCtrl.coffee
@@ -5,7 +5,7 @@

         constructor: (
             @elem = null
-            { @accept, @uploadUrl, @awscredentials, @disableNormalization }
+            { @accept, @uploadUrl, @uploadMethod, @awscredentials, @disableNormalization }
             { @onBeforeUpload, @onUploadStart, @onProgress, @onLoad, @onError }
         ) ->
             @_createInput()
@@ -110,6 +110,7 @@
             data =
                 file: file
                 url: @uploadUrl
+                method: @uploadMethod
                 name: filename
             worker.postMessage data


Fix sorting of versions

Fix sorting of versions

--- app.coffee
+++ app.coffee
@@ -137,7 +137,7 @@
 	(return -> true) if comparison is '*'

 	# normalize comparison string
-	comparison = comparison.replace /([0-9]+\.[0-9]+\.[0-9]+(?: (?:a|alpha|b|beta|d|dev|rc|pl) [0-9]+)?)/ig, (version) ->
+	comparison = comparison.replace /([0-9]+\.[0-9]+\.[0-9]+(?:([ _])(?:a|alpha|b|beta|d|dev|rc|pl)([ _])[0-9]+)?)/ig, (version) ->
 		version = version.replace(/[ _]/g, '.').replace(/a(?:lpha)/i, -3).replace(/b(?:eta)?/i, -2).replace(/d(?:ev)?/i, -4).replace(/rc/i, -1).replace(/pl/i, 1).split(/\./)
 		version[0] ?= 0
 		version[1] ?= 0
@@ -147,7 +147,6 @@
 		version = version.join '.'

 	comparison = comparison.replace /[ ]/g, ''
-
 	comparison = comparison.replace /\$v(==|<=|>=|<|>)([0-9]+\.[0-9]+\.[0-9]+.-?[0-9]+.[0-9]+)/g, (comparison, operator, v2) ->
 		# build comparison function
 		return """
@@ -336,23 +335,24 @@
 								fileCallback err
 								return

-							versions.sort (a, b) -> a.replace(/(\d\.\d\.\d)\./, '$1_').localeCompare b.replace(/(\d\.\d\.\d)\./, '$1_')
+							versions = versions.filter (versionFile) ->
+								if versionFile is 'latest'
+									false
+								else if (versionFile.substring 0, 1) is '.'
+									logger.log "notice", "Skipping dotfile #{packageFolder}/#{versionFile}"
+									false
+								else if /^([0-9]+\.[0-9]+\.[0-9]+(?:_(?:a|alpha|b|beta|d|dev|rc|pl)_[0-9]+)?)\.txt$/i.test versionFile
+									false
+								else unless /^([0-9]+\.[0-9]+\.[0-9]+(?:_(?:a|alpha|b|beta|d|dev|rc|pl)_[0-9]+)?)\.tar$/i.test versionFile
+									logger.log "notice", "Skipping #{packageFolder}/#{versionFile}, as it does not match a valid version number"
+									false
+								else
+									true
+							versions.sort (a, b) ->
+								return 0 if a is b
+								return 1 if (createComparator "$v > #{b.replace(/\.tar$/, '')}")(a.replace(/\.tar$/, ''))
+								return -1
 							async.eachSeries versions, (versionFile, versionsCallback) ->
-								if versionFile is 'latest'
-									versionsCallback null
-									return
-								if (versionFile.substring 0, 1) is '.'
-									logger.log "notice", "Skipping dotfile #{packageFolder}/#{versionFile}"
-									versionsCallback null
-									return
-								if /^([0-9]+\.[0-9]+\.[0-9]+(?:_(?:a|alpha|b|beta|d|dev|rc|pl)_[0-9]+)?)\.txt$/i.test versionFile
-									versionsCallback null
-									return
-								unless /^([0-9]+\.[0-9]+\.[0-9]+(?:_(?:a|alpha|b|beta|d|dev|rc|pl)_[0-9]+)?)\.tar$/i.test versionFile
-									logger.log "notice", "Skipping #{packageFolder}/#{versionFile}, as it does not match a valid version number"
-									versionsCallback null
-									return
-
 								versionFile = packageFolder + '/' + versionFile
 								logger.log "debug", "Parsing #{versionFile}"
 								fs.stat versionFile, (err, versionFileStat) ->


updating 'database'

updating 'database'

--- app.coffee
+++ app.coffee
@@ -41,34 +41,15 @@
 app.use express.static(path.join(__dirname, "/public"))

+BASE_URL = "http://rodeo-releases.s3.amazonaws.com"
 rodeoVersions = [
   {
-    version: "1.0.3",
+    version: "1.1.0",
     pub_date: "2015-11-15T12:29:53+01:00",
     urls: {
       "linux-32"    : "linux-32.zip"
       "linux-64"    : "linux-64.zip"
-      "darwin_x64"  : "http://rodeo-releases.s3.amazonaws.com/1.0.3/Rodeo.zip"
-      "windows"     : "windows.zip"
-    }
-  },
-  {
-    version: "1.0.2",
-    pub_date: "2015-11-15T12:29:53+01:00",
-    urls: {
-      "linux-32"    : "linux-32.zip"
-      "linux-64"    : "linux-64.zip"
-      "darwin_x64"  : "http://rodeo-releases.s3.amazonaws.com/1.0.2/Rodeo.zip"
-      "windows"     : "windows.zip"
-    }
-  },
-  {
-    version: "1.0.1" ,
-    pub_date: "2015-11-14T12:29:53+01:00",
-    urls: {
-      "linux-32"    : "linux-32.zip"
-      "linux-64"    : "linux-64.zip"
-      "darwin_x64"  : "http://rodeo-releases.s3.amazonaws.com/1.0.2/Rodeo.zip"
+      "darwin_x64"  : "#{BASE_URL}/1.1.0/Rodeo-v1.1.0-darwin_64.zip"
       "windows"     : "windows.zip"
     }
   }


"Corrected debug statement that somehow vanished"

--- app.coffee
+++ app.coffee
@@ -45,4 +45,4 @@

 if require.main == module
   app.listen process.env.PORT || 8080
-  conso+  console.log "Listening on port %d", process.env.PORT || 8080


port 80 for no.de

port 80 for no.de

--- app.coffee
+++ app.coffee
@@ -16,8 +16,8 @@
     templateDir: 'views'
     connect.logger()
 )
-#server.listen(80)
-server.listen(8888)
+server.listen(80)
+#server.listen(8888)

 console.log 'listening...'


use OutputCacheManager to construct static path to files

--- app.coffee
+++ app.coffee
@@ -12,6 +12,7 @@
 Metrics.open_sockets.monitor(logger)

 ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager"
+OutputCacheManager = require "./app/js/OutputCacheManager"

 require("./app/js/db").sync()

@@ -68,8 +69,8 @@
 		res.set("Content-Type", "text/plain")

 app.get "/project/:project_id/output/*", require("./app/js/SymlinkCheckerMiddlewear"), (req, res, next) ->
-	if req.query?.build? && req.query.build.match(/^[0-9]+$/)
-		req.url = "/#{req.params.project_id}/.cache/clsi/#{req.query.build}/#{req.params[0]}"
+	if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX)
+		req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.query.build) + "/#{req.params[0]}"
 	else
 		req.url = "/#{req.params.project_id}/#{req.params[0]}"
 	staticServer(req, res, next)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -38,7 +38,7 @@

   equals engine.closest(".enemy", player.position()), enemy1
   equals engine.closest(".enemy", player2.position()), enemy2
-  <l
+

   @param {String} selector
   @param {Point} position


adding event subscription

adding event subscription

--- src/event-announcer.coffee
+++ src/event-announcer.coffee
@@ -30,12 +30,15 @@
     for listener, eventMatches of subs
       for eventMatch in eventMatches
         if eventName.lastIndexOf eventName != -1
-          robot.send listener, message
+          robot.send listener, "#{eventName}: #{message}"

   getListener = (msg) ->
     msg.message.user.reply_to || msg.message.user.room

-  robot.respond /publish (\S+) (.*)/i, (msg)->
+  robot.on "ea-event", (event) ->
+    publishEvent event.name, event.message
+
+  robot.respond /publish (\S+) (.*)/i, (msg) ->
     eventName = msg.match[1]
     message = msg.match[2]
     publishEvent eventName, message


Fixed #22 - Request header values are sent lowercased by mistake

--- src/flatten-headers.coffee
+++ src/flatten-headers.coffee
@@ -2,7 +2,7 @@
   flatHeaders = {}
   # flatten headers object from blueprint structure
   for name, values of blueprintHeaders
-    flatHeaders[name] = values['value'].toLowerCase()
+    flatHeaders[name] = values['value']
   return flatHeaders

 module.exports = flattenHeaders


Add ecosystem comments to book object

Add ecosystem comments to book object

--- src/flux/ecosystems.coffee
+++ src/flux/ecosystems.coffee
@@ -34,7 +34,7 @@

     allBooks: ->
       _.map @_ecosystems, (ecosystem) ->
-        _.extend( _.first(ecosystem.books), {ecosystemId: "#{ecosystem.id}"} )
+        _.extend( _.first(ecosystem.books), {ecosystemId: "#{ecosystem.id}", ecosystemComments: ecosystem.comments} )

     first: ->
       _.first @_ecosystems


update code

update code

--- bot.coffee
+++ bot.coffee
@@ -4,7 +4,7 @@

 AUTH = process.env.AUTH
 ROOM = "mashupfm";
-bot = new PlugAPI(AUTH, "p9R*");
+bot = new PlugAPI(AUTH, "4w@fWs$");

 songLengthLimit = 8
 songLengthLimitSeconds = songLengthLimit * 60


Add support for lowdash templates

Add support for lowdash templates

--- src/marbles/view.js.coffee
+++ src/marbles/view.js.coffee
@@ -1,5 +1,31 @@
 Marbles.Views = {}
 Marbles.View = class View
+  class @Template
+    @find: (template_path) ->
+      unless Marbles.View.templates ?= window.HoganTemplates
+        Marbles.throwAsync new Error("Marbles.View.templates is not defined. Expected an object conatining compiled Hogan (mustache) or Lo-Dash templates.")
+        return null
+
+      template = Marbles.View.templates[template_path]
+      unless template
+        Marbles.throwAsync new Error("Marbles.View.templates[#{template_path}] is not defined.")
+        return null
+
+      if (typeof template.render) is 'function'
+        # Hogan
+        template
+      else
+        if (typeof template) is 'function'
+          # Lowdash
+          new Marbles.View.Template(template)
+        else
+          Marbles.throwAsync new Error("Marbles.View.templates[#{template_path}] does not have a render method and is not a function. Expected either a compiled Hogan or Lo-Dash template.")
+          null
+
+    constructor: (@compiled_template) ->
+    render: (context, partials = {}) =>
+      @compiled_template(_.extend({ partials: partials }, context))
+
   @instances: {
     all: {}
   }
@@ -9,13 +35,8 @@
   @find: (cid) ->
     @instances.all[cid]

-  # Marbles assumes you have pre-compiled hogan templates
-  # TODO: Should be able to use any templating library
   @getTemplate: (template_path) ->
-    return Marbles.throwAsync new Error("Marbles.View.templates is not defined. Expected an object conatining compiled hogan (mustache) templates.") unless (@templates ?= window.HoganTemplates)
-    template = @templates[template_path]
-    return Marbles.throwAsync new Error("Marbles.View.templates[#{template_path}] is not defined.") unless template
-    template
+    Marbles.View.Template.find(template_path)

   @detach: (cid) ->
     delete @instances.all[cid]


quote pull request notification's mentioned line too.

--- scripts/github-notify.coffee
+++ scripts/github-notify.coffee
@@ -145,7 +145,7 @@
       mentioned = extract_mentions(robot, data.pull_request.body)

       if mentioned.length > 0
-        mentioned_line = "\nMentioned: #{mentioned.join(", ")}"
+        mentioned_line = "\n> Mentioned: #{mentioned.join(", ")}"
       else
         mentioned_line = ''


Fix bug

Fix bug

--- index.cjsx
+++ index.cjsx
@@ -5,7 +5,7 @@
 filterNames = ["空", "编成任务", "出击任务", "演习任务", "远征任务", "补给/入渠任务", "工厂任务", "改装任务", "结婚任务", "日常任务", "周常任务", "月常任务"]
 categoryNames = ["", "编成", "出击", "演习", "远征", "补给/入渠", "工厂", "改装"]
 typeNames = ["", "单次任务", "每日任务", "每周任务", "3/7/0日任务", "2/8日任务", "每月任务"]
-typeFreqs = [0, 1, 4, 3, 4, 4, 2]
+typeFreqs = [0, 1, 5, 3, 4, 4, 2]

 module.exports =
   name: "quest-info"
@@ -14,7 +14,7 @@
   description: "任务信息查询 & 任务面板强化"
   author: "马里酱"
   link: "https://github.com/malichan"
-  version: "1.1.3"
+  version: "1.1.4"
   reactClass: React.createClass
     getInitialState: ->
       fs = require "fs-extra"


add taisen support

add taisen support

--- index.cjsx
+++ index.cjsx
@@ -344,6 +344,12 @@
     for airBase in body.api_air_base_attack
       if airBase.api_stage3?.api_edam?
         SupportFire enemyFleet, airBase.api_stage3.api_edam
+  # Opening taisen
+  if body.api_opening_taisen?
+    if combinedFlag > 0
+      Shelling escortFleet, enemyFleet, body.api_opening_taisen
+    else
+      Shelling mainFleet, enemyFleet, body.api_opening_taisen
   # Opening battle
   if body.api_opening_atack?
     if combinedFlag > 0


add id to Tabs

add id to Tabs

it is required in the new Tabs API

--- index.cjsx
+++ index.cjsx
@@ -399,7 +399,7 @@
         <Grid>
           <Row>
             <Col xs={12}>
-              <Tabs defaultActiveKey={1} animation={false} bsStyle='pills' className='areaTabs'>
+              <Tabs defaultActiveKey={1} animation={false} bsStyle='pills' className='areaTabs' id='areaTabs'>
                 {
                   {$mapareas, $missions} = window
                   if $mapareas?


Translate enemy ships

Translate enemy ships

--- index.cjsx
+++ index.cjsx
@@ -111,9 +111,9 @@
   enemyHp.max = Object.clone body.api_maxhps.slice(7, 13)
   for shipId, i in enemyInfo.lv
     continue if shipId == -1
-    enemyInfo.name[i] = $ships[shipId].api_name
+    enemyInfo.name[i] = window.i18n.resources.__ $ships[shipId].api_name
     if $ships[shipId].api_yomi != '-' && !isPractice
-      enemyInfo.name[i] = enemyInfo.name[i] + $ships[shipId].api_yomi
+      enemyInfo.name[i] = window.i18n.resources.__(enemyInfo.name[i]) + $ships[shipId].api_yomi
   enemyInfo.lv = Object.clone body.api_ship_lv.slice(1, 7)

 getResult = (sortieHp, enemyHp, combinedHp, leastHp, mvpPos) ->


Fix bug - routes are now paths not route names

--- shared/accountButtons.coffee
+++ shared/accountButtons.coffee
@@ -1,12 +1,11 @@
-Handlebars.registerHelper(
+Handlebars.registerHelper
   "accountButtons", ->
     return new Handlebars.SafeString(Template.entryAccountButtons())
-  )

 Template.entryAccountButtons.helpers
   profileUrl: ->
     return false unless AccountsEntry.settings.profileRoute
-    Router.path(AccountsEntry.settings.profileRoute)
+    AccountsEntry.settings.profileRoute

   wrapLinks: ->
     AccountsEntry.settings.wrapLinks


Added supported for undocumented MVMAX response to inform about the maximum volume

--- telnet-app-protocol.coffee
+++ telnet-app-protocol.coffee
@@ -10,6 +10,7 @@
   commands =
     POWER: /^(PW)([A-Z]+)/
     VOLUME: /^(MV)([0-9]+)/
+    MAXVOLUME: /^(MVMAX)[ ]{0,1}([0-9]+)/
     MAINMUTE: /^(MU)([A-Z]+)/
     Z2MUTE: /^(Z2MU)([A-Z]+)/
     Z3MUTE: /^(Z3MU)([A-Z]+)/


Updated Simulate('change') snipper

Updated Simulate('change') snipper

--- snippets/simulateChange.cson
+++ snippets/simulateChange.cson
@@ -1,5 +1,5 @@
 ".source.js, .source.jsx, .source.ts":
-  "simulate("change")":
+  "simulate('change')":
     "prefix": "simChange"
     "body": """
       ${1:input}.simulate("change", {


(test) fixed usage spec

(test) fixed usage spec

--- spec/core/usages.spec.coffee
+++ spec/core/usages.spec.coffee
@@ -17,7 +17,7 @@

       runs =>
         expect( @session.stdout ).toMatch("xtended usage information")
-        expect( @session.stdout ).toMatch("--no-connect-screen")
+        expect( @session.stdout ).toMatch("--no-connect")

     it "has emulate", =>
       expect( @session.stdout ).toMatch("steroids emulate")


Add test coverage for copying auto{Width,Height}

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -92,6 +92,7 @@

   describe ".copy()", ->
     it "returns a different editor with the same initial state", ->
+      editor.update({autoHeight: false, autoWidth: true})
       editor.setSelectedBufferRange([[1, 2], [3, 4]])
       editor.addSelectionForBufferRange([[5, 6], [7, 8]], reversed: true)
       editor.firstVisibleScreenRow = 5
@@ -106,6 +107,8 @@
       expect(editor2.getFirstVisibleScreenRow()).toBe 5
       expect(editor2.getFirstVisibleScreenColumn()).toBe 5
       expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
+      expect(editor2.getAutoWidth()).toBeTruthy()
+      expect(editor2.getAutoHeight()).toBeFalsy()

       # editor2 can now diverge from its origin edit session
       editor2.getLastSelection().setBufferRange([[2, 1], [4, 3]])


:lipstick:

:lipstick:

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -2477,25 +2477,23 @@
           expect(editor.getCursorScreenPosition()).toEqual [0, editor.getTabLength() * 2]

     describe "clipboard operations", ->
-      beforeEach ->
-        editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
-
       describe ".cutSelectedText()", ->
         it "removes the selected text from the buffer and places it on the clipboard", ->
+          editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
           editor.cutSelectedText()
           expect(buffer.lineForRow(0)).toBe "var  = function () {"
           expect(buffer.lineForRow(1)).toBe "  var  = function(items) {"
-
           expect(clipboard.readText()).toBe 'quicksort\nsort'

         describe "when no text is selected", ->
           beforeEach ->
-            editor.setSelectedBufferRanges([[1, 0], [1, 0]])
-            editor.addCursorAtBufferPosition([5, 0])
+            editor.setSelectedBufferRanges([
+              [[0, 0], [0, 0]],
+              [[5, 0], [5, 0]],
+            ])

           it "cuts the lines on which there are cursors", ->
             editor.cutSelectedText()
-
             expect(buffer.getLineCount()).toBe(11)
             expect(buffer.lineForRow(1)).toBe("    if (items.length <= 1) return items;")
             expect(buffer.lineForRow(4)).toBe("      current < pivot ? left.push(current) : right.push(current);")
@@ -2578,6 +2576,7 @@
           textEditor.cutSelectedText()

         it "pastes text into the buffer", ->
+          editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
           atom.clipboard.write('first')
           editor.pasteText()
           expect(editor.lineTextForBufferRow(0)).toBe "var first = function () {"
@@ -2657,7 +2656,8 @@
               waitsForPromise ->
                 atom.packages.activatePackage('language-coffee-script')
               waitsForPromise ->
-                atom.project.open('coffee.coffee', autoIndent: false).then (o) -> coffeeEditor = o
+                atom.project.open('coffee.coffee', autoIndent: false).then (o) ->
+                  coffeeEditor = o

               runs ->
                 atom.config.set('.source.js', 'editor.normalizeIndentOnPaste', true)
@@ -2703,6 +2703,9 @@
             expect(editor.lineTextForBufferRow(7)).toBe "  }"

         describe 'when the clipboard has many selections', ->
+          beforeEach ->
+            editor.setSelectedBufferRanges([[[0, 4], [0, 13]], [[1, 6], [1, 10]]])
+
           it "pastes each selection separately into the buffer", ->
             atom.clipboard.write('first\nsecond', {selections: ['first', 'second'] })
             editor.pasteText()
@@ -2711,6 +2714,7 @@

           describe 'and the selections count does not match', ->
             it "pastes the whole text into the buffer", ->
+              atom.clipboard.write('first\nsecond', {selections: ['first', 'second'] })
               atom.config.set("editor.autoIndentOnPaste", false)
               atom.clipboard.write('first\nsecond\nthird', {selections: ['first', 'second', 'third'] })
               editor.pasteText()


:sunflower: spec for moveLineDown

:sunflower: spec for moveLineDown

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -1929,28 +1929,28 @@

           describe "when the preceding row consists of folded code", ->
             it "moves the line above the folded row and preseveres the correct folds", ->
-                expect(editor.lineTextForBufferRow(8)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
-                expect(editor.lineTextForBufferRow(9)).toBe "  };"
-
-                editor.createFold(4, 7)
-
-                expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(8)).toBeFalsy()
-
-                editor.setSelectedBufferRange([[8, 0], [8,4]])
-                editor.moveLineUp()
-
-                expect(editor.getSelectedBufferRange()).toEqual [[4,0], [4, 4]]
-                expect(editor.lineTextForBufferRow(4)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
-                expect(editor.lineTextForBufferRow(5)).toBe "    while(items.length > 0) {"
-                expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(8)).toBeTruthy()
-                expect(editor.isFoldedAtBufferRow(9)).toBeFalsy()
+              expect(editor.lineTextForBufferRow(8)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
+              expect(editor.lineTextForBufferRow(9)).toBe "  };"
+
+              editor.createFold(4, 7)
+
+              expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(8)).toBeFalsy()
+
+              editor.setSelectedBufferRange([[8, 0], [8, 4]])
+              editor.moveLineUp()
+
+              expect(editor.getSelectedBufferRange()).toEqual [[4, 0], [4, 4]]
+              expect(editor.lineTextForBufferRow(4)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
+              expect(editor.lineTextForBufferRow(5)).toBe "    while(items.length > 0) {"
+              expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(8)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(9)).toBeFalsy()

         describe "when the selection spans multiple lines", ->
           it "moves the lines spanned by the selection to the preceding row", ->
@@ -1983,7 +1983,7 @@
         describe "when there are multiple selections and the preceeding row is a folded row", ->
           it "moves the lines spanned by the selection to the preceeding row, but preserves the folded code", ->
             expect(editor.lineTextForBufferRow(8)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
-            expect(editor.lineTextForBufferRow(9)).toBe "  };"
+            expect(editor.lineTextForBufferRow(9)).toBe "  };"

             editor.createFold(4, 7)
             expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
@@ -1992,12 +1992,12 @@
             expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
             expect(editor.isFoldedAtBufferRow(8)).toBeFalsy()

-            editor.setSelectedBufferRange([[8, 0], [9,2]])
+            editor.setSelectedBufferRange([[8, 0], [9, 2]])
             editor.moveLineUp()

-            expect(editor.getSelectedBufferRange()).toEqual [[4,0], [5, 2]]
+            expect(editor.getSelectedBufferRange()).toEqual [[4, 0], [5, 2]]
             expect(editor.lineTextForBufferRow(4)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
-            expect(editor.lineTextForBufferRow(5)).toBe "  };"
+            expect(editor.lineTextForBufferRow(5)).toBe "  };"
             expect(editor.lineTextForBufferRow(6)).toBe "    while(items.length > 0) {"
             expect(editor.isFoldedAtBufferRow(5)).toBeFalsy()
             expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
@@ -2070,35 +2070,163 @@
             expect(editor.getSelectedBufferRanges()).toEqual [[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[13, 0], [13, 0]]]

     describe ".moveLineDown", ->
-      describe "when there is only one line selected", ->
-        it "moves the line down by one row", ->
-          editor.setSelectedBufferRange([[3, 2], [3, 9]])
-          expect(editor.getSelectedBufferRange()).toEqual [[3, 2], [3, 9]]
-
-          editor.moveLineDown()
-
-          expect(editor.getSelectedBufferRange()).toEqual [[4, 2], [4, 9]]
-
-      describe "when there is multiple selections", ->
-        it "moves the selected lines down by one row", ->
-          editor.setSelectedBufferRanges([[[1, 2], [1, 9]], [[3, 2], [3, 9]], [[5, 2], [5, 9]]])
-
-          expect(editor.getSelectedBufferRanges()).toEqual [[[1, 2], [1, 9]], [[3, 2], [3, 9]], [[5, 2], [5, 9]]]
-
-          editor.moveLineDown()
-
-          expect(editor.getSelectedBufferRanges()).toEqual [[[6, 2], [6, 9]], [[4, 2], [4, 9]], [[2, 2], [2, 9]]]
-
-        describe "when there is multiple lines selected and moved downward until the bottom-most line is at the last row", ->
-          it "moves all the lines downward until the bottom-most is at bottom row, then no lines are moved downward", ->
-            editor.setSelectedBufferRanges([[[7, 2], [7, 5]], [[8, 2], [8, 9]], [[11, 2], [11, 5]]])
-
-            expect(editor.getSelectedBufferRanges()).toEqual [[[7, 2], [7, 5]], [[8, 2], [8, 9]], [[11, 2], [11, 5]]]
-
+      describe "when there is a single selection", ->
+        describe "when the selection spans a single line", ->
+          describe "when there is no fold in the following row", ->
+            it "moves the line to the following row", ->
+              expect(editor.lineTextForBufferRow(2)).toBe "    if (items.length <= 1) return items;"
+              expect(editor.lineTextForBufferRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+
+              editor.setSelectedBufferRange([[2, 2], [2, 9]])
+              editor.moveLineDown()
+
+              expect(editor.getSelectedBufferRange()).toEqual [[3, 2], [3, 9]]
+              expect(editor.lineTextForBufferRow(2)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+              expect(editor.lineTextForBufferRow(3)).toBe "    if (items.length <= 1) return items;"
+
+          describe "when the following row is a folded row", ->
+            it "moves the line below the folded row and preserves the fold", ->
+              expect(editor.lineTextForBufferRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+              expect(editor.lineTextForBufferRow(4)).toBe "    while(items.length > 0) {"
+
+              editor.createFold(4, 7)
+
+              expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(8)).toBeFalsy()
+
+              editor.setSelectedBufferRange([[3, 0], [3, 4]])
+              editor.moveLineDown()
+
+              expect(editor.getSelectedBufferRange()).toEqual [[7, 0], [7, 4]]
+              expect(editor.lineTextForBufferRow(3)).toBe "    while(items.length > 0) {"
+              expect(editor.isFoldedAtBufferRow(3)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(7)).toBeFalsy()
+
+
+              expect(editor.lineTextForBufferRow(7)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+
+        describe "when the slecetion spans multiple lines", ->
+          it "moves the lines spanned by the selection to the following row", ->
+            expect(editor.lineTextForBufferRow(2)).toBe "    if (items.length <= 1) return items;"
+            expect(editor.lineTextForBufferRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+            expect(editor.lineTextForBufferRow(4)).toBe "    while(items.length > 0) {"
+
+            editor.setSelectedBufferRange([[2, 2], [3, 9]])
             editor.moveLineDown()
+
+            expect(editor.getSelectedBufferRange()).toEqual [[3, 2], [4, 9]]
+            expect(editor.lineTextForBufferRow(2)).toBe "    while(items.length > 0) {"
+            expect(editor.lineTextForBufferRow(3)).toBe "    if (items.length <= 1) return items;"
+            expect(editor.lineTextForBufferRow(4)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+
+        describe "when the selection spans multiple lines, but ends at column 0", ->
+          it "does not move the last line of the selection", ->
+            expect(editor.lineTextForBufferRow(2)).toBe "    if (items.length <= 1) return items;"
+            expect(editor.lineTextForBufferRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+            expect(editor.lineTextForBufferRow(4)).toBe "    while(items.length > 0) {"
+
+            editor.setSelectedBufferRange([[2, 2], [3, 0]])
             editor.moveLineDown()

-            expect(editor.getSelectedBufferRanges()).toEqual [[[12, 2], [12, 5]], [[9, 2], [9, 9]], [[8, 2], [8, 5]]]
+            expect(editor.getSelectedBufferRange()).toEqual [[3, 2], [4, 0]]
+            expect(editor.lineTextForBufferRow(2)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+            expect(editor.lineTextForBufferRow(3)).toBe "    if (items.length <= 1) return items;"
+            expect(editor.lineTextForBufferRow(4)).toBe "    while(items.length > 0) {"
+
+        describe "when there are multiple selections and the following row is a folded row", ->
+          it "moves the lines spanned by the selection to the following row, but preserves the folded code", ->
+            expect(editor.lineTextForBufferRow(2)).toBe "    if (items.length <= 1) return items;"
+            expect(editor.lineTextForBufferRow(3)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+
+            editor.createFold(4, 7)
+            expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(8)).toBeFalsy()
+
+            editor.setSelectedBufferRange([[2, 0], [3, 2]])
+            editor.moveLineDown()
+
+            expect(editor.getSelectedBufferRange()).toEqual [[6, 0], [7, 2]]
+            expect(editor.lineTextForBufferRow(2)).toBe "    while(items.length > 0) {"
+            expect(editor.isFoldedAtBufferRow(1)).toBeFalsy()
+            expect(editor.isFoldedAtBufferRow(2)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(3)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+            expect(editor.isFoldedAtBufferRow(6)).toBeFalsy()
+            expect(editor.lineTextForBufferRow(6)).toBe "    if (items.length <= 1) return items;"
+
+      describe "when there are multiple selections", ->
+        describe "when all the selections span different lines", ->
+          describe "when there is no folds", ->
+            it "moves all lines that are spanned by a selection to the following row", ->
+              editor.setSelectedBufferRanges([[[1, 2], [1, 9]], [[3, 2], [3, 9]], [[5, 2], [5, 9]]])
+              editor.moveLineDown()
+
+              expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [2, 9]], [[4, 2], [4, 9]], [[6, 2], [6, 9]]]
+              expect(editor.lineTextForBufferRow(1)).toBe "    if (items.length <= 1) return items;"
+              expect(editor.lineTextForBufferRow(2)).toBe "  var sort = function(items) {"
+              expect(editor.lineTextForBufferRow(3)).toBe "    while(items.length > 0) {"
+              expect(editor.lineTextForBufferRow(4)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+              expect(editor.lineTextForBufferRow(5)).toBe "      current < pivot ? left.push(current) : right.push(current);"
+              expect(editor.lineTextForBufferRow(6)).toBe "      current = items.shift();"
+
+          describe "when there is a fold", ->
+            it "moves all lines that spanned by a selection to preceding row, preserving all folds", ->
+              editor.createFold(4, 7)
+
+              expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(7)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(8)).toBeFalsy()
+
+              editor.setSelectedBufferRanges([[[1, 2], [1, 6]], [[3, 0], [3, 4]], [[8, 0], [8, 3]]])
+              editor.moveLineDown()
+
+              expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [2, 6]], [[7, 0], [7, 4]], [[9, 0], [9, 3]]]
+              expect(editor.lineTextForBufferRow(2)).toBe "  var sort = function(items) {"
+              expect(editor.isFoldedAtBufferRow(3)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(5)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(6)).toBeTruthy()
+              expect(editor.isFoldedAtBufferRow(7)).toBeFalsy()
+              expect(editor.lineTextForBufferRow(7)).toBe "    var pivot = items.shift(), current, left = [], right = [];"
+              expect(editor.lineTextForBufferRow(9)).toBe "    return sort(left).concat(pivot).concat(sort(right));"
+
+        describe "when some of the selections span the same lines", ->
+          it "moves lines that contain multiple selections correctly", ->
+            editor.setSelectedBufferRanges([[[3, 2], [3, 9]], [[3, 12], [3, 13]]])
+            editor.moveLineDown()
+
+            expect(editor.getSelectedBufferRanges()).toEqual [[[4, 2], [4, 9]], [[4, 12], [4, 13]]]
+            expect(editor.lineTextForBufferRow(3)).toBe "    while(items.length > 0) {"
+
+        describe "when one of the selections spans the last buffer row ", ->
+          it "doesn't move any lines", ->
+            editor.setSelectedBufferRanges([[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[12, 2], [12, 2]]])
+
+            editor.moveLineDown()
+
+            expect(editor.getSelectedBufferRanges()).toEqual [[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[12, 2], [12, 2]]]
+            expect(buffer.isModified()).toBe false
+
+        describe "when one of the selections spans the last line, and it is empty", ->
+          it "doesn't move any lines, since the last line can't move", ->
+            buffer.append('\n')
+            editor.setSelectedBufferRanges([[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[13, 0], [13, 0]]])
+
+            editor.moveLineDown()
+
+            expect(editor.getSelectedBufferRanges()).toEqual [[[0, 2], [1, 9]], [[2, 2], [2, 9]], [[13, 0], [13, 0]]]

     describe ".insertText(text)", ->
       describe "when there is a single selection", ->


clean up variable declarations in spec with beforeEach

--- spec/time_parser_spec.coffee
+++ spec/time_parser_spec.coffee
@@ -1,29 +1,25 @@
 describe 'TimeParser', ->
   describe 'from_minutes', ->
-    it 'should do less than an hour', ->
-      parser = new TimeParser()
-      expect(parser.from_minutes(40)).toEqual "0:40"
+    beforeEach ->
+      @parser = new TimeParser()
+
+    it 'should do less than an hour', ->
+      expect(@parser.from_minutes(40)).toEqual "0:40"

     it 'should zero pad less than 10 minutes', ->
-      parser = new TimeParser()
-      expect(parser.from_minutes(7)).toEqual "0:07"
+      expect(@parser.from_minutes(7)).toEqual "0:07"

     it 'should handle more than an hour', ->
-      parser = new TimeParser()
-      expect(parser.from_minutes(90)).toEqual "1:30"
+      expect(@parser.from_minutes(90)).toEqual "1:30"

     it 'should zero page between an hour and an hour ten', ->
-      parser = new TimeParser()
-      expect(parser.from_minutes(124)).toEqual "2:04"
+      expect(@parser.from_minutes(124)).toEqual "2:04"

     it 'should handle zero', ->
-      parser = new TimeParser()
-      expect(parser.from_minutes(0)).toEqual "0:00"
+      expect(@parser.from_minutes(0)).toEqual "0:00"

     it 'should go to zero on negative numbers', ->
-      parser = new TimeParser()
-      expect(parser.from_minutes(-45)).toEqual "0:00"
+      expect(@parser.from_minutes(-45)).toEqual "0:00"

     it 'should round on decimals', ->
-      parser = new TimeParser()
-      expect(parser.from_minutes(45.2)).toEqual "0:45"+      expect(@parser.from_minutes(45.2)).toEqual "0:45"


:cool::sweat_smile: Updated in browser at strd6.github.io/editor

--- pixie.cson
+++ pixie.cson
@@ -1,4 +1,5 @@
-version: "0.1.1"
+version: "0.1.2"
+entryPoint: "haml-jr"
 remoteDependencies: [
   "https://cdnjs.cloudflare.com/ajax/libs/coffee-script/1.6.3/coffee-script.min.js"
 ]


doc: tweak eventNames param description

doc: tweak eventNames param description

--- src/app/event-emitter.coffee
+++ src/app/event-emitter.coffee
@@ -6,11 +6,10 @@
 module.exports =
   # Public: Associates an event name with a function to perform.
   #
-  # This is called endlessly, until the event is turned {.off}. The {.on} method
-  # calls an `eventName` only once.
+  # This is called endlessly, until the event is turned {.off}.
   #
-  # eventNames - A {String} name identifying one or more events
-  # handler - A {Function} that's executed when the event is triggered
+  # eventNames - A {String} containing one or more space-separated events.
+  # handler - A {Function} that's executed when the event is triggered.
   on: (eventNames, handler) ->
     for eventName in eventNames.split(/\s+/) when eventName isnt ''
       [eventName, namespace] = eventName.split('.')
@@ -58,7 +57,7 @@

   # Public: Stops executing handlers for a registered event.
   #
-  # eventNames - A {String} name identifying one or more events
+  # eventNames - A {String} containing one or more space-separated events.
   # handler - The {Function} to remove from the event. If not provided, all handlers are removed.
   off: (eventNames, handler) ->
     if eventNames


Remove console print

Remove console print

--- src/coffee/naivebayes.coffee
+++ src/coffee/naivebayes.coffee
@@ -29,7 +29,6 @@
            if probabilities[klass] > best_score
                best_score = probabilities[klass]
                best_klass = klass
-       #console.log(probabilities)
        if best_score > @threshold
            return best_klass
        else


feat(badges): take branch as optional parameter for badges

--- src/components/badges.coffee
+++ src/components/badges.coffee
@@ -14,12 +14,14 @@
   # These can be changed by setting only a subset as options.badges
   # @param (options) [Object] An optional set of options
   # @option options badges [Array<String>] Badges from badges.json to use
+  # @option options branch [String] The branch to use for the documentation ['master']
   constructor: (@options) ->
     @options ?= {}
     @options.badges ?= [
-      'npm-version', 'travis', 'coveralls', 'dependencies',
+      'npm-version', 'minzipped', 'travis', 'coveralls', 'dependencies',
       'devDependencies', 'gitter'
     ]
+    @options.branch ?= @options.git?.branch ? 'master'

   # Create data for badges
   # @param pkg [Object] package.json data
@@ -30,8 +32,9 @@
       logger.debug " Not adding badges due to missing git repo info"
       return Promise.resolve []
     logger.debug " Adding badges " + @options.badges
+    data = Object.assign { branch: @options.branch }, pkg

     return Promise.resolve @options.badges.map (badgeName) ->
-      name: mustache.render badgeData[badgeName].name, pkg
-      img: mustache.render badgeData[badgeName].img, pkg
-      url: mustache.render badgeData[badgeName].url, pkg
+      name: mustache.render badgeData[badgeName].name, data
+      img: mustache.render badgeData[badgeName].img, data
+      url: mustache.render badgeData[badgeName].url, data


remove a bit of logging

remove a bit of logging

--- kapuut/kapuut.coffee
+++ kapuut/kapuut.coffee
@@ -58,7 +58,6 @@

     Template.view.helpers
         correctIndicator: ->
-            console.log Template.currentData()
             correct = Template.parentData(1).correctAnswer is Template.parentData(1).answers.indexOf(Template.currentData())
             if correct
                 "plus"


Fix to ensure that module value is checked properly.

--- client/room/room.coffee
+++ client/room/room.coffee
@@ -141,7 +141,7 @@

     isPartOf = ''
     series_title = ''
-    if $('#module-id').val() isnt 'none__'
+    if $('#module-id').val() and $('#module-id').val() isnt 'none__'
       isPartOf = $('#module-id').val()
       series_title_with_code = $('#module-id option:selected').text()
       code = isPartOf.split '__'


Using HTML5 plugin with flash ass fallback

--- client/src/Sound.coffee
+++ client/src/Sound.coffee
@@ -30,9 +30,15 @@
       data: 1
       id: "user-yellow"
     ]
+
+    # Use HTML5 (and Flash as a fallback)
+    createjs.SoundJS.registerPlugins [createjs.HTMLAudioPlugin, createjs.FlashPlugin]
+
     @preloader = new createjs.PreloadJS();
     #Install SoundJS as a plugin, then PreloadJS will initialize it automatically.
     @preloader.installPlugin(createjs.SoundJS);
+
+

     #Available PreloadJS callbacks
     @preloader.onFileLoad = (event) ->


client api for disabling idle monitor

client api for disabling idle monitor

--- client/ts_client.coffee
+++ client/ts_client.coffee
@@ -19,11 +19,14 @@

 idleComp = null

-TurkServer.enableIdleMonitor = (threshold, idleOnBlur) ->
+TurkServer.disableIdleMonitor = () ->
   if idleComp?
     # If monitor is already started, stop it before trying new settings
     idleComp.stop()
     UserStatus.stopMonitor() if Deps.nonreactive -> UserStatus.isMonitoring()
+
+TurkServer.enableIdleMonitor = (threshold, idleOnBlur) ->
+  TurkServer.disableIdleMonitor()

   idleComp = Deps.autorun ->
     if TurkServer.inExperiment()


Add open, close, and toggle events to DrawerView.

--- src/drawer/DrawerView.coffee
+++ src/drawer/DrawerView.coffee
@@ -126,9 +126,10 @@
 ###
 module.exports = class DrawerView extends PaneView

-  constructor: (init, events) ->
-    defaults init, {tagName: TAG_NAME}
-    super arguments...
+  constructor: (init, events = []) ->
+    events = events.concat ['open', 'close', 'toggle']
+    init = defaults init, {tagName: TAG_NAME}
+    super init, events

     # Don't immediately show the view.
     @options.showOnRender = false
@@ -391,6 +392,7 @@
   close: =>
     if @canTransitionOut()
       @_setElementClasses 'closing'
+      @fire 'close'
       @contents.getRenderedDimensions().then ({width, height}) =>
         @root.style.left = "-#{width}px"
         @transitionOut().then => @_setElementClasses 'closed'
@@ -407,7 +409,7 @@
   open: =>
     if @canTransitionIn()
       @_setElementClasses 'opening'
-      #@_setupDrawer().then =>
+      @fire 'open'
       @transitionIn().then => @_setElementClasses 'open'
     else
       Promise.rejected()
@@ -436,4 +438,5 @@
   # @memberof stout-ui/drawer/DrawerView#
   ###
   toggle: =>
+    @fire 'toggle'
     if @visible then @close() else @open()


Fix duplicate Update method (#49)

Fix duplicate Update method (#49)

--- src/features/modifier.coffee
+++ src/features/modifier.coffee
@@ -22,7 +22,7 @@

       return @m.Request uri, 'GET', null, callback, error

-    Update: (product, modifier, callback, error) ->
+    Delete: (product, modifier, callback, error) ->

       return @m.Request 'products/'+product+'/modifiers/'+modifier, 'DELETE', null, callback, error


Add method for checking content analyst role

--- src/flux/current-user.coffee
+++ src/flux/current-user.coffee
@@ -121,6 +121,7 @@
     getCSRFToken: -> CSRF_Token
     getName: -> @_user.name
     isAdmin: -> @_user.is_admin
+    isContentAnalyst: -> @_user.is_content_analyst
     getProfileUrl: -> @_user.profile_url

     # Loads the store if it's not already loaded or loading


remove word count, add BadgeForth

remove word count, add BadgeForth

--- projects.wme03370.cson
+++ projects.wme03370.cson
@@ -1,12 +1,4 @@
 [
-  {
-    title: "LF Word Count"
-    group: "Atom"
-    paths: [
-      "~/Devel/atom/lloyd-flanagan-word-count"
-    ]
-    icon: "atom-icon"
-  }
   {
     title: "Atom Configuration"
     group: "Atom"
@@ -99,8 +91,8 @@
     title: "Badge2017"
     group: "HackRVA"
     paths: [
-      "/home/aflanagan/Devel/personal/hackrva/Harmony-Badge-2017/firmware"
-      "/home/aflanagan/Devel/personal/hackrva/microchip/harmony/v1_09"
+      "~/Devel/personal/hackrva/Harmony-Badge-2017/firmware"
+      "~/Devel/personal/hackrva/microchip/harmony/v1_09"
     ]
     icon: "mdi mdi-radioactive"
   }
@@ -126,7 +118,7 @@
     title: "Site Setup"
     group: "BH Media"
     paths: [
-      "/home/aflanagan/Devel/site_setup"
+      "~/Devel/site_setup"
     ]
     icon: "mdi mdi-beta"
   }
@@ -134,7 +126,7 @@
     title: "Nebhssports"
     group: "BH Media"
     paths: [
-      "/home/aflanagan/Devel/nebhssports"
+      "~/Devel/nebhssports"
     ]
     icon: "devicon-python-plain"
   }
@@ -142,7 +134,7 @@
     title: "Node Find"
     group: "Node"
     paths: [
-      "/home/aflanagan/Devel/personal/node_find"
+      "~/Devel/personal/node_find"
     ]
     icon: "icon-search"
   }
@@ -150,7 +142,8 @@
     title: "Api Helper"
     group: "BH Media"
     paths: [
-      "/home/aflanagan/Devel/realmatch/api_helper"
+      "~/Devel/realmatch/api_helper"
+      "~/Devel/realmatch/realmatch_code/docs"
     ]
     icon: "devicon-php-plain"
   }
@@ -158,7 +151,8 @@
     title: "Realmatch Code"
     group: "BH Media"
     paths: [
-      "/home/aflanagan/Devel/realmatch/realmatch_code"
+      "~/Devel/realmatch/realmatch_code"
+      "~/Devel/realmatch/api_helper"
     ]
     icon: "devicon-php-plain"
   }
@@ -166,7 +160,16 @@
     title: "Www"
     group: "BH Media"
     paths: [
-      "/home/aflanagan/Devel/www"
+      "~/Devel/www"
     ]
   }
+  {
+    title: "BadgeForth"
+    group: "HackRVA"
+    paths: [
+      "~/Devel/personal/hackrva/BadgeForth/firmware"
+      "~/Devel/personal/hackrva/microchip/harmony/v2_03b"
+    ]
+    icon: "mdi mdi-radioactive"
+  }
 ]


Flatten modules in output bundle.

Flatten modules in output bundle.

--- src/resolver.coffee
+++ src/resolver.coffee
@@ -84,10 +84,12 @@
     normalizedPath = normalizePath absolutePath, basePath
     relativePath   = normalizePath absolutePath, cwd
     requireAs      = normalizedPath.replace extension, ''
-                                   .replace /^node_modules\//, ''
                                    .replace /\/index$/, ''

-    unless /^node_modules/.test normalizedPath
+    # Strip off any leading node_modules paths (we flatten modules)
+    if (match = /(.*node_modules\/)([/._-\w])+$/.exec requireAs)
+      requireAs = requireAs.replace match[1], ''
+    else
       requireAs = './' + requireAs

     cache resolveFrom, requiredAs,


Make sure msg.http supports options

Make sure msg.http supports options

--- src/response.coffee
+++ src/response.coffee
@@ -86,7 +86,7 @@
   # send the request.
   #
   # Returns a ScopedClient instance.
-  http: (url) ->
-    @robot.http(url)
+  http: (url, options) ->
+    @robot.http(url, options)

 module.exports = Response


vm: make method `VirtualizationController#fetchDefaultVmName` group context scoped

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -159,8 +159,8 @@
         return callback null

       # If there is just one return it
-      if vmNames.length is 1
-        return callback @defaultVmName = vmNames.first
+      # if vmNames.length is 1
+      #   return callback @defaultVmName = vmNames.first

       # If current group is 'koding' ask to backend for the default one
       KD.remote.api.JVM.fetchDefaultVm (err, defaultVmName)=>
@@ -181,11 +181,11 @@
           return callback @defaultVmName = vm.alias

         # Check for personal VMs in Koding or Guests group
-        for vm in userVMs when vm.groupSlug in ['koding', 'guests']
-          return callback @defaultVmName = vm.alias
+        # for vm in userVMs when vm.groupSlug in ['koding', 'guests']
+        #   return callback @defaultVmName = vm.alias

         # Fallback to Koding VM if exists
-        return callback @defaultVmName = defaultVmName
+        return callback()

   createGroupVM:(type='user', planCode, callback=->)->
     vmCreateCallback = (err, vm)->


TerminalSession: registerKite closure scope is changed by fat arrow

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -276,7 +276,7 @@
     Promise.all vms.map @bound 'registerKite'

   registerKite: (vm, callback) ->
-    new Promise (resolve) ->
+    new Promise (resolve) =>
       alias = vm.hostnameAlias
       kite = @getKite vm, 'os'


stack-editor: show correct template name

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -57,7 +57,7 @@
     title   = stackTemplate?.title or generatedStackTemplateTitle
     content = stackTemplate?.template?.content

-    @createStackNameInput generateStackTemplateTitle
+    @createStackNameInput title

     @addSubView @tabView = new kd.TabView
       hideHandleCloseIcons : yes
@@ -229,6 +229,7 @@
       cssClass: 'template-title'
       autogrow: yes
       defaultValue: title or generatedStackTemplateTitle
+      placeholder: generatedStackTemplateTitle
       bind: 'keyup'
       keyup: (e) ->
         { changeTemplateTitle } = EnvironmentFlux.actions


typo: mult-dot => multi-dot

typo: mult-dot => multi-dot

--- register.coffee
+++ register.coffee
@@ -14,7 +14,7 @@
   for ext in CoffeeScript.FILE_EXTENSIONS
     require.extensions[ext] = loadFile

-  # Patch Node's module loader to be able to handle mult-dot extensions.
+  # Patch Node's module loader to be able to handle multi-dot extensions.
   # This is a horrible thing that should not be required. Perhaps, one day,
   # when a truly benevolent dictator comes to rule over the Republik of Node,
   # it won't be.


Fix case of require.

Fix case of require.

--- src/coffee/exporter.coffee
+++ src/coffee/exporter.coffee
@@ -1,7 +1,7 @@
 _ = require 'underscore'
 fs = require 'fs'
 Csv = require 'csv'
-Validator = require '../lib/Validator'
+Validator = require '../lib/validator'
 ExportMapping = require '../lib/exportmapping'
 Q = require 'q'
 prompt = require 'prompt'


API support for Drawing.useHref

API support for Drawing.useHref

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -723,7 +723,8 @@
     DynamicSymbol.resetAll()
     doc = domImplementation.createDocument SVGNS, 'svg'
     svg = doc.documentElement
-    svg.setAttribute 'xmlns:xlink', XLINKNS
+    svg.setAttribute 'xmlns:xlink', XLINKNS unless Drawing.useHref
+    href = if Drawing.useHref then 'href' else 'xlink:href'
     svg.setAttribute 'version', '1.1'
     #svg.appendChild defs = doc.createElementNS SVGNS, 'defs'
     ## <style> tags for CSS
@@ -801,7 +802,7 @@
         node.setAttribute 'height', height or use.getAttribute 'height'
         symbol.setAttribute 'viewBox', "0 0 #{width} #{height}"
         symbol.appendChild node
-      use.setAttribute 'xlink:href', '#' + inlineImages[attributes]
+      use.setAttribute href, '#' + inlineImages[attributes]
       false
     ## Lay out the symbols in the drawing via SVG <use>.
     viewBox = [0, 0, 0, 0]  ## initially x-min, y-min, x-max, y-max
@@ -821,7 +822,7 @@
         continue unless symbol?
         levels[symbol.zIndex] ?= []
         levels[symbol.zIndex].push use = doc.createElementNS SVGNS, 'use'
-        use.setAttribute 'xlink:href', '#' + symbol.id
+        use.setAttribute href, '#' + symbol.id
         use.setAttribute 'x', x
         use.setAttribute 'y', y
         scaleX = scaleY = 1


Use "?=" instead of "or=" for boolean option.

--- lib/core/view.coffee
+++ lib/core/view.coffee
@@ -85,7 +85,7 @@
     options.suffix            or= ""        # a String
     options.tooltip           or= null      # an Object of kdtooltip options
     options.lazyLoadThreshold  ?= no
-    options.droppable         or= no
+    options.droppable          ?= no

     # TO BE IMPLEMENTED
     options.resizable         or= null      # TBDL


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -59,7 +59,7 @@
   @param {String} selector
   @returns {Array} An array of the objects found
   ###
-  each: ()
+  each: (selector)

   find: (selector) ->
     results = []


Removing unused option

Removing unused option

--- src/extenders/index.coffee
+++ src/extenders/index.coffee
@@ -18,9 +18,6 @@

       # the message to use (defaults to the message from the thrown exception)
       message: undefined
-
-      # the message to use when there is an error and no message is available
-      defaultMessage: 'is invalid'
     }

     exRead: {


added feedback to git write

added feedback to git write

no feedback that something has been done is sad :°

--- lib/git-write.coffee
+++ lib/git-write.coffee
@@ -13,6 +13,8 @@
       cwd: dir
     stderr: (data) ->
       new StatusView(type: 'alert', message: data.toString())
+    exit: (data) ->
+      new StatusView(type: 'success', message: "added #{toStage}")
   })

 module.exports = gitWrite


Change GitWrite success message to show 'all files' or filepath

--- lib/git-write.coffee
+++ lib/git-write.coffee
@@ -14,7 +14,8 @@
     stderr: (data) ->
       new StatusView(type: 'alert', message: data.toString())
     exit: (data) ->
-      new StatusView(type: 'success', message: "Added #{toStage}")
+      file = if toStage is '.' then 'all files' else toStage
+      new StatusView(type: 'success', message: "Added #{file}")
   })

 module.exports = gitWrite


More code style fixes for test.coffee

More code style fixes for test.coffee

--- test.coffee
+++ test.coffee
@@ -35,7 +35,7 @@
       .catch onRejected

   reqMock = do ->
-    req = new IncomingMessage new Socket({readeble: true})
+    req = new IncomingMessage new Socket readeble: on
     req.headers =
       "content-type": "
         multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
@@ -74,7 +74,7 @@

   t.true isPlainObject(body), "Request should always returns a plain object"

-test "Should return files and fields in to others objects", (t) ->
+test "Should return files and fields in two other objects", (t) ->
   {body} = await request t.context.serverMock on
     .post "/"
     .set "content-type", t.context.multipartHeaderMock
@@ -94,6 +94,7 @@

 test "Should rescue types", (t) ->
   t.plan 4
+
   {body: {
     nullValue, falseValue,
     trueValue, numberValue


Documentation

Documentation

--- src/object_extensions.coffee
+++ src/object_extensions.coffee
@@ -69,7 +69,7 @@
      c: 3
    }

-   Object.reverseMerge I, {
+   Object.extend I, {
      c: 6
      d: 4
    }


Listen for finish event for successful copy instead of close

--- src/fs_utils/common.coffee
+++ src/fs_utils/common.coffee
@@ -69,7 +69,7 @@
     output = input.pipe fs.createWriteStream destination
     input.on  'error', (err) -> fsStreamErrHandler err, 'input'
     output.on 'error', (err) -> fsStreamErrHandler err, 'output'
-    output.on 'close', ->
+    output.on 'finish', ->
       if --copyCounter < 1 and copyQueue.length
         setImmediate copyQueue.shift()
       callback()


Inline most one-property objects

Inline most one-property objects

Just newline them if they are themselves property values.

This will preserve the desired round-trip behavior with fewer unneeded
side-effects

--- lib/js2coffee.coffee
+++ lib/js2coffee.coffee
@@ -501,7 +501,10 @@
   # left is a `identifier`, right can be anything.

   'property_init': (n) ->
-    "#{@property_identifier n.left()}: #{@build n.right()}"
+    left = n.left()
+    right = n.right()
+    right.is_property_value = true
+    "#{@property_identifier left}: #{@build right}"

   # `object_init`
   # An object initializer.
@@ -510,6 +513,9 @@
   'object_init': (n, options={}) ->
     if n.children.length == 0
       "{}"
+
+    else if n.children.length == 1 and not n.is_property_value
+      @build n.children[0]

     else
       list = _.map n.children, (item) => @build item


Scroll to the bottom after a message is added

--- lib/ltconsole.coffee
+++ lib/ltconsole.coffee
@@ -84,5 +84,8 @@

     @messages.add message

+    # scroll to the bottom
+    @messages.updateScroll()
+
   clear: ->
     @messages.clear()


migration: enforce required options

migration: enforce required options

--- lib/migration.coffee
+++ lib/migration.coffee
@@ -24,10 +24,10 @@
     if use_srv.local
       srv_ctxs = srv_ctxs.filter (ctx) => ctx.config.host is @config.host
     if use_srv.single
-      throw Error if srv_ctxs.length > 1
+      throw Error "Service #{use_srv_key} is marked as unique" if srv_ctxs.length > 1
     srv_ctxs = srv_ctxs.map (ctx) =>
       options = null
-      throw Error 'No key' unless use_srv.key
+      throw Error "No key for sevice: #{use_srv_key}" unless use_srv.key
       for key in use_srv.key
         unless options
           unless ctx.config[key]
@@ -44,11 +44,15 @@
         hostname: ctx.config.shortname
         fqdn: ctx.config.host
       options: merge options, nikita_options, options
-    # console.log '>>', srv_ctxs
     if use_srv.local
       srv_ctxs = srv_ctxs[0]
     if use_srv.single
       srv_ctxs = srv_ctxs[0]
+    if use_srv.required
+      if use_srv.local or use_srv.single
+        throw Error "Service #{use_srv_key} is marked as required" unless srv_ctxs
+      else
+        throw Error "Service #{use_srv_key} is marked as required" unless srv_ctxs.length
     # Note, in real future world, we will accept empty array if a service is define but not defined anywhere
     srv_ctxs = null if srv_ctxs?.length is 0
     use[use_srv_key] = srv_ctxs