a less misleading undo button, X

a less misleading undo button, X

--- obot.coffee
+++ obot.coffee
@@ -236,23 +236,13 @@
   return

 renderUndoButton = (ctx) ->
-  ics = inner_control_size
-  ctx.beginPath()
-  ctx.arc(0,0,ics*.3, -Math.PI/8, Math.PI/8, true)
-
-  arrowEnd =
-    x: Math.cos(-Math.PI/8)*ics*.3
-    y:-Math.sin(-Math.PI/8)*ics*.3
-  innerArrow =
-    x: arrowEnd.x + Math.cos(Math.PI*(1-1/8+.125))*ics*.3*.5
-    y: arrowEnd.y - Math.sin(Math.PI*(1-1/8+.125))*ics*.3*.5
-  outerArrow =
-    x: arrowEnd.x + Math.cos(Math.PI*(-1/8-.3))*ics*.3*.5
-    y: arrowEnd.y - Math.sin(Math.PI*(-1/8-.3))*ics*.3*.5
-
-  ctx.lineTo(innerArrow.x, innerArrow.y)
-  ctx.moveTo(arrowEnd.x, arrowEnd.y)
-  ctx.lineTo(outerArrow.x, outerArrow.y)
+  ics = inner_control_size*.35
+  ctx.beginPath()
+
+  ctx.moveTo(-ics,-ics)
+  ctx.lineTo(ics,ics)
+  ctx.moveTo(-ics,ics)
+  ctx.lineTo(ics,-ics)

   ctx.stroke()
   return


Remove video controls, use default youTube controls instead

--- common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
+++ common/lib/xmodule/xmodule/js/src/video/display/video_player.coffee
@@ -24,26 +24,12 @@
       @toggleFullScreen(event)

   render: ->
-    @control = new VideoControl el: @$('.video-controls')
-    @caption = new VideoCaption
-        el: @el
-        youtubeId: @video.youtubeId('1.0')
-        currentSpeed: @currentSpeed()
-        captionDataDir: @video.caption_data_dir
-    unless onTouchBasedDevice()
-      @volumeControl = new VideoVolumeControl el: @$('.secondary-controls')
-    @speedControl = new VideoSpeedControl el: @$('.secondary-controls'), speeds: @video.speeds, currentSpeed: @currentSpeed()
-    @progressSlider = new VideoProgressSlider el: @$('.slider')
-
     params = { allowScriptAccess: "always" };
     atts = { id: @videoid };
     youtubeId = @video.youtubeId()

-    window.onYouTubePlayerReady = (e) =>
-        @player = document.getElementById("#{@video.id}")
-
-    swfobject.embedSWF("http://www.youtube.com/apiplayer?video_id=#{youtubeId}&enablejsapi=1&version=3&playerapiid=ytplayer&controls=0",
-                       @video.id, "425", "356", "9", null, null, params, atts )
+    swfobject.embedSWF("http://www.youtube.com/v/#{youtubeId}?enablejsapi=1&version=3&playerapiid=ytplayer",
+                         @video.id, "425", "356", "9", null, null, params, atts )

   addToolTip: ->


spec garden for reversing the buffer selections :tulip:

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -2171,7 +2171,7 @@
               editor.setSelectedBufferRanges([[[1, 2], [1, 9]], [[3, 2], [3, 9]], [[5, 2], [5, 9]]])
               editor.moveLineDown()

-              expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [2, 9]], [[4, 2], [4, 9]], [[6, 2], [6, 9]]]
+              expect(editor.getSelectedBufferRanges()).toEqual [[[6, 2], [6, 9]], [[4, 2], [4, 9]], [[2, 2], [2, 9]]]
               expect(editor.lineTextForBufferRow(1)).toBe "    if (items.length <= 1) return items;"
               expect(editor.lineTextForBufferRow(2)).toBe "  var sort = function(items) {"
               expect(editor.lineTextForBufferRow(3)).toBe "    while(items.length > 0) {"
@@ -2192,7 +2192,7 @@
               editor.setSelectedBufferRanges([[[1, 2], [1, 6]], [[3, 0], [3, 4]], [[8, 0], [8, 3]]])
               editor.moveLineDown()

-              expect(editor.getSelectedBufferRanges()).toEqual [[[2, 2], [2, 6]], [[7, 0], [7, 4]], [[9, 0], [9, 3]]]
+              expect(editor.getSelectedBufferRanges()).toEqual [[[9, 0], [9, 3]], [[7, 0], [7, 4]], [[2, 2], [2, 6]]]
               expect(editor.lineTextForBufferRow(2)).toBe "  var sort = function(items) {"
               expect(editor.isFoldedAtBufferRow(3)).toBeTruthy()
               expect(editor.isFoldedAtBufferRow(4)).toBeTruthy()
@@ -2207,7 +2207,7 @@
             editor.setSelectedBufferRanges([[[3, 2], [3, 9]], [[3, 12], [3, 13]]])
             editor.moveLineDown()

-            expect(editor.getSelectedBufferRanges()).toEqual [[[4, 2], [4, 9]], [[4, 12], [4, 13]]]
+            expect(editor.getSelectedBufferRanges()).toEqual [[[4, 12], [4, 13]], [[4, 2], [4, 9]]]
             expect(editor.lineTextForBufferRow(3)).toBe "    while(items.length > 0) {"

     describe ".insertText(text)", ->


Fixed bug left over from refactoring.

Fixed bug left over from refactoring.

--- core/app/backbone/views/channels/auto_complete_channels_view.coffee
+++ core/app/backbone/views/channels/auto_complete_channels_view.coffee
@@ -17,7 +17,7 @@
     @initializeChildViews
       filter_on: 'slug_title'
       search_list_view: (options) -> new AutoCompleteSearchChannelsView(options)
-      search_collection_constructor: -> new TopicSearchResults
+      search_collection: -> new TopicSearchResults
       placeholder: -> Factlink.Global.t.channel_name.capitalize()

     @_results_view = new AutoCompleteResultsChannelsView(collection: @collection)


Hack around layout initialization order

Hack around layout initialization order

--- bokehjs/src/coffee/models/tools/toolbar_base.coffee
+++ bokehjs/src/coffee/models/tools/toolbar_base.coffee
@@ -95,7 +95,8 @@
     # Get the constraints from widget
     constraints = super()
     # Set the fixed size of toolbar
