ipython2 support: change start server to start exactly once in home directory.

--- salvus/page/editor.coffee
+++ salvus/page/editor.coffee
@@ -3411,7 +3411,7 @@
 ipython_notebook_server = (opts) ->
     opts = defaults opts,
         project_id : required
-        path       : required   # directory from which the files are served
+        path       : '.'   # directory from which the files are served -- default to home directory of project
         cb         : required   # cb(err, server)

     I = new IPythonNotebookServer(opts.project_id, opts.path)
@@ -3771,10 +3771,9 @@
     initialize: (cb) =>
         async.series([
             (cb) =>
-                @status("Connecting to IPython server")
+                @status("Connecting to the IPython Notebook server")
                 ipython_notebook_server
                     project_id : @editor.project_id
-                    path       : @path
                     cb         : (err, server) =>
                         @server = server
                         cb(err)


Fixed balancing and removal when removal caused a balancing of a right node that was negative black

--- src/RedBlackTree.coffee
+++ src/RedBlackTree.coffee
@@ -52,6 +52,9 @@
   this.value   = new mugs.None()
   this

+mugs.RedBlackLeaf.prototype.copy = (properties) ->
+  new mugs.RedBlackLeaf(properties.color | this.color)
+
 mugs.RedBlackLeaf.prototype.isEmpty = () -> true
 mugs.RedBlackLeaf.prototype.containsKey = (key) -> false
 mugs.RedBlackLeaf.prototype.get = (key) -> new mugs.None()
@@ -69,6 +72,15 @@
   this.right      = right
   this.comparator = if (comparator) then  else mugs.RedBlackNode.prototype.standard_comparator
   this
+
+mugs.RedBlackNode.prototype.copy = (properties) ->
+  new mugs.RedBlackNode(
+    properties.color || this.color,
+    properties.left || this.left,
+    properties.key || this.key,
+    properties.value || this.value,
+    properties.right || this.right,
+    properties.comparator || this.comparator)

 ###*
   The standard compare function. It's used if the user doesn't
@@ -159,6 +171,7 @@
 ###
 mugs.RedBlackNode.prototype.inorderTraversal = (f) ->
   if !this.left.isEmpty() then this.left.inorderTraversal(f)
+  console.log("inorder on: " + this.value)
   f({key: this.key, value: this.value})
   if !this.right.isEmpty() then this.right.inorderTraversal(f)

@@ -219,7 +232,7 @@
       # Find the largest element in the left subtree, use that value for the new tree
       # and remove the other node
       max         = tree.left.maxKey()
-      value       = tree.get(max).get
+      value       = tree.left.get(max).get()
       newLeftTree = tree.left.remove(max)
       this.bubble( tree.color, newLeftTree, max, value, tree.right)
     else
@@ -243,13 +256,13 @@
   A black is subtracted from the children, and added to the parent:
 ###
 mugs.RedBlackNode.prototype.bubble = (color, left, key, value, right) ->
-  if ( left.color == mugs.RedBlack.DOUBLE_BLACK || right.color == mugs.RedBlack.DOUBLE_BLACK)
+  if ( left.color == mugs.RedBlack.DOUBLE_BLACK || right.color == mugs.RedBlack.DOUBLE_BLACK)
     this.balance(
       color.add(),
-      new mugs.RedBlackNode(left.color.subtract(), left.left, left.key, left.value, left.right),
+      left.copy( { color: left.color.subtract() } ),
       key,
       value,
-      new mugs.RedBlackNode(right.color.subtract(), right.left, right.key, right.value, right.right))
+      right.copy( { color: right.color.subtract() }))
   else
     new mugs.RedBlackNode(color, left, key, value, right)

@@ -267,7 +280,7 @@
   The solution is always the same: Rewrite the black-red-red path as a red node with
   two black children.

-  However to support removal two additional colors have been addded to the mix:
+  However to support removal two additional colors have been added to the mix:
   Negative-black and double black.

   The double black can occur instead of black in the four configurations above.
@@ -281,7 +294,7 @@

   The negative black node can occur in the following two configurations:

-  1. Double black followed by left negative black whith two black children
+  1. Double black followed by left negative black with two black children
   2. Double black followed by right negative black with two black children

   The solution is to transform it into a node with two black children and a left (or right
@@ -345,23 +358,36 @@
       new mugs.RedBlackNode(mugs.RedBlack.BLACK,right.left.right, key,value, right)
     )
   else if (rightIsNegativeBlack())
-    new mugs.RedBlackNode(
-      mugs.RedBlack.BLACK,
-      new mugs.RedBlackNode(
-        mugs.RedBlack.BLACK,
-        left,
+    ###
+      BEFORE        AFTER
+        z             w
+      1   x         z   x
+        w   y      1 2 3  y
+       2 3 4 5           4 5
+    ###
+    #w
+    new mugs.RedBlackNode(mugs.RedBlack.BLACK,
+      #z
+      new mugs.RedBlackNode(mugs.RedBlack.BLACK,
+        left, #1
+        key,
+        value,
+        right.left.left #2
+      ),
+       right.left.key,
+       right.left.value,
+      #x
+      new mugs.RedBlackNode(mugs.RedBlack.Black,
+        right.left.right, #3
         right.key,
         right.value,
-        right.left.left
-      ),
-      right.right.key,
-      right.right.value,
-      new mugs.RedBlackNode(
-        mugs.RedBlack.BLACK,
-        right.left.right,
-        key,
-        value,
-        this.balance( mugs.RedBlack.RED, right.right.left, right.left.key, right.left.value, right.right.right )
+        #y
+        this.balance( mugs.RedBlack.RED,
+          right.right.left, #4
+          right.right.key,
+          right.right.value,
+          right.right.right #5
+        )
       )
     )
   else


Fix #2 issue

Fix #2 issue

--- src/tumblr.coffee
+++ src/tumblr.coffee
@@ -64,8 +64,12 @@
   # Request API and call callback function with response
   request = (url, fn = ->) ->
     xhr {url}, (error, request, body) ->
-      body = JSON.parse body
-      err  = body.meta.msg if body.meta.status isnt 200
+      try
+        body = JSON.parse body
+        err  = body.meta.msg if body.meta.status isnt 200
+      catch error
+        err = "Invalid Response: #{error}";
+
       fn.call body, err, body.response

 ).call(Tumblr.prototype)


Corrected missing space typo

Corrected missing space typo

Added space to separate the words "set" and "extensions" in the "Files to clean" section

--- lib/config.coffee
+++ lib/config.coffee
@@ -98,7 +98,7 @@
   file_ext_to_clean:
     title: 'Files to clean'
     order: 13
-    description: 'All files under the LaTeX project root directory with the set\
+    description: 'All files under the LaTeX project root directory with the set \
                   extensions will be removed when cleaning LaTeX project. \
                   Multiple file extensions are joint with commas.'
     type: 'string'


Remove listeners after delayed publish or failure

--- src/writer.coffee
+++ src/writer.coffee
@@ -91,9 +91,24 @@

     # Call publish again once the Writer is ready.
     unless @ready
-      @on Writer.READY, (err) =>
-        return callback err if err
+      ready = =>
+        remove()
         @publish topic, msgs, callback
+
+      failed = (err) ->
+        err or= new Error 'Connection closed!'
+        remove()
+        callback err
+
+      remove = =>
+        @removeListener Writer.READY, ready
+        @removeListener Writer.ERROR, failed
+        @removeListener Writer.CLOSED, failed
+
+      @on Writer.READY, ready
+      @on Writer.ERROR, failed
+      @on Writer.CLOSED, failed
+
       return

     msgs = [msgs] unless _.isArray msgs


Format on save now works :)

Format on save now works :)

--- lib/format.coffee
+++ lib/format.coffee
@@ -20,24 +20,29 @@
     e4x: false

   activate: (state) ->
-    atom.workspaceView.command "jsformat:format", => @format(state)
+    atom.workspaceView.command 'jsformat:format', => @format(state)

     if atom.config.get('jsformat.format_on_save') ? @configDefaults['format_on_save']
-      # event is only attached once user has called JSFormat once,
-      # figure out how to set this up at the beginning on any supported file
-      buffer = atom.workspace.getActivePaneItem().getBuffer()
+      @editorSaveSubscriptions = {}
+      @editorCloseSubscriptions = {}

-      @editorCreationSubscription = atom.workspace.onDidAddTextEditor (event) =>
-        grammar = event.textEditor.getGrammar()?.scopeName
+      @editorCreationSubscription = atom.workspaceView.eachEditorView (editorView) =>
+        editor = editorView.getEditor()
+        grammar = editor.getGrammar().scopeName

-        if grammar is 'source.json' or grammar is 'source.js'
-          @fileSaveSubscription = buffer.onWillSave =>
+        if grammar is 'source.js' or grammar is 'source.json'
+          buffer = editor.getBuffer()
+
+          @editorSaveSubscriptions[editor.id] = buffer.onWillSave =>
             buffer.transact =>
-              @formatJavascript(state)
+              @formatJavascript(editor)

-          @fileCloseSubscription = buffer.onDidDestroy =>
-            @fileSaveSubscription.dispose()
-            @fileCloseSubscription.dispose()
+          @editorCloseSubscriptions[editor.id] = buffer.onDidDestroy =>
+            @editorSaveSubscriptions[editor.id].dispose()
+            @editorCloseSubscriptions[editor.id].dispose()
+
+            delete @editorSaveSubscriptions[editor.id]
+            delete @editorCloseSubscriptions[editor.id]

   format: (state) ->
     editor = atom.workspace.activePaneItem


Add linter name

Add linter name

--- lib/linter.coffee
+++ lib/linter.coffee
@@ -25,8 +25,8 @@
     }]

 module.exports = new class # This only needs to be a class to bind lint()
-
   grammarScopes: ['source.gfm', 'source.pfm', 'text.md']
+  name: "mdast-lint"
   scope: "file"
   lintOnFly: true


Update refactor API

Update refactor API

--- lib/ripper.coffee
+++ lib/ripper.coffee
@@ -198,9 +198,9 @@
       @nodes = Ripper.generateNodes parse @tokens
     catch err
       updateSyntaxError err, code
-      callback? err
+      callback [ err ]
       return
-    callback?()
+    callback()

   find: (point) ->
     return [] unless @nodes?


为了配合 socket 做消息推送，对 router 做一些小改动，route.name 是 ctl_action 生成

--- lib/router.coffee
+++ lib/router.coffee
@@ -24,6 +24,7 @@
     apis.push "[#{verb.toUpperCase()}] #{routePath}"

     [ctl, action] = ctlAct.split('#')
+    evtName = "#{ctl}_#{action}"

     # 如果定义的对应的控制器，也有对应的方法则使用该方法
     actions = ctls[ctl][action] if ctls[ctl] and ctls[ctl][action]
