Set the full time in the title attribute

--- app/assets/javascripts/local_time.js.coffee
+++ app/assets/javascripts/local_time.js.coffee
@@ -207,6 +207,9 @@
     time = new Date Date.parse datetime
     return if isNaN time

+    unless element.hasAttribute("title")
+      element.setAttribute("title", time)
+
     element[textProperty] =
       switch local
         when "time"


socket stuff

socket stuff

--- app/assets/javascripts/socket_router.coffee
+++ app/assets/javascripts/socket_router.coffee
@@ -4,11 +4,26 @@

 window.global_room = NoDevent.room('global_room')
 global_room.join (err) ->
-  console?.log "error joining global_room", err
+  if err?
+    console?.log "error joining global_room", err
+  else
+    console?.log "joined global_room"

-window.global_room

 socket_defaults(global_room)
+
+
+global_room.on "join", (err) ->
+  if err?
+    console?.log "just got error joining global_room", err
+  else
+    console?.log "just joined global_room"
+
+global_room.on "leave", (err) ->
+  if err?
+    console?.log "just got error leaving global_room", err
+  else
+    console?.log "just left global_room"

 global_room.on "auction_list", (data) ->
   console?.log("got auction_list", data)
@@ -56,13 +71,15 @@
   PB.lots.reset(data.lots)

-global_room.on "connect", ->
-  socket.emit('list_auctions')
+NoDevent.on "connect", ->
+  message_popup("Socket Connected!")
+  console.log "socket connected.."
+  #$("#status").removeClass("offline").addClass("online").find("p").text "You are online and can bid."

-  $("#status").removeClass("offline").addClass("online").find("p").text "You are online and can bid."
-
-global_room.on "disconnect", ->
-  $("#connected").removeClass("on").find("strong").text "Offline"
-  $("#status").removeClass("online").addClass("offline").find("p").text "You are offline. please wait..."
+NoDevent.on "disconnect", ->
+  message_popup("Socket Disconnected! :-(")
+  console.log "socket disconnected.."
+  #$("#connected").removeClass("on").find("strong").text "Offline"
+  #$("#status").removeClass("online").addClass("offline").find("p").text "You are offline. please wait..."


Fix spawning out of screen issue

Fix spawning out of screen issue

--- app/scripts/components/ShipSpawnable.coffee
+++ app/scripts/components/ShipSpawnable.coffee
@@ -17,6 +17,8 @@
     return unless @lives > 0

     pos = @spawnPosition()
+    pos.x = 10 if pos.x < 10
+
     @ship = Crafty.e('PlayerControlledShip')
       .attr
         x: pos.x - Crafty.viewport.x


Remove RootView export

Remove RootView export

--- exports/atom.coffee
+++ exports/atom.coffee
@@ -21,7 +21,6 @@
   module.exports.$$ = $$
   module.exports.$$$ = $$$
   module.exports.Editor = require '../src/editor-view'
-  module.exports.RootView = require '../src/root-view'
   module.exports.WorkspaceView = require '../src/root-view'
   module.exports.SelectList = require '../src/select-list'
   module.exports.ScrollView = require '../src/scroll-view'


Fix deug error

Fix deug error

--- framer/Debug.coffee
+++ framer/Debug.coffee
@@ -50,10 +50,6 @@
 	if not _errorContext
 		_errorContext = new Context(name:"Error")

-	print event.message
-
-	console.log event
-
 	return if _errorShown

 	_errorShown = true
@@ -85,4 +81,4 @@

 	_errorWarningLayer = layer

-# _errorContext.eventManager.wrap(window).addEventListener("error", errorWarning)
+window.addEventListener("error", errorWarning)


fixed bug

fixed bug

--- lisp.coffee
+++ lisp.coffee
@@ -133,15 +133,14 @@
   constructor: ->

   eval_expr: (expr) ->
-    console.log expr
     switch expr.constructor.name
       when 'CallFun'
-        args = expr.args
+        args = expr.args.map (arg) => @eval_expr(arg)
         switch expr.funname
           when '+'
             new Atom args.reduce(((sum, n) -> sum + n.value), 0)
           when 'car'
-            new List args[0].values[0]
+            args[0].values[0]
           when 'cdr'
             new List args[0].values[1..]
           when 'cons'


Change `to.be.equal` to `to.equal`

Change `to.be.equal` to `to.equal`

--- test/elvis-test.coffee
+++ test/elvis-test.coffee
@@ -2,13 +2,13 @@

 describe 'el', ->
   it 'can create div by default', ->
-    expect(el().tagName).to.be.equal('DIV')
+    expect(el().tagName).to.equal('DIV')

   it 'can create an input field', ->
-    expect(el('input').tagName).to.be.equal('INPUT')
+    expect(el('input').tagName).to.equal('INPUT')

   it 'can create a span', ->
-    expect(el('span').tagName).to.be.equal('SPAN')
+    expect(el('span').tagName).to.equal('SPAN')

   it 'does not add id attribute by default', ->
     expect(el().hasAttribute('id')).to.be.false
@@ -18,117 +18,117 @@

   it 'can create div with class', ->
     element = el('.class')
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.className).to.be.equal('class')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.className).to.equal('class')

   it 'can create div with multiple classes', ->
     element = el('.class1.class2')
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.className).to.be.equal('class1 class2')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.className).to.equal('class1 class2')

   it 'can create div with id', ->
     element = el('#some-element')
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.id).to.be.equal('some-element')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.id).to.equal('some-element')

   it 'can create div with id and classes', ->
     element = el('.class1#some-element.class2')
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.id).to.be.equal('some-element')
-    expect(element.className).to.be.equal('class1 class2')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.id).to.equal('some-element')
+    expect(element.className).to.equal('class1 class2')

   it 'can create span with class', ->
     element = el('span.class')
-    expect(element.tagName).to.be.equal('SPAN')
-    expect(element.className).to.be.equal('class')
+    expect(element.tagName).to.equal('SPAN')
+    expect(element.className).to.equal('class')

   it 'can create anchor with href', ->
     element = el('a(href="/foo/bar")', 'baz')
-    expect(element.tagName).to.be.equal('A')
-    expect(element.href).to.match(new RegExp('/foo/bar'))
-    expect(element.innerHTML).to.be.equal('baz')
+    expect(element.tagName).to.equal('A')
+    expect(element.href).to.match(new RegExp('/foo/bar'))
+    expect(element.innerHTML).to.equal('baz')

   it 'can create input with name and type', ->
     element = el('input(name="verify",type="password")')
-    expect(element.tagName).to.be.equal('INPUT')
-    expect(element.name).to.be.equal('verify')
-    expect(element.type).to.be.equal('password')
+    expect(element.tagName).to.equal('INPUT')
+    expect(element.name).to.equal('verify')
+    expect(element.type).to.equal('password')

   it 'can create anchor with id, classes, attribute value and inner text', ->
     tagSpec = '''
       input#verify-password.class1.class2(name="verify",type="password")
     '''
     element = el(tagSpec, 'baz')
-    expect(element.tagName).to.be.equal('INPUT')
-    expect(element.id).to.be.equal('verify-password')
-    expect(element.className).to.be.equal('class1 class2')
-    expect(element.name).to.be.equal('verify')
-    expect(element.type).to.be.equal('password')
+    expect(element.tagName).to.equal('INPUT')
+    expect(element.id).to.equal('verify-password')
+    expect(element.className).to.equal('class1 class2')
+    expect(element.name).to.equal('verify')
+    expect(element.type).to.equal('password')

   it 'can set tag with attributes', ->
     element = el('a', href: '/foo/bar')
-    expect(element.tagName).to.be.equal('A')
+    expect(element.tagName).to.equal('A')
     expect(element.href).to.match(new RegExp('/foo/bar'))

   it 'can set tag with id set through attributes', ->
     element = el('div#foo', id: 'bar')
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.id).to.be.equal('bar')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.id).to.equal('bar')

   it 'can set tag with className set through attributes', ->
     element = el('div.foo', className: 'bar')
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.className).to.be.equal('bar')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.className).to.equal('bar')

   it 'can set tag with tag attributes overridden', ->
     element = el('a(href="/foo/bar")', href: '/baz/quux')
-    expect(element.tagName).to.be.equal('A')
+    expect(element.tagName).to.equal('A')
     expect(element.href).to.match(new RegExp('/baz/quux'))

   it 'can set tag with attributes and inner text', ->
     element = el('a', { href: '/foo/bar' }, 'foo')
-    expect(element.tagName).to.be.equal('A')
-    expect(element.href).to.match(new RegExp('/foo/bar'))
-    expect(element.innerHTML).to.be.equal('foo')
+    expect(element.tagName).to.equal('A')
+    expect(element.href).to.match(new RegExp('/foo/bar'))
+    expect(element.innerHTML).to.equal('foo')

   it 'can set tag with attributes and single child element', ->
     child = el('span', 'foo')
     element = el('a', { href: '/foo/bar' }, child)
-    expect(element.tagName).to.be.equal('A')
-    expect(element.href).to.match(new RegExp('/foo/bar'))
-    expect(element.innerHTML).to.be.equal('foo')
+    expect(element.tagName).to.equal('A')
+    expect(element.href).to.match(new RegExp('/foo/bar'))
+    expect(element.innerHTML).to.equal('foo')

   it 'can set tag with attributes and children', ->
     element = el('a', { href: '/foo/bar' }, [ 'foo', ' ', 'bar' ])
-    expect(element.tagName).to.be.equal('A')
-    expect(element.href).to.match(new RegExp('/foo/bar'))
-    expect(element.innerHTML).to.be.equal('foo bar')
+    expect(element.tagName).to.equal('A')
+    expect(element.href).to.match(new RegExp('/foo/bar'))
+    expect(element.innerHTML).to.equal('foo bar')

   it 'can append element text as second argument', ->
     element = el('div', 'foo bar')
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.innerHTML).to.be.equal('foo bar')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.innerHTML).to.equal('foo bar')

   it 'can append element text as array of text', ->
     element = el('div', [ 'foo', ' ', 'bar' ])
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.innerHTML).to.be.equal('foo bar')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.innerHTML).to.equal('foo bar')

   it 'can append element child', ->
     element = el('div', el('em', 'foo'))
-    expect(element.innerHTML).to.be.equal('foo')
+    expect(element.innerHTML).to.equal('foo')

   it 'can append array of children', ->
     element = el('div', [
       el('em', 'foo')
       ' bar'
     ])
-    expect(element.innerHTML).to.be.equal('foo bar')
+    expect(element.innerHTML).to.equal('foo bar')

   it 'ignores null', ->
     element = el('div', null)
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.innerHTML).to.be.equal('')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.innerHTML).to.equal('')

   it 'ignores null in array', ->
     element = el('div', [
@@ -136,18 +136,18 @@
       null
       'bar'
     ])
-    expect(element.tagName).to.be.equal('DIV')
-    expect(element.innerHTML).to.be.equal('foobar')
+    expect(element.tagName).to.equal('DIV')
+    expect(element.innerHTML).to.equal('foobar')

   it 'can modify element setting text content', ->
     element = el('div', 'foo')
     el(element, 'bar')
-    expect(element.innerHTML).to.be.equal('bar')
+    expect(element.innerHTML).to.equal('bar')

   it 'can modify setting HTML content', ->
     element = el('div', 'foo')
     el(element, el('em', 'foo'))
-    expect(element.innerHTML).to.be.equal('foo')
+    expect(element.innerHTML).to.equal('foo')

   it 'can modify setting array of HTML content', ->
     element = el('div', 'foo')
@@ -155,54 +155,54 @@
       el('em', 'foo')
       ' bar'
     ])
-    expect(element.innerHTML).to.be.equal('foo bar')
+    expect(element.innerHTML).to.equal('foo bar')

 describe 'el.css', ->
   it 'can create basic css properties', ->
     css = el.css(color: '#00f')
-    expect(css).to.be.equal('color:#00f;')
+    expect(css).to.equal('color:#00f;')

   it 'can create multiple properties', ->
     css = el.css(color: '#00f', padding: '10px')
-    expect(css).to.be.equal('color:#00f;padding:10px;')
+    expect(css).to.equal('color:#00f;padding:10px;')

   it 'can receive selectors', ->
     css = el.css(body: padding: '10px')
-    expect(css).to.be.equal('body{padding:10px;}')
+    expect(css).to.equal('body{padding:10px;}')

 describe 'el.setAttr', ->
   it 'can set element id', ->
     element = el()
     el.setAttr(element, 'id', 'some-id')
-    expect(element.id).to.be.equal('some-id')
+    expect(element.id).to.equal('some-id')

   it 'can set element className', ->
     element = el()
     el.setAttr(element, 'className', 'class1 class2')
-    expect(element.className).to.be.equal('class1 class2')
+    expect(element.className).to.equal('class1 class2')

   it 'can set element innerHTML with html', ->
     element = el()
     el.setAttr(element, 'html', 'foo bar')
-    expect(element.innerHTML).to.be.equal('foo bar')
+    expect(element.innerHTML).to.equal('foo bar')

   it 'can set / get element text content', ->
     element = el()
     el.setAttr(element, 'text', 'foo bar')
