Fix carousel test in eoy client

Fix carousel test in eoy client

--- apps/editorial_features/test/components/eoy/client.coffee
+++ apps/editorial_features/test/components/eoy/client.coffee
@@ -28,67 +28,67 @@
       $.fn.resize = sinon.stub()
       sinon.stub Backbone, 'sync'
       @curation = new Curation
-          _id: "5829db77b5989e6f98f779a5",
-          intro: "Article Intro statement: Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Vestibulum id ligula porta felis euismod semper. Curabitur blandit tempus porttitor.",
-          header_video: "https://artsy-media-uploads.s3.amazonaws.com/yB2-P7P5J3v1rihLw1v8nQ%2FUrmhuJUpFOnZ52kc8DUJsg-paintbrushes_slow_1200.mp4",
-          name: "Year In Art 2016",
-          type: "editorial-feature",
-          sections: [
-            {
-              headline: "The Artist Becomes Political",
-              callout: "Lorum Ipsum",
-              body: "In 2016, artists have produced work urging us to not simply look at painful current events we might rather ignore, but to go further—to imagine a better future. Perhaps the most prominent and controversial has been artist and activist Ai Weiwei, who made the Syrian refugee crisis the subject of his artistic practice this year, even setting up a studio on the Greek island of Lesbos where many come ashore, seeking refuge.\r\n\r\nAi provoked criticism when he posed in a black and white image that showed the artist prostrate on a Turkish beach, his body replacing that of drowned Syrian toddler Aylan Kurdi, who was the subject of a viral news photo. Both detractors and champions of Ai’s rendition shared it widely across social media, and for a brief moment, the all-too-quickly repressed struggle faced by migrants once again made headlines. Ai even brought the crisis to the United States with “Laundromat,” an installation at New York’s Deitch Projects of thousands of pristine and folded garments collected from an abandoned refugee camp. The thesis is simple: if we look, we will act. It goes to show art isn’t simply political—art can change politics.",
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              caption: "Installation view of Carrie Mae Weems.",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/Aw572XiO__5T0T-Tc0j4SA%2FF+Lotus%2C+2016.mp4",
-              caption_second: "Lorum Ipsum caption"
-            },
-            {
-              headline: "headline 2",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.mp4",
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg"
-            },
-            {
-              headline: "headline 3",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg"
-            },
-            {
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.mp4",
-              headline: "headline 4"
-            },
-            {
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              headline: "headline 5"
-            },
-            {
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              headline: "headline 6"
-            },
-            {
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              headline: "headline 7"
-            },
-            {
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              headline: "headline 8"
-            },
-            {
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              headline: "headline 9"
-            },
-            {
-              image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
-              headline: "headline 10"
-            },
-          ]
+        _id: "5829db77b5989e6f98f779a5",
+        intro: "Article Intro statement: Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Vestibulum id ligula porta felis euismod semper. Curabitur blandit tempus porttitor.",
+        header_video: "https://artsy-media-uploads.s3.amazonaws.com/yB2-P7P5J3v1rihLw1v8nQ%2FUrmhuJUpFOnZ52kc8DUJsg-paintbrushes_slow_1200.mp4",
+        name: "Year In Art 2016",
+        type: "editorial-feature",
+        sections: [
+          {
+            headline: "The Artist Becomes Political",
+            callout: "Lorum Ipsum",
+            body: "In 2016, artists have produced work urging us to not simply look at painful current events we might rather ignore, but to go further—to imagine a better future. Perhaps the most prominent and controversial has been artist and activist Ai Weiwei, who made the Syrian refugee crisis the subject of his artistic practice this year, even setting up a studio on the Greek island of Lesbos where many come ashore, seeking refuge.\r\n\r\nAi provoked criticism when he posed in a black and white image that showed the artist prostrate on a Turkish beach, his body replacing that of drowned Syrian toddler Aylan Kurdi, who was the subject of a viral news photo. Both detractors and champions of Ai’s rendition shared it widely across social media, and for a brief moment, the all-too-quickly repressed struggle faced by migrants once again made headlines. Ai even brought the crisis to the United States with “Laundromat,” an installation at New York’s Deitch Projects of thousands of pristine and folded garments collected from an abandoned refugee camp. The thesis is simple: if we look, we will act. It goes to show art isn’t simply political—art can change politics.",
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            caption: "Installation view of Carrie Mae Weems.",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/Aw572XiO__5T0T-Tc0j4SA%2FF+Lotus%2C+2016.mp4",
+            caption_second: "Lorum Ipsum caption"
+          },
+          {
+            headline: "headline 2",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.mp4",
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg"
+          },
+          {
+            headline: "headline 3",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg"
+          },
+          {
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.mp4",
+            headline: "headline 4"
+          },
+          {
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            headline: "headline 5"
+          },
+          {
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            headline: "headline 6"
+          },
+          {
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            headline: "headline 7"
+          },
+          {
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            headline: "headline 8"
+          },
+          {
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            headline: "headline 9"
+          },
+          {
+            image: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            image_second: "https://artsy-media-uploads.s3.amazonaws.com/WU6t2X-XoeBlY1KWVOQbdQ%2FAW_01.jpg",
+            headline: "headline 10"
+          },
+        ]
       sd.CURATION = @curation.toJSON()
       @options = {
         curation: @curation,
@@ -233,4 +233,5 @@
   describe '#setupCarousel', ->

     it 'calls initCarousel', ->
+      @view.setupCarousel()
       @carousel.callCount.should.equal 1


:x: :bug:

:x: :bug:

--- scripts/triggers.coffee
+++ scripts/triggers.coffee
@@ -22,7 +22,7 @@

     oldResponse = robot.brain.get("trigger:#{trigger}")
     if oldResponse?
-      msg.send "'#{trigger}' already triggers '#{response}'. Use trigger remove #{trigger} to delete it."
+      msg.send "'#{trigger}' already triggers '#{oldResponse}'. Use trigger remove #{trigger} to delete it."
     else
       robot.brain.set("trigger:#{trigger}", response)
       robot.brain.set('triggers', robot.brain.get('triggers').push(trigger)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -41,8 +41,7 @@
     collisionMargin: Point(0, 0)

   ###*
-  Distance between two objects. Proxies to Point.distance. Both
-  obje
+  Distance between two objects. Proxies to Point.distance. B

   player = Core


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/bounded.coffee
+++ src/bounded.coffee
@@ -60,7 +60,7 @@
     width: 7
     height: 20

-  equals player.distance(enemy)
+  player.distance(enemy)
   # => 92.19544457292888
   </pre></code>


video: use a constant for opentok url

video: use a constant for opentok url

--- client/app/lib/videocollaboration/services/opentok.coffee
+++ client/app/lib/videocollaboration/services/opentok.coffee
@@ -1,7 +1,11 @@
 kd                   = require 'kd'
 $                    = require 'jquery'
 helper               = require '../helper'
+globals              = require 'globals'
+getNick              = require 'app/util/nick'
 KodingAppsController = require '../../kodingappscontroller'
+
+OPENTOK_URL = '//static.opentok.com/webrtc/v2.2/js/opentok.min.js'

 module.exports = class OpenTokService extends kd.Object

@@ -47,7 +51,7 @@

     options =
       identifier : 'open-tok'
-      url        : '//static.opentok.com/webrtc/v2.2/js/opentok.min.js'
+      url        : OPENTOK_URL

     KodingAppsController.appendHeadElement 'script', options, =>
       @emit 'ClientLoaded'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -35,8 +35,8 @@
   obj2.I.x = 100
   obj2.I.y = 50000

-  obj.update()
+  obj2.update()

-  ok destroySpy, 'obj should be destroyed when it goes outside the x bounds'
+  ok destroySpy, 'obj2 should be destroyed when it goes outside the x bounds'

 module()


Use watchCollection for chart.series

Use watchCollection for chart.series


Skip connect by instance name integration test if instance name not configured in test configuration.

--- test/integration/connection-test.coffee
+++ test/integration/connection-test.coffee
@@ -116,6 +116,12 @@
   )

 exports.connectByInstanceName = (test) ->
+  if !getInstanceName()
+    # Config says don't do this test (probably because SQL Server Browser is not available).
+    console.log('Skipping goodInstance test')
+    test.done()
+    return
+
   test.expect(2)

   config = getConfig()


Update config to use lodash

Update config to use lodash

--- cnxuser/assets/scripts/config.coffee
+++ cnxuser/assets/scripts/config.coffee
@@ -11,7 +11,7 @@

     # ## Core Libraries
     jquery: 'libs/jquery/jquery'
-    underscore: 'libs/underscore'
+    underscore: 'libs/lodash'
     backbone: 'libs/backbone/backbone'

     # Bootstrap Plugins
@@ -48,9 +48,6 @@
   shim:

     # ## Core Libraries
-    underscore:
-      exports: '_'
-
     backbone:
       deps: ['underscore', 'jquery']
       exports: 'Backbone'


Don't allow unfollow when the user doesn't already follow

--- core/app/backbone/models/user.coffee
+++ core/app/backbone/models/user.coffee
@@ -57,6 +57,8 @@

   unfollow: ->
     self = currentUser.following.get(@id)
+    return unless self
+
     self.destroy
       error: =>
         currentUser.following.add @


Fix for mouseWheelEnabled

Fix for mouseWheelEnabled

Regardless if you pass the option when you create the component it will always default to false. This should pass the option in when it's initialized so it works as expected.

--- framer/Components/ScrollComponent.coffee
+++ framer/Components/ScrollComponent.coffee
@@ -86,7 +86,7 @@
 		# Because we did not have a content layer before, we want to re-apply
 		# the options again so everything gets configures properly.
 		@_applyOptionsAndDefaults(options)
-		@_enableMouseWheelHandling()
+		@_enableMouseWheelHandling(options.mouseWheelEnabled)

 		if options.hasOwnProperty("wrap")
 			wrapComponent(@, options.wrap)


more info into console reporter

more info into console reporter

--- lib/browser.coffee
+++ lib/browser.coffee
@@ -3,6 +3,7 @@

 ConsoleReporter = (runner) ->
   runner.on "start", ->
+    console.time("Test Suite")

   runner.on "suite", (suite) ->
     console.group(suite.title)
@@ -10,12 +11,14 @@
   runner.on "test", ->

   runner.on "pass", (test) ->
-    console.log("%c #{test.title}", "color: #0c0")
+    console.log("%c#{test.title}", "color: #0c0")

   runner.on "fail", (test, err) ->
     console.error(test.title, err.stack)

-  runner.on "pending", ->
+  runner.on "pending", (test) ->
+    console.log("%c#{test.title}", "color: #ccca1b")
+
   runner.on "hook", ->
   runner.on "test end", ->

@@ -23,6 +26,7 @@
     console.groupEnd()

   runner.on "end", ->
+    console.timeEnd("Test Suite")

 suite = new Suite()
 dsl = suite.withDSL()


Middlewarized /profile/:username/.

Middlewarized /profile/:username/.

--- main.coffee
+++ main.coffee
@@ -242,6 +242,16 @@
 ).asyncMiddleware()