@@ -46,6 +47,7 @@
     # 将每一个action都用try catch处理
     actions = _.map(actions, (action) ->
       (req, res, next) ->
+        req.route.evtName = evtName
         try
           action(req, res, next)
         catch e


use inheretance for Relation and Alias

use inheretance for Relation and Alias

--- Backbone.V1.coffee
+++ Backbone.V1.coffee
@@ -20,8 +20,8 @@

   getSelectTokens = (schema) ->
     _(schema).map (item) ->
+      return getQueryFor(item.type, item.attribute) if item instanceof Relation
       return item.attribute if item instanceof Alias
-      return getQueryFor(item.type, item.attribute) if item instanceof Relation
       return item if _.isString(item)

   constructor: (options) ->
@@ -63,12 +63,14 @@
       _(schema).each (item) ->
         if(_.isString(item))
           transformedRow[item] = row[item]
-        if(item instanceof Alias)
-          transformedRow[item.alias] = row[item.attribute]
-        if(item instanceof Relation)
-          children = aliasRows(row[item.attribute], item.type)
-          transformedRow[item.alias] = new item.type(children[0]) if item.isSingle()
-          transformedRow[item.alias] = new item.type(children) if item.isMulti()
+        else if(item instanceof Alias)
+          if(item instanceof Relation)
+            children = aliasRows(row[item.attribute], item.type)
+            transformedRow[item.alias] = new item.type(children[0]) if item.isSingle() && children[0]?
+            transformedRow[item.alias] = new item.type(children) if item.isMulti()
+          else
+            transformedRow[item.alias] = row[item.attribute]
+

       transformedRow

@@ -84,14 +86,10 @@

   @alias = (attribute) -> new Alias(attribute)

-  class Relation
+  class Relation extends Alias
     constructor: (@attribute) ->
+      super(@attribute)
       @type = V1.Collection
-      @alias = @attribute
-
-    as: (alias) ->
-      @alias = alias
-      this

     isMulti: ->
       @type.prototype instanceof V1.Collection or @type is V1.Collection


Fix bug in import logs

Fix bug in import logs

--- views/akashic-advanced-module.cjsx
+++ views/akashic-advanced-module.cjsx
@@ -78,7 +78,7 @@

 resolveFile = (fileContent, tableTabEn)->
   tableTab = {}
-  tableTab['en-US'] = tableTabEn
+  tableTab['en-US'] = Object.clone tableTabEn
   for key in ['ja-JP', 'zh-CN', 'zh-TW']
     tableTab[key] = translateTableTab tableTabEn, key
   for key, tabs of tableTab


adding onJoin callback to beat race condition on channel join

--- assets/cs/phoenix.coffee
+++ assets/cs/phoenix.coffee
@@ -11,11 +11,15 @@

     bindings: null

-    constructor: (@channel, @topic, @message, @callback, @socket) ->
+    constructor: (@channel, @topic, @message, @callback, @onJoin, @socket) ->
       @reset()

-    reset: -> @bindings = []
+    reset: ->
+      if @onJoin
+        @bindings = [{event: "join", callback: @onJoin}]
+      else
+        @bindings = []

     on: (event, callback) -> @bindings.push({event, callback})

@@ -140,8 +144,8 @@
       chan.callback(chan)

-    join: (channel, topic, message, callback) ->
-      chan = new exports.Channel(channel, topic, message, callback, this)
+    join: (channel, topic, message, callback, onJoin) ->
+      chan = new exports.Channel(channel, topic, message, callback, onJoin, this)
       @channels.push(chan)
       @rejoin(chan) if @isConnected()

@@ -176,4 +180,4 @@
       for chan in @channels when chan.isMember(channel, topic)
         chan.trigger(event, message)

-  exports
+  exports


Fix agentSpawner declaration in game state

--- assets/game_state.coffee
+++ assets/game_state.coffee
@@ -9,7 +9,7 @@

   money: 1000000
   reputation: 0.5
-  agent_spawner = AgentSpawner()
+  agentSpawner: AgentSpawner()

   addAgent: (agent) ->
     @agents.append(agent)


Skipping some tests on Windows (symlink repo).

--- test/simple_repo.coffee
+++ test/simple_repo.coffee
@@ -94,6 +94,11 @@
   testRepo = new TestRepository path.parse(__filename).name
   repo = null
   before ->
+    isWindows = process.platform == 'win32'
+    if isWindows
+      this.skip()
+      return
+
     testRepo.init()

   beforeEach ->


apiKey not apikey

apiKey not apikey

--- test/unit/helper.coffee
+++ test/unit/helper.coffee
@@ -11,6 +11,13 @@
   global.Backbone = require 'backbone'
   global.Backbone.setDomLibrary global.jQuery

+  global.user =
+    shortName: 'test'
+    apiKey: 'fakeapikey'
+    email: 'test@example.com'
+    displayName: 'Tesuto Tesoto-San'
+
+
 fakeWindow()

 # Concatenate our JS and eval it


Update spec for new deprecation data

Update spec for new deprecation data

--- spec/package-manager-spec.coffee
+++ spec/package-manager-spec.coffee
@@ -70,10 +70,10 @@
       it "returns null", ->
         grim.includeDeprecatedAPIs = false
         expect(atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'packages', 'wordcount'))).toBeNull()
-        expect(atom.packages.isDeprecatedPackage('wordcount', '2.0.9')).toBe true
-        expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.0')).toBe true
-        expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.1')).toBe false
-        expect(atom.packages.getDeprecatedPackageMetadata('wordcount').version).toBe '<=2.1.0'
+        expect(atom.packages.isDeprecatedPackage('wordcount', '2.1.9')).toBe true
+        expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.0')).toBe true
+        expect(atom.packages.isDeprecatedPackage('wordcount', '2.2.1')).toBe false
+        expect(atom.packages.getDeprecatedPackageMetadata('wordcount').version).toBe '<=2.2.0'

     it "invokes ::onDidLoadPackage listeners with the loaded package", ->
       loadedPackage = null


Add unit test.

Add unit test.

--- spec/snippet-loading-spec.coffee
+++ spec/snippet-loading-spec.coffee
@@ -223,6 +223,18 @@
         expect(Object.keys(snippets).length).toBe 1
         atom.config.set('core.packagesWithSnippetsDisabled', originalConfig)

+    it "still includes a disabled package's snippets in the list of unparsed snippets", ->
+      originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
+      atom.config.set('core.packagesWithSnippetsDisabled', [])
+
+      activateSnippetsPackage()
+      runs ->
+        atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets'])
+        allSnippets = snippetsService.getUnparsedSnippets()
+        scopedSnippet = allSnippets.find (s) ->
+          s.selectorString is '.package-with-snippets-unique-scope'
+        expect(scopedSnippet).toNotBe undefined
+
     it "never loads a package's snippets when that package is disabled in config", ->
       originalConfig = atom.config.get('core.packagesWithSnippetsDisabled')
       atom.config.set('core.packagesWithSnippetsDisabled', ['package-with-snippets'])


Add some atom snippets

Add some atom snippets

--- atom/.atom/snippets.cson
+++ atom/.atom/snippets.cson
@@ -33,3 +33,21 @@
   'propTypes arrayOf':
     'prefix': 'rpa'
     'body': 'React.PropTypes.arrayOf(${0})'
+  'functional component':
+    'prefix': 'rcf'
+    'body': """
+      const ${1} = (${2}) => (
+
${3}

+      );${0}
+    """
+  'class component':
+    'prefix': 'rcc'
+    'body': """
+      class ${1} extends React.Component {
+        render() {
+          return (
+
${3}

+          );
+        }
+      }${0}
+    """


Fix event tracking not being injecting

Fix event tracking not being injecting

--- services/web/public/coffee/ide/AutoCompileOnboardingController.coffee
+++ services/web/public/coffee/ide/AutoCompileOnboardingController.coffee
@@ -1,7 +1,7 @@
 define [
 	"base"
 ], (App) ->
-	App.controller "AutoCompileOnboardingController", ($scope) ->
+	App.controller "AutoCompileOnboardingController", ($scope, event_tracking) ->
 		recompileBtn = angular.element('#recompile')
 		popover = angular.element('.onboarding__autocompile')
 		{ top, left } = recompileBtn.offset()


Escape closes help dialog.

Escape closes help dialog.

With the help dialog in an iframe, Escape no longer closes it if that
iframe loses the focus.  This fixes that.

See point 2 of #2045.

This is not a perfect solution: it only works if the focus ends up in
the frame from which the help dialog was launched.  However, that is the
the common case and, in particular, it is the case which arises on the
options page -- which is a particularly important use case.

--- content_scripts/mode_key_handler.coffee
+++ content_scripts/mode_key_handler.coffee
@@ -37,12 +37,17 @@
   onKeydown: (event) ->
     keyChar = KeyboardUtils.getKeyCharString event
     isEscape = KeyboardUtils.isEscape event
-    if isEscape and @countPrefix == 0 and @keyState.length == 1
-      @continueBubbling
-    else if isEscape
+    if isEscape and (@countPrefix != 0 or @keyState.length != 1)
       @keydownEvents[event.keyCode] = true
       @reset()
       false # Suppress event.
+    # If the help dialog loses the focus, then Escape should hide it; see point 2 in #2045.
+    else if isEscape and HelpDialog?.showing
+      @keydownEvents[event.keyCode] = true
+      HelpDialog.hide()
+      @stopBubblingAndTrue
+    else if isEscape
+      @continueBubbling
     else if @isMappedKey keyChar
       @keydownEvents[event.keyCode] = true
       @handleKeyChar keyChar


Visual/edit modes: implement "V" in visual mode.

--- content_scripts/mode_visual_edit.coffee
+++ content_scripts/mode_visual_edit.coffee
@@ -284,6 +284,7 @@
     defaults =
       name: "visual"
       badge: "V"
+      singleton: VisualMode
       exitOnEscape: true
       alterMethod: "extend"
     super extend defaults, options
@@ -293,6 +294,7 @@
         # Special case: "yy" (the first from edit mode, and now the second).
         @selectLine() if @options.expectImmediateY and @keyQueue == ""
         @yank()
+      "V": -> new VisualLineMode @options

     if @options.underEditMode
       extend @commands,


only show median box blot label when there is room

--- src/viz/types/box.coffee
+++ src/viz/types/box.coffee
@@ -115,7 +115,7 @@
           position: if h is "height" then "top" else "right"
           shape:    "whisker"
           static:   true
-          text:     median
+          text:     if boxData.d3plus[h] > 20 then median else false

       medianData.d3plus[w]        = space
       medianData.d3plus[discrete] = x


Make code around async.map more readable and advocate better using map instead of each.


updating script

updating script

--- scripts/random_noise.coffee
+++ scripts/random_noise.coffee
@@ -9,4 +9,8 @@
 module.exports = (robot) ->

   robot.hear /nicki|gruffalo|yorganic|minaj|muffin/ig, (msg) ->
-    robot.adapter.sound sounds[parseInt(sounds.length*Math.random())]
+    robot.bot.Room(msg.message.user.room).sound sounds[parseInt(sounds.length*Math.random())]
+    #@message text, "SoundMessage", callback
+    #self.post "/room/#{id}/speak", body, callback
+    #robot.adapter.sound sounds[parseInt(sounds.length*Math.random())]
+    #msg.send


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"

-test "should move player left while ke", ->
+test "should move player left while key is pressed", ->
   one = 1

   # Test for equality of two objects


Handle disposing correctly for non-Dart windows.

If you try to disable / enable the pacjage and you're not in a Dart
project, then you'll get a null reference.


update tests

update tests

--- test/flubr.coffee
+++ test/flubr.coffee
@@ -4,19 +4,25 @@

 expect = chai.expect

-describe 'Flubr:', ->
-  flubr_module = require('../src/flubr')
+describe 'hubot-flubr', ->
+
+  PASS = 'build passed'
+  FAIL = 'build failed'

   beforeEach ->
+    process.env.HUBOT_FLUBR_URL = 'http://example.com'
+    process.env.HUBOT_FLUBR_PASS = PASS
+    process.env.HUBOT_FLUBR_FAIL = FAIL
     @robot =
       respond: sinon.spy()
       hear: sinon.spy()
     @msg =
       send: sinon.spy()
       random: sinon.spy()
-    @flubr_module = flubr_module(@robot)
+    @flubr_module = require('../src/flubr')(@robot)

-  describe 'summon a business cat', ->
+  it 'registers a hear listener', ->
+    expect(@robot.hear).to.have.been.calledWith(new RegExp PASS)

-    it 'should register a hear listener', ->
-      expect(@robot.hear).to.have.been.calledWith(/we should create a strategic partnership/)
+  it 'registers a hear listener', ->
+    expect(@robot.hear).to.have.been.calledWith(new RegExp FAIL)


More tests.

More tests.

--- test/index.coffee
+++ test/index.coffee
@@ -537,6 +537,23 @@
       dest = doc.forJson()
       dest.should.eql attr: [], embed: 1

+    it "should not include embeds without any default values", ->
+      DefaultJson = Db.document 'defaultJsonEmbedDeep',
+        attr: 'attr'
+        embed: Embed 'embed',
+          embed: Embed 'embed',
+            attr: 'attr'
+            attr2: 'attr2'
+
+      doc = DefaultJson()
+      doc.forJson().should.eql {}
+      doc.attr = 'a'
+      doc.forJson().should.eql attr: 'a'
+      doc.embed = {}
+      doc.forJson().should.eql attr: 'a', embed: {}
+      doc.embed.attr = 'b'
+      doc.forJson().should.eql attr: 'a', embed: attr: 'b'
+

 describe "Cursor", ->
   before (done) ->


fix test case

fix test case

--- test/setup.coffee
+++ test/setup.coffee
@@ -15,14 +15,13 @@
     engines: [
         {
             type: 'FlashMP3Core'
-            #type: 'AudioCore'
         },
-#        {
-            #type: 'FlashMP4Core'
-        #},
-        #{
-            #type: 'AudioCore'
-        #}
+        {
+            type: 'FlashMP4Core'
+        },
+        {
+            type: 'AudioCore'
+        }
     ]
 })
 pl = p.playlist


BW & XY: Add message to Focus Punch.

BW & XY: Add message to Focus Punch.

--- server/bw/data/moves.coffee
+++ server/bw/data/moves.coffee
@@ -670,6 +670,7 @@

 extendMove 'Focus Punch', ->
   @beforeTurn = (battle, user) ->
+    battle.message "#{user.name} is tightening its focus!"
     user.attach(Attachment.FocusPunch)

 makeIdentifyMove("Foresight", "Normal")


Load public settings

Load public settings

--- server/lib/_settings.coffee
+++ server/lib/_settings.coffee
@@ -23,6 +23,7 @@
 Settings.find().observe
 	added: (settings) ->
 		Meteor.settings = settings
+		__meteor_runtime_config__?.PUBLIC_SETTINGS = Meteor.settings?.public
 		configLoginServices settings
 		loadEnvConfigs settings
 		configCDN settings
@@ -30,6 +31,7 @@

 	changed: (settings) ->
 		Meteor.settings = settings
+		__meteor_runtime_config__?.PUBLIC_SETTINGS = Meteor.settings?.public
 		configLoginServices settings
 		loadEnvConfigs settings
 		configCDN settings


swap class-attributes to instance-attributes for Excel WB