-    expect(element.innerHTML).to.be.equal('<em>foo</em> bar')
+    expect(element.innerHTML).to.equal('<em>foo</em> bar')

   it 'can set HTML content from DOM element', ->
     element = el()
     el.setAttr(element, 'html', el('em', 'foo'))
-    expect(element.innerHTML).to.be.equal('foo')
+    expect(element.innerHTML).to.equal('foo')

   it 'can set multiple attributes', ->
     element = el()
     el.setAttr(element, id: 'some-id', className: 'class')
-    expect(element.id).to.be.equal('some-id')
-    expect(element.className).to.be.equal('class')
+    expect(element.id).to.equal('some-id')
+    expect(element.className).to.equal('class')

   it 'can set href on anchor', ->
     element = el('a')
@@ -212,32 +212,32 @@

 describe 'el.getAttr', ->
   it 'can get element id', ->
-    expect(el.getAttr(el('#some-id'), 'id')).to.be.equal('some-id')
+    expect(el.getAttr(el('#some-id'), 'id')).to.equal('some-id')

   it 'can get element className', ->
     expect(el.getAttr(el('.class1.class2'), 'className'))
-      .to.be.equal('class1 class2')
+      .to.equal('class1 class2')

   it 'can get element innerHTML with html', ->
     element = el()
     el.setAttr(element, 'html', 'foo bar')
-    expect(el.getAttr(element, 'html')).to.be.equal('foo bar')
+    expect(el.getAttr(element, 'html')).to.equal('foo bar')

   it 'can get element text content with text', ->
     element = el()
     el.setAttr(element, 'html', 'foo bar')
-    expect(el.getAttr(element, 'text')).to.be.equal('foo bar')
+    expect(el.getAttr(element, 'text')).to.equal('foo bar')

 describe 'el.text', ->
   it 'can create a text node', ->
     node = el.text('foo')
-    expect(node.nodeType).to.be.equal(document.TEXT_NODE)
-    expect(node.textContent).to.be.equal('foo')
+    expect(node.nodeType).to.equal(document.TEXT_NODE)
+    expect(node.textContent).to.equal('foo')

   it 'can be appended to regularly with el', ->
     element = el('div', el.text('foo'))
-    expect(element.innerHTML).to.be.equal('foo')
+    expect(element.innerHTML).to.equal('foo')

 describe 'el.registerPlugin', ->
@@ -255,7 +255,7 @@
     ]
     el.registerPlugin((child) -> children.shift())
     expect(el('div', [ 'foo', 'bar' ]).innerHTML)
-      .to.be.equal('bazquux')
+      .to.equal('bazquux')

 describe 'el.resetPlugins', ->


* [bug] get the Task class from task-registry-series

--- test/index-test.coffee
+++ test/index-test.coffee
@@ -10,8 +10,8 @@

 isNumber        = require 'util-ex/lib/is/type/number'
-Task            = require 'task-registry'
 Tasks           = require '../src'
+Task            = require('task-registry-series').super_
 register        = Task.register
 aliases         = Task.aliases


Oscillator

Oscillator

--- test/oscillator.coffee
+++ test/oscillator.coffee
@@ -1,7 +1,7 @@
 module "Oscillator"

 test "various values", ->
-  #
+  # Cosine O
   o = Oscillator
     period: 30
     amplitude: 10


updated podMinVersion to 0.38.2

updated podMinVersion to 0.38.2

--- main.coffee
+++ main.coffee
@@ -18,7 +18,7 @@
 projNameRx      = /\$PROJECT_NAME\$/g
 projNameHyphRx  = /\$PROJECT_NAME_HYPHENATED\$/g
 projNameUnderRx = /\$PROJECT_NAME_UNDERSCORED\$/g
-podMinVersion   = '0.36.4'
+podMinVersion   = '0.38.2'

 log = (s, color = 'green') ->


Showing monsters in `/node-game/`. Refs #296.

--- main.coffee
+++ main.coffee
@@ -40,7 +40,7 @@
 swig.setFilter 'tf', stubFilter # TODO: actually implement
 swig.setFilter 'nl2p', stubFilter # TODO: actually implement
 swig.setFilter 'nl2br', stubFilter # TODO: actually implement
-swig.setFilter 'length', -> 0 # TODO: actually implement
+swig.setFilter 'length', (x) -> x.length

 app.engine 'html', swig.renderFile
 app.engine 'twig', swig.renderFile
@@ -202,11 +202,14 @@
 		options.ways.forEach (i) -> # Facepalm. #273
 			i.name = i.text
 			i.to = i.id
-		tmpUsers = utils.game.getUsersOnLocation.sync null, mysqlConnection, result.id
+		tmpUsers = utils.game.getNearbyUsers.sync null,
+			mysqlConnection, request.uonline.basicOpts.userid, result.id
 		tmpUsers.forEach (i) -> # Facepalm. Refs #273 too.
 			i.name = i.user
 		options.players_list = tmpUsers
-		options.monsters_list = [] # TODO: broken
+		tmpMonsters = utils.game.getNearbyMonsters.sync null, mysqlConnection, result.id
+		console.log tmpMonsters
+		options.monsters_list = tmpMonsters
 		options.fight_mode = false # TODO: broken
 		options.autoinvolved_fm = false # TODO: broken
 		response.render 'game', options


add `throttle` and `debounce`

add `throttle` and `debounce`

--- src/Bu.coffee
+++ src/Bu.coffee
@@ -82,6 +82,31 @@
 Function::property = (prop, desc) ->
 	Object.defineProperty @prototype, prop, desc

+# throttle
+Function::throttle = (limit = 0.5) ->
+	currTime = 0
+	lastTime = 0
+
+	return () =>
+		currTime = Date.now()
+		if currTime - lastTime > limit * 1000
+			@apply null, arguments
+			lastTime = currTime
+
+# debounce
+Function::debounce = (delay = 0.5) ->
+	args = null
+	timeout = null
+
+	later = =>
+		@apply null, args
+
+	return () ->
+		args = arguments
+		clearTimeout timeout
+		timeout = setTimeout later, delay * 1000
+
+
 # Array
 Array::each or= (fn) ->
 	i = 0


Fixed message replying for anon orgs

Fixed message replying for anon orgs

--- static/coffee/services.coffee
+++ static/coffee/services.coffee
@@ -96,7 +96,7 @@
         if msg.urn
           urns.push(msg.urn)
         else
-          contacts.push(msg.contact)
+          contacts.push(msg.contact.uuid)

       @_messagesSend('B', text, urns, contacts, null, callback)


More speed!!!

More speed!!!

--- test/speed_test.coffee
+++ test/speed_test.coffee
@@ -22,6 +22,6 @@
     expect: ->
       dt = new Date().getTime() - start_time
       @db.get 'n:1000', (err, str) =>
-        @assert.equal str, 1000
-        @assert.equal true,  (dt < 3000)
+        @assert.equal str, '1000'
+        @assert.equal true,  (dt < 2000)
         @finish()


minor typo

minor typo

--- scripts/pipeline.coffee
+++ scripts/pipeline.coffee
@@ -15,9 +15,9 @@
     msg.send "Checking Pipeline 2 server load..."
     load = getLoad msg, (load) ->
       msg.send "Pipeline 2 engine CPU: "+load.engine.cpu+" %"
-      msg.send "Pipeline 2 engine Memory: "+load.engine.mem+" %"
-      msg.send "Pipeline 2 engine CPU: "+load.webui.cpu+" %"
-      msg.send "Pipeline 2 engine Memory: "+load.webui.mem+" %"
+      msg.send "Pipeline 2 engine memory: "+load.engine.mem+" %"
+      msg.send "Pipeline 2 web ui CPU: "+load.webui.cpu+" %"
+      msg.send "Pipeline 2 web ui memory: "+load.webui.mem+" %"

   getLoad = (msg, callback) ->
     child_process.exec "nproc", (error, stdout, stderr) ->


Split login to function

Split login to function

--- scripts/strength.coffee
+++ scripts/strength.coffee
@@ -36,20 +36,18 @@

 module.exports = (robot) ->

-  robot.respond /@?([a-z0-9]+)\s*の(強み|資質)は(何(だっけ)?)?？/i, (msg) ->
-    user_name = msg.match[1]
-    strength_label = msg.match[3]
-    strength_name = robot.brain.data.strength[user_name][0]
+  respond_user_strength = (msg) ->
+    [user_name, strength_label, rank_index] = msg.data
+    strength_name = robot.brain.data.strength[user_name][rank_index]
     strength_title = StrengthList.getTitle(strength_name)

     if strength_title?
-      msg.send "やだなぁ、#{user_name}の#{strength_label}は *#{strength_title}* じゃないですか"
+      msg.send "やだなぁ、#{user_name}の#{rank_index+1}番の#{strength_label}は *#{strength_title}* じゃないですか"
     else
       msg.send "登録されていません！"

-
-  robot.respond /@?([a-z0-9]+)\s*の(強み|資質)は(?!何)(.+?)(です。?)?$/i, (msg) ->
-    [full_text, user_name, strength_label, strength_text] = msg.match
+  save_user_strength = (msg) ->
+    [full_text, user_name, strength_label, strength_text, rank_index] = msg.data
     strength_arr = strength_text.split /\s*[,、]\s*/
     ranking = []

@@ -65,6 +63,29 @@
         msg.send "#{user_name}の#{strength_label}は#{strength.title}ですね。"
       else
         for strength, i in ranking
+          robot.brain.data.strength[user_name][i] = strength.title
           msg.send "#{user_name}の#{i+1}番目の#{strength_label}は#{strength.title}ですね。"
     else
       msg.send "該当する#{strength_label}が見当たりません。"
+
+  robot.respond /@?([a-z0-9]+)\s*の(強み|資質)は(何(だっけ)?)?？/i, (msg) ->
+    msg.data = [
+      msg.match[1],
+      msg.match[2],
+      0
+    ]
+    respond_user_strength msg
+
+  robot.respond /@?([a-z0-9]+)\s*の(\d)番目?の(強み|資質)は(何(だっけ)?)?？/i, (msg) ->
+    msg.data = [
+      msg.match[1],
+      msg.match[3],
+      msg.match[2]-1
+    ]
+    respond_user_strength msg
+
+
+  robot.respond /@?([a-z0-9]+)\s*の(強み|資質)は(?!何)(.+?)(です。?)?$/i, (msg) ->
+    msg.data = msg.match
+    msg.data.push 0
+    save_user_strength msg


fix test for webhook

fix test for webhook

--- test/pagerv2_hook_test.coffee
+++ test/pagerv2_hook_test.coffee
@@ -34,8 +34,8 @@
   beforeEach ->
     process.env.PAGERV2_API_KEY = 'xxx'
     process.env.PAGERV2_SCHEDULE_ID = '42'
-    process.env.PAGERV2_ANNOUNCE_ROOM = "#dev"
-    process.env.PORT = 8088
+    process.env.PAGERV2_ANNOUNCE_ROOM = '#dev'
+    process.env.PORT = 8089
     room = helper.createRoom()
     room.robot.brain.userForId 'user', {
       name: 'user'
@@ -49,40 +49,22 @@
       phid: 'PHID-USER-123456789'
     }

-
   afterEach ->
     delete process.env.PAGERV2_API_KEY
     delete process.env.PAGERV2_SCHEDULE_ID
     delete process.env.PAGERV2_ANNOUNCE_ROOM

   # ---------------------------------------------------------------------------------
-  context.only 'it is not a task', ->
-    beforeEach ->
-      @postData = '{
-        "storyID": "7373",
-        "storyType": "PhabricatorApplicationTransactionFeedStory",
-        "storyData": {
-          "objectPHID": "PHID-PSTE-m4pqx64n2dtrwplk7qkh",
-          "transactionPHIDs": {
-            "PHID-XACT-PSTE-zmss7ubkaq5pzor": "PHID-XACT-PSTE-zmss7ubkaq5pzor"
-          }
-        },
-        "storyAuthorPHID": "PHID-USER-7p4d4k6v4csqx7gcxcbw",
-        "storyText": "ash created P6 new test paste.",
-        "epoch": "1469408232"
-      }'
+  context 'it is a proper message', ->

-    afterEach ->
-      room.destroy()
-
-    it 'should not react', ->
-      expected = {
-        message: 'mose triaged T2569: setup webhooks as High priority.',
-        rooms: [ ]
-      }
+    it 'should react', ->
+      expected = [
+        '[undefined] CPU Load High on xdb_production_echo - trigger (Laura Haley)'
+      ]
       pagerv2 = new Pagerv2 room.robot
-      pagerv2.parseWebhook('console', require('./fixtures/webhook_trigger.json'))
-      .then (announce) ->
+      msg = require('./fixtures/webhook_trigger.json')
+      pagerv2.parseWebhook(
+        'console',
+        require('./fixtures/webhook_trigger.json').messages
+      ).then (announce) ->
         expect(announce).to.eql expected
-      .catch (e) ->
-        expect(e).to.eql 'no room to announce in'


failing tests for numeric list items

failing tests for numeric list items

--- test/unit/test_updates.coffee
+++ test/unit/test_updates.coffee
@@ -65,6 +65,22 @@
       disabled: true
       checked: false