+fetchStatsFromURL = ((request, response) ->
+	chars = lib.game.getUserCharacters.sync null, dbConnection, request.param 'username'
+	if not chars?
+		throw new Error '404'
+	for i of chars
+		request.uonline[i] = chars[i]
+	return
+).asyncMiddleware()
+
+
 fetchBattleGroups = ((request, response) ->
 	if request.uonline.fight_mode
 		request.uonline.participants = lib.game.getBattleParticipants.sync null,
@@ -333,20 +343,8 @@

 app.get '/profile/:username/',
-	(request, response) ->
-		# TODO: bug #482
-		# Тут можно смеяться, пускать пузыри и ничего не делать.
-		username = request.param('username')
-		chars = lib.game.getUserCharacters.sync null, dbConnection, username
-		if chars is null
-			throw new Error '404'
-		options = request.uonline.legacyOpts
-		options.instance = 'profile'
-		options.profileIsMine = (options.loggedIn is true) and (chars.id == options.userid)
-		for i of chars
-			options[i] = chars[i]
-		options.username = username
-		response.render 'profile', options
+	fetchStatsFromURL,
+	setInstance('profile'), render('profile')

 app.get '/monster/:id/',


Updated red color.

Updated red color.

--- main.coffee
+++ main.coffee
@@ -7,7 +7,8 @@
 borderColor = backgroundColor
 cursorColor = foregroundColor

-red = "#fd5656"
+# red = "#fd5656"
+red = "#ff7676"
 green = "#75bb36"
 yellow = "#e7e781"
 blue = "#267faf"
@@ -15,6 +16,8 @@
 cyan = "#22bae4"
 lightCyan = "#58dbff"
 lightGreen = "#90cc5b"
+lightBlack = "#65737e"
+lightBlue = blue

 decorateConfig = (config) -> assign {}, config, {
   backgroundColor, foregroundColor, cursorColor, borderColor,
@@ -22,8 +25,8 @@
     black: backgroundColor, white: foregroundColor,
     red, green, blue, cyan, magenta, yellow,
     lightRed: red, lightYellow: yellow, lightMagenta: magenta,
-    lightBlack: backgroundColor, lightWhite: foregroundColor,
-    lightCyan: cyan, lightBlue: blue, lightGreen
+    lightBlack, lightWhite: foregroundColor,
+    lightCyan, lightBlue, lightGreen
   }
   css: """
     #{config.css}


Consolidate post-code.

Consolidate post-code.

--- main.coffee
+++ main.coffee
@@ -1,12 +1,9 @@
-
-

 $("#widget-menu").menu select: (event, ui) ->
     console.log "ui type?", ui.item[0].innerHTML
     return

 $("#tabs").tabs()
-

 marked.setOptions
     renderer: new (marked.Renderer)
@@ -52,16 +49,16 @@
         ([@spec.rowHeaders[m]].concat row for row, m in @spec.data)

     toLocal: ->
-        eval("#{@spec.id} = $blab.sheet['#{@spec.id}'].spec.data")
+        eval("#{@spec.id} = $blab.component.sheet['#{@spec.id}'].spec.data")

     fromLocal: (u)->
-        $blab.sheet[@spec.id].spec.data = u
+        $blab.component.sheet[@spec.id].spec.data = u

 class PlotXY extends Widget

     constructor: (@spec) ->
-        @sheets = ($blab.sheet[id] for id in @spec.sheetIds)
+        @sheets = ($blab.component.sheet[id] for id in @spec.sheetIds)
         defaults = {}
         @chart = c3.generate(
             bindto: $("##{@spec.id}")[0]
@@ -85,12 +82,11 @@
         @spec.x ?= randPos(0.8, 0.1)
         @spec.y ?= randPos(0.8, 0.1)

-        @sheet = $blab.sheet[@spec.id]
+        @sheet = $blab.component.sheet[@spec.id]
+
         container = $("##{@spec.id}")
         container.append("

")
         container.css("position", "absolute")
-        #x = Math.round(Math.random()*100)
-        #y = Math.round(Math.random()*100)
         container.css("left", @spec.x)
         container.css("top", @spec.y)

@@ -114,7 +110,7 @@

     constructor: (@spec) ->

-        @sheet = $blab.sheet[@spec.id]
+        @sheet = $blab.component.sheet[@spec.id]

         @container = $("##{@spec.id}")
         @container.append("

")
@@ -151,121 +147,12 @@
     update: ->
         @sheet.spec.data[0][0] =  @slider.slider("value")

-
 toolbox =
     sheet: Sheet
     figure: PlotXY
     table: Table
     markdown: Markdown

-## From GUI
-
-# sheets
-
-###
-$blab.sheet = []
-sh = (id, data) ->
-    new Sheet {id:id, data:data, compute: true}
-
-$blab.sheet =
-    A: sh "A", [[1,2],[3,4]]
-    x: sh "x", [[5],[6]]
-    b: sh "b", [[0],[0]]
-    y: sh "y", [[30, 200, 100, 400, 150, 250],[50,  20,  10,  40,  15,  25]]
-    z: sh "z", [[50]]
-    q: sh "q", [[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]]
-    u: sh "u"
-    x1: sh "x1", [[30, 50, 100, 230, 300, 310]]
-    y1: sh "y1", [[30, 200, 100, 400, 150, 250], [130, 300, 200, 300, 250, 450]]
-
-$blab.sheet['y'].spec.rowHeaders = []
-$blab.sheet['y'].spec.colHeaders = []
-$blab.sheet['y'].spec.rowHeaders = ['dA','dB']
-$blab.sheet['y'].spec.colHeaders = ['i','ii','iii','iv','v','vi']
-
-$blab.sheet['q'].spec.rowHeaders = ['one','two']
-$blab.sheet['q'].spec.colHeaders = ['i','ii','iii','iv','v','vi']
-###
-
-###
-# slider
-
-slid = (id) -> new Slider
-    id:id
-    value: 3
-
-$blab.slider =
-    z: slid "z"
-
-# tables
-
-tab = (id) -> new Table {id:id, compute:true}
-$blab.table =
-    A: tab "A"
-    x: tab "x"
-    b: tab "b"
-    y: tab "y"
-
-# figures
-
-fig1 =  -> new PlotXY
-    id: "fig1"
-    sheetIds: ["q"]
-    data:
-        x: ""
-        types:
-            one: 'area'
-            two: 'area-spline'
-
-fig2 =  -> new PlotXY
-    id: "fig2"
-    sheetIds: ["x1", "q"]
-    data:
-        x: "r0"
-        types:
-            one: 'spline'
-            two: 'line'
-
-$blab.figure =
-    fig1: fig1()
-    fig2: fig2()
-
-# markdown
-
-md = """
-
-## markdown
-
-*emphasis*
-
-**bold**
-
-~~strike~~
-
-[marked + mathjax](http://kerzol.github.io/markdown-mathjax/editor.html)
-
-```javascript
-var s = "JavaScript syntax highlighting";
-alert(s);
-```
-
-| Tables        | Are           | Cool  |
-| ------------- |:-------------:| -----:|
-| col 3 is      | right-aligned | $1600 |
-| col 2 is      | centered      |   $12 |
-| zebra stripes | are neat      |    $1 |
-
-
-"""
-
-markdn = -> new Markdown
-    id: "content"
-    md: md
-
-$blab.markdown =
-    md1: markdn()
-
-###

 # user code

@@ -280,10 +167,8 @@
         console.log $blab.slider[sl].stringify()

     # local copy of vars
-    #console.log "?????", $blab.sheet
-    for sym of $blab.sheet
-        #console.log "sh??", sym
-        $blab.sheet[sym].toLocal()
+    for sym of $blab.component.sheet
+        $blab.component.sheet[sym].toLocal()

     console.log "######## user-code ########"

@@ -296,27 +181,18 @@

     console.log "######## post-code ########"

-    console.log "#### sheets ####"
-    for s of $blab.sheet
-        $blab.sheet[s].fromLocal(eval(s))
-        console.log $blab.sheet[s].stringify()
-
-    console.log "#### tables ####"
-    for t of $blab.table
-        console.log "t???", t
-        $blab.table[t].update()
-        console.log $blab.table[t].stringify()
-
-    console.log "#### figures ####"
-    for f of $blab.figure
-        $blab.figure[f].update()
-        console.log $blab.figure[f].stringify()
-
-    console.log "#### mardowns ####"
-    for m of $blab.markdown
-        #$blab.markdown[m].update()
-        console.log $blab.markdown[m].stringify()
-
+    $blab.spec = {}
+    for c of $blab.component
+        $blab.spec[c] = {}
+        for i of $blab.component[c]
+            $blab.spec[c][i] = $blab.component[c][i].spec
+            if c == "sheet"
+                $blab.component.sheet[i].fromLocal(eval(i))
+            else
+                $blab.component[c][i].update()
+
+    console.log ">>>", JSON.stringify($blab.spec)
+

 loadgist = (gistid, filename) ->

@@ -327,10 +203,11 @@

     ).success((gistdata) ->
         specs = JSON.parse(gistdata.data.files[filename].content)
-
         build = (w) ->
-            $blab[w] = {}
-            $blab[w][spec.id] = new toolbox[w] spec for spec in specs[w]
+            $blab["component"][w] = {}
+            $blab["component"][w][spec.id] = new toolbox[w] spec for spec in specs[w]
+
+        $blab["component"] = {}

         for spec of specs
             build(spec)
@@ -341,8 +218,6 @@
         # ajax error
         return

-loadgist("f673df3f600fdeb17608", "app.json");
-
-
-
-
+loadgist("f673df3f600fdeb17608", "app.json")
+
+


minor cleanup

minor cleanup

--- scripts/learn.coffee
+++ scripts/learn.coffee
@@ -12,7 +12,7 @@
 # Examples:
 #   hubot holman is an ego surfer
 #   hubot holman is not an ego surfer
-#
+#
 # Author:
 #   amwmedia

@@ -50,10 +50,6 @@
   if process.env.HUBOT_AUTH_ADMIN?
     robot.logger.warning 'The HUBOT_AUTH_ADMIN environment variable is set not going to load roles.coffee, you should delete it'
     return
-
-  getAmbiguousUserText = (users) ->
-    "Be more specific, I know #{users.length} people named like that: #{(user.name for user in users).join(", ")}"
-

 # --------------------- #
 # ====== SETTING ====== #


Improve proof search

Improve proof search

When the result of the proof search returns an :ok message starting
with a ?, display that the proof search was not succesful, otherwise
insert the found proof.

Fixes #199

--- lib/idris-controller.coffee
+++ lib/idris-controller.coffee
@@ -451,18 +451,22 @@
       [res] = msg

       @hideAndClearMessagePanel()
-
-      editor.transact ->
-        # Move the cursor to the beginning of the word
-        editor.moveToBeginningOfWord()
-        # Because the ? in the Holes isn't part of
-        # the word, we move left once, and then select two
-        # words
-        editor.moveLeft()
-        editor.selectToEndOfWord()
-        editor.selectToEndOfWord()
-        # And then replace the replacement with the guess..
-        editor.insertText res
+      console.log res
+      if (res.startsWith("?"))
+        # proof search returned a new hole
+        @clearMessagePanel 'Idris: Searching proof was not successful.'
+      else
+        editor.transact ->
+          # Move the cursor to the beginning of the word
+          editor.moveToBeginningOfWord()
+          # Because the ? in the Holes isn't part of
+          # the word, we move left once, and then select two
+          # words
+          editor.moveLeft()
+          editor.selectToEndOfWord()
+          editor.selectToEndOfWord()
+          # And then replace the replacement with the guess..
+          editor.insertText res

     @model
       .load uri


grunt-contrib-coffee added

grunt-contrib-coffee added

--- demo/grid/Gruntfile.coffee
+++ demo/grid/Gruntfile.coffee
@@ -2,13 +2,19 @@
 	#Project configuration
 	grunt.initConfig
 		pkg : grunt.file.readJSON "package.json"
+
+		coffee:
+			compile:
+				files:
+					"src/app.js" : "src/app.coffee"
+					"src/drive.js":"src/drive.coffee"
 		concat:
 			options:
 				stripBanners : true
 				banner: '/*! <%= pkg.name %> - v<%= pkg.version %> */'

    dist:
-        		src:['src/app.js','src/drive.js','components/jquery/jquery.js','components/bootstrap/docs/assets/js/bootstrap.js']
+        		src:['components/jquery/jquery.js','../../dist/mithgrid.js','src/app.js','src/drive.js','components/bootstrap/docs/assets/js/bootstrap.js']
         		dest: 'src/<%= pkg.name %>.js'

 		uglify:
@@ -20,7 +26,8 @@

 	grunt.loadNpmTasks 'grunt-contrib-uglify'
 	grunt.loadNpmTasks 'grunt-contrib-concat'
+	grunt.loadNpmTasks 'grunt-contrib-coffee'

 	grunt.registerTask 'default', ['uglify']

-	grunt.registerTask 'build', ['concat','uglify']
+	grunt.registerTask 'build', ['coffee','concat','uglify']


Added test for Auto-terminate

Added test for Auto-terminate

--- test/spec/services/workerfactory.coffee
+++ test/spec/services/workerfactory.coffee
@@ -19,6 +19,7 @@
       postMessage: ->
         listener {state: 'progress', data: {}}
         listener {state: 'complete', data: true}
+      terminate: jasmine.createSpy 'terminate'

     inject (_WorkerFactory_, _$timeout_, _$rootScope_) ->
       WorkerFactory = _WorkerFactory_
@@ -29,13 +30,34 @@
     window.Worker = SavedWorker

   it 'should return a promise when run', ->
+    # arrange
     Worker = new WorkerFactory "scriptName.js"
     callback = jasmine.createSpy "callback"
+
+    # act
     promise = Worker.run()
       .then callback

     $rootScope.$apply()

+    # assert
     expect callback
       .toHaveBeenCalledWith true

+  it 'should automatically terminate unresponsive workers after 5 minutes'
+    # arrange
+    ResponsiveWorker = new Worker 'responsive'
+    UnresponsiveWorker = new Worker 'unresponsive'
+
+    jasmine.stub UnresponsiveWorker, 'postMessage'
+
+    # act
+    ResponsiveWorker.run()
+    UnresponsiveWorker.run()
+    $timeout.flush()
+
+    # assert
+    expect ResponsiveWorker.terminate
+      .not.toHaveBeenCalled()
+    expect UnresponsiveWorker.terminate
+      .toHaveBeenCalled()


Update context menu after deselection click

--- src/content.coffee
+++ src/content.coffee
@@ -21,7 +21,9 @@
 #= require LinkGrabber

 document.body.onmouseup = (event) ->
-	chrome.extension.sendMessage({type:"verifySelection", links:getLinksFromSelection()})
+	window.setTimeout(->
+		chrome.extension.sendMessage({type:"verifySelection", links:getLinksFromSelection()})
+	, 0)

 document.body.onkeyup = (event) ->
 	chrome.extension.sendMessage({type:"verifySelection", links:getLinksFromSelection()})


Add inapplicable context code

Add inapplicable context code

--- src/context.coffee
+++ src/context.coffee
@@ -24,6 +24,8 @@
     @TIMEOUT_MSG = 'TIMEOUT'
     @WARN = 8
     @WARN_MSG = 'WARN'
+    @INAPPLICABLE = 9
+    @INAPPLICABLE_MSG = 'INAPPLICABLE'
     @tmp = {}
   # Return a list of servers with a give action
   hosts_with_module: (module, qtt) ->


Fetch only statistical districts for statistics visualisation

--- src/control.coffee
+++ src/control.coffee
@@ -396,6 +396,7 @@
         showDivisions: (filters, statisticsKey ,cancelToken) ->
             @divisions.clearFilters()
             @divisions.setFilter 'geometry', true
+            @divisions.setFilter 'type', 'statistical_district'
             for key, val of filters
                 @divisions
                     .setFilter key, val


Extracted component

Extracted component

--- core/app/backbone/views/evidence/react_add_comment.coffee
+++ core/app/backbone/views/evidence/react_add_comment.coffee
@@ -14,6 +14,30 @@
         onClick: => @props.onToggle?(true)
       ],
         "Search for #{Factlink.Global.t.factlinks} (beta)"
+
+
+ReactSearchFacts = React.createClass
+  displayName: 'ReactSearchFacts'
+
+  render: ->
+    return _div() unless @props.opened
+
+    _div ['add-comment-search-facts'],
+      ReactFactSearch
+        model: @_factSearchResults()
+        onInsert: @props.onInsert
+
+  _factSearchResults: ->
+    unless @____factSearchResults
+      recently_viewed_facts = new RecentlyViewedFacts
+      recently_viewed_facts.fetch()
+
+      @____factSearchResults = new FactSearchResults [],
+        fact_id: @props.fact.id
+        recently_viewed_facts: recently_viewed_facts
+
+    @____factSearchResults
+

 window.ReactAddComment = React.createBackboneClass
   displayName: 'ReactAddComment'
@@ -51,11 +75,10 @@
           ReactSearchLink
             opened: @state.searchOpened
             onToggle: (opened) => @setState searchOpened: opened
-          if @state.searchOpened
-            _div ['add-comment-search-facts'],
-              ReactFactSearch
-                model: @_factSearchResults()
-                onInsert: @_onSearchInsert
+          ReactSearchFacts
+            opened: @state.searchOpened
+            fact: @model()
+            onInsert: @_onSearchInsert

   _onTextareaChange: (text) ->
     @setState(text: text)
@@ -65,7 +88,7 @@
     @state.shareProviders[providerName] = checked
     @forceUpdate()

-  _onSearchInsert: (text) =>
+  _onSearchInsert: (text) ->
     @refs.textarea.insert text
     @setState searchOpened: false

@@ -92,13 +115,3 @@
       content: $.trim(@state.text)
       created_by: currentUser.toJSON()

-  _factSearchResults: ->
-    unless @____factSearchResults
-      recently_viewed_facts = new RecentlyViewedFacts
-      recently_viewed_facts.fetch()
-
-      @____factSearchResults = new FactSearchResults [],
-        fact_id: @model().fact.id
-        recently_viewed_facts: recently_viewed_facts
-
-    @____factSearchResults


Fix failing build (test, not SUT) text area view now expects input to be focused for autosizing to work, and enlarges directly on focus.

--- core/spec/javascripts/factlink/text_area_view_spec.coffee
+++ core/spec/javascripts/factlink/text_area_view_spec.coffee
@@ -15,6 +15,8 @@
     #since we need to check how layout works, we need to to include
     #the view in the document.
     $(document.body).append view.render().$el
+    textarea().trigger 'focus'
+

   it "shows the text", ->
     expect(textarea().val()).to.equal initialText
@@ -29,7 +31,7 @@
     expect(model.get('text')).to.equal 'bla - bla!'

   it "is initially small and scrollbarless", ->
-    expect(textarea().height()).to.be.below 45
+    expect(textarea().height()).to.be.below 55
     expect(textarea()[0].scrollHeight).to.equal textarea()[0].clientHeight

   it "grows vertically and avoids a scrollbar when text doesn't fit", ->


Catch errors from Task::start

Catch errors from Task::start

Closes atom/atom#3326

--- lib/spell-check-view.coffee
+++ lib/spell-check-view.coffee
@@ -57,6 +57,10 @@
       @append(view)

   updateMisspellings: ->
-    @task.start @buffer.getText(), (misspellings) =>
-      @destroyViews()
-      @addViews(misspellings) if @buffer?
+    # Task::start can throw errors atom/atom#3326
+    try
+      @task.start @buffer.getText(), (misspellings) =>
+        @destroyViews()
+        @addViews(misspellings) if @buffer?
+    catch error
+      console.warn('Error starting spell check task', error.stack ? error)


Don't serve /browserified 'cause it doesn't exist.

--- main.coffee
+++ main.coffee
@@ -84,7 +84,6 @@
 ))

 app.use '/assets', express.static "#{__dirname}/assets"
-app.use '/static/browserified', express.static "#{__dirname}/browserified"
 app.use '/static/bower_components', express.static "#{__dirname}/bower_components"

 app.set 'view engine', 'jade'


create lein project

create lein project

--- main.coffee
+++ main.coffee
@@ -31,6 +31,19 @@
     execSync 'type lein'
     execSync 'type pod'

+    log 'Creating Leiningen project'
+    execSync "lein new #{ projNameHyph }"
+
+    log 'Updating Leiningen project'
+    process.chdir projNameHyph
+    execSync "cp #{ resources }project.clj project.clj"
+    editSync 'project.clj', [[projNameHyphRx, projNameHyph]]
+    corePath = "src/#{ projNameUs }/core.clj"
+    fs.unlinkSync corePath
+    corePath += 's'
+    execSync "cp #{ resources }core.cljs #{ corePath }"
+    editSync corePath, [[projNameHyphRx, projNameHyph], [projNameRx, projName]]
+

   catch e
     logErr e.message


Finished the basing on graph for hoisting and working where lists

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -391,20 +391,27 @@

 compileWheres = (ast) ->
   ast.scope = topScopeDefines()
-  wheres = whereList inside ast
-  compileWhereImpl wheres
+  (compileWhereImpl inside ast).map(compileDef).join '\n'

 compileWheresInModule = (ast) ->
   ast.scope = topScopeDefines()
-  wheres = checkEvenness sortWheres whereList inside ast
-  "#{wheres.map(compileExportedDef).join '\n'}"
+  (compileWhereImpl inside ast).map(compileExportedDef).join '\n'
+
+compileWhereImpl = (tokens) ->
+  if tokens.length % 2 != 0
+    throw new Error "missing definition in top level assignment"
+  [readyWheres, hoistableWheres] = wheresWithHoisting whereList tokens
+  if hoistableWheres.length > 0
+    undefinedNames = []
+    for {names} in hoistableWheres
+      undefinedNames.push names...
+    throw new Error "#{undefinedNames.join ', '} used but not defined"
+  addToScopeAllNames readyWheres
+  graphToWheres readyWheres

 compileExportedDef = ([name, def]) ->
-  if !def?
-    throw new Error 'missing definition in top level assignment'
   if name.token
     identifier = validIdentifier name.token
-    addToEnclosingScope name.token, def
     "var #{identifier} = exports['#{identifier}'] = #{compileImpl def};"
   else
     compileDef [name, def]
@@ -576,24 +583,30 @@
     throw new Error "Missing definition of a function"
   # Find invalid (hoistable) wheres and let the function body
   # define them
-
-  [validWheres, hoistableWheres] = findHoistableWheres wheres
-  wheresDef = compileWhereImpl validWheres
-  bodyDef = compileImpl body, hoistableWheres
+  [readyWheres, hoistableWheres] = wheresWithHoisting wheres
+  addToScopeAllNames readyWheres
+  wheresDef = (graphToWheres readyWheres).map(compileDef).join '\n'
+  bodyDef = compileImpl body, graphToWheres hoistableWheres
   [wheresDef, bodyDef]

-compileWhereWithHoisting = (wheres) ->
+addToScopeAllNames = (graph) ->
+  for {names, def: [pattern, def]} in graph
+    for name in names
+      addToEnclosingScope name, def
+  return
+
+wheresWithHoisting = (wheres) ->
   graph = constructDependencyGraph wheres
-
-
-# Returns two lists, first the wheres that can be defined, second
-# the wheres which use an identifier which is not in scope (possibly
-# defined inside match)
-
-  # get list of names from wheres
-  # for each where, get list of used names
-  #   if some used name is not in newly defined or any parent scope
-  #     save it for later, maybe it's defined inside body
+  [hoistableGraph, readyGraph] = findHoistableWheres graph
+  [
+    sortTopologically readyGraph
+    sortTopologically hoistableGraph
+  ]
+
+graphToWheres = (graph) ->
+  graph.map ({def}) -> def
+
+# Returns two new graphs, one which needs hoisting and one which doesnt
 findHoistableWheres = ([graph, lookupTable]) ->
   reversedDependencies = reverseGraph graph
   hoistableNames = {}
@@ -609,29 +622,26 @@
           for n in dep.names
             hoistableNames[n] = yes
   for where in graph
-    {def, names} = where
+    {names} = where
     hoisted = no
     for n in names
       # if one of the names needs hoisting
       if hoistableNames[n]
-        hoistable.push def
+        hoistable.push where
         hoisted = yes
         break
     if not hoisted
       valid.push where
-  return [hoistable, [valid, lookupTable]]
-
-
-compileWhereImpl = (wheres) ->
-  sorted = checkEvenness sortWheres wheres
-  "#{sorted.map(compileDef).join '\n'}"
-
-checkEvenness = (wheres) ->
-  if wheres.length > 0
-    [_, lastDef] = wheres[wheres.length - 1]
-    if !lastDef?
-      throw new Error "Mising definition in definition list"
-  wheres
+  [
+    [hoistable, lookupTableForGraph hoistable]
+    [valid, lookupTableForGraph valid]
+  ]
+
+
+#compileWhereImpl = (wheres) ->
+#  sorted = checkEvenness sortWheres wheres
+#  "#{sorted.map(compileDef).join '\n'}"
+

 sortWheres = (wheres) ->
   xs = (sortTopologically constructDependencyGraph wheres).map ({def}) -> def
@@ -676,17 +686,14 @@
   lookupByName = {}
   deps = dependencySet()
   # find all defined names
-  graph = []
-  for [pattern, def], i in wheres
-    graph[i] = whereDef =
-      def: [pattern, def]
-      set: dependencySet()
-      names: []
-      missing: []
-    crawl pattern, (node) ->
-      if node.label is 'name'
-        whereDef.names.push node.token
-        lookupByName[node.token] = whereDef
+  graph = for [pattern, def], i in wheres
+    def: [pattern, def]
+    set: dependencySet()
+    names: findNames pattern
+    missing: []
+
+  lookupByName = lookupTableForGraph graph
+
   # then construct local graph
   for [pattern, def], i in wheres

@@ -705,6 +712,22 @@
           child.missing.push node.token

   [graph, lookupByName]
+
+lookupTableForGraph = (graph) ->
+  table = {}
+  for where in graph
+    {names} = where
+    for name in names
+      table[name] = where
+  table
+
+findNames = (pattern) ->
+  names = []
+  crawl pattern, (node) ->
+    if node.label is 'name'
+      names.push node.token
+  names
+

 dependencySet = ->
   numDependencies: 0


Save all defer reasons

Save all defer reasons

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -234,7 +234,7 @@
       definedNames: []
       usedNames: []
       deferrable: yes
-      _defer: undefined
+      _defers: []

   bareDefine: ->
     @definePattern()
@@ -667,8 +667,7 @@

   doDefer: (expression, dependencyName) ->
     definition = @_deferrableDefinition()
-    definition._defer =
-      (@_deferReasonOf definition) or [expression, dependencyName, @currentScopeIndex()]
+    definition._defers.push [expression, dependencyName, @currentScopeIndex()]

   deferReason: ->
     @_deferReasonOf @_deferrableDefinition()
@@ -677,7 +676,7 @@
     !!(@_deferReasonOf @_deferrableDefinition())

   _deferReasonOf: (definition) ->
-    definition?._defer
+    definition?._defers[0]

   addDeferredDefinition: ([expression, dependencyName, useScopeIndex, lhs, rhs]) ->
     @_scope().deferred.push [expression, dependencyName, useScopeIndex, lhs, rhs]
@@ -1564,9 +1563,9 @@

       #log "compiling wheres", pairs wheres
       compiledWheres = definitionListCompile ctx, pairs wheres
+
       # 1. Construct dependency graph
       # 2. Add to context
-
       ctx.setAuxiliaryDefinitions compiledWheres
       # compiledWheres = concat filter _is, compiledWheres


LaTeX format supports \$ and \includegraphics[width/height=...]

Supporting \$ is tricky because we don't want the resulting $ to
get considered as math.  So the LaTeX format now does the math
substitution itself, then does \$ -> $, then gets formatEither to
call KaTeX or just put the math back, as appropriate.

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -90,7 +90,19 @@
   .replace /\\subsubsection\s*\*?\s*{((?:[^{}]|{[^{}]*})*)}/g, '
$1
'
   .replace /\\paragraph\s*\*?\s*{((?:[^{}]|{[^{}]*})*)}\s*/g, '
$1 '
   .replace /\\footnote\s*{((?:[^{}]|{[^{}]*})*)}/g, '[$1]'
-  .replace /\\includegraphics\s*{([^{}]*)}/g, ''
+  .replace /\\includegraphics\s*(\[[^\[\]]*\]\s*)?{((?:[^{}]|{[^{}]*})*)}/g,
+    (match, optional, graphic) ->
+      style = ''
+      optional.replace /width\s*=\s*([0-9.]+)\s*([a-zA-Z]*)/g,
+        (match2, value, unit) ->
+          style += "width: #{value}#{unit};"
+          ''
+      .replace /height\s*=\s*([0-9.]+)\s*([a-zA-Z]*)/g,
+        (match2, value, unit) ->
+          style += "height: #{value}#{unit};"
+          ''
+      style = ' style="' + style + '"' if style
+      """"""
   .replace /\\begin\s*{(problem|theorem|conjecture|lemma|corollary)}/g, (m, p1) -> "
#{s.capitalize p1}: "
   .replace /\\end\s*{(problem|theorem|conjecture|lemma|corollary)}/g, '
'
   .replace /\\begin\s*{(proof|pf)}/g, 'Proof: '
@@ -122,7 +134,7 @@
   .replace /`/g, '‘'
   .replace /'/g, '’'
   .replace /\\&/g, '&'
-  .replace /\\([{}])/g, '$1'
+  .replace /\\([${}])/g, '$1'
   .replace /~/g, ' '
   .replace /\\\s/g, ' '
   .replace /---/g, '—'
@@ -130,8 +142,7 @@
   .replace /\n\n+/g, '\n
\n'
   .replace /
\s*(<h[1-9]>)/g, '$1'
   .replace /\[DOUBLEBACKSLASH\]/g, '\\\\'
-  ## Restore math
-  tex = tex.replace /MATH(\d+)ENDMATH/g, (match, p1) -> math[p1]
+  [tex, math]

 @formats =
   markdown: (text, title) ->
@@ -188,6 +199,10 @@
     math[i] = block
     "MATH#{i}ENDMATH"
   [text, math]
+
+putMathBack = (tex, math) ->
+  ## Restore math
+  tex.replace /MATH(\d+)ENDMATH/g, (match, p1) -> math[p1]

 postprocessKaTeX = (text, math) ->
   replacer = (block) ->
@@ -246,27 +261,33 @@
     #  after: sanitized
   sanitized

+formatEither = (isTitle, format, text, leaveTeX = false) ->
+  ## LaTeX format is special because it does its own math preprocessing at a
+  ## specific time during its formatting.  Other formats don't touch math.
+  if format == 'latex'
+    [text, math] = formats[format] text, isTitle
+  else
+    [text, math] = preprocessKaTeX text unless leaveTeX
+    if format of formats
+      text = formats[format] text, isTitle
+    else
+      console.warn "Unrecognized format '#{format}'"
+  ## Remove surrounding
 block caused by Markdown and LaTeX formatters.
+  if isTitle
+    text = text
+    .replace /^\s*
\s*/i, ''
+    .replace /\s*<\/P>\s*$/i, ''
+  if leaveTeX
+    text = putMathBack text, math if format == 'latex'
+  else
+    text = postprocessKaTeX text, math
+  sanitize postprocessCoauthorLinks text
+
 @formatBody = (format, body, leaveTeX = false) ->
-  [body, math] = preprocessKaTeX body unless leaveTeX or format == 'latex'
-  if format of formats
-    body = formats[format] body, false
-  else
-    console.warn "Unrecognized format '#{format}'"
-  body = postprocessKaTeX body, math unless leaveTeX
-  sanitize postprocessCoauthorLinks body
+  formatEither false, format, body, leaveTeX

 @formatTitle = (format, title, leaveTeX = false) ->
-  [title, math] = preprocessKaTeX title unless leaveTeX or format == 'latex'
-  if format of formats
-    title = formats[format] title, true
-  else
-    console.warn "Unrecognized format '#{format}'"
-  ## Remove surrounding
 block caused by Markdown and LaTeX formatters.
-  title = title
-  .replace /^\s*
\s*/i, ''
-  .replace /\s*<\/P>\s*$/i, ''
-  title = postprocessKaTeX title, math unless leaveTeX
-  sanitize postprocessCoauthorLinks title
+  formatEither true, format, title, leaveTeX

 @formatBadFile = (fileId) ->
   """<unknown file with ID #{fileId}>"""


Allowed the save option to be changed without reloading

The save option was only read on activate, meaning that the package
had to be reloaded before the wipOnSave option was reapplied. Changed
the option to be read on every save so that it can be dynamically
toggled.

--- lib/git-wip.coffee
+++ lib/git-wip.coffee
@@ -24,10 +24,9 @@

     # Register event listener for file saves
-    shouldWipOnSave = atom.config.get "#{@packageName}.wipOnSave"
-    if shouldWipOnSave
-      @subscriptions.add atom.workspace.observeTextEditors (editor) =>
-        editor.onDidSave (event) =>
+    @subscriptions.add atom.workspace.observeTextEditors (editor) =>
+      editor.onDidSave (event) =>
+        if atom.config.get "#{@packageName}.wipOnSave"
           @doGitWip(editor.getPath())

     # Register command that toggles this view


Remove extraneous operators in precedence function


Update index.coffee

Update index.coffee

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,6 +1,3 @@
-# express = require "express"
-# app = module.exports = express()
-
 util = require "util"
 _ = require "underscore"
 path = require "path"
@@ -30,6 +27,8 @@
   else
     @ds = new DataStore()

+  @middleware = []
+
   if opts? then _.extend @, opts

   # define our route uri w/ version, etc
@@ -48,6 +47,11 @@

   # set app to listen for all router paths
   # will fallback to `req.query` and `req.body`
+  if @middleware.length > 0
+    for mid in @middleware
+      app.all @uri, mid
+      app.all @uri + "/:id", mid
+
   app.all @uri, router

   # set our app to listen for :id


Removed some debugging

Removed some debugging

--- lib/index.coffee
+++ lib/index.coffee
@@ -427,10 +427,6 @@
                   r.c ||= index for r in newResults

                   diff = arraydiff results, newResults, (a, b) ->
-                    unless a and b
-                      console.log '####### undefined stuffs'
-                      console.log results
-                      console.log newResults
                     return false unless a and b
                     a.docName is b.docName and a.c is b.c
                   if diff.length


runtime: Fix exception when removing some IIPs

--- lib/runtime.coffee
+++ lib/runtime.coffee
@@ -44,7 +44,7 @@

 connectionsWithoutEdge = (connections, findConn) ->
     edgeEq = (a, b) ->
-        return a.port == b.port and a.process == b.process
+        return a?.port == b?.port and a?.process == b?.process
     newList = []
     for conn in connections
         if conn.src and edgeEq conn.src, findConn.src and edgeEq conn.tgt, findConn.tgt


Refactored code and updated documentation

--- lib/session.coffee
+++ lib/session.coffee
@@ -37,11 +37,11 @@
   # Authentication is provided by passing the name of a module which Node must be able to load, either from /lib/server, /vendor/module/lib, or from npm.
   # The module must implement an 'authenticate' function which expects a params object normally in the form of username and password, but could also be biometric or iPhone device id, SSO token, etc.
   #
-  # The callback must be an object with a 'status' attribute (boolean) and an 'id' attribute (number or string) if successful.
+  # The callback must be an object with a 'status' attribute (boolean) and a 'user_id' attribute (number or string) if successful.
   #
   # Additional info, such as number of tries remaining etc, can be passed back within the object and pushed upstream to the client. E.g:
   #
-  # {success: true, id: 21323, info: {username: 'joebloggs'}}
+  # {success: true, user_id: 21323, info: {username: 'joebloggs'}}
   # {success: false, info: {num_retries: 2}}
   # {success: false, info: {num_retries: 0, lockout_duration_mins: 30}}
   authenticate: (module_name, params, cb) ->
@@ -52,7 +52,7 @@
   # Users
   assignUser: (data) ->
     return null unless data.user_id
-    @user = @_encodeAttributes data.attributes
+    @user = JSON.parse data.attributes

   loggedIn: ->
     @user?
@@ -65,16 +65,8 @@
     return null if id is undefined or user_data is undefined
     R.hset @key(), 'user_id', id
     @assignUser(user_data)
-    R.hset @key(), 'attributes', @_decodeAttributes Object.extend @getAttributes(), user_data # Set attributes
+    R.hset @key(), 'attributes', JSON.stringify Object.extend @getAttributes(), user_data # Set attributes

   getAttributes: ->
     attr = R.hget @key(), 'attributes'
-    @_encodeAttributes if attr is undefined then '{}' else attr
-
-  # Encode a hash to a string for storing in Redis. This should be deprecated once Redis 2.2 is in use.
-  _encodeAttributes: (attr) ->
-    JSON.parse attr
-
-  # Decode a string from Redis into a hash. This should be deprecated once Redis 2.2 is in use.
-  _decodeAttributes: (attr) ->
-    JSON.stringify attr+    JSON.parse if attr is undefined then '{}' else attr


swapped order of `or` arguments in `signed_data` assignment

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -394,7 +394,7 @@

       saml_response_str = saml_response.toString()
       for cert in idp_certificates or []
-        signed_data = check_saml_signature(saml_response_str, cert) or check_saml_signature result, cert
+        signed_data = check_saml_signature(result, cert) or check_saml_signature saml_response_str, cert
         unless signed_data
           continue # Cert was not valid, try the next one

@@ -626,4 +626,3 @@
   module.exports.parse_assertion_attributes = parse_assertion_attributes
   module.exports.set_option_defaults = set_option_defaults
   module.exports.extract_certificate_data = extract_certificate_data
-


Attempt to fix comment erratic behaviour

Which it’s not working completely, but it seems to be a bit better …

--- grammars/pug.cson
+++ grammars/pug.cson
@@ -10,28 +10,6 @@
     name: "comment.other.doctype.jade"
   }
   {
-    begin: "^(\\s*)//-"
-    comment: "Unbuffered (pug-only) comments."
-    end: "^(?!(\\1\\s)|\\s*$)"
-    name: "comment.unbuffered.block.pug"
-  }
-  {
-    begin: "^(\\s*)//"
-    comment: "Buffered (html) comments."
-    end: "^(?!(\\1\\s)|\\s*$)"
-    name: "string.comment.buffered.block.pug"
-    patterns: [
-      {
-        captures:
-          "1":
-            name: "invalid.illegal.comment.comment.block.pug"
-        comment: "Buffered comments inside buffered comments will generate invalid html."
-        match: "^\\s*(//)(?!-)"
-        name: "string.comment.buffered.block.pug"
-      }
-    ]
-  }
-  {
     begin: "^(\\s*)-$"
     comment: "Unbuffered code block."
     end: "^(?!(\\1\\s)|\\s*$)"
@@ -269,6 +247,12 @@
     comment: "All constructs that generally span a single line starting with any number of white-spaces."
     end: "$"
     patterns: [
+      {
+        include: "#unbuffered_comments"
+      }
+      {
+        include: "#buffered_comments"
+      }
       {
         include: "#inline_pug"
       }
@@ -360,6 +344,29 @@
   }
 ]
 repository:
+
+  unbuffered_comments:
+    begin: "^(\\s*)//-"
+    comment: "Unbuffered (pug-only) comments."
+    end: "^(?!(\\1\\s)|\\s*$)"
+    name: "comment.unbuffered.block.pug"
+
+  buffered_comments:
+    begin: "^(\\s*)//"
+    comment: "Buffered (html) comments."
+    end: "^(?!(\\1\\s)|\\s*$)"
+    name: "string.comment.buffered.block.pug"
+    patterns: [
+      {
+        captures:
+          "1":
+            name: "invalid.illegal.comment.comment.block.pug"
+        comment: "Buffered comments inside buffered comments will generate invalid html."
+        match: "^\\s*(//)(?!-)"
+        name: "string.comment.buffered.block.pug"
+      }
+    ]
+
   blocks_and_includes:
     captures:
       "1":


PUBDEV-1175 Format diagonal in binomial confusion matrices

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -168,17 +168,17 @@

 formatConfusionMatrix = (cm) ->
   [[tn, fp], [fn, tp]] = cm
-  [ table, tbody, tr, td ] = Flow.HTML.template 'table.flow-matrix', 'tbody', 'tr', 'td'
+  [ table, tbody, tr, normal, yellow ] = Flow.HTML.template 'table.flow-matrix', 'tbody', 'tr', 'td', 'td.bg-yellow'

   table [
     tbody [
       tr [
-        td tn
-        td fp
+        yellow tn
+        normal fp
       ]
       tr [
-        td fn
-        td tp
+        normal fn
+        yellow tp
       ]
     ]
   ]


Fix all frame-related inspect/grid operations

--- src/ext/modules/routines.coffee
+++ src/ext/modules/routines.coffee
@@ -3,6 +3,7 @@
 createVector = window.plot.createVector
 createFactor = window.plot.createFactor
 createList = window.plot.createList
+createDataframe = window.plot.createFrame

 _assistance =
   importFiles:
@@ -33,6 +34,12 @@
     target[i] = if element is 'NaN' then undefined else element
   target

+parseNulls=  (source) ->
+  target = new Array source.length
+  for element, i in source
+    target[i] = if element? then element else undefined
+  target
+
 repeatValues = (count, value) ->
   target = new Array count
   for i in [0 ... count]
@@ -270,7 +277,7 @@

     modelKeys = (model.key for model in models)

-    window.plot.createFrame 'parameters', vectors, (sequence models.length), null,
+    createDataframe 'parameters', vectors, (sequence models.length), null,
       description: "Parameters for models #{modelKeys.join ', '}"
       origin: "getModels #{stringify modelKeys}"

@@ -298,7 +305,7 @@
         when TObject
           createList name, data, stringify

-    window.plot.createFrame 'parameters', vectors, (sequence parameters.length), null,
+    createDataframe 'parameters', vectors, (sequence parameters.length), null,
       description: "Parameters for model '#{model.key.name}'" #TODO frame key
       origin: "getModel #{stringify model.key.name}"

@@ -313,7 +320,7 @@
       createVector 'mse_valid', TNumber, parseNaNs output.mse_valid
     ]