--- server/methods/excel.coffee
+++ server/methods/excel.coffee
@@ -15,8 +15,9 @@
     (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000)

 class Workbook
-    SheetNames: []
-    Sheets: {}
+    constructor: () ->
+        @SheetNames = []
+        @Sheets = {}

 sheet_from_array_of_arrays = (data) ->
     ws = {}


Add note about more accurate duration calculation


Add 2dspacial index for test collection

Add 2dspacial index for test collection

--- test/suite.coffee
+++ test/suite.coffee
@@ -27,9 +27,11 @@
   mongo.dropCollection 'test'
   mongo.dropCollection 'steder'

-  mongo.collection('steder').insert steder, {safe: true, w: 1}, (err, msg) ->
+  mongo.collection('steder').ensureIndex geojson: '2dsphere', (err) ->
     assert.ifError(err)
-    done()
+    mongo.collection('steder').insert steder, {safe: true, w: 1}, (err, msg) ->
+      assert.ifError(err)
+      done()

 describe 'Cache', ->
   require './cache-spec.coffee'


Lint fix

Lint fix


adjust row index for spreadsheet

adjust row index for spreadsheet

--- ComputerSide/PressTo/Web/Server/devinfo.coffee
+++ ComputerSide/PressTo/Web/Server/devinfo.coffee
@@ -20,7 +20,7 @@
     throw err  if err
     spreadsheet.receive (err, rows, info)->
       throw err  if err
-      devs = _.filter rows, (row, index)-> index >3 and index < 29
+      devs = _.filter rows, (row, index)-> index >2 and index < 29

       client.get "api/users.list?token=#{Secret.token}", (err, response, profiles)->
         _(devs).each (dev, index)->


update gamepadprocedure

update gamepadprocedure

--- plugins/gamepadprocedure.coffee
+++ plugins/gamepadprocedure.coffee
@@ -1,8 +1,7 @@
-_GAMEPADPROCEDURE_ = []
 _start_rt = 0
 _start_lt = 0

-_GAMEPADPROCEDURE_['firefox_gamepad'] =(gamepadsinfo)->
+_GAMEPADPROCEDURE =(browser, gamepadsinfo)->
     padresult = []
     for padnum in [0...gamepadsinfo.length]
         gamepad = gamepadsinfo[padnum]
@@ -13,94 +12,35 @@
         buttons = gamepad.buttons
         axes = gamepad.axes
         index = gamepad.index
+        #mapping = gamepad.mapping
+        connected = gamepad.connected
         id = gamepad.id

-        # ゲームパッドボタン情報取得
-        # 各種ゲームパッドで共通の情報が取れるがボタンが8つなので、それ以降のボタン情報は破棄する
-        max = 13
-
-        padbuttons = []
-        analogstick = []
-
-        if (id.match(/.*45e.*?28e.*/))
-            xbox360pad = true
-        else
-            xbox360pad = false
-
-        if (xbox360pad)
-            ###
-            for btnum in [0...max]
-                bt = buttons[btnum]
-                padbuttons[btnum] = bt.pressed
-            ###
-            padbuttons[0] = buttons[11].pressed
-            padbuttons[1] = buttons[12].pressed
-            padbuttons[2] = buttons[13].pressed
-            padbuttons[3] = buttons[14].pressed
-
-            padbuttons[4] = buttons[8].pressed
-            padbuttons[5] = buttons[9].pressed
-
-            padbuttons[6] = buttons[5].pressed
-            padbuttons[7] = buttons[4].pressed
-
-            if (_start_rt == axes[2])
-                padbuttons[8] = 0
-            else
-                padbuttons[8] = ((axes[2] + 1) / 2)
-            if (_start_lt == axes[5])
-                padbuttons[9] = 0
-            else
-                padbuttons[9] = ((axes[5] + 1) / 2)
-
-            padbuttons[10]= buttons[6].pressed
-            padbuttons[11]= buttons[7].pressed
-
-            padbuttons[12]= buttons[10].pressed
-
-            analogstick[0] = [axes[0], axes[1]]
-            analogstick[1] = [axes[3], axes[4]]
-        else
-            ###
-            for btnum in [0...max]
-                bt = buttons[btnum]
-                padbuttons[btnum] = bt.pressed
-            ###
-            padbuttons[0] = buttons[0].pressed
-            padbuttons[1] = buttons[1].pressed
-            padbuttons[2] = buttons[2].pressed
-            padbuttons[3] = buttons[3].pressed
-
-            padbuttons[4] = buttons[6].pressed
-            padbuttons[5] = buttons[7].pressed
-
-            padbuttons[6] = buttons[8].pressed
-            padbuttons[7] = buttons[9].pressed
-
-            padbuttons[8] = parseFloat(if (buttons[4].pressed) then 1.0 else 0.0)
-            padbuttons[9] = parseFloat(if (buttons[5].pressed) then 1.0 else 0.0)
-
-            padbuttons[10] = buttons[10].pressed
-            padbuttons[11] = buttons[11].pressed
-
-            padbuttons[12] = 0
-
-            analogstick[0] = [axes[0], axes[1]]
-            analogstick[1] = [axes[2], axes[3]]
+        ###
+        for btnum in [0...max]
+            bt = buttons[btnum]
+            padbuttons[btnum] = bt.pressed
+        ###
+        kind = _getGamePadKind(id)
+        ret = _getControllerValue(browser, kind, buttons, axes)
+        if (!ret?)
+            return undefined
+        padbuttons = ret[0]
+        analogstick = ret[1]

         # 水平方向ボタンデータ取得
         padaxes = []
-        if ((gamepad.buttons[2]? && gamepad.buttons[2].pressed) || gamepad.axes[0].pressed || parseInt(gamepad.axes[0]) < 0)
+        if (gamepad.axes[0].pressed || parseInt(gamepad.axes[0]) < 0)
             padaxes[0] = -1
-        else if ((gamepad.buttons[3]? && gamepad.buttons[3].pressed) || gamepad.axes[0].pressed || parseInt(gamepad.axes[0]) > 0)
+        else if (gamepad.axes[0].pressed || parseInt(gamepad.axes[0]) > 0)
             padaxes[0] = 1
         else
             padaxes[0] = 0

         # 垂直方向ボタンデータ取得
-        if ((gamepad.buttons[0]? && gamepad.buttons[0].pressed) || gamepad.axes[1].pressed || parseInt(gamepad.axes[1]) < 0)
+        if (gamepad.axes[1].pressed || parseInt(gamepad.axes[1]) < 0)
             padaxes[1] = -1
-        else if ((gamepad.buttons[1]? && gamepad.buttons[1].pressed) || gamepad.axes[1].pressed || parseInt(gamepad.axes[1]) > 0)
+        else if (gamepad.axes[1].pressed || parseInt(gamepad.axes[1]) > 0)
             padaxes[1] = 1
         else
             padaxes[1] = 0
@@ -113,112 +53,255 @@

     return padresult

-_GAMEPADPROCEDURE_['chrome_gamepad'] =(gamepadsinfo)->
-    padresult = []
-    for padnum in [0...gamepadsinfo.length]
-        gamepad = gamepadsinfo[padnum]
-        if (!gamepad?)
-            continue
-
-        # 各種ボタン情報取得
-        buttons = gamepad.buttons
-        axes = gamepad.axes
-        index = gamepad.index
-        id = gamepad.id
-
+_getControllerValue = (browser, kind, buttons, axes)->
+    ret = undefined
+    if (kind.match(/.*xbox.*/))
         # ゲームパッドボタン情報取得
         # 各種ゲームパッドで共通の情報が取れるがボタンが8つなので、それ以降のボタン情報は破棄する
         max = 13