+    @orderedList = $ '<ol>', class: 'task-list'
+
+    @orderedCompleteItem = $ '<li>', class: 'task-list-item'
+    @orderedCompleteCheckbox = $ '<input>',
+      type: 'checkbox'
+      class: 'task-list-item-checkbox'
+      disabled: true
+      checked: true
+
+    @orderedIncompleteItem = $ '<li>', class: 'task-list-item'
+    @orderedIncompleteCheckbox = $ '<input>',
+      type: 'checkbox'
+      class: 'task-list-item-checkbox'
+      disabled: true
+      checked: false
+
     @field = $ '<textarea>', class: 'js-task-list-field', text: """
       - [x] complete
       - [ ] incomplete
@@ -73,6 +89,8 @@
       > - [ ] quoted incomplete
       > > - [x] inner complete
       > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
     """

     @changes =
@@ -84,6 +102,8 @@
       > - [ ] quoted incomplete
       > > - [x] inner complete
       > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
       """
       toQuotedComplete: """
       - [x] complete
@@ -93,6 +113,8 @@
       > - [ ] quoted incomplete
       > > - [x] inner complete
       > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
       """
       toInnerComplete: """
       - [x] complete
@@ -102,6 +124,19 @@
       > - [ ] quoted incomplete
       > > - [ ] inner complete
       > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