-    window.plot.createFrame 'output', vectors, (sequence size), null,
+    createDataframe 'output', vectors, (sequence size), null,
       description: "Output for GBM model '#{model.key.name}'"
       origin: "getModel #{stringify model.key.name}"

@@ -330,7 +337,7 @@

     ]

-    window.plot.createFrame 'output', vectors, (sequence 1), null,
+    createDataframe 'output', vectors, (sequence 1), null,
       description: "Output for k-means model '#{model.key.name}'"
       origin: "getModel #{stringify model.key.name}"

@@ -343,7 +350,7 @@
       createVector 'within_mse', TNumber, output.within_mse
     ]

-    window.plot.createFrame 'cluster_means', vectors, (sequence output.size.length), null,
+    createDataframe 'cluster_means', vectors, (sequence output.size.length), null,
       description: "Cluster means for k-means model '#{model.key.name}'"
       origin: "getModel #{stringify model.key.name}"

@@ -357,7 +364,7 @@

     vectors.unshift createFactor 'cluster', TString, output.centers.rowHeaders

-    window.plot.createFrame 'clusters', vectors, (sequence output.centers_raw.length), null,
+    createDataframe 'clusters', vectors, (sequence output.centers_raw.length), null,
       description: "Clusters for k-means model '#{model.key.name}'"
       origin: "getModel #{stringify model.key.name}"