-
-        padbuttons = []
-        analogstick = []
-
-        if (id.match(/.*45e.*?28e.*/))
-            xbox360pad = true
-            max = 9
-        else
-            xbox360pad = false
-
-        if (xbox360pad)
-            ###
-            for btnum in [0...max]
-                bt = buttons[btnum]
-                padbuttons[btnum] = bt.pressed
-            ###
-            padbuttons[0] = buttons[0].pressed
-            padbuttons[1] = buttons[1].pressed
-            padbuttons[2] = buttons[2].pressed
-            padbuttons[3] = buttons[3].pressed
-
-            padbuttons[4] = buttons[4].pressed
-            padbuttons[5] = buttons[5].pressed
-
-            padbuttons[6] = buttons[8].pressed
-            padbuttons[7] = buttons[9].pressed
-
-            padbuttons[8] = buttons[6].value
-            padbuttons[9] = buttons[7].value
-
-            padbuttons[10] = buttons[10].pressed
-            padbuttons[11] = buttons[11].pressed
-
-            padbuttons[12] = buttons[16].pressed
-
-            analogstick[0] = [axes[0], axes[1]]
-            analogstick[1] = [axes[2], axes[3]]
-        else
-            ###
-            for btnum in [0...max]
-                bt = buttons[btnum]
-                padbuttons[btnum] = bt.pressed
-            ###
-            padbuttons[0] = buttons[0].pressed
-            padbuttons[1] = buttons[1].pressed
-            padbuttons[2] = buttons[2].pressed
-            padbuttons[3] = buttons[3].pressed
-
-            padbuttons[4] = buttons[6].pressed
-            padbuttons[5] = buttons[7].pressed
-
-            padbuttons[6] = buttons[8].pressed if (buttons[8]?)
-            padbuttons[7] = buttons[9].pressed if (buttons[9]?)
-
-            padbuttons[8] = if (buttons[4].pressed) then 1.0 else 0.0
-            padbuttons[9] = if (buttons[5].pressed) then 1.0 else 0.0
-
-            padbuttons[10] = buttons[10].pressed
-            padbuttons[11] = buttons[11].pressed
-
-            padbuttons[12] = 0
-            ##
-
-            analogstick[0] = [axes[0], axes[1]]
-            analogstick[1] = [axes[2], axes[5]]
-
-        # 水平方向ボタンデータ取得
-        padaxes = []
-        if ((gamepad.buttons[14]? && gamepad.buttons[14].pressed) || gamepad.axes[0].pressed || parseInt(gamepad.axes[0]) < 0)
-            padaxes[0] = -1
-        else if ((gamepad.buttons[15]? && gamepad.buttons[15].pressed) || gamepad.axes[0].pressed || parseInt(gamepad.axes[0]) > 0)
-            padaxes[0] = 1
-        else
-            padaxes[0] = 0
-
-        # 垂直方向ボタンデータ取得
-        if ((gamepad.buttons[12]? && gamepad.buttons[12].pressed) || gamepad.axes[1].pressed || parseInt(gamepad.axes[1]) < 0)
-            padaxes[1] = -1
-        else if ((gamepad.buttons[13]? && gamepad.buttons[13].pressed) || gamepad.axes[1].pressed || parseInt(gamepad.axes[1]) > 0)
-            padaxes[1] = 1
-        else
-            padaxes[1] = 0
-
-        padresult[index] = []
-        padresult[index].id = id
-        padresult[index].padbuttons = padbuttons
-        padresult[index].padaxes = padaxes
-        padresult[index].analogstick = analogstick
-
-    return padresult
+        ret = _getXBOX360Controller(browser, buttons, axes)
+    else if (kind.match(/.*psx.*/))
+        # ゲームパッドボタン情報取得
+        # 各種ゲームパッドで共通の情報が取れるがボタンが8つなので、それ以降のボタン情報は破棄する
+        max = 13
+        ret = _getPSXController(browser, buttons, axes)
+    else if (kind.match(/.*bsgp1204p.*/))
+        # ゲームパッドボタン情報取得
+        # 各種ゲームパッドで共通の情報が取れるがボタンが8つなので、それ以降のボタン情報は破棄する
+        max = 13
+        ret = _getBSGP1204pController(browser, buttons, axes)
+    else
+        # ゲームパッドボタン情報取得
+        # 各種ゲームパッドで共通の情報が取れるがボタンが8つなので、それ以降のボタン情報は破棄する
+        max = 8
+        ret = _getOtherController(browser, buttons, axes)
+    return ret
+
+_getXBOX360Controller = (browser, buttons, axes)->
+    padbuttons = []
+    analogstick = []
+    if (browser == "firefox")
+        padbuttons[0] = buttons[11].pressed
+        padbuttons[1] = buttons[12].pressed
+        padbuttons[2] = buttons[13].pressed
+        padbuttons[3] = buttons[14].pressed
+
+        padbuttons[4] = buttons[8].pressed
+        padbuttons[5] = buttons[9].pressed
+
+        padbuttons[6] = buttons[5].pressed
+        padbuttons[7] = buttons[4].pressed
+
+        if (_start_rt == axes[2])
+            padbuttons[8] = 0
+        else
+            padbuttons[8] = ((axes[2] + 1) / 2)
+
+        if (_start_lt == axes[5])
+            padbuttons[9] = 0
+        else
+            padbuttons[9] = ((axes[5] + 1) / 2)
+
+        padbuttons[10]= buttons[6].pressed
+        padbuttons[11]= buttons[7].pressed
+
+        padbuttons[12]= buttons[10].pressed
+
+        analogstick[0] = [axes[0], axes[1]]
+        analogstick[1] = [axes[3], axes[4]]
+
+        ret = [padbuttons, analogstick]
+    else if (browser == "chrome")
+        padbuttons[0] = buttons[0].pressed
+        padbuttons[1] = buttons[1].pressed
+        padbuttons[2] = buttons[2].pressed
+        padbuttons[3] = buttons[3].pressed
+
+        padbuttons[4] = buttons[4].pressed
+        padbuttons[5] = buttons[5].pressed
+
+        padbuttons[6] = buttons[8].pressed
+        padbuttons[7] = buttons[9].pressed
+
+        if (_start_rt == buttons[6].value)
+            padbuttons[8] = 0
+        else
+            padbuttons[8] = ((buttons[6].value + 1) / 2)
+
+        if (_start_lt == buttons[7].value)
+            padbuttons[9] = 0
+        else
+            padbuttons[9] = ((buttons[7].value + 1) / 2)
+
+        padbuttons[10]= buttons[10].pressed
+        padbuttons[11]= buttons[11].pressed
+
+        padbuttons[12]= buttons[16].pressed
+
+        analogstick[0] = [axes[0], axes[1]]
+        analogstick[1] = [axes[3], axes[2]]
+
+        ret = [padbuttons, analogstick]
+
+    return ret
+
+_getPSXController = (browser, buttons, axes)->
+    padbuttons = []
+    analogstick = []
+    if (browser == "firefox")
+        padbuttons[0] = buttons[0].pressed
+        padbuttons[1] = buttons[1].pressed
+        padbuttons[2] = buttons[2].pressed
+        padbuttons[3] = buttons[3].pressed
+
+        padbuttons[4] = buttons[4].pressed
+        padbuttons[5] = buttons[6].pressed
+
+        padbuttons[6] = buttons[8].pressed
+        padbuttons[7] = buttons[9].pressed
+
+        padbuttons[8] = parseFloat(if (buttons[5].pressed) then 1.0 else 0.0)
+        padbuttons[9] = parseFloat(if (buttons[7].pressed) then 1.0 else 0.0)
+
+        padbuttons[10] = buttons[10].pressed
+        padbuttons[11] = buttons[11].pressed
+
+        padbuttons[12] = 0
+
+        analogstick[0] = [axes[0], axes[1]]
+        analogstick[1] = [axes[2], axes[3]]
+
+        ret = [padbuttons, analogstick]
+    else if (browser == "chrome")
+        padbuttons[0] = buttons[0].pressed
+        padbuttons[1] = buttons[1].pressed
+        padbuttons[2] = buttons[2].pressed
+        padbuttons[3] = buttons[3].pressed
+
+        padbuttons[4] = buttons[4].pressed
+        padbuttons[5] = buttons[6].pressed
+
+        padbuttons[6] = buttons[8].pressed
+        padbuttons[7] = buttons[9].pressed
+
+        padbuttons[8] = parseFloat(if (buttons[5].pressed) then 1.0 else 0.0)
+        padbuttons[9] = parseFloat(if (buttons[7].pressed) then 1.0 else 0.0)
+
+        padbuttons[10] = buttons[10].pressed
+        padbuttons[11] = buttons[11].pressed
+
+        padbuttons[12] = 0
+
+        analogstick[0] = [axes[0], axes[1]]
+        analogstick[1] = [axes[2], axes[3]]
+
+        ret = [padbuttons, analogstick]
+
+    return ret
+
+_getBSGP1204pController = (browser, buttons, axes)->
+    padbuttons = []
+    analogstick = []
+    if (browser == "firefox")
+        padbuttons[0] = buttons[0].pressed
+        padbuttons[1] = buttons[1].pressed
+        padbuttons[2] = buttons[2].pressed
+        padbuttons[3] = buttons[3].pressed
+
+        padbuttons[4] = buttons[6].pressed
+        padbuttons[5] = buttons[7].pressed
+
+        padbuttons[6] = buttons[8].pressed
+        padbuttons[7] = buttons[9].pressed
+
+        padbuttons[8] = parseFloat(if (buttons[4].pressed) then 1.0 else 0.0)
+        padbuttons[9] = parseFloat(if (buttons[5].pressed) then 1.0 else 0.0)
+
+        padbuttons[10] = buttons[10].pressed
+        padbuttons[11] = buttons[11].pressed
+
+        padbuttons[12] = 0
+
+        analogstick[0] = [axes[0], axes[1]]
+        analogstick[1] = [axes[2], axes[3]]
+
+        ret = [padbuttons, analogstick]
+    else if (browser == "chrome")
+        padbuttons[0] = buttons[0].pressed
+        padbuttons[1] = buttons[1].pressed
+        padbuttons[2] = buttons[2].pressed
+        padbuttons[3] = buttons[3].pressed
+
+        padbuttons[4] = buttons[6].pressed
+        padbuttons[5] = buttons[7].pressed
+
+        padbuttons[6] = buttons[8].pressed
+        padbuttons[7] = buttons[9].pressed
+
+        padbuttons[8] = parseFloat(if (buttons[4].pressed) then 1.0 else 0.0)
+        padbuttons[9] = parseFloat(if (buttons[5].pressed) then 1.0 else 0.0)
+
+        padbuttons[10] = buttons[10].pressed
+        padbuttons[11] = buttons[11].pressed
+
+        padbuttons[12] = 0
+
+        analogstick[0] = [axes[0], axes[1]]
+        analogstick[1] = [axes[2], axes[5]]
+
+        ret = [padbuttons, analogstick]
+
+    return ret
+
+_getOtherController = (browser, buttons, axes)->
+    padbuttons = []
+    analogstick = []
+    if (browser == "firefox")
+        padbuttons[0] = buttons[0].pressed
+        padbuttons[1] = buttons[1].pressed
+        padbuttons[2] = buttons[2].pressed
+        padbuttons[3] = buttons[3].pressed
+
+        padbuttons[4] = buttons[4].pressed
+        padbuttons[5] = buttons[5].pressed
+
+        padbuttons[6] = buttons[6].pressed
+        padbuttons[7] = buttons[7].pressed
+
+        analogstick[0] = [0.0, 0.0]
+        analogstick[1] = [0.0, 0.0]
+
+        ret = [padbuttons, analogstick]
+    else if (browser == "chrome")
+        padbuttons[0] = buttons[0].pressed
+        padbuttons[1] = buttons[1].pressed
+        padbuttons[2] = buttons[2].pressed
+        padbuttons[3] = buttons[3].pressed
+
+        padbuttons[4] = buttons[4].pressed
+        padbuttons[5] = buttons[5].pressed
+
+        padbuttons[6] = buttons[6].pressed
+        padbuttons[7] = buttons[7].pressed
+
+        analogstick[0] = [0.0, 0.0]
+        analogstick[1] = [0.0, 0.0]
+
+        ret = [padbuttons, analogstick]
+
+    return ret
+
+
+_getGamePadKind = (id)->
+    kind = "unknown"
+    if (id.match(/.*45e.*28e.*/))
+        kind = "xbox360.wired"
+    else if (id.match(/.*d9d.*3013.*/))
+        kind = "psx.wired"
+    else if (id.match(/.*1dd8.*0010.*/))
+        kind = "ibuffalo.bsgp1204p"
+
+    return kind

 gamepadProcedure =->
     # ブラウザ大分類
@@ -233,11 +316,9 @@
         _browserMajorClass = "unknown"

     padresult = []
-    browserGamepadFunctionName = _browserMajorClass+"_gamepad"
-    if (typeof _GAMEPADPROCEDURE_[browserGamepadFunctionName] == 'function')
-        gamepadsinfo = if (navigator.getGamepads) then navigator.getGamepads() else (if (navigator.webkitGetGamepads) then navigator.webkitGetGamepads else [])
-        if (gamepadsinfo? && gamepadsinfo.length > 0)
-            padresult = _GAMEPADPROCEDURE_[browserGamepadFunctionName](gamepadsinfo)
+    gamepadsinfo = if (navigator.getGamepads) then navigator.getGamepads() else (if (navigator.webkitGetGamepads) then navigator.webkitGetGamepads else [])
+    if (gamepadsinfo? && gamepadsinfo.length > 0)
+        padresult = _GAMEPADPROCEDURE(_browserMajorClass, gamepadsinfo)

     return padresult


Minor safe guard

Minor safe guard

--- src/objects/locals.coffee
+++ src/objects/locals.coffee
@@ -44,7 +44,7 @@

 # Use
 exports.u = (self)->
-  junction self.path
+  junction self?.path

 # Full path
 exports.f = (self = do exports.a)->


Adding support for the most recent version of Font Awesome.

--- src/plugins/reundo.coffee
+++ src/plugins/reundo.coffee
@@ -17,7 +17,7 @@
           uuid: @options.uuid
           editable: @options.editable
           label: label
-          icon: if cmd is 'undo' then 'icon-undo' else 'icon-repeat'
+          icon: if cmd is 'undo' then 'fa-undo' else 'fa-repeat'
           command: cmd
           queryState: false
           cssClass: @options.buttonCssClass


feat(atom): FireCode font

feat(atom): FireCode font

--- atom/config.cson
+++ atom/config.cson
@@ -29,7 +29,7 @@
     whitespace:
       removeTrailingWhitespace: false
   editor:
-    fontFamily: "Menlo"
+    fontFamily: "Fira Code"
     fontSize: 15
     invisibles: {}
     nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?…"


demo is working: "dc.alwaysUpdate = true" is necessary

--- demo/index.coffee
+++ demo/index.coffee
@@ -1,25 +1,27 @@
 {runDemo, demoMap} = require('./util')
+
+dc.alwaysUpdate = true

 window.onload = ->
 #  comp = accordion()
 #  comp = demoCombo()
 #  comp = demoTriangle()
-#  comp = demoMap["show hide"]
-#  comp = demoMap["counter"]
-#  comp = demoMap["dialog"]
-#  comp = demoMap["event"]
-#  comp = demoMap["controls"]
-#  comp = demoMap["if"]
-#  comp = demoMap["each1"]
-#  comp = demoMap["each2"]
-#  comp = demoMap["each3"]
-#  comp = demoMap["each4"]
-#  comp = demoMap["switch 1 2 3 4"]
-#  comp = demoMap["splitter"]
-#  comp = demoMap["sum"]
-#  comp = demoMap["text model"]
-#  comp = demoMap["auto width edit"]
-#  comp = demoMap["mount/unmount"]
+#  comp = demoMap["show hide"]()
+#  comp = demoMap["counter"]()
+#  comp = demoMap["dialog"]()
+#  comp = demoMap["event"]()
+#  comp = demoMap["controls"]()
+  comp = demoMap["if"]()
+#  comp = demoMap["each1"]()
+#  comp = demoMap["each2"]()
+#  comp = demoMap["each3"]()
+#  comp = demoMap["each4"]()
+#  comp = demoMap["switch 1 2 3 4"]()
+#  comp = demoMap["splitter"]()
+#  comp = demoMap["sum"]()
+#  comp = demoMap["text model"]()
+#  comp = demoMap["auto width edit"]()
+#  comp = demoMap["mount/unmount"]()
 #  comp = chooseFramework()
 #  comp.mount()


Sort checkins by date

Sort checkins by date

--- www/js/collections/checkins.coffee
+++ www/js/collections/checkins.coffee
@@ -4,6 +4,10 @@
   CheckinCollection = Backbone.Collection.extend
     model: Checkin
     offlineStore: new Store 'Checkins'
+
+    # Sort all checkins based on their creation date.
+    comparator: (episode) ->
+      -(new Date(episode.createdAt).getTime())

     # Get a checkin based on Foursquare ID. Will make a request to the
     # Foursquare API if this checkin is not available in the local datastore.


Fixing asynchronous callback in @_instantiate

--- www/src/theoricus/mvc/model.coffee
+++ www/src/theoricus/mvc/model.coffee
@@ -112,8 +112,9 @@

     req.done ( data )=>
       fetcher.loaded = true
-      fetcher.records = @_instantiate data
-      fetcher.onload?( fetcher.records )
+      @_instantiate data, (results)->
+        fetcher.records = results
+        fetcher.onload?( fetcher.records )

     req.error ( error )=>
       fetcher.error = true
@@ -166,20 +167,25 @@

   @param [Object] data  Data to be parsed
   ###
-  @_instantiate = ( data ) ->
+  @_instantiate = ( data, callback ) ->
     classname = ("#{@}".match /function\s(\w+)/)[1]
     records = []
-    for record in [].concat data
-      Factory.model classname, record, (model)->
-        records.push model

-    ###
-    When calling the rest service multiple times, the collection variable keeps
-    the old data and duplicate the recordset between a rest call and another one.
-    For now, just flush the old collection when instantiate a new model instance
-    ###
-    @_collection = []
+    for record, at in data

-    @_collection = ( @_collection || [] ).concat records
+      Factory.model classname, record, (_model)=>