+      """
+      toOrderedComplete: """
+      - [x] complete
+      - [ ] incomplete
+      - [#{@nbsp}] incompleteNBSP
+      > - [x] quoted complete
+      > - [ ] quoted incomplete
+      > > - [x] inner complete
+      > > - [ ] inner incomplete
+      > 0. [ ] ordered complete
+      > 0. [ ] ordered incomplete
       """
       toIncomplete: """
       - [x] complete
@@ -111,6 +146,8 @@
       > - [ ] quoted incomplete
       > > - [x] inner complete
       > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
       """
       toQuotedIncomplete: """
       - [x] complete
@@ -120,6 +157,8 @@
       > - [x] quoted incomplete
       > > - [x] inner complete
       > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
       """
       toInnerIncomplete: """
       - [x] complete
@@ -129,6 +168,19 @@
       > - [ ] quoted incomplete
       > > - [x] inner complete
       > > - [x] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
+      """
+      toOrderedIncomplete: """
+      - [x] complete
+      - [ ] incomplete
+      - [#{@nbsp}] incompleteNBSP
+      > - [x] quoted complete
+      > - [ ] quoted incomplete
+      > > - [x] inner complete
+      > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [x] ordered incomplete
       """
       toIncompleteNBSP: """
       - [x] complete
@@ -138,6 +190,8 @@
       > - [ ] quoted incomplete
       > > - [x] inner complete
       > > - [ ] inner incomplete
+      > 0. [x] ordered complete
+      > 0. [ ] ordered incomplete
       """

     @completeItem.append @completeCheckbox
@@ -164,7 +218,6 @@
     @quotedIncompleteItem.expectedIndex = 5

     @blockquote.append @quotedList
-    @blockquote.append @field

     @innerCompleteItem.append @innerCompleteCheckbox
     @innerList.append @innerCompleteItem
@@ -181,6 +234,18 @@

     @container.append @blockquote

+    @orderedCompleteItem.append @orderedCompleteCheckbox
+    @orderedList.append @orderedCompleteItem
+    @orderedCompleteItem.expectedIndex = 8
+
+    @orderedIncompleteItem.append @orderedIncompleteCheckbox
+    @orderedList.append @orderedIncompleteItem
+    @orderedIncompleteItem.expectedIndex = 9
+
+    @container.append @orderedList
+
+    @blockquote.append @field
+
     $('#qunit-fixture').append(@container)
     @container.taskList()

@@ -286,3 +351,31 @@

   @innerCompleteCheckbox.click()

+asyncTest "updates the source of an ordered list item, marking the incomplete item as complete", ->
+  expect 3
+
+  @field.on 'tasklist:changed', (event, index, checked) =>
+    ok checked
+    equal index, @orderedIncompleteItem.expectedIndex
+    equal @field.val(), @changes.toOrderedIncomplete
+
+  setTimeout ->
+    start()
+  , 20
+
+  @orderedIncompleteCheckbox.click()
+
+asyncTest "updates the source of an ordered list item, marking the complete item as incomplete", ->
+  expect 3
+
+  @field.on 'tasklist:changed', (event, index, checked) =>
+    ok !checked
+    equal index, @orderedCompleteItem.expectedIndex
+    equal @field.val(), @changes.toOrderedComplete
+
+  setTimeout ->
+    start()
+  , 20
+
+  @orderedCompleteCheckbox.click()
+


undo HTML that was injected in models/contents/page

--- src/scripts/helpers/backbone/views/editable.coffee
+++ src/scripts/helpers/backbone/views/editable.coffee
@@ -71,6 +71,24 @@
                 # Wait for Aloha to start up
                 Aloha.ready () ->
                   $editable.addClass('aloha-root-editable') # the semanticblockplugin needs this for some reason
+
+
+                  # Undo everything added in `models/contents/page.coffee`
+                  # Unwrap title and content elements in header and section elements, respectively
+                  $editable.find('.example, .exercise, .note').each (index, el) ->
+                    $el = $(el)
+                    # Remove the `<section>` element
+                    $el.children('section').children().unwrap()
+                    $title = $el.children('.title')
+                    $title.removeAttr('data-label-parent')
+                    $el.removeClass('ui-has-child-title')
+
+
+                  # UnWrap solutions in a div
+                  $solutions = $editable.find('.solution')
+                  $solutions.children('section.ui-body').unwrap()
+                  $solutions.find('ui-toggle-wrapper').remove()
+
                   $HACK = Aloha.jQuery($editable[0])
                   $HACK.aloha()


optimize for setTimeout(fn, 0)

optimize for setTimeout(fn, 0)

--- src/server/virtual_browser/DOMWindowFactory.coffee
+++ src/server/virtual_browser/DOMWindowFactory.coffee
@@ -67,7 +67,7 @@
     patchImage : (window) ->
         # TODO MEM
         self = this
-        # Thanks Zombie for Image code
+        # Thanks Zombie for Image code
         window.Image = (width, height) ->
             img = new self.jsdom
                       .dom
@@ -76,7 +76,7 @@
             img.width = width
             img.height = height
             img
-
+
     patchNavigator : (window) ->
         window.navigator.javaEnabled = false
         window.navigator.language = 'en-US'
@@ -95,15 +95,19 @@
         # window.setTimeout and setInterval piggyback off of Node's functions,
         # but emit events before/after calling the supplied function.
         ['setTimeout', 'setInterval'].forEach (timer) ->
-            # FIXME: using native node's implement temporarily, there is a memory leak in jsdom's implementation
+            # FIXME: using native node's implement temporarily, there is a memory leak in jsdom's implementation
             old = global[timer]
             window[timer] = (fn, interval, args...) ->
-                old () ->
+                fnWrap = ()->
                     self.logger("trigger #{timer}")
                     self.browser.emit('EnteredTimer')
                     fn.apply(window, args)
                     self.browser.emit('ExitedTimer')
-                , interval
+                #optimize for setTimeout(fn, 0)
+                if (not interval? or interval is 0) and timer is 'setTimeout'
+                    return setImmediate(fnWrap)
+
+                return old(fnWrap, interval)
         # expose setImmediate
         window.setImmediate = (fn, args...)->
             setImmediate(()->


Fix memory leak and possible performance hit

--- src/client/serve/ondemand.coffee
+++ src/client/serve/ondemand.coffee
@@ -17,12 +17,12 @@

   serve = (processor) ->
     (request, response) ->
-      if options.packAssets && queryCache[request.url]
-        utils.serve.js(queryCache[request.url], response)
+      path = utils.parseUrl(request.url)
+      if options.packAssets && queryCache[path]
+        utils.serve.js(queryCache[path], response)
       else
-        path = utils.parseUrl(request.url)
         processor request, response, path, (output) ->
-          queryCache[request.url] = output
+          queryCache[path] = output
           utils.serve.js(output, response)

   # Async Code Loading


Apply fuzzy search to global T search in favor of server search

--- SingularityUI/app/views/globalSearch.coffee
+++ SingularityUI/app/views/globalSearch.coffee
@@ -1,4 +1,6 @@
 View = require './view'
+
+Requests = require '../collections/Requests'

 class GlobalSearchView extends View

@@ -12,6 +14,8 @@
             'click [data-action="close-global-seach"]': 'hide'

     initialize: ->
+        @requests = new Requests [], {'all'}
+
         $(window).on 'keydown', (event) =>
             focusBody = $(event.target).is 'body'
             focusInput = $(event.target).is @$ 'input[type="search"]'
@@ -42,18 +46,19 @@

             @lastSearchQuery = query

-            $.ajax
-                url: "#{ config.apiRoot }/history/requests/search"
-                data: requestIdLike: query
+            fuse = new Fuse(
+                @requests.toJSON()
+                keys: ["request.id"]
+                threshold: 0.6
+                id: "request.id")
+            results = fuse.search(query)

-                success: (response) =>
-                    @lastResponse = response
-                    process response
+            process results.slice(0, 10)

         # Debounce event so we don't spam the server
         sourceFunction = _.debounce sourceFunction, 200

-        @$('input').typeahead
+        @$('input').typeahead
             highlight: true
         ,
             source: sourceFunction
@@ -62,16 +67,17 @@
         @$('input').on 'typeahead:selected', (event, requestId) =>
             @hide()
             app.router.navigate "/request/#{ requestId }", { trigger: true }
-
+
     reset: ->
         @$('input').val ''
         @$('ul').removeClass 'dropdown-menu-hidden'
         @$('li').remove()

     show: ->
-        @reset()
-        @$el.parent().addClass 'global-search-active'
-        @$('input').focus()
+        @requests.fetch().done =>
+            @reset()
+            @$el.parent().addClass 'global-search-active'
+            @$('input').focus()

     hide: (event) ->
         if event?


MER-41: process batched lists, match and create sync update actions.

--- src/coffee/product-import.coffee
+++ src/coffee/product-import.coffee
@@ -1,4 +1,6 @@
 debug = require('debug')('sphere-product-sync-import')
+_ = require 'underscore'
+_.mixin require('underscore-mixins')
 Promise = require 'bluebird'
 {SphereClient, ProductSync} = require 'sphere-node-sdk'

@@ -8,4 +10,77 @@
     @sync = new ProductSync
     @client = new SphereClient options

+  _resetSummary: ->
+    @_summary =
+      emptySKU: 0
+      created: 0
+      updated: 0
+
+
+  performStream: (chunk, cb) ->
+    @_processBatches(chunk).then -> cb()
+
+  _processBatches: (products) ->
+    batchedList = _.batchList(products, 30) # max parallel elem to process
+    Promise.map batchedList, (productsToProcess) =>
+      debug 'Chunk: %j', productsToProcess
+      uniqueProductsToProcessBySku = @_uniqueProductsBySku(productsToProcess)
+      debug 'Chunk (unique products): %j', uniqueProductsToProcessBySku
+
+      skus = _.map uniqueProductsToProcessBySku, (s) =>
+        @_summary.emptySKU++ if _.isEmpty s.sku
+        # TODO: query also for channel?
+        "\"#{s.sku}\""
+      predicate = "sku in (#{skus.join(', ')})"
+
+      @client.products
+      .where(predicate)
+      .fetch()
+      .then (results) =>
+        debug 'Fetched products: %j', results
+        queriedEntries = results.body.results
+        @_createOrUpdate productsToProcess, queriedEntries
+      .then (results) =>
+        _.each results, (r) =>
+          switch r.statusCode
+            when 201 then @_summary.created++
+            when 200 then @_summary.updated++
+        Promise.resolve()
+    ,{concurrency: 1} # run 1 batch at a time
+
+  _uniqueProductsBySku: (products) ->
+    _.reduce products, (acc, product) ->
+      foundProduct = _.find acc, (p) -> p.sku is product.sku
+      acc.push product unless foundProduct
+      acc
+    , []
+
+
+  _match: (entry, existingEntries) ->
+    _.find existingEntries, (existingEntry) ->
+      if entry.sku is existingEntry.sku
+        true
+      else
+        false
+
+  _createOrUpdate: (productsToProcess, existingEntries) ->
+    debug 'Products to process: %j', {toProcess: productsToProcess, existing: existingEntries}
+
+    posts = _.map productsToProcess, (entry) =>
+      existingEntry = @_match(entry, existingEntries)
+      if existingEntry?
+        synced = @sync.buildActions(entry, existingEntry)
+        if synced.shouldUpdate()
+          @client.productsToProcess.byId(synced.getUpdateId()).update(synced.getUpdatePayload())
+        else
+          Promise.resolve statusCode: 304
+      else
+        @client.productsToProcess.create(entry)
+
+    debug 'About to send %s requests', _.size(posts)
+    Promise.all(posts)
+
+
+
+
 module.exports = ProductImport


cabal.cson: support common stanza (#122)

https://www.haskell.org/cabal/users-guide/developing-packages.html#common-stanzas

--- grammars/cabal.cson
+++ grammars/cabal.cson
@@ -39,7 +39,7 @@
       }

       {
-          'match': '(benchmark|executable|flag|source-repository|test-suite)\\s+(\\S+)'
+          'match': '(benchmark|common|executable|flag|source-repository|test-suite)\\s+(\\S+)'
           'captures':
             '1':
                 'name':'entity.name.function'


Updated

Updated

--- grammars/marko.cson
+++ grammars/marko.cson
@@ -68,28 +68,10 @@
         match: "[=]"
       }
       {
-        include: "#expression-string-single"
-      }
-      {
-        include: "#expression-string-double"
-      }
-      {
-        include: "#expression-group-parens"
-      }
-      {
-        include: "#expression-group-brackets"
-      }
-      {
-        include: "#expression-group-braces"
+        include: "#expression-common"
       }
       {
         include: "#expression-operator-no-gt"
-      }
-      {
-        include: "#expression-special-class"
-      }
-      {
-        include: "#expression-special-operator"
       }
       {
         include: "#placeholder"
@@ -134,6 +116,16 @@
     comment: "A JavaScript expression"
     patterns: [
       {
+        include: "#expression-common"
+      }
+      {
+        include: "#expression-operator"
+      }
+    ]
+  "expression-common":
+    comment: "A JavaScript expression"
+    patterns: [
+      {
         include: "#expression-string-single"
       }
       {
@@ -149,7 +141,13 @@
         include: "#expression-group-braces"
       }
       {
-        include: "#expression-operator"
+        include: "#expression-constant"
+      }
+      {
+        include: "#expression-hex"
+      }
+      {
+        include: "#expression-numeric"
       }
       {
         include: "#expression-special-class"
@@ -158,6 +156,21 @@
         include: "#expression-special-operator"
       }
     ]
+  "expression-constant":
+    captures:
+      "1":
+        name: "constant.language.boolean.true.js"
+      "2":
+        name: "constant.language.boolean.false.js"
+      "3":
+        name: "constant.language.infinity.js"
+      "4":
+        name: "constant.language.nan.js"
+      "5":
+        name: "constant.language.null.js"
+      "6":
+        name: "constant.language.undefined.js"
+    match: "(true)|(false)|(Infinity)|(NaN)|(null)|(undefined)"
   "expression-group-braces":
     begin: "\\["
     beginCaptures:
@@ -200,6 +213,24 @@
         include: "#expression"
       }
     ]
+  "expression-hex":
+    match: "\\b0[xX]\\h+\\b"
+    name: "constant.numeric.hex.js"
+  "expression-numeric":
+    match: '''
+      (?x)
+      				(?<!\\w)									# Ensure word boundry
+      				(?>
+      					(
+      						(0|[1-9][0-9]*)(\\.[0-9]*)?		# 0 or 1 or 1. or 1.0
+      					  | \\.[0-9]+						# .1
+      					)
+      					([eE][+-]?[0-9]+)?					# Exponent
+      				)
+      				(?!\\w)									# Ensure word boundry
+
+    '''
+    name: "constant.numeric.js"
   "expression-operator":
     match: "!|%|&|\\*|\\-\\-|\\-|\\+\\+|\\+|~|===|==|=|!=|!==|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\\|\\||\\?\\:|\\*=|(?<!\\()/=|%=|\\+=|\\-=|&=|\\^="
     name: "keyword.operator.js"
@@ -368,16 +399,7 @@
       }
     ]
   "tag-concise":
-    begin: "^\\s*(?=[a-zA-Z0-9_.#-])"
-    comment: "Concise tag"
-    end: "$"
-    patterns: [
-      {
-        include: "#html-line-concise"
-      }
-      {
-        include: "#attr-stuff"
-      }
+    patterns: [
       {
         include: "#tag-name-custom-concise"
       }
@@ -411,17 +433,35 @@
       }
     ]
   "tag-name-concise":
-    captures:
+    begin: "^\\s*([a-zA-Z0-9_.#-]+)(?=(\\s|$|\\())"
+    beginCaptures:
       "1":
         name: "entity.name.tag"
     comment: "A concise tag name"
-    match: "([a-zA-Z0-9_#.]+)(?=(\\s|$|\\())"
+    end: "$"
+    patterns: [
+      {
+        include: "#html-line-concise"
+      }
+      {
+        include: "#attr-stuff"
+      }
+    ]
   "tag-name-custom-concise":
-    captures:
-      "0":
-        name: "support.function.marko-tag"
+    begin: "^\\s*(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|[#.][a-zA-Z0-9_#.-]+|[a-zA-Z0-9_#.]+([:-])[a-zA-Z0-9_:-]+)(?=(\\s|$|\\())"
+    beginCaptures:
+      "1":
+        name: "entity.name.tag"
     comment: "A concise custom tag name"
-    match: "(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|[#.][a-zA-Z0-9_#.-]+|[a-zA-Z0-9_#.]+([:-])[a-zA-Z0-9_:-]+)(?=(\\s|$|\\())"
+    end: "$"
+    patterns: [
+      {
+        include: "#html-line-concise"
+      }
+      {
+        include: "#attr-stuff"
+      }
+    ]
   "tag-name-custom-html":
     begin: "<(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|[a-zA-Z0-9_]+[-:][a-zA-Z0-9\\-_:]+)(?=(>|/>|\\s|\\())"
     beginCaptures:


remove superfluous code

remove superfluous code

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -163,21 +163,6 @@
   {
     'include': '#subsetting'
   }
-  # {
-  #   'begin': '(\\()'
-  #   'beginCaptures':
-  #     '1':
-  #       'name': 'keyword.operator.parentheses.stata'
-  #   'end': '(\\))'
-  #   'endCaptures':
-  #     '1':
-  #       'name': 'keyword.operator.parentheses.stata'
-  #   'patterns': [
-  #     {
-  #       'include': '$self'
-  #     }
-  #   ]
-  # }
   {
     'begin': '\\b(gl(o|ob|oba|obal)?)\\s+(?=[a-zA-Z_`\\$])'
     'beginCaptures':
@@ -993,5 +978,3 @@
        '\\b(gen(e|er|era|erat|erate)?|egen)\\s((byte|int|long|float|double|str[1-9]?[0-9]?[0-9]?[0-9]?|strL)?\\s)?(_all|_b|byte|_coef|_cons|double|float|if|in|int|long|_n|_N|_pi|_pred|_rc|_skip|str[0-9]+|strL|using|with|[0-9][A-Za-z0-9_]+|[A-Za-z_][A-Za-z0-9_]{32,})\\b\\s*(///)?\\s*\\n?\\s*='
       'name': 'invalid.illegal.name.stata'
       'comment': 'highlights as illegal if variable name is more than 32 characters'
-# scratch
-# prefix commands: by (bysort) statsby rolling bootstrap jackknife permute simulate svy "mi estimate" nestreg stepwise xi fp mfp capture noisily quietly version


+1 test ensure bracket

+1 test ensure bracket

--- test/translator.coffee
+++ test/translator.coffee
@@ -38,8 +38,10 @@
       it JSON.stringify(sample), ()->
         assert.equal full(sample), sample

+  # ensure bracket
   kv =
-    '((a))': '(a)' # ensure bracket
+    '((a))': '(a)'
+    '((a)+(b))': '((a)+(b))'
   for k,v of kv
     do (k,v)->
       it "#{k} -> #{v}", ()->


.

Former-commit-id: deb8c1fff5e5e33e0fa1d8688696b5a720670b77 [formerly 379855189e4dc7cc1bee1658353ec2f9b3279d46] [formerly 3f6684473c32ddfd8682563a61ff676a00e6ec8a [formerly 4236b27f4cfee63d9b3ead386cf678762f5f55cb]] [formerly f7c5d6ae4bec1d70fb42870ec8858632552b6d09 [formerly eaae65fb211b2a80e83d4299353aa82396ab1dc4] [formerly b0b1249441f8adcae46dfb48076d801523ba7f9c [formerly b0b1249441f8adcae46dfb48076d801523ba7f9c [formerly 6399517b628e245162ee092bf6da27aca8d5a9f1]]]]
Former-commit-id: eadfc1d8ec24c0eec78d4f4a6967f7e384e2ba5c [formerly eaf029a4e7a01d5c397c8179eabe84cc011790ae] [formerly 1d90f6c25a2c9c9cd259a2b1ebdde000d2fd8489 [formerly 58ffbcc05bf8f8e8cbecc8f131ef4d683b148381]]
Former-commit-id: e9640f4a383b25b6f45f99ce180d2488af69c045 [formerly 3304d3599ca2fcdea99ea878a75845c581e42382]
Former-commit-id: dab8018f32e099d985d537be1a5b97b7eae1983b
Former-commit-id: 6917f8f6fe08af86397b11dbd994edd2fed374ec

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -1748,7 +1748,7 @@
                 style: {margin: '0 0 0 12px', fontSize: 24, height: 42, width: 152, padding: '4px 20px'}
                 onChange: onChange
               BR null
-              SPAN style: {fontSize: 18}, 'Your local congressional race and measures will be revealed!'
+              SPAN style: {fontSize: 18}, 'Your city council candidates will be revealed!'

           DIV
             style:


Projection: Add done callback to isInitialized spec

--- src/projection/projection_feature.spec.coffee
+++ src/projection/projection_feature.spec.coffee
@@ -39,12 +39,13 @@

     describe 'when initializing the projection', ->

-      it 'should set the projection to initialized', ->
+      it 'should set the projection to initialized', (done) ->
         exampleContext.addProjection 'ExampleProjection', ->
           stores: ['inmemory']

           handleExampleCreated: (domainEvent) ->
             expect(@isInitialized).to.equal true
+            done()

         exampleContext.initialize()


types dropdown: first arg is event

types dropdown: first arg is event

--- src/components/multi-select.cjsx
+++ src/components/multi-select.cjsx
@@ -19,7 +19,7 @@
     onSelect: React.PropTypes.func

-  onSelect: (selection) ->
+  onSelect: (ev, selection) ->
     @props.onSelect?( _.findWhere(@props.selections, id: selection))

   renderMenuSelection: (selection) ->


Don't fire controller actions at all if a beforeAction returns false.

--- src/controller/controller.coffee
+++ src/controller/controller.coffee
@@ -98,13 +98,12 @@
     oldRedirect = Batman.navigator?.redirect
     Batman.navigator?.redirect = @redirect

-    @fireLifecycleEvent('beforeAction', frame.params, frame)
-    result = @[action](params) if not @_afterFilterRedirect
+    if @fireLifecycleEvent('beforeAction', frame.params, frame) != false
+      result = @[action](params) if not @_afterFilterRedirect
+      @render() if not frame.operationOccurred

-    @render() if not frame.operationOccurred
     frame.finishOperation()
-
-    result
+    return result

   redirect: (url) =>
     frame = @_actionFrames[@_actionFrames.length - 1]


Set env var before loading script

Set env var before loading script

--- tests/poo-tracker_test.coffee
+++ tests/poo-tracker_test.coffee
@@ -1,6 +1,7 @@
 assert = require 'assert'

 Helper = require('hubot-test-helper')
+process.env.POO_REDIS_URL = "redis://h:pass@localhost:6379/0"
 helper = new Helper('../scripts/poo-tracker.coffee')

 describe 'poo-tracker', ->


TESTS THE SYMBOLS

TESTS THE SYMBOLS

--- tests/loud_test.coffee
+++ tests/loud_test.coffee
@@ -17,6 +17,21 @@
       expect(room.robot.brain.get('louds').length).to.eql 3
       expect(room.robot.brain.get('louds')).to.eql ['WHO THE HECK ARE YOU', 'WHAT IS GOING ON HERE', 'HELP I AM TRAPPED IN A UNIT TEST']

+    it 'should support funny characters', ->
+      room.user.say 'alice',   '"FOO"'
+      room.user.say 'bob',     'FOO+'
+      room.user.say 'charlie', 'FOO-'
+      room.user.say 'alice',   'FOO!'
+      room.user.say 'bob',     'FOO@BAR'
+      room.user.say 'charlie', 'FOO #BAR'
+      room.user.say 'alice',   'FOO$'
+      room.user.say 'bob',     'FOO%'
+      room.user.say 'charlie', 'FOO?'
+      room.user.say 'alice',   'FOO & BAR'
+      room.user.say 'bob',     'FOO%'
+      room.user.say 'charlie', 'FOO"BAR"'
+      expect(room.robot.brain.get('louds').length).to.eql 12
+
     it 'should only have 1 loud stored after deleting the second', ->
       room.user.say 'alice', 'FOO'
       room.user.say 'bob',   'BAR'


Enforcing the use a single test and browser, adding more debug calls

--- tests/www/tests/runner.coffee
+++ tests/www/tests/runner.coffee
@@ -84,5 +84,9 @@

       for file in files

+        console.log 'testing ', file
         {test} = require file
-        test browser, pass, timeout+        test browser, pass, timeout
+        return
+
+    return


fix terse tf op output

fix terse tf op output

--- src/tf.coffee
+++ src/tf.coffee
@@ -27,7 +27,6 @@
   o = sendqueue.shift()
   msg = o['msg']
   out = o['out']
-  console.log JSON.stringify o
   msg.send {room: msg.message.user.name}, out

 module.exports = (robot) ->
@@ -176,16 +175,20 @@
         if stdout.length < 1024
           return msg.send {room: msg.message.user.name}, "```\n#{stdout}\n```"
         else unless verbose
-          return msg.send {room: msg.message.user.name}, "```\n#{line}\n```" if line.match /^Plan: / for line in stdout.split "\n"
+          out = 'Error'
+          for line in stdout.split "\n"
+            if line.match /^Plan: /
+              out = line
+          return msg.send {room: msg.message.user.name}, "```\n#{out}\n```"
         out = []
-        waitms = 200
+        waitms = 333
         textchunk = ''
         for line in stdout.split "\n"
           if line.match /^(?:\+\s|Plan: )/
             textchunk = out.join "\n"
             sendqueue.push { msg: msg, out: "```\n#{textchunk}\n```" }
             setTimeout servicequeue, waitms
-            waitms = waitms + 200
+            waitms = waitms + 333
             out = []
           out.push line
         textchunk = out.join "\n"


add language(Traditional Chinese)

add language(Traditional Chinese)

--- app/assets/javascripts/anjlab/datepicker.js.coffee
+++ app/assets/javascripts/anjlab/datepicker.js.coffee
@@ -26,6 +26,15 @@
       daysMin: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]
       months: ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"]
       monthsShort: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"]
+  zh_tw:
+    dates:
+      format: 'yyyy/mm/dd'
+      weekStart: 0
+      days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
+      daysShort: ["日", "一", "二", "三", "四", "五", "六", "日"]
+      daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"]
+      months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
+      monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]

 DateTools =
   modes: [


should check if TS is defined

should check if TS is defined

--- app/assets/javascripts/type_station/init.js.coffee
+++ app/assets/javascripts/type_station/init.js.coffee
@@ -1,4 +1,4 @@
-window.TS = {}
+window.TS = {} unless window.TS
 window.TS.onSave = -> null unless window.TS['onSave']
 window.TS.onSaved = -> null unless window.TS['onSaved']


Show load value in the diagram when load is zero

--- ivy/app/assets/javascripts/topologies.js.coffee
+++ ivy/app/assets/javascripts/topologies.js.coffee
@@ -81,7 +81,7 @@
       .attr('dx', 0).attr('dy', 17) # Relative x/y coords.
       .attr('text-anchor', 'middle')
       .text((data) ->
-        if data.load
+        if data.load?
           "#{ data.name } (#{ data.load })"
         else
           data.name


course: adding all students button (small usability aspect)

--- src/smc-webapp/course/students_panel.cjsx
+++ src/smc-webapp/course/students_panel.cjsx
@@ -153,6 +153,20 @@
         @actions(@props.name).add_students(students)
         @setState(err:undefined, add_select:undefined, selected_option_nodes:undefined, add_search:'')

+    add_all_students: (options) ->
+        students = []
+        for entry in @state.add_select
+            account_id = entry.account_id
+            if misc.is_valid_uuid_string(account_id)
+                students.push(
+                    account_id    : account_id
+                    email_address : entry.email_address
+                )
+            else
+                students.push(email_address : entry.email_address)
+        @actions(@props.name).add_students(students)
+        @setState(err:undefined, add_select:undefined, selected_option_nodes:undefined, add_search:'')
+
     get_add_selector_options: ->
         v = []
         seen = {}
@@ -174,6 +188,7 @@
                 {options}
             </FormControl>
             {@render_add_selector_button(options)}
+            {@render_add_all_students_button(options)}
         </FormGroup>

     render_add_selector_button: (options) ->
@@ -194,6 +209,16 @@
                 else "Add #{nb_selected} students"
         disabled = options.length == 0 or (options.length >= 2 and nb_selected == 0)
         <Button onClick={=>@add_selected_students(options)} disabled={disabled}><Icon name='user-plus' /> {btn_text}</Button>
+
+    render_add_all_students_button: (options) ->
+        disabled = (options.length == 0)
+        disabled or= ((@state.selected_option_nodes?.length ? 0) > 0)
+        <Button
+            onClick  = {=>@add_all_students(options)}
+            disabled = {disabled}
+        >
+            <Icon name={'user-plus'} /> Add all students
+        </Button>

     render_error: ->
         ed = null


:art: adding notify method to display temporary debug information

--- termap.coffee
+++ termap.coffee
@@ -85,6 +85,10 @@
 getStatus = ->
   "view: [#{view.toString()}] scale: [#{size}] columns: [#{process.stdout.columns}]"

+notify = (text) ->
+  return if drawing
+  process.stdout.write "\r\x1B[K#{getStatus()} #{text}"
+
 moving = null
 process.stdin.on 'mousepress', (info) ->
   # TODO: file bug @keypress, fails after x>95 / sequence: '\u001b[M#B'
@@ -122,7 +126,10 @@
     else
       false

-  draw() if result
+  if result
+    draw()
+  else
+    notify JSON.stringify key

 process.stdout.on 'resize', ->
   init()


better naming by @michaelficarra

better naming by @michaelficarra

--- browser.coffee
+++ browser.coffee
@@ -3,16 +3,16 @@
 CoffeeScript = require './coffee-script'
 CoffeeScript.require = require

-isModernBrowser = if window? then -> true if btoa? and JSON? else ->
+canGenerateDataUris = btoa? and JSON?

 compile = (code, options = {}) ->
   res = undefined
-  if isModernBrowser()
+  if canGenerateDataUris
     options.sourceMap = true
     options.inline = true
     {js, v3SourceMap} = CoffeeScript.compile code, options
-    answer = btoa v3SourceMap
-    res = "#{js}\n//@ sourceMappingURL=data:application/json;base64,#{answer}\n//@ sourceURL=coffeescript"
+    base64SourceMap = btoa v3SourceMap
+    res = "#{js}\n//@ sourceMappingURL=data:application/json;base64,#{base64SourceMap}\n//@ sourceURL=coffeescript"
   else
     res = CoffeeScript.compile code, options
   res


Don't apply autosize to summernote textareas

This fixes another cause of rendering slowness that affected all browsers.

--- app/assets/javascripts/autosize_init.coffee
+++ app/assets/javascripts/autosize_init.coffee
@@ -1,4 +1,4 @@
 init = ->
-  autosize($("textarea"))
+  autosize($("textarea:not([data-provider=summernote])"))

 $(document).ready(init)


[B1] should actually null it

[B1] should actually null it

--- app/assets/javascripts/monster/popup.coffee
+++ app/assets/javascripts/monster/popup.coffee
@@ -22,7 +22,7 @@
     @$.submit = true
     @whitelist.push 'id' if @whitelist
   pop:(e,data={})=>
-    @$.errors = {}
+    @$.errors = null
     @$root.$broadcast 'reset_popup_position'
     _.each data, (v,k)=> @$[k] = v unless k is 'model'


Refactored edit description directive

Refactored edit description directive

--- app/coffee/modules/common/components.coffee
+++ app/coffee/modules/common/components.coffee
@@ -515,69 +515,63 @@
 #############################################################################

 EditableDescriptionDirective = ($rootscope, $repo, $confirm, $compile, $loading) ->
-    viewTemplate = _.template("""
-
<%= descriptionHtml %>

-      <% if (canEdit) { %>
-
-      <% } %>
-    """)
-
-    editTemplate = _.template("""
-      <textarea placeholder="Write a description of your user story"
-                ng-model="item.description"
-                tg-markitup="tg-markitup"><%- item.description %></textarea>
-
-    """)
+    template = """
+

+

+
+

+

+            <textarea placeholder="Write a description of your user story"
+                      ng-model="item.description"
+                      tg-markitup="tg-markitup"></textarea>
+
+

+    """

     link = ($scope, $el, $attrs, $model) ->
-        editing = false
-        scope = $scope.$new()
-
-        render = ->
-            if editing
-                $el.html($compile(editTemplate({item: scope.item}))(scope))
-            else
-                canEdit = $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
-                $el.html(viewTemplate({descriptionHtml: $model.$modelValue.description_html, canEdit: canEdit}))
-
-        $scope.$watch $attrs.ngModel, (item) ->
-            return if not item
-            render()
-
         $scope.$on "$destroy", ->
             $el.off()

         $el.on "click", ".edit", ->
-            if not editing and $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
-                editing = true
-                scope.item = {description: $model.$modelValue.description}
-                render()
-                $el.find('textarea').focus()
+            $el.find('div.edit-description').show()
+            $el.find('div.view-description').hide()
+            $el.find('textarea').focus()

         $el.on "click", ".save", ->
-            $model.$modelValue.description = scope.item.description
+            $model.$modelValue.description = $scope.item.description
+
             $loading.start($el.find('.save-container'))
             promise = $repo.save($model.$modelValue)
             promise.then ->
                 $confirm.notify("success")
                 $rootscope.$broadcast("history:reload")
-                editing = false
-                $loading.finish($el.find('.save-container'))
-                render()
+                $el.find('div.edit-description').hide()
+                $el.find('div.view-description').show()
             promise.then null, ->
                 $confirm.notify("error")
+            promise.finally ->
                 $loading.finish($el.find('.save-container'))

         $el.on "keyup", "textarea", ->
             if event.keyCode == 27
-                editing = false
-                $model.$modelValue.revert()
-                render()
+                $scope.item.revert()
+                $el.find('div.edit-description').hide()
+                $el.find('div.view-description').show()
+
+        $scope.$watch $attrs.ngModel, (value) ->
+            return if not value
+            $scope.item = value
+            if $scope.project.my_permissions.indexOf($attrs.requiredPerm) != -1
+                $el.find('div.view-description span.edit').show()
+
+        $el.find('div.edit-description').hide()
+        $el.find('div.view-description span.edit').hide()

     return {
         link: link
         restrict: "EA"
         require: "ngModel"
+        template: template
     }

 module.directive("tgEditableDescription", ["$rootScope", "$tgRepo", "$tgConfirm", "$compile", "$tgLoading",


Fix error: undefined is not a function (evaluating \'$scope.us.revert()\')

--- app/coffee/modules/common/lightboxes.coffee
+++ app/coffee/modules/common/lightboxes.coffee
@@ -240,13 +240,13 @@
             $scope.isNew = true
             $scope.usStatusList = statusList

-            $scope.us = {
+            $scope.us = $model.make_model("userstories", {
                 project: projectId
                 points : {}
                 status: status
                 is_archived: false
                 tags: []
-            }
+            })

             # Update texts for creation
             $el.find(".button-green span").html("Create") #TODO: i18n


Update routing.coffee

Update routing.coffee

Add a comment to better document.

--- guts/routing.coffee
+++ guts/routing.coffee
@@ -4,6 +4,9 @@
 getControllers = require './getControllers'

 module.exports = (app) ->
+        # This grabs the controllers and returns back a promise'd object of
+        # them. It made more sense to put this in a separate file, since it's
+        # arguably useful in its own right.
         getControllers()
         .then (controllerObject) ->
                 #Let's load in the routes file


Remove debug msg

Remove debug msg

--- app/scripts/components/ShipSpawnable.coffee
+++ app/scripts/components/ShipSpawnable.coffee
@@ -36,7 +36,6 @@
       @addPoints(20)
     @listenTo @ship, 'Shoot', ->
       @stats.shotsFired += 1
-      console.log "#{@name} shot #{@stats.shotsFired}"
     Crafty.trigger('ShipSpawned', @ship)
     # We start it after the spawned event, so that listeners can
     # reposition it before


I feel this interface behavior is more consistent

--- obot.coffee
+++ obot.coffee
@@ -1,3 +1,39 @@
+###
+Interface rules (not yet implemented):
+
+0. Initially in mode STOP
+
+1. If you click a command, the command should be added to the list
+STOP: enter mode PLAY
+
+exception A: not during TURNAROUND or PLAYREV
+exception B: list is full, show error sparks on command list
+
+2. If you click play, playback should reset and enter mode PLAYFWD
+
+3. If you click undo, last command should be removed from the list,
+   playback reset and fast-forwarded to the new last command, enter mode STOP
+
+4. At the start of a command cycle
+PLAY: advance to next command, if no more commands, STOP
+STOP: nothing
+PLAYFWD: advance to next command, if no commands, STOP,
+         if no more commands (and any commands), TURNAROUND
+TURNAROUND: PLAYREV
+PLAYREV: advance to previous command, if no more commands, STOP
+
+5. @show_current_command is the currently executing command (i.e. the command
+   currently animating), this is the highlighted command
+5a. @show_current_command is set to the length of the commands array (an
+   invalid command index) when in TURNAROUND
+
+6. @next_command is the next command to be run, once it is run increment it. It
+   is the length of the total array (invalid index) when no more commands are
+   left to be executed in PLAY or PLAYFWD, respectively -1 in PLAYREV
+
+###
+
+
 level_cnv = document.getElementById('level')

 prog_cnv = document.getElementById('program')
@@ -50,7 +86,7 @@
   ctx.restore()

   # item
-  @bot.has_item = true
+  #@bot.has_item = true
   if !@bot.has_item
     ctx.translate( (@item_location.xi+.5)*cell_size,
                    (@item_location.yi+.5)*cell_size)
@@ -71,23 +107,23 @@
   for cmd, i in @commands
     col = i

-    cur = (@show_current_command? and i == @show_current_command)
+    cur = i == @show_current_command
     row = 0
     renderCommand(ctx, cmd, (x: col*command_size, y: row*command_size),
       cur and (
-        @mode == levelMode.FULLPLAYING or
-        @mode == levelMode.TURNAROUND), false)
-
-    cur = (@show_current_command? and i == @show_current_command)
+        @mode == levelMode.PLAY or
+        @mode == levelMode.PLAYFWD), false)
+
+    cur = i == @show_current_command
     row = 1
     renderCommand(ctx, cmd, (x: col*command_size, y: row*command_size),
-      cur and @mode == levelMode.REVPLAYING, true)
+      cur and @mode == levelMode.PLAYREV, true)

   if @commands.length > 0
     renderCommand(ctx, reverse_cmd,
       (x: @commands.length*command_size, y: .5*command_size),
-      @show_current_command? and @commands.length == @show_current_command and
-      @mode == levelMode.REVPLAYING)
+      @commands.length == @show_current_command and
+      @mode == levelMode.TURNAROUND)

   return

@@ -294,11 +330,11 @@
 # stepLevelSimulation?
 moveCommand = (level) ->
   switch level.mode
-    when levelMode.PLAYING, levelMode.FULLPLAYING
+    when levelMode.PLAY, levelMode.PLAYFWD
       level.bot.xi += @dir.dx
       level.bot.yi += @dir.dy
       level.bot.dir = @dir.theta
-    when levelMode.REVPLAYING
+    when levelMode.PLAYREV
       dir = reverseDir(@dir)
       level.bot.xi += dir.dx
       level.bot.yi += dir.dy
@@ -354,21 +390,26 @@
 max_commands = 9

 addCommandButtonAction = (level) ->
+  if level.mode == levelMode.TURNAROUND || level.mode == levelMode.PLAYREV
+    return false
   if level.commands.length >= max_commands
+    # TODO: display error
     return false

-  switch level.mode
-    when levelMode.PLAYING, levelMode.FULLPLAYING
-      level.commands[level.commands.length] = @cmd
-    when levelMode.REVPLAYING, levelMode.TURNAROUND
-      level.reset()
-      level.commands[level.commands.length] = @cmd
-
-  true
+  level.commands[level.commands.length] = @cmd
+
+  if level.mode == levelMode.STOPDONE
+    level.reset()
+  if level.mode == levelMode.STOP
+    level.mode = levelMode.PLAY
+    level.animation_mode = levelAnimMode.READY
+    level.last_t = Date.now()/1000
+
+  return true

 playButtonAction = (level) ->
   level.resetAndRun()
-  level.mode = levelMode.FULLPLAYING
+  level.mode = levelMode.PLAYFWD
   return

 undoButtonAction = (level) ->
@@ -377,15 +418,11 @@

   level.commands = level.commands[...-1]

-  if level.mode == levelMode.STOP
-    level.reset()
-    return true
-
-  if level.current_command >= level.commands.length or
-     level.mode != levelMode.PLAYING
-    level.resetAndRun()
-    while level.step()
-      true
+  level.resetAndRun()
+  while level.next_command < level.commands.length && level.step()
+    true
+
+  level.mode = levelMode.STOP

 arrowButtonRender = (ctx) ->
   renderArrow(ctx, @dir.theta, inner_control_size)
@@ -420,30 +457,31 @@
 # level

 levelMode =
-  PLAYING: 2
-  FULLPLAYING: 3
+  STOP: 1
+  PLAY: 2
+  PLAYFWD: 3
   TURNAROUND: 4
-  REVPLAYING: 5
+  PLAYREV: 5

 levelAnimMode =
   READY: 1  # ready to run the next command
-#  TURNING: 2  # animating turn
-  MOVING: 3 # animating movement
-  DONE: 4   # end of the program
+  MOVING: 2 # animating movement

 resetLevel = ->
   @bot.showxi = @bot.xi = @initial_bot_location.xi
   @bot.showyi = @bot.yi = @initial_bot_location.yi
   @bot.showdir = @bot.dir = @initial_bot_location.dir

-  @mode = levelMode.PLAYING
-  @current_command = 0
+  @show_current_command = 0
+  @next_command = 0
+
+  @mode = levelMode.STOP

   return

 resetAndRunLevel = ->
   @reset()
-  @mode = levelMode.PLAYING
+  @mode = levelMode.PLAYFWD
   @last_t = Date.now()/1000
   @animation_mode = levelAnimMode.READY
   delete @move_control
@@ -461,20 +499,48 @@
   #console.log("mode = " + @mode + " animMode = " + @animation_mode)

   if @animation_mode == levelAnimMode.READY
-    if @mode == levelMode.PLAYING
-      @show_current_command = null
-    else
-      @show_current_command = @current_command
+    step_ok = false
+
+    switch @mode
+      when levelMode.PLAY
+        if @next_command < @commands.length
+          step_ok = true
+        else
+          @mode = levelMode.STOP
+
+      when levelMode.PLAYFWD
+        if @next_command < @commands.length
+          step_ok = true
+        else if @commands.length > 0
+          @mode = levelMode.TURNAROUND
+          step_ok = true
+        else
+          @mode = levelMode.STOP
+
+      when levelMode.TURNAROUND
+        @next_command = @commands.length-1
+        @mode = levelMode.PLAYREV
+
+      when levelMode.PLAYREV
+        if @next_command >= 0
+          step_ok = true
+        else
+          @mode = levelMode.STOPDONE
+
     @bot.showxi = @bot.xi
     @bot.showyi = @bot.yi
     @bot.showdir = @bot.dir

-    next_move_control =
-      start: (t: t, x: @bot.xi, y: @bot.yi, dir: @bot.dir)
-      duration: 1/bot_speed
-    if @step()
-      @move_control = next_move_control
-      @animation_mode = levelAnimMode.MOVING
+    if step_ok
+      step_command = @next_command
+
+      next_move_control =
+        start: (t: t, x: @bot.xi, y: @bot.yi, dir: @bot.dir)
+        duration: 1/bot_speed
+      if @step()
+        @move_control = next_move_control
+        @animation_mode = levelAnimMode.MOVING
+        @show_current_command = step_command

   # immediately run this if we switched to MOVING above
   if @animation_mode == levelAnimMode.MOVING
@@ -491,28 +557,15 @@
   return

 stepLevelSimulation = ->
-  if @commands.length == 0 and @mode == levelMode.FULLPLAYING
-    @mode = levelMode.PLAYING
-
-  if @current_command != null &&
-     @current_command >= 0 &&
-     @current_command < @commands.length
-    # TODO: errors for bad movement
-    @commands[@current_command].action(this)
-
-    @advanceCurrentCommand()
-
-    if @mode == levelMode.FULLPLAYING &&
-       @current_command == @commands.length
-      @mode = levelMode.TURNAROUND
+  # TODO: detect illegal moves (obstacles), show error
+  if @mode == levelMode.TURNAROUND
+    # NOTE: even TURNAROUND might fail if something hits you
     return true
-  else if @mode == levelMode.TURNAROUND
-    @mode = levelMode.REVPLAYING
-    @advanceCurrentCommand()
-    return true
-  else
-    return false
-
+
+  @commands[@next_command].action(this)
+  @advanceNextCommand()
+
+  return true

 setupLevel = (initial_bot_location, item_location) ->
   o =
@@ -520,20 +573,18 @@
     item_location: item_location
     bot: (render: renderBot)
     commands: [ ]
-    current_command: 0
+    next_command: 0
+    show_current_command: -1
     render: renderLevel
     animate: animateLevel
     step: stepLevelSimulation

-    mode: levelMode.PLAYING
-    animation_mode: levelAnimMode.READY
-
-    advanceCurrentCommand: ->
+    advanceNextCommand: ->
       switch @mode
-        when levelMode.PLAYING, levelMode.FULLPLAYING
-          @current_command++
-        when levelMode.REVPLAYING
-          @current_command--
+        when levelMode.PLAY, levelMode.PLAYFWD
+          @next_command++
+        when levelMode.PLAYREV
+          @next_command--
     reset: resetLevel
     resetAndRun: resetAndRunLevel
   o.reset()


Disable web socket tracking by default

Disable web socket tracking by default

--- pace.coffee
+++ pace.coffee
@@ -65,7 +65,7 @@
     trackMethods: ['GET']

     # Should we track web socket connections?
-    trackWebSockets: true
+    trackWebSockets: false

 now = ->
   performance?.now?() ? +new Date


Have tests be automatically added again

Have tests be automatically added again

--- config.coffee
+++ config.coffee
@@ -23,4 +23,20 @@
         after: ['vendor/styles/bootstrap/responsive.less']

     templates:
-      joinTo: 'javascripts/app.js'+      joinTo: 'javascripts/app.js'
+
+  # Redo wrapper so that tests modules are automatically added.
+  modules:
+    wrapper: (path, data, isVendor) ->
+      if isVendor
+        code = "#{data};\n"
+      else
+        path = path.replace(/\.\w+$/, '').replace(/^app\//, '')
+        code = """
+        window.require.define({#{JSON.stringify path}: function(exports, require, module) {
+          #{data.replace /(\\)?\n(?!\n)/g, ($0, $1) -> if $1 then $0 else '\n  '}
+        }});\n\n
+        """
+        if /^test[\\/]/.test path
+          code += "window.require(#{JSON.stringify path});\n\n"
+      code


set some builder tests back

set some builder tests back

--- test/components/task-plan/builder.spec.coffee
+++ test/components/task-plan/builder.spec.coffee
@@ -18,8 +18,8 @@
 tomorrow = (new Date(Date.now() + 1000 * 3600 * 24)).toString()
 dayAfter = (new Date(Date.now() + 1000 * 3600 * 48)).toString()

-getDateString = (value) -> moment.utc(moment(value)).format(TutorDateFormat)
-getISODateString = (value) -> moment.utc(moment(value)).format(ISO_DATE_FORMAT)
+getDateString = (value) -> moment(value).format(TutorDateFormat)
+getISODateString = (value) -> moment(value).format(ISO_DATE_FORMAT)

 COURSES = require '../../../api/user/courses.json'
 NEW_READING = ExtendBasePlan({id: "_CREATING_1", settings: {page_ids: []}}, false, false)


Updating processes resource snippet

Updating processes resource snippet

--- snippets/processes.cson
+++ snippets/processes.cson
@@ -1,4 +1,7 @@
 '.source.inspec':
   'Describe process':
     'prefix': 'process'
-    'body': "# http://inspec.io/docs/reference/resources/process/\ndescribe process('${1:name}') do\n  it { should${2:_not} ${3:exist} }${4}\nend"
+    'body': "# http://inspec.io/docs/reference/resources/process/\ndescribe processes('${1:name}') do\n  its('${2:setting}') { should${3:_not} eq '${4:value}' }${5}\nend"
+  'Describe processes':
+    'prefix': 'processes'
+    'body': "# http://inspec.io/docs/reference/resources/process/\ndescribe processes('${1:name}') do\n  its('${2:setting}') { should${3:_not} eq '${4:value}' }${5}\nend"


Revert "Also copy JS files individually"

Whoops, that might have broken building on the worker.

--- config.coffee
+++ config.coffee
@@ -277,7 +277,7 @@
     if stat.isDirectory()
       dirStack.push(fullPath)
     else
-      if _.str.endsWith(file, '.coffee') or _.str.endsWith(file, '.js')
+      if _.str.endsWith(file, '.coffee')
         coffeeFiles.push(fullPath)
       else if _.str.endsWith(file, '.jade')
         jadeFiles.push(fullPath)


minor

minor

--- js_rewriter.coffee
+++ js_rewriter.coffee
@@ -291,26 +291,6 @@
           throw new Error("No power rewrites for #{letter}")
         return Math.min( orders... )

-testRewriter = (appendRewrite, sSource, sExpected)->
-  gSource = groupPowersVd sSource
-  gExpected = groupPowersVd sExpected
-
-  reversed = (s)->
-      rs = s[..]
-      rs.reverse()
-      return rs
-
-  result = appendRewrite( null, reversed(gSource) )
-  expected = appendSimple( null, reversed(gExpected));
-
-  if chainEquals(result, expected)
-    console.log("Test #{sSource}->#{sExpected} passed")
-  else
-    console.log("Test #{sSource}->#{sExpected} failed")
-    console.log("   expected result:"+showNode(expected))
-    console.log("   received result:"+showNode(result))
-
-
 reverseSuffixTable = (ruleset, ignorePowers = true)->
     revTable = {}

@@ -352,19 +332,8 @@
   g.debug=false

   appendRewriteOnce = eval g.generateAppendRewriteOnce()
-  appendRewrite = repeatRewrite appendRewriteOnce
-
-  throw new Error("Failed to compilation gave nothing?") unless appendRewrite?
+  throw new Error("Rewriter failed to compile") unless appendRewriteOnce?
+
+  appendRewrite = repeatRewrite appendRewriteOnce
   return appendRewrite

-main = ->
-    table = {'ba': 'AB', 'bB': '', 'BAB': 'a', 'BBB': 'b', 'Bb': '', 'aBB': 'BAb', 'ABA': 'b', 'AAA': 'a', 'Aa': '', 'bAA': 'ABa', 'ab': 'BA', 'aBA': 'AAb', 'bAB': 'BBa', 'bb': 'BB', 'aA': '', 'aa': 'AA'}
-    s = new RewriteRuleset(table)
-
-    appendRewrite = makeAppendRewrite s
-
-    for [s, t] in s.items()
-       testRewriter(appendRewrite, s,t)
-    return
-
-main()


Image field now tells server when it's been cleared

Closes #566

--- assets/js/views/image-upload/preview.coffee
+++ assets/js/views/image-upload/preview.coffee
@@ -42,10 +42,20 @@
       return

     onDelete: () ->
+
+      # Clear the old preview
       @$imagePreview.attr 'src', ''
       @$el.removeClass 'has-image'
       @$file.removeClass 'hidden'
+
+      # Replace the file input with a clone because you can't clear a file field
       @$file.replaceWith @$file = @$file.clone(true)
+
+      # Add a hidden field of the same name that tells Decoy to delete the
+      # previous.
+      $('<input type="hidden" value="">')
+        .attr('name', @$file.attr('name'))
+        .insertBefore(@$file)
       @trigger 'deleteImage'

       return


cleanup/unfocus

cleanup/unfocus

--- spec/prefix-spec.coffee
+++ spec/prefix-spec.coffee
@@ -451,23 +451,24 @@
     it "repeat operator and motion respectively", ->
       ensure '3 d 2 w', text: "666 777 888 999"

-  fdescribe "blackholeRegisteredOperators settings", ->
+  describe "blackholeRegisteredOperators settings", ->
+    originalText = "initial clipboard content"
     beforeEach ->
       set
         textC: "a|bc"

     describe "when false(default)", ->
-      it "default", -> ensure register: {'"': text: "initial clipboard content"}
-      it 'c mutate register', -> ensure 'c l', register: {'"': text: 'b'}
-      it 'C mutate register', -> ensure 'C', register: {'"': text: 'bc'}
-      it 'x mutate register', -> ensure 'x', register: {'"': text: 'b'}
-      it 'X mutate register', -> ensure 'X', register: {'"': text: 'a'}
-      it 'y mutate register', -> ensure 'y l', register: {'"': text: 'b'}
-      it 'Y mutate register', -> ensure 'Y', register: {'"': text: "abc\n"}
-      it 's mutate register', -> ensure 's', register: {'"': text: 'b'}
-      it 'S mutate register', -> ensure 'S', register: {'"': text: 'abc\n'}
-      it 'd mutate register', -> ensure 'd l', register: {'"': text: 'b'}
-      it 'D mutate register', -> ensure 'D', register: {'"': text: 'bc'}
+      it "default", -> ensure register: {'"': text: originalText}
+      it 'c update', -> ensure 'c l', register: {'"': text: 'b'}
+      it 'C update', -> ensure 'C', register: {'"': text: 'bc'}
+      it 'x update', -> ensure 'x', register: {'"': text: 'b'}
+      it 'X update', -> ensure 'X', register: {'"': text: 'a'}
+      it 'y update', -> ensure 'y l', register: {'"': text: 'b'}
+      it 'Y update', -> ensure 'Y', register: {'"': text: "abc\n"}
+      it 's update', -> ensure 's', register: {'"': text: 'b'}
+      it 'S update', -> ensure 'S', register: {'"': text: 'abc\n'}
+      it 'd update', -> ensure 'd l', register: {'"': text: 'b'}
+      it 'D update', -> ensure 'D', register: {'"': text: 'bc'}

     describe "when true(default)", ->
       describe "blackhole all", ->
@@ -493,17 +494,17 @@
             # "substitute*"
           ]

-        it "default", -> ensure register: {'"': text: "initial clipboard content"}
-        it 'c NOT mutate register', -> ensure 'c l', register: {'"': text: "initial clipboard content"}
-        it 'C NOT mutate register', -> ensure 'C', register: {'"': text: "initial clipboard content"}
-        it 'x NOT mutate register', -> ensure 'x', register: {'"': text: "initial clipboard content"}
-        it 'X NOT mutate register', -> ensure 'X', register: {'"': text: "initial clipboard content"}
-        it 'y NOT mutate register', -> ensure 'y l', register: {'"': text: "initial clipboard content"}
-        it 'Y NOT mutate register', -> ensure 'Y', register: {'"': text: "initial clipboard content"}
-        it 's NOT mutate register', -> ensure 's', register: {'"': text: "initial clipboard content"}
-        it 'S NOT mutate register', -> ensure 'S', register: {'"': text: "initial clipboard content"}
-        it 'd NOT mutate register', -> ensure 'd l', register: {'"': text: "initial clipboard content"}
-        it 'D NOT mutate register', -> ensure 'D', register: {'"': text: "initial clipboard content"}
+        it "default",      -> ensure        register: {'"': text: originalText}
+        it 'c NOT update', -> ensure 'c l', register: {'"': text: originalText}
+        it 'C NOT update', -> ensure 'C',   register: {'"': text: originalText}
+        it 'x NOT update', -> ensure 'x',   register: {'"': text: originalText}
+        it 'X NOT update', -> ensure 'X',   register: {'"': text: originalText}
+        it 'y NOT update', -> ensure 'y l', register: {'"': text: originalText}
+        it 'Y NOT update', -> ensure 'Y',   register: {'"': text: originalText}
+        it 's NOT update', -> ensure 's',   register: {'"': text: originalText}
+        it 'S NOT update', -> ensure 'S',   register: {'"': text: originalText}
+        it 'd NOT update', -> ensure 'd l', register: {'"': text: originalText}
+        it 'D NOT update', -> ensure 'D',   register: {'"': text: originalText}

       describe "blackhole selectively", ->
         beforeEach ->
@@ -513,17 +514,17 @@
             "substitute" # s
           ]

-        it "default", -> ensure register: {'"': text: "initial clipboard content"}
-        it 'c mutate register', -> ensure 'c l', register: {'"': text: 'b'}
-        it 'C NOT mutate register', -> ensure 'C', register: {'"': text: "initial clipboard content"}
-        it 'x NOT mutate register', -> ensure 'x', register: {'"': text: "initial clipboard content"}
-        it 'X mutate register', -> ensure 'X', register: {'"': text: 'a'}
-        it 'y mutate register', -> ensure 'y l', register: {'"': text: 'b'}
-        it 'Y mutate register', -> ensure 'Y', register: {'"': text: "abc\n"}
-        it 's NOT mutate register', -> ensure 's', register: {'"': text: "initial clipboard content"}
-        it 'S mutate register', -> ensure 'S', register: {'"': text: 'abc\n'}
-        it 'd mutate register', -> ensure 'd l', register: {'"': text: 'b'}
-        it 'D mutate register', -> ensure 'D', register: {'"': text: 'bc'}
+        it "default",      -> ensure        register: {'"': text: originalText}
+        it 'c update',     -> ensure 'c l', register: {'"': text: 'b'}
+        it 'C NOT update', -> ensure 'C',   register: {'"': text: originalText}
+        it 'x NOT update', -> ensure 'x',   register: {'"': text: originalText}
+        it 'X update',     -> ensure 'X',   register: {'"': text: 'a'}
+        it 'y update',     -> ensure 'y l', register: {'"': text: 'b'}
+        it 'Y update',     -> ensure 'Y',   register: {'"': text: "abc\n"}
+        it 's NOT update', -> ensure 's',   register: {'"': text: originalText}
+        it 'S update',     -> ensure 'S',   register: {'"': text: 'abc\n'}
+        it 'd update',     -> ensure 'd l', register: {'"': text: 'b'}
+        it 'D update',     -> ensure 'D',   register: {'"': text: 'bc'}

       describe "blackhole by wildcard", ->
         beforeEach ->
@@ -534,16 +535,16 @@
             # "yank*"
           ]

-        it "default", -> ensure register: {'"': text: "initial clipboard content"}
-        it 'c NOT mutate register', -> ensure 'c l', register: {'"': text: "initial clipboard content"}
-        it 'c still CAN update register if specified explicitly', -> ensure '" a c l', register: {'a': text: "b"}
-        it 'c NOT mutate register', -> ensure 'c l', register: {'"': text: "initial clipboard content"}
-        it 'C NOT mutate register', -> ensure 'C', register: {'"': text: "initial clipboard content"}
-        it 'x NOT mutate register', -> ensure 'x', register: {'"': text: "initial clipboard content"}
-        it 'X NOT mutate register', -> ensure 'X', register: {'"': text: "initial clipboard content"}
-        it 'y mutate register', -> ensure 'y l', register: {'"': text: 'b'}
-        it 'Y mutate register', -> ensure 'Y', register: {'"': text: "abc\n"}
-        it 's mutate register', -> ensure 's', register: {'"': text: 'b'}
-        it 'S mutate register', -> ensure 'S', register: {'"': text: 'abc\n'}
-        it 'd NOT mutate register', -> ensure 'd l', register: {'"': text: "initial clipboard content"}
-        it 'D NOT mutate register', -> ensure 'D', register: {'"': text: "initial clipboard content"}
+        it "default",      -> ensure        register: {'"': text: originalText}
+        it 'c NOT update', -> ensure 'c l', register: {'"': text: originalText}
+        it 'c update if specified', -> ensure '" a c l', register: {'a': text: "b"}
+        it 'c NOT update', -> ensure 'c l', register: {'"': text: originalText}
+        it 'C NOT update', -> ensure 'C',   register: {'"': text: originalText}
+        it 'x NOT update', -> ensure 'x',   register: {'"': text: originalText}
+        it 'X NOT update', -> ensure 'X',   register: {'"': text: originalText}
+        it 'y update',     -> ensure 'y l', register: {'"': text: 'b'}
+        it 'Y update',     -> ensure 'Y',   register: {'"': text: "abc\n"}
+        it 's update',     -> ensure 's',   register: {'"': text: 'b'}
+        it 'S update',     -> ensure 'S',   register: {'"': text: 'abc\n'}
+        it 'd NOT update', -> ensure 'd l', register: {'"': text: originalText}
+        it 'D NOT update', -> ensure 'D',   register: {'"': text: originalText}


reset browser between tests

reset browser between tests

--- spec/router_test.coffee
+++ spec/router_test.coffee
@@ -84,13 +84,16 @@
         throw new Error("should have raised an exception!")
       catch
       eq(["uups!"], errors)
-  describe "should handle url rendering", ->
-    it "should render correct view", zhain().
-      window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
-      window_open("/index.html", (w) -> w.router.initDone; eq("/index.html", text(w, "#txt"))).
-      test()
-    it "should handle click to /foo without reloading page", zhain().
-      window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
-      click("#pageSame", (w) -> eq("Ok!", text(w, "#pageSame"))).
-      click("#link_foo", (w) -> eq(["Ok!", "/foo"], [text(w, "#pageSame"), text(w, "#txt")])).
-      test()
+  describe "in browser", ->
+    beforeEach ->
+      window.open("about:blank", "test_window")
+    describe "should handle url rendering", ->
+      it "should render correct view", zhain().
+        window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
+        window_open("/index.html", (w) -> w.router.initDone; eq("/index.html", text(w, "#txt"))).
+        test()
+      it "should handle click to /foo without reloading page", zhain().
+        window_open("/", (w) -> w.router.initDone; eq("No content!", text(w, "#txt"))).
+        click("#pageSame", (w) -> eq("Ok!", text(w, "#pageSame"))).
+        click("#link_foo", (w) -> eq(["Ok!", "/foo"], [text(w, "#pageSame"), text(w, "#txt")])).
+        test()


Do not insert newline if autocomplete popup is visible

--- keymaps/emmet.cson
+++ keymaps/emmet.cson
@@ -28,7 +28,7 @@
   'ctrl-shift-/': 'emmet:toggle-comment'

 # Override Enter key for specific syntaxes only
-'atom-pane atom-text-editor[data-grammar~="html"]:not([mini]), atom-pane atom-text-editor[data-grammar~="xml"]:not([mini])':
+'atom-pane atom-text-editor[data-grammar~="html"]:not([mini]):not(.autocomplete-active), atom-pane atom-text-editor[data-grammar~="xml"]:not([mini]):not(.autocomplete-active)':
   'enter': 'emmet:insert-formatted-line-break-only'

 # language-specific Tab triggers


#2 добавлен метод установки состояния

#2 добавлен метод установки состояния

--- builder.coffee
+++ builder.coffee
@@ -89,7 +89,14 @@
     @_lock = no
     while @commandQue.length > 0
       @commandQue.shift()()
-
+
+  setState: (name, value) ->
+    if typeof @state[name] is 'object'
+      @state[name] = _.extend @state[name], value
+    else
+      @state[name] = value
+
+    console.log @state
   run: (cmdstr) ->
     cmdpath = cmdstr.split(':')
     @execConfig(cmdpath[cmdpath.length-1], @_findCommandConfig(cmdpath))


revert changes I changed  to verify logic

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -312,7 +312,7 @@
   _keystroke: (keys, options={}) =>
     target = @editorElement
     keystrokesToExecute = keys.split(/\s+/)
-    lastKeystrokeIndex = keystrokesToExecute.length - 2
+    lastKeystrokeIndex = keystrokesToExecute.length - 1

     for key, i in keystrokesToExecute
       waitsForFinish = (i is lastKeystrokeIndex) and options.waitsForFinish


:art: cleanup spec helper

:art: cleanup spec helper


.

Former-commit-id: c06d762844a556aa02e1f62fa803aa1246e99019 [formerly 6bc2599fb4ae989c87655c5f8030f3ef3794c606] [formerly de49edc39c1e2f97071061e5bfe9c2f3ae17f688 [formerly 7e00839032b6905def322fdff3f551241ce07fd1]] [formerly 01ac824ef9e9c865a6d3485a2724c45f96d12480 [formerly e7ddeca86c4974ee91805d93194e6b13620a5255] [formerly 600d0bb51aaaa7f1468813b5a4896263d516d797 [formerly 600d0bb51aaaa7f1468813b5a4896263d516d797 [formerly d58e414eb7cfe95c343add79eb3f29602d7913f6]]]]
Former-commit-id: 94ad32d3ccb210a8414fbf8a16522b2c5b418b6a [formerly 4a1e105095cfd8e4902d7268251549174fb714ef] [formerly 9e31ac93f8fe2c3b2f720700d65503835ef49348 [formerly a15df6c8badfdbdb0e05eb768112a29fd666ef92]]
Former-commit-id: 47a60bc5073b36c1d26b023e459c58649801d8be [formerly 7f0cfa965a5e8f8fe2e4eb59cc0bf3705c67c508]
Former-commit-id: c15b863129b44e7088becc2dfb52389f7770b87b
Former-commit-id: 4c1a26907fbfdad41344ab7fd1b8a41d10dcabd4

--- @client/franklin.coffee
+++ @client/franklin.coffee
@@ -1859,8 +1859,10 @@
       # TODO: I'd like to have this defined at a higher level
       onTouchMove: ->
         window.is_swipping = true
+        true
       onTouchEnd: ->
         window.is_swipping = false
+        true

       style:
         width: PAGE_WIDTH()


.

Former-commit-id: 3b1c9e3fb1b53dde0a5871b06e9bcbd567f9553f [formerly 4fc9e18a3fa2872b52d118d96d13c0896a43b172] [formerly 9c7309064a5537bda7aa4e4e3c4d240da8c1f3cd [formerly cf8b02982158c7916dbbf0a0a7f3e313f8fa62be]] [formerly 048f4c3fb182f2aad213f9c80f1a3075d916eee0 [formerly f6b38c9a667f4f4eeb5b59cb6803890ccfc772cd] [formerly 7d48aa23c49378f953976cace70b1e44dd5a32cf [formerly 7d48aa23c49378f953976cace70b1e44dd5a32cf [formerly 6c367f27bece7867ff6e409757046f3e9975412e]]]]
Former-commit-id: a245b48885d6ae027d3e826231413d95ae280cb0 [formerly 59cf4aef8433f2b136b98b84b765c5043c5a93ec] [formerly 2d820d3af745021540bac8c871cc20934754ca51 [formerly 005edf7b1f96702d5290d216a756cf15a312d611]]
Former-commit-id: d14a54280ec707c458eb2994685390bd85949f02 [formerly da2df1bea99459944918d58c7cb9ff584f792058]
Former-commit-id: c479eb70a642137763c816e7345ab2ab8c2b0476
Former-commit-id: 3e78a78a2cc512491f7b902a12e5e4667b48d673

--- @client/testmike.coffee
+++ @client/testmike.coffee
@@ -24,8 +24,8 @@
       R.div null, 'My nutshell is:',
       R.div null, "Am I anonymous to ya? ",
         R.span style : {'font-weight':'bold' },
-          "#{point.hide_name}"
-       R.div style: {'margin': '10px', 'font-style':'italic'},
+           "#{point.hide_name}"
+      R.div style: {'margin': '10px', 'font-style':'italic'},
         point.nutshell)
 $(document).ready ->
   React.renderComponent Top({'moo', 'bar'}), document.getElementById('content')


Add a comment

Add a comment

--- tether.coffee
+++ tether.coffee
@@ -266,6 +266,13 @@
           right: next.page.right - offsetPosition.right - scrollLeft
           bottom: next.page.bottom - offsetPosition.bottom - scrollTop

+    # There is one more optimization we could make here, if the parents are overflow: visible,
+    # we don't really care if we're within their bounding box or not.  Realistically though,
+    # nobody uses overflow: visible, so it's not worth the trouble.
+    #
+    # We could also travel up the DOM and try each containing context, rather than only
+    # looking at the body.
+
     @move next

     @history.unshift next


Forward unref call to actual socket

Forward unref call to actual socket

--- src/ClientSocket.coffee
+++ src/ClientSocket.coffee
@@ -131,4 +131,7 @@
         @_closed = true
         @_disconnect()

+    unref: ->
+        @_socket.unref()
+
 module.exports = ClientSocket


jenkinsMessageTemplate

jenkinsMessageTemplate

--- public/js/wireroom.coffee
+++ public/js/wireroom.coffee
@@ -1,4 +1,18 @@
 $.getScript("/js/json2.js") if typeof(JSON) is "undefined"
+
+jenkinsMessageTemplate = () ->
+  div class: "github message clearfix", ->
+    span class: "column icon", ->
+      span class: "icon icon-cogs", ->
+    span class: "column author", -> "Jenkins"
+    span class: "column job", ->
+      a target: "_blank", href: @job.url, -> @job.name
+    span class: "column build", ->
+      a target: "_blank", href: @build.url, -> @job.number
+    span class: "column phase #{ @phase.toLowerCase() }", ->
+      @phase.toCapitalCase()
+    span class: "column status #{ @status.toLowerCase() }", ->
+      @status.toCapitalCase()

 githubCommitTemplate = () ->
   div class: "github message clearfix", ->


Adds advised production config for socket.io

--- config.coffee
+++ config.coffee
@@ -7,3 +7,17 @@
   @configure
     production: =>
       @enable 'minify'
+
+  @io.configure 'production', =>
+    @io.enable('browser client minification');
+    @io.enable('browser client etag');
+    @io.enable('browser client gzip');
+    @io.set('log level', 1);
+    @io.set 'transports', [
+      'websocket',
+      'flashsocket',
+      'htmlfile',
+      'xhr-polling',
+      'jsonp-polling'
+    ]
+


Stop reversing levels in CampaignEditor

Stop reversing levels in CampaignEditor

Campaign saves started being reversed, and removing this
code brings it back to "normal".

--- app/views/editor/campaign/SaveCampaignModal.coffee
+++ app/views/editor/campaign/SaveCampaignModal.coffee
@@ -24,18 +24,5 @@

   onClickSaveButton: ->
     @showLoading()
-    @reverseLevelsBeforeSave()
     modelsBeingSaved = (model.patch() for model in @modelsToSave.models)
     $.when(_.compact(modelsBeingSaved)...).done(-> document.location.reload())
-
-  reverseLevelsBeforeSave: ->
-    # For some unfathomable reason, not in our code anywhere, the levels get reversed during the save somehow.
-    # Since we want to maintain their orders, we reverse them first, so that when they're reversed again, it's right.
-    # Yaaaay!
-    return unless campaign = _.find @modelsToSave.models, (m) -> m.constructor.className is 'Campaign'
-    levelsReversed = {}
-    levels = campaign.get 'levels'
-    levelIDs = _.keys(levels).reverse()
-    for levelID in levelIDs
-      levelsReversed[levelID] = levels[levelID]
-      campaign.set 'levels', levelsReversed


Add ReviewViews

Add ReviewViews

--- CakefileIncludes.coffee
+++ CakefileIncludes.coffee
@@ -115,6 +115,7 @@
         # jquestion                 : "./server/app/bongo/models/messages/question.coffee"
         # jprivatemessage           : "./server/app/bongo/models/messages/privatemessage.coffee"
         # japp                      : "./server/app/bongo/models/app.coffee"
+        # jreview                   : "./server/app/bongo/models/messages/review.coffee"

         # jinvitation               : "./server/app/bongo/models/invitation.coffee"
         # jinvitationrequest        : "./server/app/bongo/models/invitationrequest.coffee"
@@ -534,6 +535,11 @@
         CommentViewHeader           : "./client/app/CommonViews/comments/commentviewheader.coffee"
         CommentListItemView         : "./client/app/CommonViews/comments/commentlistitemview.coffee"
         CommentNewCommentForm       : "./client/app/CommonViews/comments/newcommentform.coffee"
+
+        ReviewView                  : "./client/app/CommonViews/reviews/reviewview.coffee"
+        ReviewListViewController    : "./client/app/CommonViews/reviews/reviewlistviewcontroller.coffee"
+        ReviewListItemView          : "./client/app/CommonViews/reviews/reviewlistitemview.coffee"
+        ReviewNewReviewForm         : "./client/app/CommonViews/reviews/newreviewform.coffee"

         OpinionView                   : "./client/app/CommonViews/opinions/opinionview.coffee"
         DiscussionActivityOpinionView : "./client/app/CommonViews/opinions/discussionactivityopinionview.coffee"


fix crash when the notifications was not successfully sent

--- src/models/gcm/gcm-sender.coffee
+++ src/models/gcm/gcm-sender.coffee
@@ -9,11 +9,14 @@
       targets = if target.constructor is Array then target else [target]

       @sender.send content, {registrationTokens: targets}, (err, res) ->
-        results = []
-        for result in res.results
-          results.push
-            message_id: result.message_id ? null
-            error: result.error ? null
+        if err
+          response = null
+        else
+          results = []
+          for result in res.results
+            results.push
+              message_id: result.message_id ? null
+              error: result.error ? null

-        response = new PushResponse res.success, res.failure, results
+          response = new PushResponse res.success, res.failure, results
         done err, response


Better handling of paragraph breaks in list items. Fix #399

--- lib/formats.coffee
+++ lib/formats.coffee
@@ -248,13 +248,13 @@
   .replace /\\colorbox\s*{((?:[^{}]|{(?:[^{}]|{[^{}]*})*})*)}/g, '$2'
   ## Nested list environments: process all together (with listStyles global)
   ## to handle special \item formatting.
-  .replace ///(\s*)(?:
+  .replace ///((?:\s|
)*)(?:
     \\begin\s*{(itemize)}
    |\\begin\s*{enumerate}(?:\s*\[((?:[^\[\]]|{[^{}]*})*)\])?
    |\\end\s*{(itemize|enumerate)}
    |\\item\b\s*(?:\[([^\[\]]*)\]\s*)?
   )///g, (match, space, beginItemize, enumArg, end, itemArg) ->
-    space.replace(/\n\s*\n/, '\n') +  ## eat paragraphs
+    space.replace(/\n\s*\n|
/g, '\n') +  ## eat paragraphs
     switch match[space.length + 1]
       when 'b' ## \begin
         listStyles.push enumArg
@@ -292,6 +292,13 @@
           """
#{itemArg}"""
         else
           '
'
+  ## Because we define styles for first
 child of
, we need to make
+  ## sure that, if an
 has any
 in it, then it also starts with one.
+  .replace /(<li\b[^<>]*>)([^]*?)<p\b[^<>]*>/gi, (match, li, inner) ->
+    unless inner.match /<li\b|<\/\s*[uo]l\b/i
+      li + '
' + match[li.length..]
+    else
+      match
   .replace /\\chapter\s*\*?\s*{((?:[^{}]|{(?:[^{}]|{[^{}]*})*})*)}/g, '
$1

'
   .replace /\\section\s*\*?\s*{((?:[^{}]|{(?:[^{}]|{[^{}]*})*})*)}/g, '
$1

'
   .replace /\\subsection\s*\*?\s*{((?:[^{}]|{(?:[^{}]|{[^{}]*})*})*)}/g, '
$1

'


:new: Add tempFile method

:new: Add tempFile method

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -2,8 +2,11 @@
 path = require 'path'
 fs = require 'fs'
 path = require 'path'
+tmp = require('tmp')
+
 xcache = new Map
 XRegExp = null
+
 module.exports = Helpers =
   # Based on an API demoed out in:
   #   https://gist.github.com/steelbrain/43d9c38208bf9f2964ab
@@ -118,3 +121,22 @@
           return filePath
       startDir.pop()
     return null
+  tempFile: (fileName, fileContents, callback) ->
+    throw new Error('Invalid fileName provided') unless typeof fileName is 'string'
+    throw new Error('Invalid fileContent provided') unless typeof fileContents is 'string'
+    throw new Error('Invalid Callback provided') unless typeof callback is 'function'
+
+    return new Promise (resolve, reject) ->
+      tmp.dir {prefix: 'atom-linter_'}, (err, dirPath, cleanupCallback) ->
+        return reject(err) if err
+        filePath = path.join(dirPath, fileName)
+        fs.writeFile filePath, fileContents, (err) ->
+          if err
+            cleanupCallback()
+            return reject(err)
+          (
+            new Promise (resolve) ->
+              resolve(callback(filePath))
+          ).then (result) ->
+            cleanupCallback()
+            resolve(result)