@@ -422,7 +429,7 @@
       createVector 'scoring_time', TString, [ prediction.scoring_time ]
     ]

-    window.plot.createFrame 'prediction', vectors, (sequence 1), null,
+    createDataframe 'prediction', vectors, (sequence 1), null,
       description: "Prediction output for model '#{model.name}' on frame '#{frame.name}'"
       origin: "getPrediction #{stringify model.name}, #{stringify frame.name}"

@@ -440,7 +447,7 @@
       createFactor 'threshold_criterion', TString, [ auc.threshold_criterion ]
     ]

-    window.plot.createFrame 'prediction', vectors, (sequence 1), null,
+    createDataframe 'prediction', vectors, (sequence 1), null,
       description: "Prediction output for model '#{model.name}' on frame '#{frame.name}'"
       origin: "getPrediction #{stringify model.name}, #{stringify frame.name}"

@@ -466,7 +473,7 @@
       createFactor 'frame', TString, concatArrays (repeatValues prediction.auc.threshold_criteria.length, prediction.frame.name for prediction in predictions)
     ]

-    window.plot.createFrame 'metrics', vectors, (sequence (head vectors).count()), null,
+    createDataframe 'metrics', vectors, (sequence (head vectors).count()), null,
       description: "Metrics for the selected predictions"
       origin: formulateGetPredictionsOrigin opts
       plot: """
@@ -486,7 +493,7 @@
       #createFactor 'threshold_criterion', TString, (prediction.auc.threshold_criterion for prediction in predictions)
     ]

-    window.plot.createFrame 'predictions', vectors, (sequence predictions.length), null,
+    createDataframe 'predictions', vectors, (sequence predictions.length), null,
       description: "Prediction output for selected predictions."
       origin: formulateGetPredictionsOrigin opts
       plot: """