-    return if records.length is 1 then records[0] else records
+        records.push _model
+
+        if records.length is data.length
+
+          ###
+          When calling the rest service multiple times, the collection variable keeps
+          the old data and duplicate the recordset between a rest call and another one.
+          For now, just flush the old collection when instantiate a new model instance
+          ###
+
+          @_collection = ( @_collection || [] ).concat records
+
+          callback (if records.length is 1 then records[0] else records)
+


Add unit 2 details in utils.coffee

Add unit 2 details in utils.coffee

--- app/core/utils.coffee
+++ app/core/utils.coffee
@@ -111,31 +111,19 @@
 courseIDs =
   ONE_FREE_HOUR: '5d41d731a8d1836b5aa3cba1'
   CHAPTER_ONE: '5d8a57abe8919b28d5113af1'
+  CHAPTER_TWO: '5e2759cce8919b668b7737b1'

 orderedCourseIDs = [
   courseIDs.ONE_FREE_HOUR
   courseIDs.CHAPTER_ONE
+  courseIDs.CHAPTER_TWO
 ]

 courseAcronyms = {}
 courseAcronyms[courseIDs.ONE_FREE_HOUR] = 'P'
 courseAcronyms[courseIDs.CHAPTER_ONE] = 'C1'
-
-hourOfCodeOptions = {
-  campaignId: freeCampaignIds[0],
-  courseId: courseIDs.ONE_FREE_HOUR,
-  name: 'Prologue: Sky Mountain',
-  progressModalAfter: 1500000 #25 mins
-}
-
-registerHocProgressModalCheck = ->
-  hocProgressModalCheck = setInterval(() =>
-    if window.sessionStorage?.getItem('hoc_progress_modal_time') < new Date().getTime()
-      window.sessionStorage.setItem('show_hoc_progress_modal', true)
-      window.sessionStorage.removeItem('hoc_progress_modal_time')
-      clearInterval(hocProgressModalCheck)
-  , 60000) # every 1 min
+courseAcronyms[courseIDs.CHAPTER_TWO] = 'C2'

 # Harcoding module names for simplicity
 # Use db to store these later when we add sophisticated module functionality, right now its only used for UI
@@ -151,6 +139,26 @@
   '5': 'Capstone Intro',
   '6': 'Capstone Project'
 }
+
+# TODO update module names before shipping 2UP
+courseModules[courseIDs.CHAPTER_TWO] = {
+  '1': 'Temp module'
+}
+
+hourOfCodeOptions = {
+  campaignId: freeCampaignIds[0],
+  courseId: courseIDs.ONE_FREE_HOUR,
+  name: 'Prologue: Sky Mountain',
+  progressModalAfter: 1500000 #25 mins
+}
+
+registerHocProgressModalCheck = ->
+  hocProgressModalCheck = setInterval(() =>
+    if window.sessionStorage?.getItem('hoc_progress_modal_time') < new Date().getTime()
+      window.sessionStorage.setItem('show_hoc_progress_modal', true)
+      window.sessionStorage.removeItem('hoc_progress_modal_time')
+      clearInterval(hocProgressModalCheck)
+  , 60000) # every 1 min

 petThangIDs = [
   '578d320d15e2501f00a585bd' # Wolf Pup


lowering vote interval to 10 seconds

lowering vote interval to 10 seconds

--- app/middleware.coffee
+++ app/middleware.coffee
@@ -115,8 +115,8 @@
 exports.vote = (req, res, next) ->
     now = new Date()
     if req.session.next_vote < now.getTime() or not req.session.next_vote?
-        new_minutes = now.getMinutes() + 5
-        now.setMinutes(new_minutes)
+        new_seconds = now.getSeconds() + 10
+        now.setSeconds new_seconds
         req.session.next_vote = now.getTime()
         Boulder.vote(req.params['boulder'], req.params['stars'])


Added big ships

Added big ships

--- src/scripts/shipit.coffee
+++ src/scripts/shipit.coffee
@@ -32,7 +32,8 @@
   "http://1.bp.blogspot.com/_v0neUj-VDa4/TFBEbqFQcII/AAAAAAAAFBU/E8kPNmF1h1E/s640/squirrelbacca-thumb.jpg",
   "http://dl.dropbox.com/u/602885/github/soldier-squirrel.jpg",
   "http://dl.dropbox.com/u/602885/github/squirrelmobster.jpeg",
-  "http://f.cl.ly/items/0S1M2d1h0I132S082A05/flying-squirrel.gif"
+  "http://f.cl.ly/items/0S1M2d1h0I132S082A05/flying-squirrel.gif",
+  "https://s3.amazonaws.com/uploads.hipchat.com/64658/777317/rRlqx3m4FGsV5AI/29EcG.jpg"
 ]

 module.exports = (robot) ->


:art: Use only @firstNonWhitespaceIndex

:art: Use only @firstNonWhitespaceIndex

--- src/tokenized-line.coffee
+++ src/tokenized-line.coffee
@@ -11,6 +11,7 @@
 class TokenizedLine
   endOfLineInvisibles: null
   lineIsWhitespaceOnly: false
+  firstNonWhitespaceIndex: 0
   foldable: false

   constructor: ({tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold, @tabLength, @indentLevel, @invisibles}) ->
@@ -162,12 +163,7 @@
     @lineEnding is null

   isColumnOutsideIndentation: (column) ->
-    column >= @firstNonWhitespaceIndex and @isColumnOutsideSoftWrapIndentation(column)
-
-  isColumnOutsideSoftWrapIndentation: (column) ->
-    return true if @softWrapIndentationTokens.length == 0
-
-    column > @softWrapIndentationDelta
+    column >= @firstNonWhitespaceIndex

   isColumnInsideSoftWrapIndentation: (column) ->
     return false if @softWrapIndentationTokens.length == 0


Fix indentation

Fix indentation

--- src/trix/text_view.coffee
+++ src/trix/text_view.coffee
@@ -143,13 +143,13 @@
       element = element.parentNode
     false

-createTreeWalker = (element) ->
-  whatToShow = NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT
+  createTreeWalker = (element) ->
+    whatToShow = NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT

-  acceptNode = (node) ->
-    if node.trixPosition?
-      NodeFilter.FILTER_ACCEPT
-    else
-      NodeFilter.FILTER_SKIP
+    acceptNode = (node) ->
+      if node.trixPosition?
+        NodeFilter.FILTER_ACCEPT
+      else
+        NodeFilter.FILTER_SKIP

-  document.createTreeWalker(element, whatToShow, {acceptNode})
+    document.createTreeWalker(element, whatToShow, {acceptNode})


Fixing typo

Fixing typo

--- source/core/module.coffee
+++ source/core/module.coffee
@@ -55,7 +55,7 @@
   @method template
   @param  {value}    String or Function
   ###
-  @template = (value) ->
+  @template: (value) ->
     if value? and typeof value is "string"
       @template = value
     else


fixed parsing logic -- now builds a tree directly from the text

needs some cleanup and refactoring

--- dramagrama.coffee
+++ dramagrama.coffee
@@ -1,77 +1,46 @@
 class Controller
 	makeItGo: (inputText, paper)->
+		@blocksThatIHaveDrawn = {}
 		@parser = new Parser
 		return unless inputText
-
-		parsedBits = @parser.parse inputText
-		return unless parsedBits
+
+		blockTree = @parser.parse inputText
+		return unless blockTree

 		@drawer = new Drawer(paper)
-
-		@drawBlocks(parsedBits)
-
-	# create a map that holds all the children of each block
-	# we need this so that we can properly space them
-	getAllBlockPairs: (parsedBits) ->
-		allTheBlockPairs = {}
-
-		for bit in parsedBits
-			if bit
-				if allTheBlockPairs[bit.first.name]
-					allTheBlockPairs[bit.first.name].push bit
-				else
-					allTheBlockPairs[bit.first.name] = [bit]
-
-		return allTheBlockPairs
-
-
-	drawBlocks: (parsedBits) ->
-		blockLinesByParent = @getAllBlockPairs parsedBits
+		@drawBlocks(blockTree)

-		# keep track of all the blocks that we've drawn
-		# so that we can link blocks even if they haven't
-		# been typed in order
-		blocksThatIHaveDrawn = {}
-
-		for parentName, lines of blockLinesByParent
-			# if i've drawn this block before, start from that rectangle
-			numActualChildren = @getNumLinkedChildren lines
-			parentBlock = @getOrDrawParentBlock parentName, lines[0].first, blocksThatIHaveDrawn, numActualChildren
-
-			# draw all the connecting children
-			i = 0
-			for line in lines
-				# the parent may be a standalone block and not have any children
-				if line.second.name != ""
-					childBlock = blocksThatIHaveDrawn[line.second.name]
-
-					if childBlock
-						@drawer.connectRectangles(parentBlock, childBlock, "down", line.arrow)
-					else
-						childBlock = @drawer.connectToRectangle(parentBlock, line.second, i, "down", line.arrow)
-						blocksThatIHaveDrawn[line.second.name] = childBlock
-					i++
-		return null
+	drawBlocks: (blockTree) ->
+		@drawRootedBlock block for block in blockTree.children when block
+
+	drawRootedBlock: (block) ->
+		numActualChildren = @getNumLinkedChildren block.children
+
+		# if i've drawn this block before, start from that rectangle
+		parentBlock = @blocksThatIHaveDrawn[block.name]
+		if !parentBlock
+			parentBlock = @drawer.drawRectangle(null, block, numActualChildren)
+			@blocksThatIHaveDrawn[block.name] = parentBlock
+
+		# draw all the connecting children
+		i = 0
+		for child in block.children
+			continue unless child
+			childBlock = @blocksThatIHaveDrawn[child.name]
+			# has this child been drawn? if so, let's connect to it
+			if childBlock
+				@drawer.connectExistingBlocks(parentBlock, childBlock, "down", child.arrow )
+			else
+				childBlock = @drawer.connectToRectangle(parentBlock, child, i, "down", child.arrow)
+				@blocksThatIHaveDrawn[child.name] = childBlock
+				@drawRootedBlock child
+				i++

 	# get the number of non-standalone children
 	getNumLinkedChildren: (lines) ->
 		total = 0
-		(total = total + 1) for line in lines when line.second.name isnt ""
-
+		(total = total + 1) for line in lines when line
 		return total
-
-	getOrDrawParentBlock: (parentName, lineBlock, blocksThatIHaveDrawn, numChildren) ->
-		block = blocksThatIHaveDrawn[parentName]
-
-		if block
-			# we may have to update the colour
-			if (lineBlock.colour)
-				block.svg.attr("fill":lineBlock.colour)
-			return block
-		else
-			newBlock = @drawer.drawRectangle(null, lineBlock, numChildren)
-			blocksThatIHaveDrawn[parentName] = newBlock;
-			return newBlock

 class Parser
 	constructor: ->
@@ -83,7 +52,63 @@
 		# <3 coffescript
 		parsedBits.push(@parseLine line) for line in allTheLines;

-		return parsedBits
+		return @parseTree parsedBits
+
+	parseTree: (parsedBits) ->
+		tree = {children: []}
+
+		for bit in parsedBits
+			continue unless bit
+			aname = bit.first.name
+			bname = bit.second.name
+
+			a = @findNodeInTree aname, tree;
+			b = @findNodeInTree bname, tree;
+
+			# do we need to add the parent block to the tree?
+			if !a
+				a = {name: aname, colour: "", children:[]}
+				tree.children.push a
+
+			# if b doesn't exist, it's easy. just add it and be done
+			if !b and bname != ""
+				b = {name: bname, colour: "", children:[]}
+				a.children.push b
+			else if b
+				a.children.push b
+				# dislodge the first level node if needed
+				if @isFirstLevelNode b, tree
+					# TODO: this is pretty crappy
+					tree.children[tree.children.indexOf(b)] = null
+
+			# if the colours or arrow have updated, save them
+			if a
+				a.colour = bit.first.colour if bit.first.colour
+			if b
+				b.colour = bit.second.colour if bit.second.colour
+				b.arrow = bit.arrow if bit.arrow
+
+		return tree
+
+	findNodeInTree: (findMe, tree) ->
+		return tree if tree.name is findMe
+
+		# maybe it's one of the children?
+		for child in tree.children
+			continue if !child
+			return child if child.name is findMe
+
+			# maybe it's a grand child?
+			maybeInChild = @findNodeInTree findMe, child
+			return maybeInChild if maybeInChild
+
+		return null
+
+	isFirstLevelNode: (node, tree) ->
+		for child in tree.children
+			return true if child and child.name == node.name
+
+		return false

 	parseLine: (text) ->
 		return unless text # hey there paranoia
@@ -273,6 +298,12 @@
 		connector = parentBlock.rectangle.getConnectorForDirection direction
 		myConnector = childBlock.rectangle.getConnectorForDirection "up"
 		@drawLine connector, myConnector, arrow
+
+	connectExistingBlocks:(block1, block2, direction, arrow) ->
+		connector1 = block1.rectangle.getConnectorForDirection direction
+		connector2 = block2.rectangle.getConnectorForDirection "up"
+
+		@drawLine connector1, connector2, arrow

 	drawLine: (point1, point2, arrow) ->
 		# arrow.direction gives left/right/both. if both, you need to draw both paths