-    constraints.push(EQ(@_sizeable, -30))
+    if @_sizeable?
+      constraints.push(EQ(@_sizeable, -30))
     return constraints

   @define {


clone site objects seo set to empty object

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -656,12 +656,7 @@
               async.forEach objects, ((object, next) ->
                 data = object.val()
                 delete data._id
-                data.seo = {
-                  description: ''
-                  image: ''
-                  keywords: ''
-                  title: ''
-                }
+                data.seo = {}
                 data.site_id = dst_site.get('_id').val()
                 db.get('objects').insert data, (err) ->
                   exit err if err


trying sync

trying sync

--- lib/esl.coffee
+++ lib/esl.coffee
@@ -69,6 +69,33 @@

 #### CallServer Example
 #
+# From the dialplan, use
+#    <action application="socket" data="127.0.0.1:7000 async full"/>
+# to hand the call over to an ESL server.
+#
+# If you'd like to get realtime channel variables and synchronous commands, do
+#
+#     server = esl.createCallServer true
+#
+#     server.on 'CONNECT', (req,res) ->
+#       res.execute 'verbose_events', null, (req,res) ->
+#         # You may now access realtime variables as
+#         foo = req.body.variable_foo
+#         res.execute 'play-file', 'voicemail/vm-hello', (req,res) ->
+#           # This call with wait for the command to finish
+#           # Your application continues here
+#
+#     server.listen 7000
+#
+# Note that although the socket command was called (in the dialplan above) with
+# the "async" parameter, createCallServer is processing the commands synchronously.
+# Of course you can work synchronously without using verbose_events; you may still
+# obtain realtime values by using the API (lookup the "eval" command).
+# However you cannot use verbose_events asynchronously, unless you capture the
+# CHANNEL_EXECUTE_COMPLETE event.
+#
+# An asynchronous server will look this way:
+#
 #     server = esl.createCallServer()
 #
 #     server.on 'CONNECT', (req,res) ->
@@ -88,9 +115,6 @@
 #
 #     # Start the ESL server on port 7000.
 #     server.listen 7000
-#     # From the dialplan, use
-#     #   <action application="socket" data="127.0.0.1:7000 async full"/>
-#     # to hand the call over to this ESL server.
 #

 #### Headers parser
@@ -209,6 +233,18 @@
 class eslResponse
   constructor: (@socket) ->

+  # If a callback is provided we attempt to trigger it
+  # when the operation completes.
+  # By default, the trigger is done as soon as the ESL command
+  # finishes.
+  intercept_response: (command,args,cb) ->
+    # Make sure we are the only one receiving command replies
+    @socket.removeAllListeners('esl_command_reply')
+    @socket.removeAllListeners('esl_api_response')
+    # Register the callback for the proper event types.
+    @socket.on 'esl_command_reply', cb
+    @socket.on 'esl_api_response', cb
+
   # A generic way of sending commands back to FreeSwitch.
   #
   #      send (string,hash,function(req,res))
@@ -219,13 +255,8 @@
       if exports.debug
         util.log util.inspect command: command, args: args

-      # Make sure we are the only one receiving command replies
-      @socket.removeAllListeners('esl_command_reply')
-      @socket.removeAllListeners('esl_api_response')
-      # Register the callback for the proper event types.
       if cb?
-        @socket.on 'esl_command_reply', cb
-        @socket.on 'esl_api_response', cb
+        @intercept_reponse command, args, cb

       # Send the command out.
       @socket.write "#{command}\n"
@@ -367,7 +398,13 @@
 #### Connection Listener (socket events handler)
 # This is modelled after Node.js' http.js

-connectionListener= (socket) ->
+connectionListener= (socket,intercept_response) ->
+
+  new_response = ->
+    res = new eslResponse socket
+    if intercept_response?
+      res.intercept_response = intercept_response
+
   socket.setEncoding('ascii')
   parser = new eslParser socket
   socket.on 'data', (data) ->  parser.on_data(data)
@@ -412,20 +449,22 @@
         event = headers['Content-Type']
     # Build request and response and send them out.
     req = new eslRequest headers,body
-    res = new eslResponse socket
+    res = new_response()
+    if intercept_response?
+      res.intercept_response = intercept_response
     if exports.debug
       util.log util.inspect event:event, req:req, res:res
     socket.emit event, req, res
   # Get things started
-  socket.emit 'esl_connect', new eslResponse socket
+  socket.emit 'esl_connect', new_response()

 #### ESL Server

 class eslServer extends net.Server
-  constructor: (requestListener) ->
+  constructor: (requestListener,intercept_response) ->
     @on 'connection', (socket) ->
       socket.on 'esl_connect', requestListener
-      connectionListener socket
+      connectionListener socket, intercept_response
     super()

 # You can use createServer(callback) from your code.
@@ -433,9 +472,9 @@

 #### ESL client
 class eslClient extends net.Socket
-  constructor: ->
+  constructor: (intercept_response) ->
     @on 'connect', ->
-      connectionListener @
+      connectionListener @, intercept_response
     super()

 exports.createClient = -> return new eslClient()
@@ -446,11 +485,26 @@
 # [prepaid code](http://stephane.shimaore.net/git/?p=ccnq3.git;a=blob;f=applications/prepaid/)
 # and handles the nitty-gritty of setting up the server properly.

-exports.createCallServer = ->
+synchronous_intercept_response: (command,args,cb) ->
+  if command is 'sendmsg' and args['call-command'] is 'execute'
+    @socket.on 'CHANNEL_EXECUTE_COMPLETE', cb
+  else
+    # Make sure we are the only one receiving command replies
+    @socket.removeAllListeners('esl_command_reply')
+    @socket.removeAllListeners('esl_api_response')
+    # Register the callback for the proper event types.
+    @socket.on 'esl_command_reply', cb
+    @socket.on 'esl_api_response', cb
+
+exports.createCallServer = (synchronous) ->
+
+    intercept_response = null
+    if synchronous
+      intercept_response = synchronous_intercept_response

     Unique_ID = 'Unique-ID'

-    server = new eslServer (res) ->
+    listener = (res) ->
       if exports.debug
         util.log "Incoming connection"
         util.log util.inspect res
@@ -494,3 +548,5 @@
               req.channel_data = channel_data
               req.unique_id = unique_id
               server.emit 'CONNECT', req, res
+
+    server = new eslServer listener, intercept_response


run: remove unused code

run: remove unused code

--- lib/run.coffee
+++ lib/run.coffee
@@ -25,7 +25,6 @@
   @setMaxListeners 100
   @config = config
   @params = params
-  # @tree = new Tree
   setImmediate =>
     # Work on each server
     contexts = {}


Fix KDToggleButton and FollowButton

Fix KDToggleButton and FollowButton

--- components/buttons/KDToggleButton.coffee
+++ components/buttons/KDToggleButton.coffee
@@ -24,7 +24,14 @@

   decorateState:(name)->
     @setTitle @state.title
-    @setIconClass @state.iconClass if @state.iconClass?
+    @setIconClass @state.iconClass  if @state.iconClass?
+
+    if @state.cssClass? or @lastUsedCssClass?
+      @unsetClass @lastUsedCssClass  if @lastUsedCssClass?
+      @setClass @state.cssClass
+      @lastUsedCssClass = @state.cssClass
+    else
+      delete @lastUsedCssClass

   getState:-> @state

@@ -39,9 +46,12 @@

   toggleState:(err)->

-    {states} = @getOptions()
+    {states}  = @getOptions()
     nextState = states[@stateIndex + 1] or states[0]
     unless err
       @setState nextState.title
     else
-      warn err.msg or "there was an error, couldn't switch to #{nextState} state!"
+      warn err.message or \
+        "There was an error, couldn't switch to #{nextState.title} state!"
+
+    @hideLoader?()


Fixed incorrect import paths in Windows

Fixed incorrect import paths in Windows

path.relative was (correctly) returning Windows style backslash paths
when running Atom on Windows. However this results in incorrect
TypeScript import paths (which are always unix style forward slash
paths)

--- lib/typescript-import.coffee
+++ lib/typescript-import.coffee
@@ -88,7 +88,7 @@
       symbolLocation = @index[selection]
       if symbolLocation && selection
         fileFolder = path.resolve(filePath + '/..')
-        relative = path.relative(fileFolder, symbolLocation).replace(/\.(js|ts)$/, '');
+        relative = path.relative(fileFolder, symbolLocation).replace(/\.(js|ts)$/, '').replace(/\\/g, '/')
         importClause = "import #{selection} from './#{relative}';\n"
         @addImportStatement(importClause)
 #        editor.insertText(selection + "\nimport #{selection} from './#{relative}'")


Fix initial choice of order from querystring

--- backbone.tableview.coffee
+++ backbone.tableview.coffee
@@ -75,6 +75,14 @@

         <% } %>
     """
+    columnsTemplate: _.template """
+        <% _.each(model, function (col, key) { %>
+            <th abbr="<%= key || col %>"
+             class="<%= !col.nosort && "sorting" %> <%= ((key || col) == data.sort_col) && "sorting_" + data.sort_dir %> <%= col.className || "" %>">
+                <%= col.header || col %>
+
+        <% }) %>
+    """
     template: _.template """

@@ -92,11 +100,7 @@

-                    <% _.each(columns, function (col, key) { %>
-                        <th abbr="<%= key || col %>" class="<%= !col.nosort && "sorting" %> <%= col.className || "" %>">
-                            <%= col.header || col %>
-
-                    <% }) %>
+                    <%= columns %>

@@ -256,10 +260,10 @@
     # and trigger an update of the collection
     render: =>
         @$el.html @template
-            columns:    @columns
             empty:      @empty or ""
             title:      @applyTemplate @titleTemplate,      @title
             search:     @applyTemplate @searchTemplate,     @search
+            columns:    @applyTemplate @columnsTemplate,    @columns
             pagination: @applyTemplate @paginationTemplate, @pagination

         @filters = _.map(@filters, (filter, name) => @createFilter(name, filter))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -37,6 +37,8 @@
     else if index is 3
       equals obj, obj4

+
+
 test "#find", ->
   engine = Engine()


extended Planning DSL example

extended Planning DSL example

--- test/integrationSpec.coffee
+++ test/integrationSpec.coffee
@@ -186,28 +186,57 @@
       JE.run(context)

-## Planning DSL example:
-#
+# # Planning DSL example:
+# #
+# ## on CoffeeScript
+# #
 # describe 'TestModule', ->
 #   subject.is func.module(modArgs)      # func and modArgs will be defined later
-#
-## subject from outer describe should be accessible in the inner describe,
-## and was defined with `args` and `func` defined in the inner describe
-#
+
+# # subject from outer describe should be accessible in the inner describe,
+# # and was defined with `args` and `func` defined in the inner describe
+
 #   describe 'inner one', ->
 #     func   .is  -> new TestModule(args) # func == new TestModule('empty')
 #     modArgs.are [args]                  # modArgs == ['empty']
 #     args   .are 'empty'
-#
-#     it -> expect(subject).toBeTruthy()   # subject == (new TestModule('empty')).module(['test'])
-#     it -> expect(func).toBeDefined()
-#
+
+#     expected(subject).toBeTruthy()   # subject == (new TestModule('empty')).module(['test'])
+#     expected(func).toBeDefined()
+
 #   describe 'inner two', ->
 #     func   .is  -> new TestModule(args) # func == new TestModule('another')
 #     modArgs.are ['one', 'two']          # modArgs == ['one', 'two']
 #     args   .are 'another'
-#
-#     it -> expect(subject).toBeTruthy() # subject == (new TestModule('another')).module(['one', 'two'])
-#     it -> expect(func).toBeDefined()
-#
-## less code && less duplication == Profit!+
+#     expected(subject).toBeTruthy() # subject == (new TestModule('another')).module(['one', 'two'])
+#     expected(func).toBeDefined()
+# #
+# ## on JavaScript
+# #
+# describe('TestModule', function(){
+#   subject.is(func.module(modArgs));      # func and modArgs will be defined later
+
+# # subject from outer describe should be accessible in the inner describe,
+# # and was defined with `args` and `func` defined in the inner describe
+
+#   describe('inner one', function(){
+#     func.is(function(){ new TestModule(args) }); # func == new TestModule('empty')
+#     modArgs.are([args]);                  # modArgs == ['empty']
+#     args.are('empty');
+
+#     expected(subject).toBeTruthy();   # subject == (new TestModule('empty')).module(['test'])
+#     expected(func).toBeDefined();
+#   });
+
+#   describe('inner two', function(){
+#     func.is(function(){ new TestModule(args) }); # func == new TestModule('another')
+#     modArgs.are(['one', 'two']);          # modArgs == ['one', 'two']
+#     args.are('another');
+
+#     expected(subject).toBeTruthy(); # subject == (new TestModule('another')).module(['one', 'two'])
+#     expected(func).toBeDefined();
+#   });
+# });
+# #
+# # less code && less duplication == Profit!


added some tests

added some tests

--- test/tests/ColorTest.coffee
+++ test/tests/ColorTest.coffee
@@ -1,6 +1,6 @@
 describe "Color", ->

-	it "should allow rgb input", ->
+	it "should allow rgb input as object", ->

 		r = 140
 		g = 200
@@ -15,6 +15,35 @@
 		color.g.should.equal g
 		color.b.should.equal b

+	it "should allow rgb input as numbers", ->
+
+		color = new Color(0, 255, 0, 1)
+
+		color.r.should.equal 0
+		color.g.should.equal 255
+		color.b.should.equal 0
+
+	it "should allow rgba string in css format", ->
+
+		color = new Color "rgba(255, 0, 0, 1)"
+		color.r.should.equal 255
+
+	it "should allow hsl string in css format", ->
+
+		color = new Color "hsla(0, 0, 100, 0.5)"
+		color.r.should.equal 255
+		color.g.should.equal 255
+		color.b.should.equal 255
+		color.a.should.equal 0.5
+
+	it "should allow hsl string in css format with % for saturation and lightness", ->
+
+		color = new Color "hsla(0, 0%, 100%, 0.5)"
+		color.r.should.equal 255
+		color.g.should.equal 255
+		color.b.should.equal 255
+		color.a.should.equal 0.5
+
 	it "should be transparent when constructor option is null", ->

 		color = new Color null
@@ -25,6 +54,16 @@
 		hexColor = "#28affa"
 		color = new Color hexColor
 		color.toHexString().should.eql hexColor
+
+	it "should mix colors", ->
+
+		Red = new Color "red"
+		Yellow = new Color "yellow"
+		Orange = Red.mix(Yellow)
+
+		Orange.r.should.equal 255
+		Orange.g.should.equal 127.5
+		Orange.b.should.equal 0

 	it "should clamp constructor options", ->


improve test syntax

improve test syntax

--- test/tests/ColorTest.coffee
+++ test/tests/ColorTest.coffee
@@ -199,15 +199,15 @@
 	it "should mix with css format color strings", ->

 		mix = Color.mix("red", "yellow")
-		Color.isColorObject(mix).should.eql true
+		Color.isColorObject(mix).should.be.true

 	it "should mix color if only one input is color", ->

 		mix = Color.mix("red")
-		Color.isColorObject(mix).should.eql true
+		Color.isColorObject(mix).should.be.true

 		mix = Color.mix("redfjkdsajfalfa", "yellow")
-		Color.isColorObject(mix).should.eql true
+		Color.isColorObject(mix).should.be.true

 	it "should return css name if possible", ->

@@ -221,12 +221,12 @@
 	it "should return false if not css name", ->

 		color = new Color "#123456"
-		color.toName().should.eql false
+		color.toName().should.be.false

 	it "should return random color", ->

 		color = Color.random()
-		Color.isColorObject(color).should.eql true
+		Color.isColorObject(color).should.be.true

 	it "should check if valid color or color Object", ->


fixed some utilities tests.

fixed some utilities tests.

--- tests/unit/utilities.coffee
+++ tests/unit/utilities.coffee
@@ -74,15 +74,8 @@
           element.zanzabar = "zulu"
           testObject.push element

-        arr = junction.getKeys testObject, "zulu"
-        # arr = junction.getKeys testObject[0], "zulu" # <--- THIS WORKS
+        arr = junction.getKeys testObject[0], "zulu"
         return arr
-
-      @.echo testArray
-      @.echo "This doesn't work if I just pass the testObject object to the"
-      @.echo "getKeys function. It DOES work if I pass just one specific item"
-      @.echo "testObject doesn't work. testObject[0] does."
-      @.echo "I would think I would be able to pass the whole object."

       testArray[0].should.equal("zanzabar")

@@ -98,13 +91,9 @@
     casper.thenOpen 'http://localhost:3000?testing=all&thething=all&theotherthing=three', () ->

       testThing = @.evaluate ->
-        return junction.getQueryVariable "all"
+        return junction.getQueryVariable "testing"

-      @.echo testThing
-      @.echo "testThing is returning null. I think the problem is somewhere in"
-      @.echo "setting the results in the function. Maybe."
-
-      testThing[0].should.equal("testing")
+      testThing[0].should.equal("testing=all")

     return

@@ -129,23 +118,26 @@

   # The ISELEMENTINVIEW function determines if the element is in the view.

-  # it 'ISELEMENTINVIEW method should work', ->
-  #
-  #   casper.then ->
-  #
-  #     @.capture("../images/test.png")
-  #     testing = @.viewport(1024, 768).then ->
-  #       @.capture("../images/test2.png")
-  #       @.evaluate ->
-  #         testThing = junction("#isElement")
-  #         __utils__.echo testThing
-  #         return junction.isElementInView testThing[0]
-  #
-  #     @.echo testing
-  #
-  #     testing.should.be.true
-  #
-  #   return
+  it 'ISELEMENTINVIEW method should work', ->
+
+    # casper.then ->
+    casper.viewport(1024, 768).then ->
+
+      @.capture("../images/test.png")
+      testing = @.evaluate ->
+        testThing = junction("#isElement")
+        __utils__.echo testThing
+        return junction.isElementInView testThing
+      # testing = @.viewport(1024, 768).then ->
+      #   @.capture("../images/test2.png")
+      #   @.evaluate ->
+      #     testThing = junction("#isElement")
+      #     return junction.isElementInView testThing[0]
+
+      @.echo testing
+      testing.should.be.true
+
+    return

   # ISMOBILE ------------------------------------------------------------------


Reduce line length to below 80

Reduce line length to below 80

--- app._coffee
+++ app._coffee
@@ -74,7 +74,8 @@
 handleURL = (res, url, _) ->
   if not url?
       res.json 400, error:
-        message: 'Please give us the full URL, including the "http://" or "https://".'
+        message: 'Please give us the full URL,
+          including `http://` or `https://`.'
       return false

     content = Content.getByURL url, _


fix(spellcheck): Don’t include cld sources in build

--- build/tasks/copy-files-for-build-task.coffee
+++ build/tasks/copy-files-for-build-task.coffee
@@ -144,10 +144,11 @@
     ignoredPaths.push "#{escapeRegExp(path.sep)}linker\\.lock$"
     ignoredPaths.push "#{escapeRegExp(path.join('build', 'Release') + path.sep)}.+\\.node\\.dSYM"

-    # Hunspell dictionaries are only not needed on OS X.
-    if process.platform is 'darwin'
-      ignoredPaths.push path.join('spellchecker', 'vendor', 'hunspell_dictionaries')
+    # https://github.com/paulcbetts/node-cld#warning
+    ignoredPaths.push path.join('@paulcbetts', 'cld', 'deps', 'cld')
+
     ignoredPaths = ignoredPaths.map (ignoredPath) -> "(#{ignoredPath})"
+

     testFolderPattern = new RegExp("#{escapeRegExp(path.sep)}te?sts?#{escapeRegExp(path.sep)}")
     exampleFolderPattern = new RegExp("#{escapeRegExp(path.sep)}examples?#{escapeRegExp(path.sep)}")


fix typo

fix typo

--- views/components/ship-parts/topalert.cjsx
+++ views/components/ship-parts/topalert.cjsx
@@ -54,14 +54,14 @@
       if item.api_type[3] in [6, 7, 8]
         tempTyku += Math.sqrt(ship.api_onslot[slotId]) * item.api_tyku
         tempTyku += aircraftLevelBonus[item.api_type[3]][item.api_alv]
-        minTyku = Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv] / 10))
-        maxTyku = Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv + 1] / 10))
+        minTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv] / 10))
+        maxTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv + 1] / 10))

       else if item.api_type[3] == 10 && (item.api_type[2] == 11 || item.api_type[2] == 45)
         tempTyku += Math.sqrt(ship.api_onslot[slotId]) * item.api_tyku
         tempTyku += aircraftLevelBonus[item.api_type[2]][item.api_alv]