@@ -528,7 +535,7 @@
       createFactor 'frame', TString, concatArrays (repeatValues prediction.auc.thresholds.length, prediction.frame.name for prediction in predictions)
     ]

-    window.plot.createFrame 'scores', vectors, (sequence (head vectors).count()), null,
+    createDataframe 'scores', vectors, (sequence (head vectors).count()), null,
       description: "Scores for the selected predictions"
       origin: formulateGetPredictionsOrigin opts
       plot: """
@@ -550,42 +557,33 @@

   inspectFrameColumns = (tableLabel, frameKey, frame, frameColumns) -> ->
     attrs = [
-      [ 'label', TString ]
-      [ 'missing', TNumber ]
-      [ 'zeros', TNumber ]
-      [ 'pinfs', TNumber ]
-      [ 'ninfs', TNumber ]
-      [ 'min', TNumber ]
-      [ 'max', TNumber ]
-      [ 'mean', TNumber ]
-      [ 'sigma', TNumber ]
-      [ 'type', TString ]
-      [ 'cardinality', TNumber ]
+      'label'
+      'missing'
+      'zeros'
+      'pinfs'
+      'ninfs'
+      'min'
+      'max'
+      'mean'
+      'sigma'
+      'type'
+      'cardinality'
     ]

-    for [ name, type ] in attrs
-      data = new Array frameColumns.length
+    vectors = for name in attrs
       switch name
         when 'min'
-          for column, i in frameColumns
-            data[i] = head column.mins
+          createVector name, TNumber, (head column.mins for column in frameColumns)
         when 'max'
-          for column, i in frameColumns
-            data[i] = head column.maxs
+          createVector name, TNumber, (head column.maxs for column in frameColumns)
         when 'cardinality'
-          for column, i in frameColumns
-            data[i] = if domain = column.domain then domain.length else null
+          createVector name, TNumber, ((if domain = column.domain then domain.length else undefined) for column in frameColumns)
+        when 'label', 'type'
+          createFactor name, TString, (column[name] for column in frameColumns)
         else
-          for column, i in frameColumns
-            data[i] = column[name]
-
-      switch type
-        when TString
-          createFactor name, type, data
-        when TNumber
-          createVector name, type, data
-
-    window.plot.createFrame tableLabel, vectors, (sequence frameColumns.length), null,
+          createVector name, TNumber, (column[name] for column in frameColumns)
+
+    createDataframe tableLabel, vectors, (sequence frameColumns.length), null,
       description: "A list of #{tableLabel} in the H2O Frame."
       origin: "getFrame #{stringify frameKey}"
       plot: "plot inspect '#{tableLabel}', getFrame #{stringify frameKey}"
@@ -593,43 +591,23 @@

   inspectFrameData = (frameKey, frame) -> ->
     frameColumns = frame.columns
-    variables = for column in frameColumns
+
+    vectors = for column in frameColumns
       #XXX format functions
       switch column.type
-        when 'int'
-          Flow.Data.Variable column.label, TNumber
-        when 'real'
-          Flow.Data.Variable column.label, TNumber
+        when 'int', 'real'
+          createVector column.label, TNumber, parseNaNs column.data
         when 'enum'
-          Flow.Data.Factor column.label, column.domain
-        when 'uuid', 'string'
-          Flow.Data.Variable column.label, TString
+          domain = column.domain
+          createFactor column.label, TString, ((if index? then domain[index] else undefined) for index in column.data)
         when 'time'
-          Flow.Data.Variable column.label, TDate
-        else
-          Flow.Data.Variable column.label, TObject
-
-    Record = Flow.Data.Record variables
-    rowCount = (head frameColumns).data.length
-    rows = for i in [0 ... rowCount]
-      row = new Record()
-      for variable, j in variables
-        value = frameColumns[j].data[i]
-        switch variable.type
-          when TNumber, TNumber
-            #TODO handle +-Inf
-            row[variable.label] = if value is 'NaN' then null else value
-          else
-            row[variable.label] = value
-      row
-
-    Flow.Data.Table
-      label: 'data'
+          createVector column.label, TNumber, parseNaNs column.data
+        else # uuid / string / etc.
+          createList column.label, parseNulls column.data
+
+    createDataframe 'data', vectors, (sequence (head frameColumns).data.length), null,
       description: 'A partial list of rows in the H2O Frame.'
-      variables: variables
-      rows: rows
-      meta:
-        origin: "getFrame #{stringify frameKey}"
+      origin: "getFrame #{stringify frameKey}"

   extendFrame = (frameKey, frame) ->
     inspections =
@@ -707,7 +685,7 @@
         createVector 'count', TNumber, countData
       ]

-      window.plot.createFrame 'distribution', vectors, (sequence binCount), null,
+      createDataframe 'distribution', vectors, (sequence binCount), null,
         description: "Distribution for column '#{column.label}' in frame '#{frameKey}'."
         origin: "getColumnSummary #{stringify frameKey}, #{stringify columnName}"
         plot: """