use a relative path to load dishes.json

use a relative path to load dishes.json

--- server.coffee
+++ server.coffee
@@ -5,7 +5,7 @@
       fs = Npm.require 'fs'
       path = Npm.require 'path'
       basepath = path.resolve '.'
-      file = "/Users/basti/Development/meteor/sattelite/dishes.json"
+      file = basepath + "/dishes.json"
       dishes_json = fs.readFileSync file, 'utf8'
       dishes_init = JSON.parse dishes_json
       for dish in dishes_init


add logging

add logging

--- server.coffee
+++ server.coffee
@@ -340,9 +340,10 @@
           console.log "repo from cache", err, reply
           if not err and reply
             repo = JSON.parse reply
+            console.log "parsed repo", repo
             repo.members.push req.user
             repo.members_count = repo.members.length + repo.admins.length
-            rc.hmset "communities", repo.name, JSON.stringify(repo)
+            rc.hmset "communities", community, JSON.stringify(repo)
           res.json resp
     else
       res.send 500, {error: "Internal error"}


fix issue with leave game handling

fix issue with leave game handling

--- server.coffee
+++ server.coffee
@@ -25,19 +25,20 @@
 swapSide = (side) ->
   if side is "Corp" then "Runner" else "Corp"

-removePlayer = (socket, username, reason) ->
+removePlayer = (socket, reason) ->
   for game, i in games
     for player, j in game.players
-      if not player.user or player.user.username is username
+      if not player.user or player.id is socket.id
         game.players.splice(j, 1)
-        if reason is "disconnect"
-          if game.started
-            state = gameEngine.main.exec("disconnect", {gameid: game.gameid, text: "#{username} disconnected."})
-            lobby.to(game.gameid).emit("netrunner", {type: "state", state: state})
-          else
-            socket.to(game.gameid).emit('netrunner', {type: "say", user: "__system__", text: "#{username} left the game."})
+        if reason is "disconnect" and game.started
+          state = gameEngine.main.exec("disconnect", {gameid: game.gameid, text: "#{player.user.username} disconnected."})
+          lobby.to(game.gameid).emit("netrunner", {type: "state", state: state})
+        if reason is "leave"
+          socket.to(game.gameid).emit('netrunner', {type: "say", user: "__system__", text: "#{player.user.username} left the game."})
+        break
     if game.players.length is 0
       games.splice(i, 1)
+    break
   lobby.emit('netrunner', {type: "games", games: games})

 # Socket.io
@@ -57,24 +58,24 @@
   lobby.emit('netrunner', {type: "games", games: games})

   socket.on 'disconnect', () ->
-    removePlayer(socket, socket.request.user.username, "disconnect") if socket.request.user
+    removePlayer(socket,  "disconnect") if socket.request.user

   socket.on 'netrunner', (msg) ->
     switch msg.action
       when "create"
         game = {date: new Date(), gameid: ++gameid, title: msg.title,\
-                players: [{user: socket.request.user, side: "Corp"}]}
+                players: [{user: socket.request.user, id: socket.id, side: "Corp"}]}
         games.push(game)
         socket.join(gameid)
         socket.emit("netrunner", {type: "game", gameid: gameid})
         lobby.emit('netrunner', {type: "games", games: games, notification: "ting"})

       when "leave"
-        removePlayer(socket, socket.request.user.username, "leave")
+        removePlayer(socket, "leave")
         socket.leave(msg.gameid)

       when "quit"
-        removePlayer(socket, socket.request.user.username, "quit")
+        removePlayer(socket, "quit")
         socket.leave(msg.gameid)
         lobby.emit('netrunner', {type: "games", games: games})
         state = gameEngine.main.exec("quit", msg)
@@ -83,7 +84,7 @@
       when "join"
         for game in games
           if game.gameid is msg.gameid and game.players.length < 2 and game.players[0].user.username isnt socket.request.user.username
-            game.players.push({user: socket.request.user, side: swapSide(game.players[0].side)})
+            game.players.push({user: socket.request.user, id: socket.id, side: swapSide(game.players[0].side)})
             socket.join(game.gameid)
             socket.emit("netrunner", {type: "game", gameid: game.gameid})
             break


Remove personal database location

Remove personal database location

--- server.coffee
+++ server.coffee
@@ -70,9 +70,6 @@
 # Get background location from player.
 exports.onGeoloc = (userId, geoloc) ->
 	#log '[onGeoloc()] Geoloc from ' + Plugin.userName(userId) + '('+userId+'): ', JSON.stringify(geoloc)
-	#Store lat and lng for other uses
-	Db.personal(userId).set 'location', 'latitude', geoloc.latitude
-	Db.personal(userId).set 'location', 'longitude', geoloc.longitude
 	recieved = new Date()/1000
 	if Db.shared.peek('gameState') is 1 and (recieved - (Db.personal(userId).peek('lastNotification', 'recieved') || 0))> 60*60
 		beaconRadius = Db.shared.peek('game', 'beaconRadius')


changed method to give the last insert by date

--- server.coffee
+++ server.coffee
@@ -39,20 +39,17 @@

 getLastData = (connection, appId) ->
   console.log "querying for last insert"