-        minTyku = Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv] / 10))
-        maxTyku = Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv + 1] / 10))
+        minTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv] / 10))
+        maxTyku += Math.floor(tempTyku + Math.sqrt(aircraftExpTable[item.api_alv + 1] / 10))

   min: minTyku
   max: maxTyku


removed panning when marker dragged

removed panning when marker dragged

--- src/main/webapp/lib/views/MapMarkerView.coffee
+++ src/main/webapp/lib/views/MapMarkerView.coffee
@@ -91,7 +91,6 @@
   dragMarker: ->
     @latLng = @marker.getPosition()
     @marker.setTitle @_getTitle()
-    $a.map.panTo(@latLng)
     @model.updatePosition(@latLng)

   ################# The following handles the show and hide of node layers


use coffeescript file for app config

use coffeescript file for app config

--- app/main.coffee
+++ app/main.coffee
@@ -13,4 +13,4 @@
         'angularAMD': ['angular']
         'angular-route': ['angular']
     deps:
-        ['app']+        ['cs!app']


Crossfade region: allow multiple resetFade calls, and ignore those older than the current state.

--- core/app/backbone/factlink/cross_fade_region.coffee
+++ core/app/backbone/factlink/cross_fade_region.coffee
@@ -2,21 +2,28 @@

 class Backbone.Factlink.CrossFadeRegion extends Backbone.Marionette.Region

-  defaultFadeTime: 560
+  defaultFadeTime = 560
+
+  initialize: ->
+    @_stateToken = 0
+    @_fadeTime = @options?.fadeTime || defaultFadeTime

   crossFade: (newView) ->
     if @currentView
-      @$el.fadeOut @fadeTime(), => @show newView
+      @$el.fadeOut @_fadeTime, => @show newView
     else
       @show(newView)

   open: (view) ->
     @$el.hide()
     @$el.html view.el
-    @$el.fadeIn @fadeTime()
+    ++@_stateToken
+    @$el.fadeIn @_fadeTime
+

   resetFade: ->
-    @$el.fadeOut @fadeTime(), => @reset()
+    return if !@$el #if there's no element, nothing's been shown.
+    initialState = ++@_stateToken
+    @$el.fadeOut @_fadeTime, => @reset() if initialState == @_stateToken

-  fadeTime: ->
-    @options?.fadeTime || @defaultFadeTime
+


Add talk-moderations route back in

Add talk-moderations route back in

- Noticed it got lost in the latest routing changes

--- app/router.cjsx
+++ app/router.cjsx
@@ -64,6 +64,7 @@
     <Route name="talk-recents" path="recents/?" handler={require './talk/recents'} />
     <Route name="talk-not-found" path="not-found/?" handler={require './pages/not-found'} />
     <Route name="talk-search" path="search/?" handler={require './talk/search'} />
+    <Route name="talk-moderations" path="moderations/?" handler={require './talk/moderations'} />
     <Route name="talk-board" path=":board/?" handler={require './talk/board'} />
     <Route name="talk-board-recents" path="recents/:board/?" handler={require './talk/recents'} />
     <Route name="talk-discussion" path=":board/:discussion/?" handler={require './talk/discussion'} />


Use default invite code in register form for testing

--- client/app/MainApp/login/registerform.coffee
+++ client/app/MainApp/login/registerform.coffee
@@ -188,7 +188,7 @@
         name          : "inviteCode"
         forceCase     : "lowercase"
         placeholder   : "your code..."
-        # defaultValue  : "111"
+        defaultValue  : "newkoding"
         validate      :
           container   : this
           event       : "blur"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -32,6 +32,6 @@
     x: 9
     y: 10

-  equals engine.f
+  equals engine.first('.ty')

 module()


removing console.logs

removing console.logs

--- src/helpers/wikidata_time_to_date_object.coffee
+++ src/helpers/wikidata_time_to_date_object.coffee
@@ -1,8 +1,6 @@
 module.exports = (wikidataTime)->
   sign = wikidataTime[0]
   rest = wikidataTime[1..-1]
-  console.log 'sign', sign
-  console.log 'rest', rest
   date = fullDateData sign, rest

   if date.toString() is 'Invalid Date'


Style cleanup and better test

Style cleanup and better test

--- test/collections/partner_artist_artworks.coffee
+++ test/collections/partner_artist_artworks.coffee
@@ -7,12 +7,13 @@

   beforeEach ->
     @artwork = fabricate 'artwork'
-    @partner_artist_artwork = { artwork: @artwork, partner_artist: fabricate('artist'), position: 1 }
+    @partnerArtistArtwork = { artwork: @artwork, partner_artist: fabricate('artist'), position: 1 }
     @artworks = new Artworks [@artwork]
-    @partner_artist_artworks = new PartnerArtistArtworks [@partner_artist_artworks]
+    @partnerArtistArtworks = new PartnerArtistArtworks [@partnerArtistArtwork]

   describe '#serialize', ->

     it 'plucks artworks from the PartnerArtistArtwork results', ->
-      @partner_artist_artworks.first.should.equal @artworks.first
+      @partnerArtistArtworks.first().get('artwork').id.should.equal @artworks.first().get('id')

+


Remove needless expects assertions.

Remove needless expects assertions.

--- tests/batman/view/formfor_rendering_test.coffee
+++ tests/batman/view/formfor_rendering_test.coffee
@@ -5,7 +5,7 @@
     @User = class User extends MockClass
       name: 'default name'

-asyncTest 'it should pull in objects for form rendering', 1, ->
+asyncTest 'it should pull in objects for form rendering', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -18,7 +18,7 @@
     equal $('input', node).val(), "default name"
     QUnit.start()

-asyncTest 'it should update objects when form rendering', 1, ->
+asyncTest 'it should update objects when form rendering', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -36,7 +36,7 @@

     QUnit.start()

-asyncTest 'it should update the context for the form if the context changes', 2, ->
+asyncTest 'it should update the context for the form if the context changes', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -51,7 +51,7 @@

     QUnit.start()

-asyncTest 'it should add the errors class to an input bound to a field on the subject', 2, ->
+asyncTest 'it should add the errors class to an input bound to a field on the subject', ->
   source = '''
   <form data-formfor-user="instanceOfUser">
     <input type="text" data-bind="user.name">
@@ -87,7 +87,7 @@

     QUnit.start()

-asyncTest 'it should add the error list HTML to the default selected node', 3, ->
+asyncTest 'it should add the error list HTML to the default selected node', ->
   source = '''
   <form data-formfor-user="instanceOfUser">

@@ -109,7 +109,7 @@

-asyncTest 'it should only show the errors list when there are errors', 2, ->
+asyncTest 'it should only show the errors list when there are errors', ->
   source = '''
   <form data-formfor-user="instanceOfUser">

@@ -128,7 +128,7 @@

     QUnit.start()

-asyncTest 'it shouldn\'t override already existing showif bindings on the errors list', 2, ->
+asyncTest 'it shouldn\'t override already existing showif bindings on the errors list', ->
   source = '''
   <form data-formfor-user="instanceOfUser">

@@ -148,7 +148,7 @@

     QUnit.start()