@@ -735,7 +713,7 @@
         createVector 'percent', TNumber, percentData
       ]

-      window.plot.createFrame 'characteristics', vectors, (sequence characteristicData.length), null,
+      createDataframe 'characteristics', vectors, (sequence characteristicData.length), null,
         description: "Characteristics for column '#{column.label}' in frame '#{frameKey}'."
         origin: "getColumnSummary #{stringify frameKey}, #{stringify columnName}"

@@ -761,7 +739,7 @@
         createVector 'max', TNumber, [ maximum ]
       ]

-      window.plot.createFrame 'summary', vectors, (sequence 1), null,
+      createDataframe 'summary', vectors, (sequence 1), null,
         description: "Summary for column '#{column.label}' in frame '#{frameKey}'."
         origin: "getColumnSummary #{stringify frameKey}, #{stringify columnName}"
         plot: """


somewhat hack-ridden but effective re-attach logic when containers stop for < 10 seconds

--- src/decking.coffee
+++ src/decking.coffee
@@ -160,20 +160,42 @@

   attach: (cluster, done) ->

-    options =
-      stream: true
-      stdout: true
-      stderr: true
-      logs: true
-      tty: false
-
-    iterator = (details, callback) ->
-      name = details.name
-      container = docker.getContainer name
+
+    timeout = 200
+
+    reAttach = (name, container, attempts = 0) ->
+      setTimeout ->
+        isRunning container, (err, running) ->
+          if running
+            attach name, container, false, ->
+              logAction name, "re-attached"
+          else
+            if attempts < 50
+              reAttach name, container, attempts + 1
+            else
+              logAction name, "max re-attach attempts reached, bailing..."
+      , timeout
+
+    attach = (name, container, fetchLogs, callback) ->
+      options =
+        stream: true
+        stdout: true
+        stderr: true
+        tty: false
+        logs: fetchLogs
+
       container.attach options, (err, stream) ->
         new MultiplexStream container, stream, padName(name, "(", ")")

-        callback null
+        stream.on "end", ->
+          logAction name, "gone away, will try to re-attaching for 10 seconds..."
+          reAttach name, container
+
+        callback? err
+
+    iterator = (details, callback) ->
+      container = docker.getContainer details.name
+      attach details.name, container, true, callback

     resolveOrder @config, cluster, (list) ->


:art:

:art:

--- spec/grammar-spec.coffee
+++ spec/grammar-spec.coffee
@@ -13,7 +13,7 @@
     expect(grammar.scopeName).toBe "source.perl"

   it "tokenizes regexp replace", ->
-    {tokens} = grammar.tokenizeLine('s/text/test/')
+    {tokens} = grammar.tokenizeLine("s/text/test/")
     expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
     expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
     expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
@@ -21,7 +21,7 @@
     expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl"]
     expect(tokens[5]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl", "punctuation.definition.string.perl"]

-    {tokens} = grammar.tokenizeLine('s_text_test_')
+    {tokens} = grammar.tokenizeLine("s_text_test_")
     expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
     expect(tokens[1]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
     expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
@@ -29,7 +29,7 @@
     expect(tokens[4]).toEqual value: "test", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl"]
     expect(tokens[5]).toEqual value: "_", scopes: ["source.perl", "string.regexp.replaceXXX.format.simple_delimiter.perl", "punctuation.definition.string.perl"]

-    {tokens} = grammar.tokenizeLine('s/text/test/gxr')
+    {tokens} = grammar.tokenizeLine("s/text/test/gxr")
     expect(tokens[0]).toEqual value: "s", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl", "support.function.perl"]
     expect(tokens[1]).toEqual value: "/", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl", "punctuation.definition.string.perl"]
     expect(tokens[2]).toEqual value: "text", scopes: ["source.perl", "string.regexp.replaceXXX.simple_delimiter.perl"]
@@ -39,20 +39,20 @@
     expect(tokens[6]).toEqual value: "gxr", scopes: ["source.perl", "string.regexp.replace.perl", "punctuation.definition.string.perl", "keyword.control.regexp-option.perl"]

   it "tokenizes constant variables", ->
-    {tokens} = grammar.tokenizeLine('__FILE__')
+    {tokens} = grammar.tokenizeLine("__FILE__")
     expect(tokens[0]).toEqual value: "__FILE__", scopes: ["source.perl", "constant.language.perl"]

-    {tokens} = grammar.tokenizeLine('__LINE__')
+    {tokens} = grammar.tokenizeLine("__LINE__")
     expect(tokens[0]).toEqual value: "__LINE__", scopes: ["source.perl", "constant.language.perl"]

-    {tokens} = grammar.tokenizeLine('__PACKAGE__')
+    {tokens} = grammar.tokenizeLine("__PACKAGE__")
     expect(tokens[0]).toEqual value: "__PACKAGE__", scopes: ["source.perl", "constant.language.perl"]

-    {tokens} = grammar.tokenizeLine('__SUB__')
+    {tokens} = grammar.tokenizeLine("__SUB__")
     expect(tokens[0]).toEqual value: "__SUB__", scopes: ["source.perl", "constant.language.perl"]

-    {tokens} = grammar.tokenizeLine('__END__')
+    {tokens} = grammar.tokenizeLine("__END__")
     expect(tokens[0]).toEqual value: "__END__", scopes: ["source.perl", "constant.language.perl"]

-    {tokens} = grammar.tokenizeLine('__DATA__')
+    {tokens} = grammar.tokenizeLine("__DATA__")
     expect(tokens[0]).toEqual value: "__DATA__", scopes: ["source.perl", "constant.language.perl"]


Comment update

Comment update

--- spec/motions-spec.coffee
+++ spec/motions-spec.coffee
@@ -985,7 +985,7 @@
           keydown('n')
           expect(editor.getCursorBufferPosition()).toEqual [2, 0]

-        it "uses Smartcase sensitive search if useSmartcaseForSearch is true and searching uppercase", ->
+        it "uses case sensitive search if useSmartcaseForSearch is true and searching uppercase", ->
           atom.config.set 'vim-mode.useSmartcaseForSearch', true
           editor.commandModeInputView.editor.setText 'ABC'
           editor.commandModeInputView.editor.trigger 'core:confirm'


Expose APPLICATION_NAME in sharify

Expose APPLICATION_NAME in sharify

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -9,7 +9,7 @@
   CANONICAL_MOBILE_URL, IMAGES_URL_PREFIX, SECURE_IMAGES_URL, GOOGLE_ANALYTICS_ID, MIXPANEL_ID, SNOWPLOW_COLLECTOR_HOST, GOOGLE_SEARCH_CX, GOOGLE_SEARCH_KEY,
   COOKIE_DOMAIN, AUTO_GRAVITY_LOGIN, GOOGLE_MAPS_API_KEY, ADMIN_URL, CMS_URL, MAX_SOCKETS, ARTWORK_EMBED_URL,
   DELTA_HOST, ENABLE_AB_TEST, KIOSK_MODE, KIOSK_PAGE, SUGGESTIONS_AB_TEST, SESSION_COOKIE_KEY, SENTRY_DSN, SENTRY_PUBLIC_DSN
-  EMPTY_COLLECTION_SET_ID, GEMINI_S3_ACCESS_KEY, GEMINI_APP, GEMINI_ACCOUNT_KEY, BIDDER_H1_COPY, BIDDER_H2_COPY } = config = require "../config"
+  EMPTY_COLLECTION_SET_ID, GEMINI_S3_ACCESS_KEY, GEMINI_APP, GEMINI_ACCOUNT_KEY, BIDDER_H1_COPY, BIDDER_H2_COPY, APPLICATION_NAME } = config = require "../config"

 { parse, format } = require 'url'

@@ -79,6 +79,7 @@
   SENTRY_PUBLIC_DSN: SENTRY_PUBLIC_DSN
   ARTWORK_EMBED_URL: ARTWORK_EMBED_URL
   GOOGLE_SEARCH_CX: GOOGLE_SEARCH_CX
+  APPLICATION_NAME: APPLICATION_NAME

 CurrentUser = require '../models/current_user'


submit form when logout is clicked

submit form when logout is clicked

--- exercises/src/components/user-actions-menu.cjsx
+++ exercises/src/components/user-actions-menu.cjsx
@@ -12,6 +12,9 @@
     @crsfToken =
       document.querySelector('meta[name=csrf-token]')?.getAttribute('content')

+  onLogoutClick: ->
+    @refs.logoutForm.submit()
+
   render: ->
     name = @props.user?.full_name or
       [@props.user?.first_name, @props.user?.last_name].join(' ')
@@ -22,8 +25,9 @@
         id='navbar-dropdown'
         title={name}
         ref='navDropDown'>
-        <BS.MenuItem className='logout' onClick={@onLinkClick}>
+        <BS.MenuItem className='logout'>
           <form
+            ref='logoutForm'
             acceptCharset='UTF-8'
             action={'/accounts/logout'}
             className='-logout-form'
@@ -31,7 +35,7 @@
           >
             <input type='hidden' name='_method' value='delete'/>
             <input type='hidden' name='authenticity_token' value={@crsfToken}/>
-            <input type='submit' aria-label="Log Out" value='Log Out' />
+            <input type='submit' aria-label="Log Out" value='Log Out' onClick={@onLogoutClick} />
           </form>
         </BS.MenuItem>


Hack to get insta results working

Hack to get insta results working

--- src/js/app/CommandWorker.coffee
+++ src/js/app/CommandWorker.coffee
@@ -23,6 +23,7 @@
             type: 'command'
       else
         if value.length > 0
+          # console.log "from command worker", (@source or '') + value
           sourceAndCommand = (@source or '') + value
           try
             # [res, warnings] = compiler.compileExp value
@@ -34,6 +35,7 @@
                 @compiler.compileTopLevelAndExpression sourceAndCommand

           catch e
+            console.log sourceAndCommand
             console.log e.stack
             @sender.emit "error",
               text: e.message


Explicitly remove alert after fadeOut.

Explicitly remove alert after fadeOut.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -59,7 +59,8 @@
     if $(child).attr('id')
       localStorage["#{collection}:#{$(child).attr('id')}"] = $(child).val()
   $('#collection_form').after $('<div>').attr('class','alert alert-success').attr('id','save_success').append('Saved.')
-  $('#save_success').fadeOut(1800)
+  $('#save_success').fadeOut 1800, ->
+    $(this).remove()

 load_collection_form = ->
   collection = $('#collection_select').val()


Add comments for beginner-mode for loop

Add comments for beginner-mode for loop

--- src/languages/javascript.coffee
+++ src/languages/javascript.coffee
@@ -615,11 +615,21 @@

   return helper.DISCOURAGE

+# Check to see if a "for" loop is standard, for beginner mode.
+# We will simplify any loop of the form:
+# ```
+# for (var i = X; i < Y; i++) {
+#   // etc...
+# }
+# `
+# Where "var" is optional and "i++" can be pre- or postincrement.
 isStandardForLoop = (node) ->
   unless node.init? and node.test? and node.update?
     return false

-  # A standard for loop starts with "var a =" or "a = "
+  # A standard for loop starts with "var a =" or "a = ".
+  # Determine the variable name so that we can check against it
+  # in the other two expression.
   if node.init.type is 'VariableDeclaration'
     variableName = node.init.declarations[0].id.name
   else if node.init.type is 'AssignmentExpression' and


no message

no message


Make S3 failure integration test more robust

--- test/browser.spec.coffee
+++ test/browser.spec.coffee
@@ -67,8 +67,9 @@
       done = null
       httpHeaders = false; httpData = false; httpError = false
       runs ->
-        svc = new AWS.S3(accessKeyId: 'akid', secretAccessKey: 'secret')
-        req = svc.getObject(Bucket:'invalidbucket' + new Date(), Key: 'foo')
+        svc = new AWS.S3(accessKeyId: 'akid', secretAccessKey: 'secret', maxRetries: 0)
+        date = AWS.util.date.iso8601().replace(/[^0-9]/g,'')
+        req = svc.getObject(Bucket:'invalidbucket' + date, Key: 'foo')
         req.on 'httpHeaders', -> httpHeaders = true
         req.on 'httpData', -> httpData = true
         req.on 'httpError', -> httpError = true


add commit

add commit

--- src/gallery.coffee
+++ src/gallery.coffee
@@ -321,8 +321,7 @@
   _initRoutate: () ->
     key = @gallery.find("img").attr("src")
     degree = localStorage.getItem(key) || 0
-    degree_diff = ((degree - @rotatedegrees) % 360 + 360) % 360 / 90
-    console.log degree + " , "+ @rotatedegrees + " , " + degree_diff
+    degree_diff = ((degree - @rotatedegrees) % 360 + 360) % 360 / 90 #求相差几次旋转
     for rotate in [1 .. degree_diff]
       @_rotate()


enhanced code-folding and comment-embbeding implemenation to allow a series of comments to be embedded info code and folded

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -126,6 +126,12 @@
       else
         singleLineMatcher = blockSingleLineMatcher

+    if language.ignorePrefix?
+      stripIgnorePrefix = ///(#{language.singleLineComment.join '|'})#{whitespaceMatch}#{language.ignorePrefix}///
+
+    if language.foldPrefix?
+      stripFoldPrefix = ///(#{language.singleLineComment.join '|'})#{whitespaceMatch}#{language.foldPrefix}///
+
     inBlock = false

     for line in lines
@@ -154,30 +160,48 @@
       # Match that line to the language's single line comment syntax.
       #
       # However, we treat all comments beginning with } as inline code commentary
-      # and comments starting with ! cause that comment and the following code
+      # and comments starting with ~ cause that comment and the following code
       # block to start folded.
       else if (match = line.match singleLineMatcher)?

         value = (match[2] || match[4])

-        if !value? or value == ''
-
-        else
-          if currSegment.code.length > 0
-            segments.push currSegment
-            currSegment = new @Segment
+        if value? and value isnt ''

           # } For example, this comment should be treated as part of our code.
-          if value[0] == language.ignorePrefix
-            # } But let's not show the } character in our documentation
-            currSegment.code.push line.replace language.singleLineComment + language.ignorePrefix, language.singleLineComment
-
-          # - ..and if we'd started this comment with ! instead of } it and all the code to the next comment would start folded
-          else if value[0] == language.foldPrefix
-            currSegment.foldMarker = line.replace language.singleLineComment + language.foldPrefix, language.singleLineComment
+          # } Achieved by prefixing the comment's content with “}”
+          if stripIgnorePrefix? and value.indexOf(language.ignorePrefix) is 0
+
+            # **Unfold this code ->**
+            # ~ The previous cycle contained code, so lets start a new segment,
+            # } but only if the previous code-line isn't a comment forced to be
+            # } part of the code, as implemented here.  This allows embedding a
+            # } series of code-comments, even folded like this one.
+            if currSegment.code.length > 0 and \
+               not (currSegment.code[currSegment.code.length - 1].match singleLineMatcher)?
+              segments.push currSegment
+              currSegment = new @Segment
+
+            # Let's strip the “}” character from our documentation
+            currSegment.code.push line.replace stripIgnorePrefix, match[1]

           else
-            currSegment.comments.push value
+
+            # The previous cycle contained code, so lets start a new segment
+            if currSegment.code.length > 0
+              segments.push currSegment
+              currSegment = new @Segment
+
+            # ~ … if we start this comment with “~” instead of “}” it and all
+            # } code up to the next segment's first comment starts folded
+            if stripFoldPrefix? and value.indexOf(language.foldPrefix) is 0
+
+              # } … so folding stopped above, as this is a new segment !
+              # Let's strip the “~” character from our documentation
+              currSegment.foldMarker = line.replace stripFoldPrefix, match[1]
+
+            else
+              currSegment.comments.push value

       else
         currSegment.code.push line


model_postgres: fix nodeExists()

model_postgres: fix nodeExists()

--- src/local/model_postgres.coffee
+++ src/local/model_postgres.coffee
@@ -157,13 +157,11 @@
                     cb new errors.NotFound("Node does not exist")

     nodeExists: (node, cb) ->
-        db = @db
-        (cb) ->
-            db.query "SELECT node FROM nodes WHERE node=$1", [ node ], (err, res) ->
-                if err
-                    cb err
-                else
-                    cb null, res?.rows?[0]?
+        @db.query "SELECT node FROM nodes WHERE node=$1", [ node ], (err, res) ->
+            if err
+                cb err
+            else
+                cb null, res?.rows?[0]?

     createNode: (node, cb) ->
         db = @db


refactor(helpers): removed unused parameter from single_get_fn

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -4,11 +4,11 @@
 relationship_fields = (name, model, connector, options = {}) -> [name + '_id', name + 's', name, window[model], connector, options]

 single_get_fn = (id_param, api_name, name, model, api, options = {}) ->
-  (params = {}, callbackOrObservable) =>
-    unless callback?
-      callback = (data) =>
-        @[name] model(data)
-    else if typeof callbackOrObservable['push'] == 'undefined'
+  (callbackOrObservable) =>
+    callback = (data) =>
+      @[name] model(data)
+
+    if callbackOrObservable? && typeof callbackOrObservable['push'] == 'undefined'
       if typeof callbackOrObservable != 'function'
         throw new Error 'model.find 2nd parameter needs to be either a function or a pushable object (Array, ObservableArray).\nGiven: ' + callbackOrObservable


remove some @ from tests

remove some @ from tests

--- test/context.test.coffee
+++ test/context.test.coffee
@@ -147,27 +147,12 @@
       $el = render context_a
       expect($el.text()).to.be 'ab'

-    # TODO reconsider this behavior
-    ###
-    it "doesn't allow output after render", ->
-      context_a = Context.create_run_context [ctx, {set_test_result}]
-      Context.eval_in_context context_a, ->
-        setTimeout =>
-          expect =>
-            @text 'a'
-          .toThrow new Error 'already rendered'
-          @set_test_result true
-        , 0
-      $el = render context_a
-      waitsFor (-> result), 1000
-    ###
-
     it "allows output after render", (done) ->
       context_a = Context.create_run_context [ctx, {set_test_result}]
       Context.eval_in_context context_a, ->
         text 'a'
-        setTimeout =>
-          @text 'c'
+        setTimeout @keeping_context ->
+          text 'c'
           complete()
         , 0
         text 'b'
@@ -181,10 +166,10 @@
       Context.eval_in_context context_a, ->
         Context.nested_item @, ->
           setTimeout @keeping_context ->
-            @text 'a'
+            text 'a'
             complete()
           , 0
-        @text 'b'
+        text 'b'
       $el = render context_a
       on_complete done, ->
         expect($el.find('p').text()).to.be 'ab'


:bug: Expand the selection on repeated uses of ctrl-cmd-m.

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -257,17 +257,23 @@
         endPosition = endRange.start
     else
       if startPosition = @findAnyStartPair(@editor.getCursorBufferPosition())
+        startPositionExternal = startPosition
         startPair = @editor.getTextInRange(Range.fromPointWithDelta(startPosition, 0, 1))
         endPosition = @findMatchingEndPair(startPosition, startPair, startPairMatches[startPair])
+        endPositionExternal = endPosition.traverse([0, 1])
       else if pair = @tagFinder.findEnclosingTags()
         {startRange, endRange} = pair
         if startRange.compare(endRange) > 0
           [startRange, endRange] = [endRange, startRange]
         startPosition = startRange.end
         endPosition = endRange.start.traverse([0, -2]) # Don't select </
+        startPositionExternal = startRange.start.traverse([0, -1])
+        endPositionExternal = endRange.end.traverse([0, 1])

     if startPosition? and endPosition?
       rangeToSelect = new Range(startPosition.traverse([0, 1]), endPosition)
+      if @editor.getSelectedBufferRange().containsRange(rangeToSelect)
+        rangeToSelect = new Range(startPositionExternal, endPositionExternal)
       @editor.setSelectedBufferRange(rangeToSelect)

   # Insert at the current cursor position a closing tag if there exists an


Add human readable file size in progress

--- lib/csv-progress-element.coffee
+++ lib/csv-progress-element.coffee
@@ -1,4 +1,6 @@
 {registerOrUpdateElement, SpacePenDSL} = require 'atom-utils'
+
+byteUnits = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']

 module.exports =
 class CSVProgressElement extends HTMLElement
@@ -11,15 +13,29 @@
       @div class: 'block', =>
         @tag 'progress', max: '100', outlet: 'progress'

-  createdCallback: ->
-
-  attachedCallback: ->
-
   updateReadData: (input, lines) ->
     progressData = input.getProgress()
     @linesLabel.textContent = "#{lines} #{if lines is 1 then 'line' else 'lines'}"
-    @bytesLabel.textContent = "#{progressData.length}/#{progressData.total}"
+
+    {total, length, ration} = progressData
+
+    byteScale = @getByteScale(total)
+    byteDivider = Math.max(1, Math.pow(1000, byteScale))
+    unit = @getUnit(byteScale)
+
+    @bytesLabel.textContent = "#{(length / byteDivider).toFixed(1)}/#{(total / byteDivider).toFixed(1)}#{unit}"
     @progress.setAttribute('value', Math.floor(progressData.ratio * 100))
+
+  getByteScale: (size) ->
+    i = 0
+
+    while size > 1000
+      size = size / 1000
+      i++
+
+    i
+
+  getUnit: (scale) -> byteUnits[scale]

   updateFillTable: (lines, ratio) ->
     @linesLabel.textContent = "#{lines} #{if lines is 1 then 'row' else 'rows'} added"


Add test for Request.waitFor

Add test for Request.waitFor

--- test/request.spec.coffee
+++ test/request.spec.coffee
@@ -37,6 +37,11 @@
     it 'is not pageable if the config does not exist for the service', ->
       delete service.api.pagination
       expect(service.makeRequest('mockMethod').isPageable()).toEqual(false)
+
+  describe 'waitFor', ->
+    it 'creates a ResourceWaiter object', ->
+      service.api.waiters = state: operation: 'mockMethod'
+      expect(service.waitFor('state'))

   describe 'send', ->
     it 'accepts an optional callback', ->


fix badge data resetting

fix badge data resetting

--- src/ingress.coffee
+++ src/ingress.coffee
@@ -36,6 +36,7 @@
 ]

 module.exports = (robot) ->
+  badges = {}

   robot.brain.on "loaded", ->
     badges = robot.brain.data.ingressBadges ?= {}
@@ -57,12 +58,12 @@
     else
       who = robot.brain.userForName who

-    userBadges = who.badges ?= []
+    userBadges = badges[who.id] ?= []

     if badgeName in badgeList
       userBadges.push ":#{badgeName}:"
       if who.name == msg.envelope.user.name
-        msg.reply "Congrats on earning the :#{badgeName}: badge!"
+        msg.reply "congrats on earning the :#{badgeName}: badge!"
       else
         msg.send "@#{who.name}: congrats on earning the :#{badgeName}: badge!"
     else
@@ -76,7 +77,7 @@
     else
       who = robot.brain.userForName who

-    userBadges = who.badges ?= []
+    userBadges = badges[who.id] = []
     you = if who? and who.name == msg.envelope.user.name then true else false
     whowhat = "#{if you then 'You have' else who.name + ' has'}"


Required Downloader in Commander

Required Downloader in Commander

--- source/Commander.coffee
+++ source/Commander.coffee
@@ -2,6 +2,9 @@
   Commander module for MP3 Downloader.
   Written by Jense5.
 ###
+
+# Import Downloader module
+Downloader = require('./Downloader')

 # Fetch arguments
 components = require('minimist')(process.argv.slice(2))


Removed pending tests

Removed pending tests

--- test/sharing_test.coffee
+++ test/sharing_test.coffee
@@ -7,7 +7,7 @@
 httpMocks = require 'node-mocks-http'
 events    = require 'events'

-sharing      = rewire "#{helpers.prefix}server/controllers/sharing"
+sharing      = rewire  "#{helpers.prefix}server/controllers/sharing"
 remoteAccess = require "#{helpers.prefix}server/lib/remote_access"

 client = helpers.getClient()
@@ -54,40 +54,6 @@
                 doc.preToken.should.equal     sharing_request.preToken
                 doc.shareID.should.equal      sharing_request.shareID
             done()
-
-
-    describe 'revokeFromSharer inner module', ->
-
-        revokeFromSharer = sharing.__get__ 'revokeFromSharer'
-
-        # create a phony access for the sharing added above
-        #before (done) ->
-            #clientDS.post "data/",
-                #{docType: 'access', shareID: sharing_request.shareID},
-                #(err, res, doc) ->
-                    #console.log "DEBUG: doc: " + JSON.stringify doc
-            #done()
-
-        it 'When a sharing is revoked by a sharer'
-            #revokeFromSharer sharing_request.shareID, (err, id) ->
-                #should.not.exist err
-                #id.should.equal @docInfo._id
-                #done()
-
-        it 'Then the sharing document is removed'
-            #clientDS.get "data/exist/#{@docInfo._id}", (err, res, body) ->
-                #console.log "DEBUG: err: "  + JSON.stringify err
-                #console.log "DEBUG: body: " + JSON.stringify body
-                #done()
-
-        it 'And the correspondent access document'
-            #clientDS.post "request/access/byApp", key: @docInfo.id,
-                #(err, res, body) ->
-                    #console.log "DEBUG: body: " + JSON.stringify body
-                    #console.log "DEBUG: err: " + JSON.stringify err
-                    #should.exist body.error
-                    #body.error.should.equal "not_found: missing"
-                    #done()

     describe 'revokeFromRecipient inner module', ->
@@ -283,8 +249,6 @@
                 err.should.deep.equal error
                 done()

-        it 'When a successful authorized request is made then 200 is returned'
-

     describe 'revokeTarget module', ->

@@ -428,13 +392,6 @@
                 err.should.deep.equal error
                 done()

-        it 'When a successful authorized request is made then 200 is returned'
-        #(done) ->
-            #sharing.answer req, res
-            #res.on 'send', ->
-                #res.statusCode.should.equal 200
-                #done()
-
         it 'When an unauthorized request is made then an error is returned',
         (done) ->
             stubIsTargetAuthenticated.restore() # cancel hook stub


Tidy up and simplify the apps tests a little

Signed-off-by: Tim Perry <5ee0edb9e2229c0838f1959779f1949031de0123@resin.io>


Remove logging

Remove logging

--- src/install.coffee
+++ src/install.coffee
@@ -310,7 +310,6 @@
         commands.push (callback) =>
           @getPackageCachePath packageName, packageVersion, (error, packagePath) =>
             if packagePath
-              console.log 'installing from cache', packagePath
               callback(null, packagePath)
             else
               @downloadPackage(tarball, installGlobally, callback)


Remove unused --dev option

Remove unused --dev option

--- src/install.coffee
+++ src/install.coffee
@@ -35,7 +35,6 @@
       directory.
     """
     options.alias('h', 'help').describe('help', 'Print this usage message')