-  connection.query('SELECT m.* from data m where m.pc=? and m.id=(select max(id) from data m2 where m2.pc=?)',[appId,appId], (err, rows, fields) ->
+  connection.query('SELECT m.* from data m where m.pc=? and m.dat=(select max(dat) from data m2 where m2.pc=?)',[appId,appId], (err, rows, fields) ->
     throw err if (err)
     console.log('Query result: ', rows)
-
     return rows
   )
-

 getAllData = (connection, appId) ->
   console.log "querying for all data"
   connection.query('SELECT m.* from data m where m.pc=?',[appId], (err, rows, fields) ->
     throw err if (err)
     console.log('Query result: ', rows)
-
     return rows
   )

@@ -62,6 +59,8 @@

 #getAllData(connection, 'pc1')
 #getAllData(connection, 'pc2')
+
+

 connection.end()


Fixed: wrong object used

Fixed: wrong object used

--- server.coffee
+++ server.coffee
@@ -16,7 +16,7 @@
         'fileSlug': fileSlug
         'fileName': fileName
         'channel': channel
-        'value': data.data.replace /(\[[0-9]+m)*/g, ""
+        'value': data.replace /(\[[0-9]+m)*/g, ""

 killCommands = (commands) ->
     for fileName, command of commands


Db.mod to Db.set

Db.mod to Db.set

--- server.coffee
+++ server.coffee
@@ -455,7 +455,7 @@
 	#Call the timer to reset the time in the correct endtime in the database
 	end = Db.shared.peek 'game', 'endTime'
 	if Db.shared.peek('game', 'newEndTime') isnt 0
-		Db.shared.modify 'game', 'newEndTime', (v) -> 0
+		Db.shared.set 'game', 'newEndTime', 0
 		Timer.cancel 'endGame', {}
 		Timer.set (end-Plugin.time())*1000, 'endGame', {}
 		# Cancel event


add misawa neta

add misawa neta

--- scripts/misawa.coffee
+++ scripts/misawa.coffee
@@ -16,3 +16,6 @@
     msg.send "http://imgcc.naver.jp/kaze/mission/USER/20120712/15/1022545/47/329x237x2c75f6b1e78fdf342e6180c2.jpg"
     msg.send "http://imgcc.naver.jp/kaze/mission/USER/20120712/15/1022545/47/329x237x2c75f6b1e78fdf342e6180c2.jpg"

+  robot.respond /orenobun$/i, (msg) ->
+    msg.send "http://imgcc.naver.jp/kaze/mission/USER/20120712/15/1022545/47/329x237x2c75f6b1e78fdf342e6180c2.jpg"
+    msg.send "http://imgcc.naver.jp/kaze/mission/USER/20120712/15/1022545/47/329x237x2c75f6b1e78fdf342e6180c2.jpg"


Comment out One.

Comment out One.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -137,6 +137,6 @@
             output3 = robot.brain.data('points.username')
             output4 = robot.brain.data('data.points.username')

-            outputs = ["Output 1: #{output1}, Output 2: #{output2}, Output 3: #{output3}, Output 4: #{output4}, Output 5: #{points[username]}"]
+            outputs = ["Output 2: #{output2}, Output 3: #{output3}, Output 4: #{output4}, Output 5: #{points[username]}"]

             msg.send "Brain output tests: #{outputs}"


Calculator fixes: [#3739033], [#3739040]

[#3739033] -- pi should multiply when against a number.
[#3739040] -- parentheticals should multiply when against a number.

--- javascripts/lib/math.coffee
+++ javascripts/lib/math.coffee
@@ -20,8 +20,8 @@
       @expression.push part

     # returns part of an expression
-    last: ->
-      _.last(@expression)
+    last: (from_end=0)->
+      @expression[@expression.length - 1 - from_end]

     first: ->
       _.first(@expression)
@@ -143,16 +143,16 @@
       (new ExpressionEvaluation(@subexpression)).results()

   class ExpressionComponent
-    handled: ->
-      @is_handled = true
-    isHandled: ->
-      @is_handled
+    isOperator: -> false
+    isNumber: -> false
     eval: -> @

   class Number extends ExpressionComponent
     initialize: (@opts)->
       @val = @opts.value
-
+
+    isNumber: -> true
+
     negated: ->
       value = @val * -1
       Number.build(value: value)
@@ -204,23 +204,9 @@

   class_mixer(Number)

-  class Square extends ExpressionComponent
-    toDisplay: -> "2"
-    eval: (evaluation, pass)->
-      return if pass != "exponentiation"
-      prev = evaluation.previousValue()
-      if prev
-        evaluation.handledPrevious()
-        squared = parseFloat(prev) * parseFloat(prev)
-        Number.build(value: squared)
-      else
-        throw "Invalid Expression"
-
-  class_mixer(Square)
-
-
   class Exponentiation extends ExpressionComponent
-    toDisplay: -> "^"
+    isOperator: -> true
+    toDisplay: -> 'ˆ'
     eval: (evaluation, pass)->
       return if pass != "exponentiation"

@@ -236,13 +222,14 @@

   class Pi extends ExpressionComponent
     toDisplay: -> "π"
-    eval: ->
+    eval: ()->
       Number.build(value: Math.PI)

   class_mixer(Pi)

   class Addition extends ExpressionComponent
     toDisplay: -> "+"
+    isOperator: -> true
     eval: (evaluation, pass)->
       return if pass != "addition"

@@ -258,6 +245,7 @@

   class Subtraction extends ExpressionComponent
     toDisplay: -> "-"
+    isOperator: -> true
     eval: (evaluation, pass)->
       return if pass != "addition"

@@ -273,6 +261,7 @@
   class_mixer(Subtraction)

   class Multiplication extends ExpressionComponent
+    isOperator: -> true
     eval: (evaluation, pass)->
       return if pass != "multiplication"
       prev = evaluation.previousValue()
@@ -287,6 +276,7 @@
   class_mixer(Multiplication)

   class Division extends ExpressionComponent
+    isOperator: -> true
     toDisplay: -> "÷"
     eval: (evaluation, pass)->
       return if pass != "multiplication"
@@ -317,6 +307,9 @@

   class_mixer(RightParenthesis)

+  ##############
+  # commands!
+  ###############

   class CalculateCommand
     invoke: (expression)->
@@ -353,7 +346,9 @@
   class NumberCommand
     initialize: (@opts)->
       @val = @opts.value
-    invoke: (expression)->
+
+    invoke: (expression)->
+      expression = _ImplicitMultiplication.build().onNeitherOperatorNorNumber(expression)
       last = expression.last()
       if last instanceof Number
         new_last = last.concatenate(@val)
@@ -398,7 +393,10 @@

   class LeftParenthesisCommand
     invoke: (expression)->
-      expression.cloneAndAppend(LeftParenthesis.build())
+      _ImplicitMultiplication.build().
+        onNumeric(expression).
+        cloneAndAppend(LeftParenthesis.build())
+
   class_mixer(LeftParenthesisCommand)

   class RightParenthesisCommand
@@ -411,14 +409,34 @@
       expression.cloneAndAppend(Division.build())
   class_mixer(DivisionCommand)

+
+  class _ImplicitMultiplication
+    onNumeric: (expression)->
+      last = expression.last()
+      if last && last.isNumber()
+        expression.cloneAndAppend(Multiplication.build())
+      else
+        expression
+
+    onNeitherOperatorNorNumber: (expression)->
+      last = expression.last()
+      if last and !last.isNumber()  and !last.isOperator() and !(last instanceof LeftParenthesis)
+        expression.cloneAndAppend(Multiplication.build())
+      else
+        expression
+
+  class_mixer(_ImplicitMultiplication)
+
   class PiCommand
     invoke: (expression)->
-      expression.cloneAndAppend(Pi.build())
+      _ImplicitMultiplication.build().
+        onNumeric(expression).
+        cloneAndAppend(Pi.build())
+
   class_mixer(PiCommand)

   class SquareRootCommand
     invoke: (expression)->
-
       value = expression.evaluate().value() || 0
       root = Math.sqrt(parseFloat(value))
       unless isNaN(root)


Some more syncing with windows

Some more syncing with windows

--- lib/models/synced-fs.coffee
+++ lib/models/synced-fs.coffee
@@ -41,10 +41,16 @@
         path: project.getPaths()[0]
       },
       file: {
-        path: file.path
+        path: this.formatFilePath(file.path)
         digest: file.digest,
       },
       buffer: {
         content: window.btoa(buffer.getText())
       }
     })
+
+  formatFilePath: (path) ->
+    if path.match(/:\\/)
+      return path.sub(/(.*:\\)/, '/').gsub('\\', '/')
+    else
+      return path


Just added text

Just added text

--- dev/server.coffee
+++ dev/server.coffee
@@ -5,4 +5,4 @@
 s = new server()

 s.run (app, server) ->
-    console.log app.get 'port'
+    console.log 'Connected on port : ' + app.get 'port'


Keep text selection on style-text

Keep text selection on style-text

Keep text selection if text was selected at the start. Closes #182

--- lib/commands/style-text.coffee
+++ lib/commands/style-text.coffee
@@ -28,10 +28,11 @@
     @editor = atom.workspace.getActiveTextEditor()
     @editor.transact =>
       @editor.getSelections().forEach (selection) =>
+        retainSelection = !selection.isEmpty()
         @normalizeSelection(selection)

         if text = selection.getText()
-          @toggleStyle(selection, text)
+          @toggleStyle(selection, text, select: retainSelection)
         else
           @insertEmptyStyle(selection)

@@ -43,12 +44,13 @@
     range = utils.getTextBufferRange(@editor, scopeSelector, selection)
     selection.setBufferRange(range)

-  toggleStyle: (selection, text) ->
+  toggleStyle: (selection, text, opts) ->
     if @isStyleOn(text)
       text = @removeStyle(text)
     else
       text = @addStyle(text)
-    selection.insertText(text)
+
+    selection.insertText(text, opts)

   insertEmptyStyle: (selection) ->
     selection.insertText(@style.before)


Coalesce monthly summary data for a category

--- app/assets/javascripts/models/category.js.coffee
+++ app/assets/javascripts/models/category.js.coffee
@@ -9,5 +9,30 @@
   transactions: Ember.hasMany 'App.Transaction',
     key: 'transactions'
     embedded: true
+  monthlySummary: ( ->
+    transactions = @get('transactions')
+    summary = {}
+    transactions.forEach (t) ->
+      date = t.get('posted_at')
+      date = new Date(date.getFullYear(), date.getMonth())
+      key = date.getTime()
+      amount = t.get('amount')
+      if summary[key]
+        summary[key].count += 1
+        summary[key].amount += amount
+      else
+        summary[key] =
+          count: 1
+          amount: amount
+          date: new Date(date.getFullYear(), date.getMonth())
+          key: key
+    months = []
+    for key,value of summary
+      months.push(value)
+      value.amount = Math.round(value.amount, 2)
+    months.sort (m1,m2) ->
+      m1.key - m2.key
+    months
+  ).property('transactions.length')
 App.Category.url = "/categories"
 App.Category.adapter = Ember.RESTAdapter.create()


Fix character count test

Fix character count test

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -148,12 +148,10 @@

   describe "when the associated editor's selection changes", ->
     it "updates the selection count in the status bar", ->
-      waitsForPromise ->
-        atom.workspace.open('sample.txt')
-
-      runs ->
-        editor.setSelectedBufferRange([0,0], [0,1])
-        expect(StatusBar.selectionCount.text()).toBe '(2)'
+      atom.workspaceView.attachToDom()
+      editor.setSelectedBufferRange([[0, 0], [0, 2]])
+      editorView.updateDisplay()
+      expect(StatusBar.selectionCount.text()).toBe '(2)'

   describe "git branch label", ->
     beforeEach ->


Disambiguate scripts

Disambiguate scripts

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -173,7 +173,8 @@
         sources = _.toArray context.sources
         scripts = _.toArray context.scripts
         emptySources = "the JS sources are empty"
-        assert context.scripts = _.unique scripts
+        u = (v) -> v.match(RegExp "^(.+)/(.+)$")[2]
+        assert context.scripts = _.unique scripts, u
         assert _.isArray sources = _.unique sources
         assert hasher = crypto.createHash "md5"
         joined = context.sources.join new String


add charset spec

add charset spec

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -300,6 +300,7 @@
         assert args = [symbol, request, yes, undefined]
         @assembleContext args..., (context, compiled) ->
             length = do -> compiled.length or undefined
+            assert _.isString response.charset = "utf-8"
             response.setHeader "Content-Length", length
             response.setHeader "Content-Type", "text/html"
             response.writeHead 200, STATUS_CODES[200]


Minor change in the compilation

Minor change in the compilation

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -67,10 +67,10 @@
     # the scope. This method supports JavaScript scripts as a link or
     # JavaScript sources passed in as the remote objects. Please refer
     # to the implementation and the class for more information on it.
-    require: (context, subject) ->
-        compile = -> subject.remote?.compile?()
+    require: (context, subject, symbol) ->
         scripts = -> context.scripts.push subject
         sources = -> context.sources.push compile()
+        compile = -> subject.remote?.compile? symbol
         invalid = "not a remote object and not a link"
         return sources() if _.isString subject.remote.source
         return scripts() if _.isString subject


feat(app generator): introduce `settings` as a static `@class` to yuidoc code documentation

--- generators/app/templates/src/app.coffee
+++ generators/app/templates/src/app.coffee
@@ -159,6 +159,15 @@
 ##  [madlib]
 ##

+###*
+#   The app's globally sharable configuration settings.
+#
+#   These are exposed through the `madlib-settings` singleton object. Simply `require(...)` it wherever you have a need for them.
+#
+#   @class      Settings
+#   @static
+###
+
 ##  https://github.com/Qwerios/madlib-settings#readme
 ##
 settings        = require( 'madlib-settings' )


80 is good.

80 is good.

--- spec/app/atom-spec.coffee
+++ spec/app/atom-spec.coffee
@@ -343,7 +343,7 @@
         expect(errors).toBe 0

         coverage = parseFloat results.pop().match(/.+?%/)
-        expect(coverage).toBeGreaterThan 85
+        expect(coverage).toBeGreaterThan 80

         # stderr
         expect(docRunner.argsForCall[0][2]).toBe ''


Ignore lines after the first jasmine line

--- spec/atom-reporter.coffee
+++ spec/atom-reporter.coffee
@@ -8,9 +8,11 @@
   return stackTrace unless stackTrace

   jasminePattern = /^\s*at\s+.*\(?.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
+  firstJasmineLinePattern = /^\s*at \/.*\/jasmine(-[^\/]*)?\.js:\d+:\d+\)?\s*$/
   convertedLines = []
   for line in stackTrace.split('\n')
     convertedLines.push(line) unless jasminePattern.test(line)
+    break if firstJasmineLinePattern.test(line)

   stackTrace = convertStackTrace(convertedLines.join('\n'), sourceMaps)
   lines = stackTrace.split('\n')


Try To Determine Cause Of Test Issue On AppVeyor

--- spec/executor-spec.coffee
+++ spec/executor-spec.coffee
@@ -14,15 +14,22 @@

   describe "when executing a command", ->

-    it "succeeds", ->
+    fit "succeeds", ->
       complete = false
       runs =>
         command = if os.platform() is 'win32' then 'dir' else 'ls'
-        done = (exitcode, stdout, stderr) =>
+        done = (exitcode, stdout, stderr, messages) =>
+          console.log exitcode
+          console.log stdout
+          console.log stderr
+          console.log messages
           expect(exitcode).toBeDefined
           expect(exitcode).toBe 0
-          expect(stdout).toBeUndefined
+          expect(stdout).toBeDefined
+          expect(stdout).not.toBe ''
           expect(stderr).toBeUndefined
+          expect(stderr).toBe ''
+          expect(_.size(messages)).toBe 0
           complete = true
         result = executor.exec(command, prefix, null, done, [pathhelper.home()])

@@ -33,12 +40,13 @@
       complete = false
       runs =>
         command = if os.platform() is 'win32' then path.resolve(__dirname, 'pwd.exe') else 'pwd'
-        done = (exitcode, stdout, stderr) =>
+        done = (exitcode, stdout, stderr, messages) =>
           expect(exitcode).toBeDefined
           expect(exitcode).toBe 0
           expect(stdout).toBeDefined
           expect(stdout).toBe pathhelper.home() + '\n'
           expect(stderr).toBeUndefined
+          expect(_.size(messages)).toBe 0
           complete = true
         result = executor.exec(command, pathhelper.home(), null, done, null)

@@ -49,12 +57,13 @@
       complete = false
       runs =>
         command = if os.platform() is 'win32' then path.resolve(__dirname, 'env.exe') else 'env'
-        done = (exitcode, stdout, stderr) =>
+        done = (exitcode, stdout, stderr, messages) =>
           expect(exitcode).toBeDefined
           expect(exitcode).toBe 0
           expect(stdout).toBeDefined
           expect(stdout).toContain 'testenv=testing\n' # TODO: ensure this works on Windows
           expect(stderr).toBeUndefined
+          expect(_.size(messages)).toBe 0
           complete = true
         env =
           testenv: 'testing'


Feature/1149 Google Analytics Event Bug  (#1177)

* tweaked google analytics

changed the order and removed double code from _head

* returned ga code

* removed page view from event sends

page views are sent per default per google analytics code.

--- app/assets/javascripts/analytics/presenter.coffee
+++ app/assets/javascripts/analytics/presenter.coffee
@@ -68,10 +68,10 @@
       @automatedTranslation = $('div').hasClass('Automated-translation')

   onBeforeUnload: =>
-    ga?('send', 'timing', 'PageView', 'total', @pageViewTime)
+    ga?('send', 'timing', 'total', @pageViewTime)
     if @goalOffset
       ga?(
-        'send', 'event', 'PageView', 'unload',
+        'send', 'event', 'unload',
         "goalViewed:#{!!@goalViewed};hadToScrollToGoal:#{!!@hasToScroll};" +
         "didScroll:#{!!@didScroll};" +
         "isGoogleTranslation:#{@automatedTranslation};",


Changed selector for results list

Changed selector for results list

--- app/assets/javascripts/analytics_shared.js.coffee
+++ app/assets/javascripts/analytics_shared.js.coffee
@@ -15,7 +15,7 @@
   $("a[href^='http://'], a[href^='https://']").click (event) ->
     $a = $(@)
     # Don't hijack href's in search results and URL's to malmo.se apps
-    if $a.parents('div.search').length is 0 and !$a.attr('href').match(/.malmo\.se\//)
+    if $a.parents('.search-and-results').length is 0 and !$a.attr('href').match(/.malmo\.se\//)
       ga('send', 'pageView', 'ExternalLink ' + $a.attr("href"))
       gaDelayEvent($a, event)


Potentially fixes phantom ad cards from being displayed

--- app/assets/javascripts/lib/core/ad_manager.coffee
+++ app/assets/javascripts/lib/core/ad_manager.coffee
@@ -99,7 +99,7 @@
         # End deprecated.

         pubAds.enableSingleRequest()
-        pubAds.collapseEmptyDivs()
+        pubAds.collapseEmptyDivs(true)
         googletag.enableServices()

         # Call the display method for all ads we've defined.


dumb commit literally just deleted newlines from search_interactions.coffee

--- app/assets/javascripts/search_interactions.coffee
+++ app/assets/javascripts/search_interactions.coffee
@@ -4,8 +4,6 @@
   minWidth: 400
 .multiselectfilter()
 $(".multiselect").multiselect("checkAll")
-
-

 # testing this from http://goo.gl/69VtL2  perhaps updating $ta will serve purpose?
 ta = $('#identifiers').typeahead


Update archive button whenever archived property changes

--- app/assets/javascripts/views/question_page.coffee
+++ app/assets/javascripts/views/question_page.coffee
@@ -121,7 +121,6 @@

     @creatorAvatar.attr('src', @question.creator().avatarUrl())
     @updateQuestionBody()
-    @updateToggleQuestionArchivedButton()
     question.getField('body').onChange => @updateQuestionBody()
     $(window).on 'resize', => @adjustTopOfMainDiv()

@@ -152,8 +151,11 @@

     @selectedRankingUserId = null

-    @question.getField('archived').onChange @updateArchivedClass
+    @question.getField('archived').onChange =>
+      @updateArchivedClass()
+      @updateToggleQuestionArchivedButton()
     @updateArchivedClass()
+    @updateToggleQuestionArchivedButton()

   show: ->
     $('#all-questions-link').show()


Damage earlier

Damage earlier

--- app/scripts/components/entities/LargeDrone.coffee
+++ app/scripts/components/entities/LargeDrone.coffee
@@ -33,9 +33,9 @@
     sprite = 0
     if @health < 155000
       sprite = 1
-    if @health < 145000
+    if @health < 147000
       sprite = 2
-    if @health < 135000
+    if @health < 140200
       sprite = 3

     @sprite(sprite, 0)


Adding the support for mulitple jquery objects

--- vendor/assets/javascripts/amoeba-components/combo_button.js.coffee
+++ vendor/assets/javascripts/amoeba-components/combo_button.js.coffee
@@ -1,8 +1,12 @@
 class Amoeba.Components.ComboButton
   constructor: (@$container, @options) ->
-    @on = if @$container.hasClass(@options.offState) then false else true
-    @$button = @$container.find(@options.button)
-    @bind()
+    if @$container.length == 1
+      @on = if @$container.hasClass(@options.offState) then false else true
+      @$button = @$container.find(@options.button)
+      @bind()
+    else
+      @$container.each (i, container) =>
+        new @constructor($(container), @options)

   bind: ->
     @$button.on('click', @onClick)


Custom marker support for Yandex.Maps

Custom marker support for Yandex.Maps

--- vendor/assets/javascripts/gmaps4rails/yandex/objects/marker.coffee
+++ vendor/assets/javascripts/gmaps4rails/yandex/objects/marker.coffee
@@ -11,18 +11,16 @@
     @controller = controller

     markerLatLng = @createLatLng(args.lat, args.lng)
-
-
+
+    @style_mark = {}
+    if args.marker_picture?
+      @_styleForCustomMarker(args)

     @serviceObject = new ymaps.Placemark(markerLatLng, {
         balloonContent: @description,
         iconContent: args.marker_title
         },
-        {
-            iconImageHref: args.marker_picture,
-            iconImageSize: [args.marker_width, args.marker_height],
-            iconImageOffset: args.marker_anchor
-        }
+        @style_mark
     );

     @_addToMap(@serviceObject)
@@ -43,3 +41,22 @@

   createInfoWindow : () ->
     @serviceObject.properties.set("balloonContent", @description) if @description?
+
+  _styleForCustomMarker: (args)->
+    @style_mark.iconImageHref = args.marker_picture
+    @style_mark.iconImageSize    = [args.marker_width, args.marker_height]
+    #//adding anchor if any
+    if args.marker_anchor?
+      @style_mark.iconImageOffset = args.marker_anchor
+    #//adding shadow if any
+    if args.shadow_picture?
+      @style_mark.iconShadow = true
+      @style_mark.iconShadowImageHref = args.shadow_picture
+      @style_mark.iconShadowImageSize = [args.shadow_width, args.shadow_height]
+      #//adding shadow's height/width
+      if args.shadow_width? and  args.shadow_height?
+        @style_mark.iconShadowOffset = [args.shadow_width, args.shadow_height]
+      #//adding shadow's anchor if any
+      if args.shadow_anchor?
+        @style_mark.iconShadowOffset = args.shadow_anchor
+


Providers: Softlayer: don't pass datacenter

--- workers/social/lib/social/models/computeproviders/softlayer.coffee
+++ workers/social/lib/social/models/computeproviders/softlayer.coffee
@@ -51,7 +51,7 @@
           meta            =
             type          : 'softlayer'
             storage_size  : storage
-            datacenter    : 'sjc01'
+          # datacenter    : 'hou02'
             alwaysOn      : no

           callback null, { meta, label, credential: client.r.user.username }


lod_by_time: Fix a couple of comments

lod_by_time: Fix a couple of comments

--- lod_by_time/lod_by_time.coffee
+++ lod_by_time/lod_by_time.coffee
@@ -381,7 +381,7 @@
        .x((t) -> effXscale(t))
        .y((t,i) -> effYscale(eff[pmari][i]))

-  # effect curve function
+  # confidence bands function
   seArea = (pmari) ->
     d3.svg.area()
           .x((t) -> effXscale(t))
@@ -397,7 +397,7 @@
              .attr("fill", "none")
              .attr("stroke-width", "2")

-  # plot SE area
+  # plot confidence bands
   sePlot = (pmari) ->
     panels[3].append("path").attr("id", "seArea")
              .datum(data.times) # every other time, to speed it up


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -4,6 +4,8 @@

 App.width = 640
 App.height = 480
+
+test "sh"

 test "should remove objects outside of activeBounds", 2, ->
   obj = GameObject


tests

tests

--- test/app/lifecycle.coffee
+++ test/app/lifecycle.coffee
@@ -2,7 +2,7 @@
 rewire = require 'rewire'
 lifecycle = rewire '../../lib/app/lifecycle'

-describe '#onLocalLogin', ->
+describe 'lifecycle', ->

   beforeEach ->
     @req = { body: {}, params: {}, query: {}, session: {} }
@@ -15,84 +15,106 @@
       loginPagePath: '/login'
       afterSignupPagePath: '/signup'

-  context 'when successful', ->
+  describe '#onLocalLogin', ->

-    beforeEach ->
+    context 'when successful', ->
+
+      beforeEach ->
+        @passport.authenticate.returns (req, res, next) -> next()
+
+      it 'authenticates locally and redirects home', ->
+        lifecycle.onLocalLogin @req, @res, @next
+        @passport.authenticate.args[0][0].should.equal 'local'
+        @res.redirect.args[0][0].should.equal '/'
+
+      it 'authenticates locally and redirects back', ->
+        @req.query['redirect-to'] = '/foobar'
+        lifecycle.onLocalLogin @req, @res, @next
+        @res.redirect.args[0][0].should.equal '/foobar'
+
+      it 'sends json for xhrs', ->
+        @req.xhr = true
+        @req.user = { toJSON: -> }
+        lifecycle.onLocalLogin @req, @res, @next
+        @res.send.args[0][0].success.should.equal true
+
+      it 'redirects signups to personalize', ->
+        @req.artsyPassportSignedUp = true
+        lifecycle.onLocalLogin @req, @res, @next
+        @res.redirect.args[0][0].should.equal '/signup'
+
+    context 'when erroring', ->
+
+      beforeEach ->
+        @passport.authenticate.returns (req, res, next) => next @err
+
+      it 'redirects invalid passwords to login', ->
+        @err = response: body: error_description: 'invalid email or password'
+        lifecycle.onLocalLogin @req, @res, @next
+        @res.redirect.args[0][0]
+          .should.equal '/login?error=Invalid email or password.'
+
+      it 'sends generic error to the error handler', ->
+        @err = new Error 'moo'
+        lifecycle.onLocalLogin @req, @res, @next
+        @next.args[0][0].message.should.equal 'moo'
+
+  describe '#onError', ->
+
+    it 'handles canceled twitter logins', ->
+      lifecycle.onError new Error('twitter denied'), @req, @res, @next
+      @res.redirect.args[0][0].should.equal '/login?error=Canceled twitter login'
+
+  describe '#onLocalSignup', ->
+
+    it 'creatse a user'
+
+    it 'suggests an email if its invalid and redirects back to the signup page'
+
+    it 'sends 500s as json for xhr requests'
+
+  describe '#beforeSocialAuth', ->
+
+    it 'creates a state param for twitter', ->
       @passport.authenticate.returns (req, res, next) -> next()
+      lifecycle.beforeSocialAuth('twitter')(@req, @res, @next)
+      @req.session.twitterState.length.should.be.above 5
+      @passport.authenticate.args[0][1].callbackURL
+        .should.containEql "state=#{@req.session.twitterState}"

-    it 'authenticates locally and redirects home', ->
-      lifecycle.onLocalLogin @req, @res, @next
-      @passport.authenticate.args[0][0].should.equal 'local'
-      @res.redirect.args[0][0].should.equal '/'
+    it 'asks for linked in profile info'

-    it 'authenticates locally and redirects back', ->
-      @req.query['redirect-to'] = '/foobar'
-      lifecycle.onLocalLogin @req, @res, @next
-      @res.redirect.args[0][0].should.equal '/foobar'
+    it 'asks for email scope if not linkedin'

-    it 'sends json for xhrs', ->
-      @req.xhr = true
-      @req.user = { toJSON: -> }
-      lifecycle.onLocalLogin @req, @res, @next
-      @res.send.args[0][0].success.should.equal true
+  describe '#afterSocialAuth', ->

-    it 'redirects signups to personalize', ->
-      @req.artsyPassportSignedUp = true
-      lifecycle.onLocalLogin @req, @res, @next
-      @res.redirect.args[0][0].should.equal '/signup'
+    it 'ensures a state param for twitter', ->
+      @req.query.state = 'foo'
+      @req.session.twitterState = 'bar'
+      @passport.authenticate.returns (req, res, next) -> next()
+      lifecycle.afterSocialAuth('twitter')(@req, @res, @next)
+      @next.args[0][0].message.should.equal 'Must pass a valid `state` param.'

-  context 'when erroring', ->
+    context 'with an error', ->

-    beforeEach ->
-      @passport.authenticate.returns (req, res, next) => next @err
+      it 'redirects back to the login page and explains the account ' +
+         'was previously linked'

-    it 'redirects invalid passwords to login', ->
-      @err = response: body: error_description: 'invalid email or password'
-      lifecycle.onLocalLogin @req, @res, @next
-      @res.redirect.args[0][0]
-        .should.equal '/login?error=Invalid email or password.'
+      it 'redirects to the settings page and explains if the account is linked ' +
+         'to another account'

-    it 'sends generic error to the error handler', ->
-      @err = new Error 'moo'
-      lifecycle.onLocalLogin @req, @res, @next
-      @next.args[0][0].message.should.equal 'moo'
+      it 'passes unknown errors'

-describe '#onLocalSignup', ->
+    context 'when successful', ->

-  it 'creatse a user'
+      it 'redirects to settings if linking'

-  it 'suggests an email if its invalid and redirects back to the signup page'
+      it 'redirects to the twitter last step if signing up with twitter'

-  it 'sends 500s as json for xhr requests'
+      it 'redirects to the personalize page if signing up without twitter'

-describe '#beforeSocialAuth', ->
+      it 'reidrects back if logging in'

-  it 'asks for linked in profile info'
+  describe '#ensureLoggedInOnAfterSignupPage', ->

-  it 'asks for email scope if not linkedin'
-
-describe '#afterSocialAuth', ->
-
-  context 'with an error', ->
-
-    it 'redirects back to the login page and explains the account ' +
-       'was previously linked'
-
-    it 'redirects to the settings page and explains if the account is linked ' +
-       'to another account'
-
-    it 'passes unknown errors'
-
-  context 'when successful', ->
-
-    it 'redirects to settings if linking'
-
-    it 'redirects to the twitter last step if signing up with twitter'
-
-    it 'redirects to the personalize page if signing up without twitter'
-
-    it 'reidrects back if logging in'
-
-describe '#ensureLoggedInOnAfterSignupPage', ->
-
-  it 'redirects to the login page without a user'
+    it 'redirects to the login page without a user'


Update check of error message for node 6.x

--- test/createProject.coffee
+++ test/createProject.coffee
@@ -44,9 +44,9 @@
   describe "when template directory does not exist", ->
     it "fails", ->
       createProject(targetDir, templateDir + "abc", getValues)
-        .then (->throw new Error "createProject should have failed."), (error) ->
-          assert.equal error.toString(), "Error: ENOENT, readdir '#{templateDir}abc'"
-
+        .then (->throw new Error "createProject should have failed."), (error) ->
+          assert.equal error.toString(), "Error: ENOENT: no such file or directory, scandir '#{templateDir}abc'"
+
   describe "when all is good", ->
     before ->
       if fs.existsSync expectedDir + "/package.jumpstart.json"


add annie +1

add annie +1

--- scripts/seal-of-approval.coffee
+++ scripts/seal-of-approval.coffee
@@ -52,7 +52,8 @@
  "http://i.imgur.com/M4R0O.gif",
  "http://i.imgur.com/d0l2c.gif",
  "http://i.minus.com/iUQsaEI35WLo3.gif",
- "http://24.media.tumblr.com/tumblr_ll5sj3LUoZ1qgjpfvo1_250.gif"
+ "http://24.media.tumblr.com/tumblr_ll5sj3LUoZ1qgjpfvo1_250.gif",
+ "http://mlkshk.com/r/OTQJ.gif"
 ]

 module.exports = (robot) ->


Change typeahead arg to -1 for unlimited files

--- src/ext/components/import-files-input.coffee
+++ src/ext/components/import-files-input.coffee
@@ -8,7 +8,7 @@

   tryImportFiles = ->
     specifiedPath = _specifiedPath()
-    _.requestFileGlob specifiedPath, 0, (error, result) ->
+    _.requestFileGlob specifiedPath, -1, (error, result) ->
       if error
         _exception error.data.errmsg
       else


Update utilities.coffee

Update utilities.coffee

--- test/lib/utilities.coffee
+++ test/lib/utilities.coffee
@@ -14,7 +14,14 @@

 		# The FLATTEN function takes any array and returns a new array with all the
 		# elements flattened into one dimension.
-
+
+		###
+			@Rich
+
+			These test don't always need to use DOM elements. Many of them interact
+			with native arrays or objects
+		###
+
 		.then ->

 			testArray = @.evaluate ->


Check for selectionStart property

Check for selectionStart property

Closes #2