-asyncTest 'it should add the error list HTML to a specified selected node', 3, ->
+asyncTest 'it should add the error list HTML to a specified selected node', ->
   source = '''
   <form data-formfor-user="instanceOfUser" data-errors-list="#testy">


Fixed bug with hanging failed APK installing

--- cli/tasks/run/android.coffee
+++ cli/tasks/run/android.coffee
@@ -36,6 +36,7 @@
             logLine.stop()
             apkFiles = cp.execSync "ls -l #{dir}"
             log.debug "Available APK files: #{apkFiles}"
+            callback err
             return
         logLine.ok 'APK installed'
         logLine.stop()


KD: .getSingleton shortcut .singleton added

--- client/Main/KD.extend.coffee
+++ client/Main/KD.extend.coffee
@@ -1,8 +1,9 @@
 # this class will register itself just before application starts loading, right after framework is ready
 KD.extend

-  apiUri  : KD.config.apiUri
-  appsUri : KD.config.appsUri
+  apiUri    : KD.config.apiUri
+  appsUri   : KD.config.appsUri
+  singleton : KD.getSingleton.bind KD

   impersonate : (username)->
     KD.remote.api.JAccount.impersonate username, (err)->


Client: app: remote: replace dash usage with async.parallel

--- client/app/lib/remote.coffee
+++ client/app/lib/remote.coffee
@@ -1,9 +1,9 @@
 globals = require 'globals'
-kd = require 'kd'
+kd      = require 'kd'
 kookies = require 'kookies'
-Bongo = require '@koding/bongo-client'
-broker = require 'broker-client'
-sinkrow = require 'sinkrow'
+Bongo   = require '@koding/bongo-client'
+broker  = require 'broker-client'
+async   = require 'async'

 getSessionToken = -> kookies.get 'clientId'
@@ -42,7 +42,7 @@
               }]
           models = []
           err = null
-          queue = name.slugs.map (slug) => =>
+          queue = name.slugs.map (slug) => (fin) =>
             selector = {}
             selector[slug.usedAsPath] = slug.slug
             @api[slug.constructorName].one? selector, (err, model)->
@@ -53,9 +53,9 @@
                     "Unable to find model: #{nameStr} of type #{name.constructorName}"
                 else
                   models.push model
-                queue.fin()
+                fin()

-          sinkrow.dash queue, =>
+          async.parallel queue, =>
             @emit "modelsReady"
             cache[nameStr] = models
             callback err, models, name


Fix route

Fix route

--- client/helpers/router.coffee
+++ client/helpers/router.coffee
@@ -16,3 +16,5 @@
     path: '/need/:_id'
     data: ->
       Needs.findOne @params._id
+    waitOn: ->
+      Meteor.subscribe('needs')


Routes: Use "isTeamReactSide" instead of "isKoding"

--- client/ide/lib/routes.coffee
+++ client/ide/lib/routes.coffee
@@ -9,6 +9,7 @@
 lazyrouter      = require 'app/lazyrouter'
 dataProvider    = require 'app/userenvironmentdataprovider'
 registerRoutes  = require 'app/util/registerRoutes'
+isTeamReactSide = require 'app/util/isTeamReactSide'

 selectWorkspaceOnSidebar = (data) ->
@@ -19,10 +20,10 @@

   return no if not machine or not workspace

-  if isKoding()
+  if isTeamReactSide()
+    actions.setSelectedWorkspaceId workspace._id
+  else
     kd.getSingleton('mainView').activitySidebar.selectWorkspace data
-  else
-    actions.setSelectedWorkspaceId workspace._id

   storage = kd.singletons.localStorageController.storage 'IDE'


Add more tests

Add more tests

--- test/tests/LayerTest.coffee
+++ test/tests/LayerTest.coffee
@@ -1170,8 +1170,32 @@
 			layerPoint.x.should.equal -point.x
 			layerPoint.y.should.equal -point.y

-
-
-
-
-
+		it "should correctly convert points from layer to layer", ->
+
+			layerBOffset =
+				x: 200
+				y: 400
+
+			layerA = new Layer
+			layerB = new Layer point: layerBOffset
+
+			layerAToLayerBPoint = layerA.convertPointToLayer({}, layerB)
+			layerAToLayerBPoint.x.should.equal -layerBOffset.x
+			layerAToLayerBPoint.y.should.equal -layerBOffset.y
+
+		it "should correctly convert points when layers are nested", ->
+
+			layerBOffset =
+				x: 0
+				y: 200
+
+			layerA = new Layer
+			layerB = new Layer
+				parent: layerA
+				point: layerBOffset
+				rotation: 90
+				originX: 0
+				originY: 0
+
+			layerAToLayerBPoint = layerA.convertPointToLayer({}, layerB)
+			layerAToLayerBPoint.x.should.equal -layerBOffset.y


Fix string trimming for IE. Use jQuery.trim

--- views/app.coffee
+++ views/app.coffee
@@ -20,7 +20,7 @@

 sanitize_form = ->
   $('input[type=text]').each ->
-    $(this).val $(this).val().trim()
+    $(this).val $.trim($(this).val())
   $('#uid').val $('#uid').val().replace /@.*/, '' if $('#uid').length is 1


Make 大鸡丁's life easier

Make 大鸡丁's life easier

--- views/env.coffee
+++ views/env.coffee
@@ -105,10 +105,11 @@
         appIcon.on 'balloon-click', remote.getGlobal('mainWindow').focus

   if volume > 0.0001 && !playing
-    playing = true
     sound = new Audio(audio)
     sound.volume = volume
     sound.play()
+    sound.onplay = (e) ->
+      playing = true
     sound.onended = (e) ->
       playing = false


runSpecs -> specs

runSpecs -> specs

--- build/tasks/spec-task.coffee
+++ build/tasks/spec-task.coffee
@@ -131,7 +131,7 @@
       else
         async.parallel

-    runSpecs =
+    specs =
       if process.env.ATOM_SPECS_TASK is 'packages'
         [runPackageSpecs]
       else if process.env.ATOM_SPECS_TASK is 'core'
@@ -139,7 +139,7 @@
       else
         [runCoreSpecs, runPackageSpecs]

-    method runSpecs, (error, results) ->
+    method specs, (error, results) ->
       [coreSpecFailed, failedPackages] = results
       elapsedTime = Math.round((Date.now() - startTime) / 100) / 10
       grunt.log.ok("Total spec time: #{elapsedTime}s using #{concurrency} cores")


Add heading specs (wip)

Add heading specs (wip)

--- spec/markdown-heading-spec.coffee
+++ spec/markdown-heading-spec.coffee
@@ -1,34 +1,162 @@
-describe "Markdown/heading grammar", ->
+describe 'Markdown grammar', ->
   grammar = null

   beforeEach ->
     waitsForPromise ->
-      atom.packages.activatePackage("language-markdown")
+      atom.packages.activatePackage('language-markdown')

     runs ->
-      grammar = atom.grammars.grammarForScopeName("source.md")
+      grammar = atom.grammars.grammarForScopeName('source.md')

-  it "parses the grammar", ->
-    expect(grammar).toBeDefined()
-    expect(grammar.scopeName).toBe "source.md"
-
-  it "tokenizes simple headings", ->
+  # http://spec.commonmark.org/0.22/#atx-header
+  it 'tokenizes ATX headings', ->

     # http://spec.commonmark.org/0.22/#example-27
-    {tokens} = grammar.tokenizeLine("# Heading")
-    expect(tokens[0]).toEqual value: "#", scopes: ["source.md", "heading.md", "heading-1.md", "punctuation.md"]
-    expect(tokens[1]).toEqual value: " ", scopes: ["source.md", "heading.md", "heading-1.md"]
-    expect(tokens[2]).toEqual value: "Heading", scopes: ["source.md", "heading.md", "heading-1.md"]
+    {tokens} = grammar.tokenizeLine('# Heading')
+    expect(tokens[0]).toEqual value: '#', scopes: ['source.md', 'heading.md', 'heading-1.md', 'punctuation.md']
+    expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    expect(tokens[2]).toEqual value: 'Heading', scopes: ['source.md', 'heading.md', 'heading-1.md']
+
+    # http://spec.commonmark.org/0.22/#example-28
+    {tokens} = grammar.tokenizeLine('####### foo')
+    expect(tokens[0]).toEqual value: '####### foo', scopes: ['source.md']
+
+    # http://spec.commonmark.org/0.22/#example-29
+    {tokens} = grammar.tokenizeLine('#5 bolt')
+    expect(tokens[0]).toEqual value: '#5 bolt', scopes: ['source.md']
+
+    # Variant of #29
+    {tokens} = grammar.tokenizeLine('#foobar')
+    expect(tokens[0]).toEqual value: '#foobar', scopes: ['source.md']
+
+    # FIXME @burodepeper
+    # http://spec.commonmark.org/0.22/#example-30
+    # {tokens} = grammar.tokenizeLine('\## foo')
+    # expect(tokens[0]).toEqual value: '\## foo', scopes: ['source.md']
+
+    # FIXME
+    # http://spec.commonmark.org/0.22/#example-31
+    # {tokens} = grammar.tokenizeLine('# foo *bar* \*baz\*')
+    # expect(tokens[0]).toEqual value: '#', scopes: ['source.md', 'heading.md', 'heading-1.md', 'punctuation.md']
+    # expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    # expect(tokens[2]).toEqual value: 'foo ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    # expect(tokens[3]).toEqual value: '*', scopes: ['source.md', 'heading.md', 'heading-1.md', 'italic.md', 'punctuation.md']
+    # expect(tokens[4]).toEqual value: 'bar', scopes: ['source.md', 'heading.md', 'heading-1.md', 'italic.md']
+    # expect(tokens[5]).toEqual value: '*', scopes: ['source.md', 'heading.md', 'heading-1.md', 'italic.md', 'punctuation.md']
+    # expect(tokens[6]).toEqual value: ' \*baz\*', scopes: ['source.md', 'heading.md', 'heading-1.md']
+
+    # http://spec.commonmark.org/0.22/#example-32
+    {tokens} = grammar.tokenizeLine('#                  foo')
+    expect(tokens[0]).toEqual value: '#', scopes: ['source.md', 'heading.md', 'heading-1.md', 'punctuation.md']
+    expect(tokens[1]).toEqual value: '                  ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    expect(tokens[2]).toEqual value: 'foo', scopes: ['source.md', 'heading.md', 'heading-1.md']
+
+    # http://spec.commonmark.org/0.22/#example-33
+    {tokens} = grammar.tokenizeLine('  ## Heading')
+    expect(tokens[0]).toEqual value: '  ', scopes: ['source.md', 'heading.md']
+    expect(tokens[1]).toEqual value: '##', scopes: ['source.md', 'heading.md', 'heading-2.md', 'punctuation.md']
+    expect(tokens[2]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-2.md']
+    expect(tokens[3]).toEqual value: 'Heading', scopes: ['source.md', 'heading.md', 'heading-2.md']
+
+    # TODO 'code block' not yet implemented
+    # http://spec.commonmark.org/0.22/#example-34
+    {tokens} = grammar.tokenizeLine('    # foo')
+
+    # TODO 'code block' not yet implemented
+    # http://spec.commonmark.org/0.22/#example-35
+    {tokens} = grammar.tokenizeLine('foo\n    # bar')
+
+    # FIXME @burodepeper
+    # http://spec.commonmark.org/0.22/#example-36
+    {tokens} = grammar.tokenizeLine('## foo ##')
+    # expect(tokens[0]).toEqual value: '##', scopes: ['source.md', 'heading.md', 'heading-2.md', 'punctuation.md']
+    # expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-2.md']
+    # expect(tokens[2]).toEqual value: 'foo', scopes: ['source.md', 'heading.md', 'heading-2.md']
+    # expect(tokens[3]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-2.md']
+    # expect(tokens[4]).toEqual value: '##', scopes: ['source.md', 'heading.md', 'heading-2.md', 'punctuation.md']
+
+    # FIXME @burodepeper
+    # Variant of #36
+    {tokens} = grammar.tokenizeLine('  ###   bar    ###')
+    # expect(tokens[0]).toEqual value: '  ', scopes: ['source.md', 'heading.md']
+    # expect(tokens[1]).toEqual value: '###', scopes: ['source.md', 'heading.md', 'heading-3.md', 'punctuation.md']
+    # expect(tokens[2]).toEqual value: '   ', scopes: ['source.md', 'heading.md', 'heading-3.md']
+    # expect(tokens[3]).toEqual value: 'bar', scopes: ['source.md', 'heading.md', 'heading-3.md']
+    # expect(tokens[4]).toEqual value: '    ', scopes: ['source.md', 'heading.md', 'heading-3.md']
+    # expect(tokens[5]).toEqual value: '###', scopes: ['source.md', 'heading.md', 'heading-3.md', 'punctuation.md']
+
+    # FIXME @burodepeper
+    # http://spec.commonmark.org/0.22/#example-37
+    {tokens} = grammar.tokenizeLine('# foo ##################################')
+    # expect(tokens[0]).toEqual value: '#', scopes: ['source.md', 'heading.md', 'heading-1.md', 'punctuation.md']
+    # expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    # expect(tokens[2]).toEqual value: 'foo ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    # expect(tokens[3]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    # expect(tokens[4]).toEqual value: '##################################', scopes: ['source.md', 'heading.md', 'heading-1.md', 'punctuation.md']
+
+    # FIXME @burodepeper
+    # Variant of #37
+    {tokens} = grammar.tokenizeLine('##### foo ##')
+    # expect(tokens[0]).toEqual value: '#####', scopes: ['source.md', 'heading.md', 'heading-5.md', 'punctuation.md']
+    # expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-5.md']
+    # expect(tokens[2]).toEqual value: 'foo', scopes: ['source.md', 'heading.md', 'heading-5.md']
+    # expect(tokens[3]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-5.md']
+    # expect(tokens[4]).toEqual value: '##', scopes: ['source.md', 'heading.md', 'heading-5.md', 'punctuation.md']
+
+    # FIXME @burodepeper
+    # http://spec.commonmark.org/0.22/#example-38
+    {tokens} = grammar.tokenizeLine('### foo ###     ')
+    # expect(tokens[0]).toEqual value: '###', scopes: ['source.md', 'heading.md', 'heading-3.md', 'punctuation.md']
+    # expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-3.md']
+    # expect(tokens[2]).toEqual value: 'foo', scopes: ['source.md', 'heading.md', 'heading-3.md']
+    # expect(tokens[3]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-3.md']
+    # expect(tokens[4]).toEqual value: '###', scopes: ['source.md', 'heading.md', 'heading-3.md', 'punctuation.md']
+    # expect(tokens[5]).toEqual value: '     ', scopes: ['source.md', 'heading.md', 'heading-3.md']
+
+    # NOTE
+    # Works for now, but will fail when closing #'s are tokenized
+    # http://spec.commonmark.org/0.22/#example-39
+    {tokens} = grammar.tokenizeLine('### foo ### b')
+    expect(tokens[0]).toEqual value: '###', scopes: ['source.md', 'heading.md', 'heading-3.md', 'punctuation.md']
+    expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-3.md']
+    expect(tokens[2]).toEqual value: 'foo ### b', scopes: ['source.md', 'heading.md', 'heading-3.md']
+
+    # NOTE
+    # Works for now, but will fail when closing #'s are tokenized
+    # http://spec.commonmark.org/0.22/#example-40
+    {tokens} = grammar.tokenizeLine('# foo#')
+    expect(tokens[0]).toEqual value: '#', scopes: ['source.md', 'heading.md', 'heading-1.md', 'punctuation.md']
+    expect(tokens[1]).toEqual value: ' ', scopes: ['source.md', 'heading.md', 'heading-1.md']
+    expect(tokens[2]).toEqual value: 'foo#', scopes: ['source.md', 'heading.md', 'heading-1.md']

     # TODO
-    # Commonmark examples 28 through 32
-
-    # http://spec.commonmark.org/0.22/#example-33
-    {tokens} = grammar.tokenizeLine("  ## Heading")
-    expect(tokens[0]).toEqual value: "  ", scopes: ["source.md", "heading.md"]
-    expect(tokens[1]).toEqual value: "##", scopes: ["source.md", "heading.md", "heading-2.md", "punctuation.md"]
-    expect(tokens[2]).toEqual value: " ", scopes: ["source.md", "heading.md", "heading-2.md"]
-    expect(tokens[3]).toEqual value: "Heading", scopes: ["source.md", "heading.md", "heading-2.md"]
+    # http://spec.commonmark.org/0.22/#example-41
+    {tokens} = grammar.tokenizeLine('### foo \###')

     # TODO
-    # Commonmark examples 34 through 44
+    # Variant of #41
+    {tokens} = grammar.tokenizeLine('## foo #\##')
+
+    # TODO
+    # Variant of #41
+    {tokens} = grammar.tokenizeLine('# foo \#')
+
+    # TODO
+    # http://spec.commonmark.org/0.22/#example-42
+    {tokens} = grammar.tokenizeLine('****\n## foo\n****')
+
+    # TODO
+    # http://spec.commonmark.org/0.22/#example-43
+    {tokens} = grammar.tokenizeLine('Foo bar\n# baz\nBar foo')
+
+    # TODO
+    # http://spec.commonmark.org/0.22/#example-44
+    {tokens} = grammar.tokenizeLine('## ')
+
+    # TODO
+    # Variant of #44
+    {tokens} = grammar.tokenizeLine('#')
+
+    # TODO
+    # Variant of #44
+    {tokens} = grammar.tokenizeLine('### ###')


:fire: Remove old message registry specs

--- spec/message-registry-spec.coffee
+++ spec/message-registry-spec.coffee
@@ -1,108 +0,0 @@
-describe 'message-registry', ->
-  messageRegistry = null
-  editor = null
-  {MessageRegistry} = require('../lib/message-registry')
-  EditorLinter = require('../lib/editor-linter')
-  LinterRegistry = require('../lib/linter-registry')
-  objectSize = (obj) ->
-    size = 0
-    size++ for value of obj
-    return size
-  {getLinterRegistry, getMessage} = require('./common')
-
-  beforeEach ->
-    waitsForPromise ->
-      atom.workspace.destroyActivePaneItem()
-      atom.workspace.open('test.txt').then ->
-        editor = atom.workspace.getActiveTextEditor()
-        messageRegistry?.dispose()
-        messageRegistry = new MessageRegistry()
-        messageRegistry.debouncedUpdate = ->
-        spyOn(messageRegistry, 'debouncedUpdate').andCallThrough()
-    waitsForPromise ->
-      atom.packages.activatePackage('linter')
-
-  describe '::set', ->
-    it 'accepts info from LinterRegistry::lint', ->
-      {linterRegistry, editorLinter} = getLinterRegistry()
-      wasUpdated = false
-      linterRegistry.onDidUpdateMessages (linterInfo) ->
-        wasUpdated = true
-        messageRegistry.set(linterInfo)
-        expect(messageRegistry.debouncedUpdate).toHaveBeenCalled()
-      waitsForPromise ->
-        linterRegistry.lint({onChange: false, editor: editorLinter.editor}).then ->
-          expect(wasUpdated).toBe(true)
-          linterRegistry.dispose()
-
-  describe '::onDidUpdateMessages', ->
-    it 'is triggered asyncly with results and provides a diff', ->
-      wasUpdated = false
-      {linterRegistry, editorLinter} = getLinterRegistry()
-      linterRegistry.onDidUpdateMessages (linterInfo) ->
-        messageRegistry.set(linterInfo)
-        expect(messageRegistry.debouncedUpdate).toHaveBeenCalled()
-        messageRegistry.update()
-      messageRegistry.onDidUpdateMessages ({added, removed, messages}) ->
-        wasUpdated = true
-        expect(added.length).toBe(1)
-        expect(removed.length).toBe(0)
-        expect(messages.length).toBe(1)
-      waitsForPromise ->
-        linterRegistry.lint({onChange: false, editor: editorLinter.editor}).then ->
-          expect(wasUpdated).toBe(true)
-          linterRegistry.dispose()
-    it 'provides the same objects when they dont change', ->
-      {linterRegistry, editorLinter, linter} = getLinterRegistry()
-      timesTriggered = 0
-      message = getMessage('Error')
-      registryMessages = null
-      linterRegistry.onDidUpdateMessages (linterInfo) ->
-        messageRegistry.set(linterInfo)
-        messageRegistry.update()
-
-      messageRegistry.onDidUpdateMessages ({messages}) ->
-        ++timesTriggered
-        registryMessages = messages
-
-      linter.lint = ->
-        if timesTriggered is 0
-          return [message]
-        else
-          return [message, getMessage('Warning')]
-
-      waitsForPromise ->
-        linterRegistry.lint({onChange: false, editor: editorLinter.editor}).then( ->
-          return linterRegistry.lint({onChange: false, editor: editorLinter.editor})
-        ).then ->
-          expect(timesTriggered).toBe(2)
-          expect(registryMessages[0] is message || registryMessages[1] is message).toBe(true)
-
-  describe '::deleteByBuffer', ->
-    it 'removes messages for that buffer', ->
-      wasUpdated = 0
-      {linterRegistry, editorLinter} = getLinterRegistry()
-      buffer = editorLinter.editor.getBuffer()
-      linterRegistry.onDidUpdateMessages (linterInfo) ->
-        messageRegistry.set(linterInfo)
-        expect(messageRegistry.debouncedUpdate).toHaveBeenCalled()
-        messageRegistry.update()
-      messageRegistry.onDidUpdateMessages ({messages}) ->
-        wasUpdated = 1
-        expect(objectSize(messages)).toBe(1)
-        messageRegistry.deleteByBuffer(buffer)
-      waitsForPromise ->
-        linterRegistry.lint({onChange: false, editor: editorLinter.editor}).then ->
-          expect(wasUpdated).toBe(1)
-          linterRegistry.dispose()
-
-  describe '::update', ->
-    it 'sets name properly', ->
-      message = {
-        type: 'Error',
-        text: 'Something',
-        range: [[0, 0], [1, Infinity]]
-      }
-      messageRegistry.set({linter: {name: 'Something'}, buffer: null, messages: [message]})
-      messageRegistry.update()
-      expect(message.name).toBe('Something')


:white_check_mark: what select operator can DO

--- spec/operator-general-spec.coffee
+++ spec/operator-general-spec.coffee
@@ -1470,3 +1470,92 @@
         line2
         line3
         """
+
+  describe 'Select as operator', ->
+    beforeEach ->
+      settings.set('keymapSToSelect', true)
+      jasmine.attachToDOM(editorElement)
+
+    describe "select by target", ->
+      beforeEach ->
+        set
+          textC: """
+          0 |ooo xxx ***
+          1 xxx *** ooo
+
+          3 ooo xxx ***
+          4 xxx *** ooo\n
+          """
+
+      it "select text-object", ->
+        ensure "s p", # p is `i p` shorthand.
+          mode: ["visual", "linewise"]
+          selectedText: """
+          0 ooo xxx ***
+          1 xxx *** ooo\n
+          """
+
+      it "select occurrence in text-object with occurrence-modifier", ->
+        ensure "s o p", # p is `i p` shorthand.
+          mode: ["visual", "characterwise"]
+          selectedText: ["ooo", "ooo"]
+          selectedBufferRangeOrdered: [
+            [[0, 2], [0, 5]]
+            [[1, 10], [1, 13]]
+          ]
+
+      it "select occurrence in text-object with preset-occurrence", ->
+        ensure "g o s p", # p is `i p` shorthand.
+          mode: ["visual", "characterwise"]
+          selectedText: ["ooo", "ooo"]
+          selectedBufferRangeOrdered: [
+            [[0, 2], [0, 5]]
+            [[1, 10], [1, 13]]
+          ]
+
+      it "convert presistent-selection into normal selection", ->
+        ensure "v j enter",
+          mode: "normal"
+          persistentSelectionCount: 1
+          persistentSelectionBufferRange: [
+            [[0, 2], [1, 3]]
+          ]
+
+        ensure "j j v j",
+          persistentSelectionCount: 1
+          persistentSelectionBufferRange: [
+            [[0, 2], [1, 3]]
+          ]
+          mode: ["visual", "characterwise"]
+          selectedText: "ooo xxx ***\n4 x"
+
+        # Now it's show time, to convert persistent selection into normal selection
+        # by only `s`.
+        ensure "s",
+          mode: ["visual", "characterwise"]
+          persistentSelectionCount: 0
+          selectedTextOrdered: ["ooo xxx ***\n1 x", "ooo xxx ***\n4 x"]
+
+      it "select preset-occurrence in presistent-selection and normal selection", ->
+        ensure "g o",
+          occurrenceText: ['ooo', 'ooo', 'ooo', 'ooo']
+
+        ensure "V j enter G V",
+          persistentSelectionCount: 1
+          mode: ["visual", "linewise"]
+          selectedText: "4 xxx *** ooo\n"
+
+        ensure "s", # Notice `ooo` in row 3 is EXCLUDED.
+          persistentSelectionCount: 0
+          mode: ["visual", "characterwise"]
+          selectedText: ["ooo", "ooo", "ooo"]
+          selectedBufferRangeOrdered: [
+            [[0, 2], [0, 5]]
+            [[1, 10], [1, 13]]
+            [[4, 10], [4, 13]]
+          ]
+
+      it "select by motion", ->
+        ensure "s $", # p is `i p` shorthand.
+          mode: ["visual", "characterwise"]
+          selectedText: "ooo xxx ***"


逆転の発想

逆転の発想

--- scripts/line-beacon-listener.coffee
+++ scripts/line-beacon-listener.coffee
@@ -1,7 +1,7 @@
 module.exports = (robot) ->

   robot.router.post "/line-becon", (req, res) ->
-    data = if req.body.payload? then JSON.parse req.body.payload else req.body
-    robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊#{data}"
+    message = JSON.parse res
+    robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊#{message.type}"
     robot.send {room: "C4LEAQHPW"}, "ビーコンに反応あり"
     res.end ""


[Proxy] #18 testing middlewares

[Proxy] #18 testing middlewares

--- tests/src/app/features/proxy/module.spec.coffee
+++ tests/src/app/features/proxy/module.spec.coffee
@@ -102,7 +102,15 @@
         assert.isTrue console.error.calledOnce

+    describe 'Proxy', ->
+
+      it 'Do not set middlewares when module is not enabled', ->
+        mergedOptions.modulesData.proxy.isEnabled = no
+        module = moduleExports(mergedOptions)
+
+        assert.isTrue _.isEmpty(module.middlewares)

+


テストにもどります

テストにもどります

--- scripts/line-beacon-listener.coffee
+++ scripts/line-beacon-listener.coffee
@@ -1,7 +1,8 @@
 module.exports = (robot) ->

   robot.router.post "/line-becon", (req, res) ->
-    message = JSON.parse res
-    robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊#{message.type}"
+    data = if req.body.payload? then JSON.parse req.body.payload else req.body
+    robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊#{data}"
+    robot.logger.error "＊＊＊＊＊＊＊＊＊＊＊＊＊＊#{data.type}"
     robot.send {room: "C4LEAQHPW"}, "ビーコンに反応あり"
     res.end ""


Groups: Fixed a bug where options is being undefined.

--- client/Dashboard/views/generalsettingsview.coffee
+++ client/Dashboard/views/generalsettingsview.coffee
@@ -23,9 +23,9 @@

   createFormFields: (formOptions) ->
     group = @getData()
+    formOptions.fields = {}

     if group.slug isnt "koding"
-      formOptions.fields      = {}
       formOptions.fields.Logo =
         label                 : "Logo"
         itemClass             : GroupLogoSettings


fixes bug in request json parsing

fixes bug in request json parsing

--- server/coffee/src/middleware.coffee
+++ server/coffee/src/middleware.coffee
@@ -16,7 +16,7 @@
     if method == "GET" || method == "HEAD" || method == "OPTIONS"
       return next()

-    string = request.headers["content-type"]
+    string = request.headers["content-type"] || ""
     media_type = string.split(";")[0]
     if !(media_type == "application/json" || json_regex.test(media_type))
       return next()


Fix #3130 Hide "edited by" status in search results

--- server/methods/messageSearch.coffee
+++ server/methods/messageSearch.coffee
@@ -72,6 +72,8 @@
 				query.rid = rid
 				try
 					if Meteor.call('canAccessRoom', rid, this.userId) isnt false
+						if not RocketChat.settings.get 'Message_ShowEditedStatus'
+							options.fields = { 'editedAt': 0 }
 						result.messages = RocketChat.models.Messages.find(query, options).fetch()


Use suggested indent when auto indenting a buffer row

--- src/app/language-mode.coffee
+++ src/app/language-mode.coffee
@@ -182,8 +182,8 @@
   #
   # bufferRow - The row {Number}
   autoIndentBufferRow: (bufferRow) ->
-    @autoIncreaseIndentForBufferRow(bufferRow)
-    @autoDecreaseIndentForBufferRow(bufferRow)
+    indentLevel = @suggestedIndentForBufferRow(bufferRow)
+    @editSession.setIndentationForBufferRow(bufferRow, indentLevel)

   # Given a buffer row, this increases the indentation.
   #


Analytics added for WoW character addition.  Isuse #77.

--- scripts/char_create.coffee
+++ scripts/char_create.coffee
@@ -10,6 +10,7 @@
 				alert 'Check the server and character name and try again'
 				errorClear()
 			else
+				mixpanel.track("WoW Character Added")
 				return_to = $('#return-to').val()
 				if return_to
 					window.location = return_to


Load only no offline users

Load only no offline users

--- server/publications.coffee
+++ server/publications.coffee
@@ -65,7 +65,7 @@

 	# console.log '[publish] allUsers'.green

-	return Meteor.users.find {username: {$exists: true}}, { 'fields': {
+	return Meteor.users.find {username: {$exists: true}, status: {$in: ['online', 'away', 'busy']}}, { 'fields': {
 		username: 1
 		status: 1
 	}}


Remove the exit code section. It's excessive.

--- prompt.coffee
+++ prompt.coffee
@@ -82,14 +82,6 @@
     when: git.isRepo
     foreground: 'blue'

-  section 'exit code',
-    content: system.lastExitCode
-    format: (lastExitCode) ->
-      "exit code #{lastExitCode}"
-    when: system.lastExitCode
-    background: 'red'
-    foreground: 'white'
-
   section 'end',
     content: ['\n$', system.lastExitCode]
     format: (string, lastExitCode) ->


Show off new impromptu-git features

Show off new impromptu-git features

* Staged files count
* changed/deleted/added/new files counts
* ahead/behind counts

--- prompt.coffee
+++ prompt.coffee
@@ -24,13 +24,44 @@
     background: 'green'
     foreground: 'black'

+  section 'git:stage',
+    content: git._status
+    when: git.isRepo
+    foreground: 'magenta'
+    background: 'white'
+    format: (statuses) ->
+      staged = _.reduce statuses, (memo, status) ->
+        memo += 1 if status.staged
+        memo
+      , 0
+
+      return "#{staged} staged" if staged
+
   section 'git:status',
-    when: git.status
-    content: [git.untracked, git.modified, git.deleted]
-    format: (untracked, modified, deleted) ->
-      "(#{untracked}/#{modified}/#{deleted})"
-    background: 'green'
+    content: [git.modified, git.deleted, git.added, git.untracked]
+    format: (mod, del, add, untracked) ->
+      output = []
+      output.push "#{mod} changed"   if mod
+      output.push "#{del} deleted"   if del
+      output.push "#{add} added"     if add
+      output.push "#{untracked} new" if untracked
+
+      output.join ' | '
+    when: git.isRepo
+    foreground: 'green'
+    background: 'black'
+
+  section 'git:aheadBehind',
+    content: [git.ahead, git.behind]
+    background: 'red'
     foreground: 'white'
+    when: git.isRepo
+    format: (ahead, behind) ->
+      outputs = []
+      outputs.push "#{ahead} ahead" if ahead
+      outputs.push "#{behind} behind" if behind
+
+      outputs.join ' & '

   section 'end',
     content: '\n$'


Change widgets watchSources path to src/coffee/**/** dir from src/coffee/**

--- bokehjs/gulp/paths.coffee
+++ bokehjs/gulp/paths.coffee
@@ -39,7 +39,7 @@
         "./src/coffee/widget/main.coffee"
       ]
       watchSources: [
-        "./src/coffee/**/**",
+        "./src/coffee/widget/**",
       ]

   css:


Fix payload

Fix payload

--- components/LoginOAuth.coffee
+++ components/LoginOAuth.coffee
@@ -46,7 +46,8 @@
           url: chrome.identity.getRedirectURL()
           scopes: data.payload.scopes
       , (responseUrl) ->
-        out.codeurl.send responseUrl
+        out.codeurl.send
+          payload: responseUrl
         do callback
       return


session metadata add is_corona

session metadata add is_corona

--- snippets/language-coursescript.cson
+++ snippets/language-coursescript.cson
@@ -508,6 +508,7 @@
       description:"$10"
       cover: $11
       title: $12
+      is_corona: $13
     \"""
     """
   'Assessment':


CustomViews: use kd.CustomHTMLView as base view instead of kd.View

--- client/app/lib/commonviews/customviews.coffee
+++ client/app/lib/commonviews/customviews.coffee
@@ -8,7 +8,7 @@

   @views      =
     view      : (options, data) ->
-      new kd.View options, data
+      new kd.CustomHTMLView options, data
     text      : (text, cssClass) =>
       @views.view partial: text, cssClass: "text #{cssClass ? ''}"
     container : (options, cssClass) =>
@@ -23,7 +23,7 @@
     link      : (options) ->
       new (require 'app/customlinkview') options

-  @addTo   = (parent, views)->
+  @addTo   = (parent, views) ->

     map    = {}
     length = 0


collaboration: add end for participant

collaboration: add end for participant

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -692,6 +692,16 @@
             callbacks.success()

+  endCollaborationForParticipant: (callbacks) ->
+
+    @broadcastMessage { type: 'ParticipantWantsToLeave' }
+
+    socialHelpers.leaveChannel @socialChannel, (err) =>
+      return callbacks.error err  if err
+      @setMachineUser [nick()], no, =>
+        callbacks.success()
+
+
   showChat: ->

     switch @stateMachine.state


Collaboration: Add isKoding check

Collaboration: Add isKoding check

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -25,6 +25,7 @@
 BaseModalView                 = require 'app/providers/views/basemodalview'
 actionTypes                   = require 'app/flux/environment/actiontypes'
 generateCollaborationLink     = require 'app/util/generateCollaborationLink'
+isKoding                      = require 'app/util/isKoding'

 {warn} = kd

@@ -1054,6 +1055,8 @@

     @cleanupCollaboration()

+    return  unless isKoding()
+
     { activitySidebar } = kd.singletons.mainView
     channelId           = @getSocialChannelId()


send feed update to room

send feed update to room

--- scripts/feed_checker.coffee
+++ scripts/feed_checker.coffee
@@ -4,6 +4,7 @@
 # Dependencies:
 #   'feedparser': '*'
 #   'request': '*'
+#   'underscore': '*'
 #
 # Configuration:
 #   FEED_CHECK_INTERVAL
@@ -18,6 +19,7 @@

 FeedParser = require 'feedparser'
 request = require 'request'
+_ = require 'underscore'

 module.exports = (robot) ->
   feed_check_interval = null
@@ -27,16 +29,26 @@
     articles.every (v) ->
       v.guid != guid

+  send_message = (feed_url, msg) ->
+    usrs = robot.brain.data.feed_check_user[feed_url]
+    sent = []
+    for usr in usrs
+      is_room = usr.room?
+      continue if _.contains sent, (if is_room then usr.room else usr.id)
+
+      func = if is_room then robot.messageRoom else robot.send
+      func (if is_room then usr.room else usr), msg
+      sent.push if is_room then usr.room else usr.id
+
   robot.brain.on 'loaded', ->
     interval = parseInt process.env.FEED_CHECK_INTERVAL
-    if isNaN interval then interval = 60 * 5
+    interval = 60 * 5 if isNaN interval

     robot.logger.debug "setting feed check interval to: #{interval}"

     robot.brain.data.feed_check ||= {}
+    robot.brain.data.feed_check_user ||= {}
     feed_check_interval = setInterval ->
-      user = {}
-
       for url, prev of robot.brain.data.feed_check
         robot.logger.debug "checking feed: #{url}"

@@ -48,11 +60,12 @@
           # .on 'meta', (meta) ->
           .on 'readable', ->
             while item = @read() then next.push item
+          # all article parsed
           .on 'end', ->
             if prev.length != 0 then next.forEach (article) ->
               if is_new prev, article.guid
-                robot.send user, "new article: #{article.title} #{article.link}"
-                if article.summary? then robot.send user, "summary: #{article.summary}"
+                send_message url, "new article: #{article.title} #{article.link}"
+                send_message url, "summary: #{article.summary}" if article.summary?

             # save feed
             robot.brain.data.feed_check[url] = next
@@ -60,20 +73,23 @@

   robot.respond /check_feed (h[^ ]+)/i, (msg) ->
     url = msg.match[1]
-    robot.brain.data.feed_check[url] = []
-    msg.reply 'Subscribing to ' + url
+    robot.brain.data.feed_check[url] ||= []
+    robot.brain.data.feed_check_user[url] ||= []
+    robot.brain.data.feed_check_user[url].push msg.message.user
+    msg.reply "Subscribing to #{url}"

   robot.respond /check_feed stop ([^ ]+)/i, (msg) ->
     url = msg.match[1]
     if robot.brain.data.feed_check[url]?
-      msg.reply 'Unsubscribing from ' + url
+      msg.reply "Unsubscribing from #{url}"
       delete robot.brain.data.feed_check[url]
+      delete robot.brain.data.feed_check_user[url]
     else
-      msg.reply url + ' isn`t subscribed'
+      msg.reply "#{url} isn`t subscribed"

   robot.respond /checking_feed/i, (msg) ->
     keys = Object.keys robot.brain.data.feed_check
     if keys.length == 0
       msg.reply 'No feed subscribed'
     else
-      msg.reply 'Subscribed: ' + keys.join(' , ')
+      msg.reply "Subscribed: #{keys.join(' , ')}"


timing adjustment for tests

timing adjustment for tests

--- tests/test_loader_wd.coffee
+++ tests/test_loader_wd.coffee
@@ -1282,7 +1282,7 @@
             .sleep(DELAY)
             .clearLocalStorage()
             .get('http://127.0.0.1:3332/app/')
-            .sleep(DELAY)
+            .sleep(3*DELAY)
             .title().should.eventually.become("version_1")
             .safeExecute("localforage.clear()")
             .nodeify(done)


Update packages for new naming scheme

Update packages for new naming scheme

--- spec/status-bar-spec.coffee
+++ spec/status-bar-spec.coffee
@@ -196,8 +196,8 @@

   describe "grammar label", ->
     beforeEach ->
-      atom.activatePackage('text-tmbundle', sync: true)
-      atom.activatePackage('javascript-tmbundle', sync: true)
+      atom.activatePackage('language-text', sync: true)
+      atom.activatePackage('language-javascript', sync: true)

     it "displays the name of the current grammar", ->
       expect(statusBar.find('.grammar-name').text()).toBe 'JavaScript'


Group file click tests together

Group file click tests together

... so that only one beforeEach and afterEach must be used.

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -47,8 +47,9 @@
       expect(treeView.roots[0].directory.watchSubscription).toBeTruthy()

   afterEach ->
-    waits 10 # The async nature of specs causes uncaught exceptions, so wait a little bit before we cleanup
+    waits 50 # The async nature of specs causes uncaught exceptions, so wait a little bit before we cleanup
     temp.cleanup()
+

   describe ".initialize(project)", ->
     it "renders the root directories of the project and their contents alphabetically with subdirectories first, in a collapsed state", ->
@@ -527,86 +528,87 @@
       sampleJs.mousedown()
       expect(sampleJs).toHaveClass 'selected'

-  describe "when a file is single-clicked", ->
+  describe "when files are clicked", ->
     beforeEach ->
       jasmine.attachToDOM(workspaceElement)

-    it "selects the file and opens it in the active editor in pending state (replacing other files open in pending state), and changes focus to the file", ->
-      treeView.focus()
-
-      waitsForFileToOpen ->
-        sampleJs.trigger clickEvent(originalEvent: {detail: 1})
-
-      runs ->
-        expect(sampleJs).toHaveClass 'selected'
-        activePaneItem = atom.workspace.getActivePaneItem()
-        expect(activePaneItem.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.js')
-        expect(atom.views.getView(activePaneItem)).toHaveFocus()
-        expect(activePaneItem.isPending()).toBe true
-        expect(atom.workspace.getActivePane().getItems().length).toBe 1
-
-      waitsForFileToOpen ->
-        sampleTxt.trigger clickEvent(originalEvent: {detail: 1})
-
-      runs ->
-        expect(sampleTxt).toHaveClass 'selected'
-        expect(treeView.find('.selected').length).toBe 1
-        activePaneItem = atom.workspace.getActivePaneItem()
-        expect(activePaneItem.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.txt')
-        expect(atom.views.getView(activePaneItem)).toHaveFocus()
-        expect(activePaneItem.isPending()).toBe true
-        expect(atom.workspace.getActivePane().getItems().length).toBe 1
-
-  describe "when a file is double-clicked", ->
-    beforeEach ->
-      jasmine.attachToDOM(workspaceElement)
-
-    it "selects the file and opens it in the active editor on the first click in pending state, then terminates pending state on the second", ->
-      treeView.focus()
-
-      waitsForFileToOpen ->
-        sampleJs.trigger clickEvent(originalEvent: {detail: 1})
-
-      runs ->
-        expect(sampleJs).toHaveClass 'selected'
-        item = atom.workspace.getActivePaneItem()
-        expect(item.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.js')
-        expect(atom.views.getView(item)).toHaveFocus()
-        expect(item.isPending()).toBe true
-
-        sampleJs.trigger clickEvent(originalEvent: {detail: 2})
-        expect(atom.views.getView(item)).toHaveFocus()
-        expect(item.isPending()).toBe false
-
-        # single-clicking again does not change the status to pending
-        sampleJs.trigger clickEvent(originalEvent: {detail: 1})
-        expect(item.isPending()).toBe false
-
-  describe "when a file is single-clicked, then double-clicked", ->
-    beforeEach ->
-      jasmine.attachToDOM(workspaceElement)
-
-    it "selects the file and opens it in the active editor on the single-click in pending state, then terminates pending state on the double-click", ->
-      treeView.focus()
-
-      waitsForFileToOpen ->
-        sampleJs.trigger clickEvent(originalEvent: {detail: 1})
-
-      runs ->
-        expect(sampleJs).toHaveClass 'selected'
-        item = atom.workspace.getActivePaneItem()
-        expect(item.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.js')
-        expect(atom.views.getView(item)).toHaveFocus()
-        expect(item.isPending()).toBe true
-
-        sampleJs.trigger clickEvent(originalEvent: {detail: 1})
-        sampleJs.trigger clickEvent(originalEvent: {detail: 2})
-        expect(atom.views.getView(item)).toHaveFocus()
-        expect(item.isPending()).toBe false
-
-        sampleJs.trigger 'dblclick'
-        expect(atom.views.getView(item)).toHaveFocus()
-        expect(item.isPending()).toBe false
+    afterEach ->
+      waits 10 # The click specs cause uncaught exceptions because of their async nature, so wait a little bit before we cleanup
+
+    describe "when a file is single-clicked", ->
+
+      it "selects the file and opens it in the active editor in pending state (replacing other files open in pending state), and changes focus to the file", ->
+        treeView.focus()
+
+        waitsForFileToOpen ->
+          sampleJs.trigger clickEvent(originalEvent: {detail: 1})
+
+        runs ->
+          expect(sampleJs).toHaveClass 'selected'
+          activePaneItem = atom.workspace.getActivePaneItem()
+          expect(activePaneItem.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.js')
+          expect(atom.views.getView(activePaneItem)).toHaveFocus()
+          expect(activePaneItem.isPending()).toBe true
+          expect(atom.workspace.getActivePane().getItems().length).toBe 1
+
+        waitsForFileToOpen ->
+          sampleTxt.trigger clickEvent(originalEvent: {detail: 1})
+
+        runs ->
+          expect(sampleTxt).toHaveClass 'selected'
+          expect(treeView.find('.selected').length).toBe 1
+          activePaneItem = atom.workspace.getActivePaneItem()
+          expect(activePaneItem.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.txt')
+          expect(atom.views.getView(activePaneItem)).toHaveFocus()
+          expect(activePaneItem.isPending()).toBe true
+          expect(atom.workspace.getActivePane().getItems().length).toBe 1
+
+    describe "when a file is double-clicked", ->
+
+      it "selects the file and opens it in the active editor on the first click in pending state, then terminates pending state on the second", ->
+        treeView.focus()
+
+        waitsForFileToOpen ->
+          sampleJs.trigger clickEvent(originalEvent: {detail: 1})
+
+        runs ->
+          expect(sampleJs).toHaveClass 'selected'
+          item = atom.workspace.getActivePaneItem()
+          expect(item.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.js')
+          expect(atom.views.getView(item)).toHaveFocus()
+          expect(item.isPending()).toBe true
+
+          sampleJs.trigger clickEvent(originalEvent: {detail: 2})
+          expect(atom.views.getView(item)).toHaveFocus()
+          expect(item.isPending()).toBe false
+
+          # single-clicking again does not change the status to pending
+          sampleJs.trigger clickEvent(originalEvent: {detail: 1})
+          expect(item.isPending()).toBe false
+
+    describe "when a file is single-clicked, then double-clicked", ->
+
+      it "selects the file and opens it in the active editor on the single-click in pending state, then terminates pending state on the double-click", ->
+        treeView.focus()
+
+        waitsForFileToOpen ->
+          sampleJs.trigger clickEvent(originalEvent: {detail: 1})
+
+        runs ->
+          expect(sampleJs).toHaveClass 'selected'
+          item = atom.workspace.getActivePaneItem()
+          expect(item.getPath()).toBe atom.project.getDirectories()[0].resolve('tree-view.js')
+          expect(atom.views.getView(item)).toHaveFocus()
+          expect(item.isPending()).toBe true
+
+          sampleJs.trigger clickEvent(originalEvent: {detail: 1})
+          sampleJs.trigger clickEvent(originalEvent: {detail: 2})
+          expect(atom.views.getView(item)).toHaveFocus()
+          expect(item.isPending()).toBe false
+
+          sampleJs.trigger 'dblclick'
+          expect(atom.views.getView(item)).toHaveFocus()
+          expect(item.isPending()).toBe false

   describe "when a directory is single-clicked", ->
     it "is selected", ->


fix #2

fix #2

--- spec/vim-state-spec.coffee
+++ spec/vim-state-spec.coffee
@@ -154,8 +154,7 @@
     describe "with content", ->
       beforeEach -> editor.setText("012345\n\nabcdef")

-      # FIXME: See atom/vim-mode#2
-      xdescribe "on a line with content", ->
+      describe "on a line with content", ->
         beforeEach -> editor.setCursorScreenPosition([0, 6])

         it "does not allow the cursor to be placed on the \n character", ->


Added file upload support, and css loading for tools

--- src/coffee/directives.coffee
+++ src/coffee/directives.coffee
@@ -1,6 +1,16 @@
 require ['angular', 'lodash', 'lines', 'jschannel', 'services'], (ng, L, lines, Channel) ->

   Directives = ng.module('steps.directives', ['steps.services'])
+
+  Directives.directive 'html5FileUpload', ->
+    restrict: 'E'
+    require: ['ngModel']
+    replace: true
+    template: """<input type="file">"""
+    link: (scope, elem, attrs, [ngModel]) ->
+      elem.on 'change', -> scope.$apply ->
+        file = elem[0].files[0]
+        ngModel.$setViewValue file

   Directives.directive 'appVersion', ['version', (v) -> (scope, elm) -> elm.text(version)]

@@ -135,9 +145,11 @@
       </div>
     """

-  Directives.directive 'startingPoint', ($window) ->
+  Directives.directive 'startingPoint', ($timeout, $window) ->
     restrict: 'E'
     controller: ($scope) ->
+      $scope.buttons = {}
+      $scope.state = {}
       $scope.reset = -> $scope.$broadcast 'reset'
       $scope.act = -> $scope.$broadcast 'act'

@@ -152,6 +164,10 @@
             maxy = L.max offsets.map (o) -> o.top + o.height
             topBars = getTopBars offsets
             ystep = element[0].offsetHeight
+            unless ystep
+              $timeout doResize, 10
+              return
+
             p1 =
               x: element[0].offsetLeft
               y: element[0].offsetTop + 1 # Don't get intersects with own top bar.
@@ -168,24 +184,36 @@

             if factor > 1 and panelBody = element[0].querySelector('.panel-body')
               totalHeight = ystep * factor - margin
+              console.log totalHeight, ystep, factor
               panelBody.style.height = "#{ totalHeight - (p1.y - panelBody.offsetTop) }px"
               element.addClass 'expanded'

           $window.addEventListener 'resize', ->
             element[0].querySelector('.panel-body')?.style.height = null
             element.removeClass 'expanded'
-            setTimeout doResize, 10
-
-          setTimeout doResize, 10
+            $timeout doResize, 10
+
+          $timeout doResize, 10
+
+  toolCssLoaded = {}

   Directives.directive 'nativeTool', ($window, $compile, $injector) ->
     restrict: 'E'
     scope:
       tool: '=tool'
+      actions: '=actions'
+      state: '=state'
     link: (scope, element, attrs) ->

       scope.$watch 'tool.controllerURI', ->
         if scope.tool
+
+          if scope.tool.style and not toolCssLoaded[scope.tool.style]
+            link = $window.document.createElement('link')
+            link.rel = 'stylesheet'
+            link.href = scope.tool.style
+            document.getElementsByTagName('head')[0].appendChild(link)
+            toolCssLoaded[scope.tool.style] = true

           ctrl = $window.location.origin + scope.tool.controllerURI
           tmpl = $window.location.origin + scope.tool.templateURI


show error object

show error object

--- source/javascripts/sql_joins.coffee
+++ source/javascripts/sql_joins.coffee
@@ -149,7 +149,7 @@
         setTimeout((=>
           @copyButton.removeClass('copied').text('Copy SQL')
         ), 1000)
-    client.on 'error', (event) => console.warn "Clipy error: #{event}"
+    client.on 'error', (event) => console.warn "Clipy error", event

   # appcache
   _initAppcache: =>


Restore periods

Restore periods

--- plugins/webhook/index.coffee
+++ plugins/webhook/index.coffee
@@ -2,10 +2,13 @@

 class Webhook extends NotificationPlugin
   @receiveEvent: (config, event) ->
+    payload = JSON.stringify(event).replace(/\．/g,".")
+
     # Send the request to the url
     @request
       .post(config.url)
-      .send(event)
+      .type('json')
+      .send(payload)
       .end();

 module.exports = Webhook


Unnecessary

Unnecessary


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_game_state.coffee
+++ src/engine_game_state.coffee
@@ -17,7 +17,7 @@
     # Handle state change
     if requestedState?
       I.currentState.trigger "exit", requestedState
-      self.trigger 'stateEntered',
+      self.trigger 'stateExited',

       previousState = I.currentState
       I.currentState = requestedState


error in formdata

error in formdata

--- shared/ironTableController.coffee
+++ shared/ironTableController.coffee
@@ -202,13 +202,13 @@
                     else if col.default
                         col.checked = 'checked'
                 else if record?[key]?
-                    col.value = record[key]
+                    col.value = col.display?(record[key], record) or record[key]
                 else if col.default?
                     col.value = col.default

                 if col["staticOn_#{type}"]
                     col.static = true
-                    col.value = col.display?(record[col], record) or record?[key]
+                    col.value = col.display?(record[key], record) or record?[key]

                 col.header = (col.header || key).capitalize()
                 col.key = key


Subscribe to buttonClick

Subscribe to buttonClick

--- source/molecule/navigation.coffee
+++ source/molecule/navigation.coffee
@@ -21,5 +21,5 @@
   _trigger: (event, atom) ->
     event.preventDefault()
     atom.el.addClass("active").siblings().removeClass("active")
-    @trigger "select", atom
+    @trigger "select", event, atom


add comments.

add comments.

--- core/app/backbone/factlink/tooltip_jq.coffee
+++ core/app/backbone/factlink/tooltip_jq.coffee
@@ -15,14 +15,17 @@
   #makeTooltip: $el, $target -> $tooltip
   #removeTooltip: $el, $target, $tooltip ->

-ttCounter = 0
+ttCounter = 0 #each call to TooltipJQ gets its own id based on this.

 #returns an object with a close method which when called
 #removes all open tooltips
 Backbone.Factlink.TooltipJQ = (options) ->
   uid = ttCounter++
   uidStr = 'tooltipUid-' + uid
-  instCounter = 0
+  instCounter = 0 #one call can cause multiple tooltips
+  #we track each tooltip using the instCounter and store them
+  #in `instances`: this means we can close them not just
+  #in reponse to an event handler, but also on request
   instances = {}

   $el = options.$el
@@ -31,7 +34,7 @@
   openInstance = ($target) ->
     instId = 'tt' + instCounter++
     inTooltip = false
-    inTarget = true
+    inTarget = true #opened on hover over target

     $tooltip = options.makeTooltip $el, $target
     $tooltip.hoverIntent
@@ -54,9 +57,9 @@
   hoverTarget = (inTarget) -> (e) ->
     $target = $(e.currentTarget)
     ttActions = instances[$target.data(uidStr)]
-    if ttActions
+    if ttActions #this is a known (i.e. open) tooltip
       ttActions.setTargetHover inTarget
-    else if inTarget
+    else if inTarget #target has no tooltip but is hovered
       openInstance $target

   $el.hoverIntent


Remove downgrade to http

Remove downgrade to http

--- src/javascripts/embed.coffee
+++ src/javascripts/embed.coffee
@@ -56,8 +56,6 @@

   origin: () ->
     scriptSrc = @elem.src || @elem.getAttribute('src')
-    unless window.location.protocol.match(/^https/)
-      scriptSrc = scriptSrc.replace(/^https/, 'http')
     scriptSrc.match(/(^.*\/)/)[0].replace(/javascripts\/$/, '').replace(/\/$/, '')

   buildIframe: ->


itvl -> interval

itvl -> interval

--- src/lib/interval-tree.coffee
+++ src/lib/interval-tree.coffee
@@ -27,31 +27,31 @@
         @root = new Node(center)

-    insert: (node, itvl) ->
+    insert: (node, interval) ->

-        if itvl.end < node.center
+        if interval.end < node.center

-            newCenter = itvl.end
+            newCenter = interval.end

             node.left ?= new Node(newCenter)

-            return @insert(node.left, itvl)
+            return @insert(node.left, interval)

-        if node.center < itvl.start
+        if node.center < interval.start

-            middle = (itvl.start + itvl.end) / 2
+            middle = (interval.start + interval.end) / 2

             node.right ?= new Node(middle)

-            return @insert(node.right, itvl)
+            return @insert(node.right, interval)

-        node.insert itvl
+        node.insert interval

         return

     ###*
-    search intervals
+    search intervals at the given node

     @method pointSearch
     @private
@@ -118,8 +118,8 @@
                 return

             Object.keys(resultHash).forEach (id) =>
-                itvl = @intervalsById[id]
-                arr.push itvl.result(start, end)
+                interval = @intervalsById[id]
+                arr.push interval.result(start, end)
                 return

@@ -133,15 +133,14 @@
                 @_autoIncrement++
             id = @_autoIncrement

-        itvl = new Interval(start, end, id)
+        interval = new Interval(start, end, id)

-        @pointTree.insert new Point(itvl.start, id)
-        @pointTree.insert new Point(itvl.end,   id)
+        @pointTree.insert new Point(interval.start, id)
+        @pointTree.insert new Point(interval.end,   id)

-        @intervalsById[id] = itvl
+        @intervalsById[id] = interval

-        @insert @root, itvl
-
+        @insert @root, interval

     search: (val1, val2) ->


Fix for detection of correct version

Fix for detection of correct version

--- src/lib/mungedetector.coffee
+++ src/lib/mungedetector.coffee
@@ -146,7 +146,7 @@
         data:   {}
         onSuccess: (response, callback) ->

-            if not response? or response.length isnt 2
+            if not response.result? or response.result.length isnt 2

                 callback()
                 tryCallback && tryCallback new Error 'Failed to detect munge'


config: remove duplicate line

config: remove duplicate line

--- config/workers.coffee
+++ config/workers.coffee
@@ -477,7 +477,6 @@
       group             : "proxy"
       supervisord       :
         command         : "#{GOBIN}/tunnelserver -accesskey #{credentials.awsKeys.worker_tunnelproxymanager.accessKeyId} -secretkey #{credentials.awsKeys.worker_tunnelproxymanager.secretAccessKey} -port #{KONFIG.tunnelserver.port} -basevirtualhost #{KONFIG.tunnelserver.basevirtualhost} -hostedzone #{KONFIG.tunnelserver.hostedzone} -region #{options.region} -environment #{options.environment}"
-        command         : "#{GOBIN}/tunnelserver -accesskey #{credentials.awsKeys.worker_tunnelproxymanager.accessKeyId} -secretkey #{credentials.awsKeys.worker_tunnelproxymanager.secretAccessKey} -port #{KONFIG.tunnelserver.port} -basevirtualhost #{KONFIG.tunnelserver.basevirtualhost} -hostedzone #{KONFIG.tunnelserver.hostedzone}"
       ports             :
         incoming        : "#{KONFIG.tunnelserver.port}"
       healthCheckURL    : "http://tunnelserver/healthCheck"


simplified equation for mincha on Ereb Shabbat by using moment.min

--- js/mincha.coffee
+++ js/mincha.coffee
@@ -87,7 +87,7 @@
   else if hebrew_date.isErebYomTob()
     mincha: if hebrew_date.isErebShabbat() then sunset.subtract(33, 'minutes') else round_down_to_5_minutes(sunset.subtract(25, 'minutes'))
   else if hebrew_date.isErebShabbat()
-    mincha: (if sunset.hour() < 19 || (19 == sunset.hour() && sunset.minute() < 3) then sunset.subtract(33, 'minutes') else sunset.hour(18).minute(30))
+    mincha: moment.min(moment(sunset).subtract(33, 'minutes'), sunset.hour(18).minute(30))
   else
     mincha: recent_hadlakat_nerot, arbit: sunset


Only save data that we need to save.

Only save data that we need to save.

Not, say, isEditing. Solves problem where clicking on the gear icon didn’t bring the gadget in editing mode.

--- src/local-api/gadgets.coffee
+++ src/local-api/gadgets.coffee
@@ -18,7 +18,7 @@
   res.json req.gadget

 gadgets.post '/courses/:courseid/lessons/:lessonid/gadgets', (req, res) ->
-  gadget = req.body
+  gadget = _.pick req.body, 'config', 'userState', 'index', 'type'
   gadget.id = shortid()
   req.lesson.gadgets.push gadget
   res.send 201, gadget


:lipstick:

:lipstick:

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -289,8 +289,7 @@

   # Returns the {AtomWindow} for the given path.
   windowForPath: (pathToOpen) ->
-    for atomWindow in @windows
-      return atomWindow if atomWindow.containsPath(pathToOpen)
+    _.find @windows, (atomWindow) -> atomWindow.containsPath(pathToOpen)

   # Returns the {AtomWindow} for the given ipc event.
   windowForEvent: ({sender}) ->


Fix how the BinaryCompiler depends on the StackingCompiler

--- src/compilers/BinaryCompiler.coffee
+++ src/compilers/BinaryCompiler.coffee
@@ -1,7 +1,4 @@
 'use strict'
-
-{ StackingCompiler } = window.bef
-

 consumePair = (consume, delta) ->
 	{ consume, delta }
@@ -96,11 +93,13 @@

 generateCode = (path, maxDepth) ->
+	{ makeStack, codeMap } = window.bef.StackingCompiler
+
 	startStack = (0 for i in [0...maxDepth])

 	charList = path.getAsList()

-	stack = StackingCompiler.makeStack(
+	stack = makeStack(
 		"#{path.id}_#{maxDepth}"
 		startStack
 		{ popMethod: 'popUnsafe' }
@@ -110,7 +109,7 @@
 		if entry.string
 			stack.push entry.char.charCodeAt 0
 		else
-			codeGenerator = StackingCompiler.codeMap[entry.char]
+			codeGenerator = codeMap[entry.char]
 			if codeGenerator?
 				codeGenerator entry.x, entry.y, entry.dir, i, stack, path.from, path.to
 		return


ListView: fix adding to both data item array and list html for either it is boxed or not and with lastToFirst option.

--- src/components/list/listview.coffee
+++ src/components/list/listview.coffee
@@ -92,15 +92,12 @@

     {lastToFirst} = @getOptions()

-
-    unless index
-
-      index = 0
-
+    unless index?
       if lastToFirst
       then @items.unshift itemInstance
       else @items.push itemInstance

+      index = if lastToFirst then 0 else @items.length - 1
     else

       @items.splice index, 0, itemInstance
@@ -120,13 +117,14 @@
     {boxed, lastToFirst} = @getOptions()

     if index <= 0
-
-      if boxed
-      then @packageItem itemInstance
-      else @addSubView itemInstance, null, lastToFirst
-
-    else if index > 0
-
+      index = if boxed and lastToFirst then undefined else 0
+
+    if index >= @items.length - 1
+      index = if boxed and not lastToFirst then undefined else @items.length - 1
+
+    if boxed and not index?
+      @packageItem itemInstance
+    else
       shouldBeLastItem = index >= @items.length - 1
       item             = itemInstance.getElement()

@@ -135,14 +133,7 @@
         neighborItem.parentNode.insertBefore item, neighborItem
         itemInstance.emit 'viewAppended'  if @parentIsInDom
       else
-        @addSubView itemInstance, null, !lastToFirst
-
-
-
-
-
-
-
+        @addSubView itemInstance, null

     @scrollDown()  if @doIHaveToScroll()


prefix classes that are not used in CSS

prefix classes that are not used in CSS

--- src/components/task-plan/guide.cjsx
+++ src/components/task-plan/guide.cjsx
@@ -21,11 +21,11 @@
     courseId

   renderCrudeTable: (data) ->
-

-

+

+

         guide table

-      <BS.Table className="reading-progress-group">
+      <BS.Table className="-reading-progress-group">

test

       </BS.Table>

@@ -37,7 +37,7 @@
       guide = CourseStore.getGuide(id)
       table = _.map(guide.fields, @renderCrudeTable)

-      <BS.Panel className="course-guide-container">
+      <BS.Panel className="-course-guide-container">
         here it is: {table}
       </BS.Panel>


Some convenience methods for working with forms

--- components/form/index.coffee
+++ components/form/index.coffee
@@ -39,7 +39,7 @@

   error: (modelOrString, response, options) ->
     @state 'error'
-    @reenable false
+    @reenable()
     @$errors.text if typeof modelOrString is 'string'
       modelOrString
     else
@@ -72,5 +72,12 @@
     @$submit.attr 'data-state', state
     this

+  action: ->
+    @model?.url() or
+    @$form.attr 'action'
+
+  find: ->
+    @$form.find arguments...
+
   data: ->
     @serializer.data()


QUALITY: Remove console log when in safari

--- src/app/common/services/recorder-service.coffee
+++ src/app/common/services/recorder-service.coffee
@@ -98,7 +98,7 @@
             event.data = e.data
           else
             event.data = new Blob(e.data, {type: that.encoderMimeType})
-            console.log("Safari Blob created: " + event.data)
+            # console.log("Safari Blob created: " + event.data)
           that._onDataAvailable(event)
         )


fix webhcat comment in stop command

fix webhcat comment in stop command

--- hadoop/lib/lifecycle.coffee
+++ hadoop/lib/lifecycle.coffee
@@ -430,7 +430,7 @@
       return callback err, false if err or not running
       ctx.log "WebHCat stop"
       ctx.execute
-        # su -l hcat -c "export WEBHCAT_CONF_DIR=/etc/hcatalog/conf/webhcat; /usr/lib/hcatalog/sbin/webhcat_server.sh start"
+        # su -l hcat -c "export WEBHCAT_CONF_DIR=/etc/hcatalog/conf/webhcat; /usr/lib/hcatalog/sbin/webhcat_server.sh stop"
         cmd: "su -l #{webhcat_user} -c \"export WEBHCAT_CONF_DIR=#{webhcat_conf_dir}; /usr/lib/hcatalog/sbin/webhcat_server.sh stop\""
       , (err, stopped) ->
         callback err, stopped


Google Drive list is limited by mimeType [#108890456]

--- src/code/providers/google-drive-provider.coffee
+++ src/code/providers/google-drive-provider.coffee
@@ -87,7 +87,8 @@

   list: (metadata, callback) ->
     @_loadedGAPI =>
-      request = gapi.client.drive.files.list()
+      request = gapi.client.drive.files.list
+        q: "mimeType = '#{@mimeType}'"
       request.execute (result) =>
         return callback('Unable to list files') if not result
         list = []


[client][app] changed filter controller names

--- client/app/Applications/Activity.kdapplication/views/activityinnernavigation.coffee
+++ client/app/Applications/Activity.kdapplication/views/activityinnernavigation.coffee
@@ -3,7 +3,7 @@

   viewAppended:->

-    filterFirstController = @setListController
+    filterController = @setListController
       type: "filterme"
       itemClass: ListGroupShowMeItem
     , @filterMenuData
@@ -11,25 +11,25 @@
     {useNeo4j} = KD.config

     if useNeo4j
-      @addSubView filterFirstController.getView()
-      filterFirstController.selectItem filterFirstController.getItemsOrdered()[0]
+      @addSubView filterController.getView()
+      filterController.selectItem filterController.getItemsOrdered()[0]

     if useNeo4j
       menudata = @followerMenuData
     else
       menudata = @showMenuData

-    filterController = @setListController
+    showMeFilterController = @setListController
       type : "showme"
       itemClass : ListGroupShowMeItem
     , menudata

     KD.getSingleton('mainController').on "AccountChanged", (account)=>
-      filterFirstController.reset()
-      filterFirstController.selectItem filterFirstController.getItemsOrdered()[0]
+      filterController.reset()
+      filterController.selectItem filterController.getItemsOrdered()[0]

-    @addSubView filterController.getView()
-    filterController.selectItem filterController.getItemsOrdered()[0]
+    @addSubView showMeFilterController.getView()
+    showMeFilterController.selectItem showMeFilterController.getItemsOrdered()[0]

     @addSubView helpBox = new HelpBox
       subtitle    : "About Your Activity Feed"


added the missing dot

added the missing dot

--- client/app/Applications/Activity.kdapplication/views/tutorialactivityactions.coffee
+++ client/app/Applications/Activity.kdapplication/views/tutorialactivityactions.coffee
@@ -48,7 +48,7 @@
   pistachio:->
     """
     {{> @loader}}
-    {{> @opinionCountLink}} {{> @opinionCount}}
+    {{> @opinionCountLink}} {{> @opinionCount}} ·
     <span class='optional'>
     {{> @shareLink}} ·
     </span>


Workspace: Update ActiveTabIndex reference if workspace still exists.

--- client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
+++ client/landing/app/CommonViews/workspace/panes/collaborativetabbededitorpane.coffee
@@ -30,7 +30,9 @@

       return unless fileTab
       @tabView.removePane fileTab
-      @indexRef.set @tabView.getPaneIndex @tabView.getActivePane()
+      @workspaceRef.once "value", (snapshot) =>
+        if snapshot.val()?.keys
+          @indexRef.set @tabView.getPaneIndex @tabView.getActivePane()

     @indexRef.on "value", (snapshot) =>
       return if snapshot.val() is null


Support for Elixir

Support for Elixir

Compile Elixir projects using the built in Mix build system.

--- lib/build.coffee
+++ lib/build.coffee
@@ -55,6 +55,10 @@
       else
         # else use global installation
         exec = 'grunt'
+
+    if !exec && fs.existsSync @root + '/mix.exs'
+      exec = 'mix'
+      args = ['compile']

     if !exec && fs.existsSync @root + '/Makefile'
       exec = 'make'