-    options.alias('d', 'dev').boolean('dev').describe('dev', 'Install dev dependencies of atom packages being installed')
     options.alias('s', 'silent').boolean('silent').describe('silent', 'Set the npm log level to silent')
     options.alias('q', 'quiet').boolean('quiet').describe('quiet', 'Set the npm log level to warn')

@@ -283,8 +282,6 @@
     commands.push(@installNode)
     commands.push (callback) => @installModules(options, callback)
     commands.push (callback) => @installPackageDependencies(options, callback)
-    if options.argv.dev
-      commands.push (callback) => @installDevDependencies(options, callback)

     async.waterfall commands, callback

@@ -296,32 +293,6 @@
       packageDependencies ? {}
     catch error
       {}
-
-  # Is the given path an atom package with one or more dev dependencies?
-  isAtomPackageWithDevDependencies: (packagePath) ->
-    try
-      metadata = fs.readFileSync(path.join(packagePath, 'package.json'), 'utf8')
-      {engines, devDependencies} = JSON.parse(metadata) ? {}
-      engines?.atom? and devDependencies and Object.keys(devDependencies).length > 0
-    catch error
-      false
-
-  installDevDependencies: (options, callback) ->
-    commands = []
-    modulesDirectory = path.resolve('node_modules')
-    for child in fs.readdirSync(modulesDirectory)
-      packagePath = path.join(modulesDirectory, child)
-      continue unless @isAtomPackageWithDevDependencies(packagePath)
-      do (child, packagePath) =>
-        commands.push (callback) =>
-          options.cwd = packagePath
-          @forkInstallCommand options, (code, stderr='', stdout='') =>
-            if code is 0
-              callback()
-            else
-              callback("#{stdout}\n#{stderr}")
-
-    async.waterfall commands, callback

   createAtomDirectories: ->
     fs.makeTreeSync(@atomDirectory)


Fix ui

Fix ui

--- views/components/main/parts/teitoku-panel.cjsx
+++ views/components/main/parts/teitoku-panel.cjsx
@@ -11,12 +11,6 @@
     return "Lv. #{state.level} #{state.nickname} [#{rankName[state.rank]}]"
   else
     return '提督 [尚未登录]'
-
-getStyle = (state) ->
-  if state.nickname?
-    return 'primary'
-  else
-    return 'default'

 getMaterialImage = (idx) ->
   return "#{ROOT}/assets/img/material/0#{idx}.png"
@@ -98,7 +92,7 @@
   componentWillUnmount: ->
     window.removeEventListener 'game.response', @handleResponse
   render: ->
-    <Panel header={getHeader @state} bsStyle={getStyle @state} className="teitoku-panel">
+    <Panel header={getHeader @state} bsStyle="default" className="teitoku-panel">
       <Grid>
         <Col xs={6}>舰娘：{@state.shipCount} / {@state.maxChara}</Col>
         <Col xs={6}>装备：{@state.slotitemCount} / {@state.maxSlotitem}</Col>


Throw error if stat is not retrieved

Throw error if stat is not retrieved

--- virtual-file-system/adapters/fs-adapter.coffee
+++ virtual-file-system/adapters/fs-adapter.coffee
@@ -54,7 +54,8 @@
     @virtualFileSystem.realpath(path)

   statSync: (path) ->
-    @virtualFileSystem.stat(path)
+    @virtualFileSystem.stat(path) or
+      throw new Error("No virtual entry (file or directory) could be found by the given path '#{path}'")

   statSyncNoException: (path) ->
     @virtualFileSystem.stat(path)


fix: anonymous voting broken for non-group-members

--- vue/src/shared/services/ability_service.coffee
+++ vue/src/shared/services/ability_service.coffee
@@ -37,6 +37,7 @@
     return false unless poll.isActive()
     poll.anyoneCanParticipate or
     poll.adminsInclude(Session.user()) or
+    poll.myStance() or
     (poll.membersInclude(Session.user()) and (!poll.group() or poll.group().membersCanVote))

   canReactToPoll: (poll) ->


hold notification when clicked

hold notification when clicked

--- lib/notification-element.coffee
+++ lib/notification-element.coffee
@@ -45,6 +45,7 @@
 class NotificationElement
   animationDuration: 360
   visibilityDuration: 5000
+  autohideTimeout: null

   constructor: (@model) ->
     @fatalTemplate = TemplateHelper.create(FatalMetaNotificationTemplate)
@@ -62,6 +63,9 @@
       @model.onDidDismiss => @removeNotification()
     else
       @autohide()
+      @element.addEventListener 'click', =>
+        clearTimeout(@autohideTimeout)
+        @element.classList.add('has-close')

     @element.issue = @issue
     @element.getRenderPromise = @getRenderPromise.bind(this)
@@ -259,7 +263,7 @@
       container.style.display = 'none'

   autohide: ->
-    setTimeout =>
+    @autohideTimeout = setTimeout =>
       @removeNotification()
     , @visibilityDuration


Naming fix

Naming fix

--- lib/project-manager-view.coffee
+++ lib/project-manager-view.coffee
@@ -29,8 +29,8 @@
   attach: ->
     projects = []
     currentProjects = CSON.readFileSync(@projectManager.file)
-    for project, data of currentProjects
-      projects.push(data)
+    for title, project of currentProjects
+      projects.push(project)
     @setItems(projects)

     atom.workspaceView.append(@)


Remove result views when there are no more find results

--- lib/project/results-view.coffee
+++ lib/project/results-view.coffee
@@ -76,8 +76,7 @@
     @selectFirstResult() if not @userMovedSelection or @getPathCount() is 1

   removeResult: ({filePath}) =>
-    resultView = @getResultView(filePath)
-    resultView.renderResult(null) if resultView
+    @getResultView(filePath)?.remove()

   renderResults: ({renderAll}={}) ->
     return unless renderAll or @shouldRenderMoreResults()


move destroy markers our of markRanges

move destroy markers our of markRanges

--- lib/search-results-model.coffee
+++ lib/search-results-model.coffee
@@ -20,6 +20,7 @@

   search: =>
     return unless @searchModel.regex
+    @destroyMarkers()
     @markers = @findAndMarkRanges()
     @emit 'change:markers', markers: @markers

@@ -80,8 +81,6 @@
     @emit('add:markers', markers: markers)

   findAndMarkRanges: ->
-    @destroyMarkers()
-
     markerAttributes = @getMarkerAttributes()
     editSession = @editor.activeEditSession


adding an instanceof test for constructors-called-with-splats

--- test/test_classes.coffee
+++ test/test_classes.coffee
@@ -168,4 +168,5 @@

 list: [3, 2, 1]
 conn: new Connection list...
+ok conn instanceof Connection
 ok conn.out() is '3-2-1'


brunch doc link

brunch doc link

--- src/brunch-config.coffee
+++ src/brunch-config.coffee
@@ -128,7 +128,7 @@
 exports.matcher = matcher
 exports.isVendor = matcher ['bower_components/', 'components/', 'vendor/']

-# https://github.com/brunch/brunch/blob/master/docs/config.md
+# https://github.com/brunch/brunch/blob/1.8.5/docs/config.md
 config = exports.config =

     # TODO : take from compilers


Message should use the "text" attribute unless it is null/blank

--- src/message.coffee
+++ src/message.coffee
@@ -69,4 +69,4 @@
   # the `text` attribute (which could be blank).
   description: ->
     key = [@subclass, @context, @key].join(' ')
-    DEFAULT_RESPONSE_MAPPINGS[key] or @text
+	(@text? || @text!='') ? @text : DEFAULT_RESPONSE_MAPPINGS[key]


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -126,7 +126,7 @@
   @name meter
   @methodOf Metered#
   @param {Object} options The meter configuration options
-    -
+    b
   ###
   meter: (options={}) ->
     Object.reverseMerge options,


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

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

   player.meter
     borderColor: 'brown'
-    color: ''
+    color: 'pink'
     showName: true
     name: 'health'
     position:


Do not run testSystem self-test

Do not run testSystem self-test

--- server/server.coffee
+++ server/server.coffee
@@ -66,8 +66,8 @@
 start = () ->
     await console.log(JSON.parse(await download 'https://api.ipify.org/?format=json')["ip"])
     testPromises = []
-    for id, system of REGISTRY
-        testPromises.push(system.selfTest())
+    #for id, system of REGISTRY
+    #    testPromises.push(system.selfTest())

     await Promise.all(testPromises)


add nodetime logging middleware - only box creation for now

--- code/serv.coffee
+++ code/serv.coffee
@@ -18,11 +18,10 @@
 Box = require 'models/box'
 User = require 'models/user'

-if process.env.NODETIME_KEY
-  require('nodetime').profile
-    accountKey: process.env.NODETIME_KEY
-    appName: "#{process.env.CO_NODETIME_APP} #{require('os').hostname()}"
-
+nodetime = require('nodetime')
+nodetime.profile
+  accountKey: process.env.NODETIME_KEY
+  appName: "#{process.env.CO_NODETIME_APP} #{require('os').hostname()}"
 app = express()

 # Trust the headers from nginx and change req.ip to the real IP
@@ -52,6 +51,14 @@

 # Templating language
 app.set('view engine', 'ejs')
+
+nodetimeLog = (req, res, next) ->
+  res.nodetimePromise = nodetime.time 'Cobalt request ', req.url, req
+  oldSend = res.send
+  res.send = (args... ) ->
+    res.nodetimePromise.end()
+    oldSend.apply res, args
+  next()

 parse_settings = (text) ->
   try
@@ -176,7 +183,7 @@
 # Create a box.
 # Since we're creating a box, it doesn't have to exist, so we
 # don't need to call check_box().
-app.post "/box/:newboxname/?", check_api_key, (req, res) ->
+app.post "/box/:newboxname/?", nodetimeLog, check_api_key, (req, res) ->
   console.tick "got request create box #{req.params.newboxname}"
   res.header('Content-Type', 'application/json')
   boxname = req.params.newboxname


Float <-> Int conversion fallbacks appear to work properly now!