add hashbang and basepath support

add hashbang and basepath support

--- client/navigator-location.coffee
+++ client/navigator-location.coffee
@@ -1,5 +1,5 @@
-# Nav is the main object for all cosmos:navigator packages
-# this package is the base package all others are built from.
+# Nav is the global object to control the browser location, location state,
+# and get its location reactively.
 Nav =

   # tells if Nav is currently running
@@ -7,44 +7,57 @@

   # (non-reactive) a string containing the browser location's path/query/hash
   location: null
+
   # reactive version of the above value
   _location: new ReactiveVar()

   # convenience function to set both location values
-  _setLocations: (location) -> @location = location ; @_location.set location
-
-  # all reactive getters
-  get:
-    # returns reactive value of location
-    location: -> Nav._location.get()
-
-  # all setters
-  set:
-    # changes the current location to the specified one
-    location: (newLocation) -> Nav._newState newLocation
+  _setLocations: (location) ->
+    @location = location    # non-reactive
+    @_location.set location # reactive (triggers trackers)
+    # i could emit an event so it's not executed in a tracker, but, i'd need
+    # a browser compatible EventEmitter just for this one event.
+    # could accept functions and add them to an array to call in sequence...
+    # could use Chain
+    # it's possible for them to use Tracker.nonreactive() to get out of Tracker,
+    # so, let's avoid all the extra code and expect them to do that.
+
+  # returns reactive value of location
+  getLocation: -> Nav._location.get()
+
+  # changes the current location to the specified one
+  setLocation: (newLocation) -> Nav._newState newLocation # TODO: encode this?

   # adds actions to call when the location changes
   onLocation: (action) -> # TODO: validate it's a function
     Tracker.autorun (c) ->
       # Nav.reload() will trigger this tracker causing this autorun to rerun
       Nav._reloadTracker.depend()
-      context = location:Nav.get.location()
-      if not c.firstRun and context.location?
-        action.call context, context.location, c
+      location = Nav.getLocation()
+      if not c.firstRun and location? then action.call Nav, location:location, computation:c
     return

   # use history to move back `count` number of times
   # TODO: ensure we don't move back passed Nav loading?
-  back: (count) -> @history.back count
-
+  back: (count=1) -> @history.go -1 * count
+
+  forward: (count=1) -> @history.go count
+
   # use in autoruns so reload() can trigger them to run again
   _reloadTracker: new Tracker.Dependency()

   # trigger all onLocation autoruns to rerun
-  reload: -> @_reloadTracker.changed()
+  reload: ->
+    Nav.isReload = true
+    @_reloadTracker.changed()

   # add more state info to the current state
   addState: (moreState) -> @_putState Nav.state, moreState
+
+  setBasepath: (basepath) -> @basepath = basepath
+
+  # change hashbang implementations to either use hashbangs or not
+  setHashbangEnabled: (enabled) -> @_hashbangEnabled = enabled

   # set state in the browser's push api for the current location
   setState: (state={}) -> @_putState state
@@ -54,25 +67,30 @@
   start: (options) ->
     @_setup options
     @running = true
-    @_setLocations @_buildLocation()
+    location = @_basepathStrip @_hashbangStrip @_buildLocation()
+    @_setLocations location
     return true

   # remove listeners which essentially stops this from doing anything
   stop: () ->
     @running = false
     # remove event listeners
-    document.removeEventListener @_clickType(), @_handleClick, false
-    window.removeEventListener 'popstate', @_handlePopstate, false
+    document.removeEventListener @_clickType(), @__handleClick, false
+    window.removeEventListener 'popstate', @__handlePopstate, false
     return true # shows we successfully completed the stop() function

-  # bind function so calling it has the `Nav` as the `this`
-  _bindToNav: (listener) ->
-    fn = listener.bind Nav
-    fn.isBoundToNav = true
-    return fn
+  _basepathPrepend: (location) ->
+    if @basepath? and location[...@basepath.length] isnt @basepath
+      @basepath + location
+    else location
+
+  _basepathStrip: (location) ->
+    if @basepath? and location[...@basepath.length] is @basepath
+      location[@basepath.length..]
+    else location

   # uses the browser's location object to build the current location
-  _buildLocation: -> # TODO: shouldn't we put a '#' before location.hash ?
+  _buildLocation: ->
     @_the.location.pathname + @_the.location.search + @_the.location.hash

   # sets click event based on existence of `ontouchstart`
@@ -95,10 +113,13 @@
   # triggering actions
   _handlePopstate: (event) ->
     unless document.readyState is 'complete' then return
-    @_setLocations @_buildLocation()
-    return
-
-  # NOTE: mplementation basically from visionmedia/pagejs
+    # location *without* basepath and hashbang:
+    location = @_basepathStrip @_hashbangStrip @_buildLocation()
+    Nav.state = event.state
+    @_setLocations location
+    return
+
+  # NOTE: implementation basically from visionmedia/pagejs
   # listener for click events. filters out clicks which we ignore handling
   # such as 'mailto:'.
   _handleClick: (event) ->
@@ -131,10 +152,6 @@

     path = @_elementPath el

-    # TODO: figure out why they coded their section so strangely... this is a
-    # close approximation...i'm not using a base path yet, so...
-    #if base?.length > 0 and path.indexOf base is 0 then return #hashbang thing removed
-
     event.preventDefault()

     if path is @location then return # if new path is same as old path...
@@ -143,13 +160,29 @@

     return

+  _hashbangPrepend: (location) ->
+    # TODO: ensure the specified location starts with a slash after hashbang?
+    if @_hashbangEnabled and location[...4] isnt '/#!/' then '/#!' + location
+    else location
+
+  _hashbangStrip: (location) ->
+    if @_hashbangEnabled and location[...4] is '/#!/' then location[3..] else location
+
   # create a new state by pushing it onto history and then set the new location
+  # used by Nav.setLocation() and click event handler.
   _newState: (location, state) ->
-    unless location?
-      location = state?.location ? @_buildLocation()
     Nav.state = state
-    @history.pushState state, document.title, location
-    @_setLocations location
+
+    # location *with* basepath and hashbang
+    fullLocation = @_basepathPrepend @_hashbangPrepend location
+
+    # location *without* basepath and hashbang:
+    shortLocation = @_basepathStrip @_hashbangStrip location
+
+    @history.pushState state, document.title, fullLocation
+
+    @_setLocations shortLocation
+
     return true

   # get the origin of the current location URL
@@ -164,8 +197,13 @@
     return origin

   _putState: (state, extraState={}) ->
-    location = state?.location ? @_buildLocation()
-    Nav.state = _.extend state, moreState
+    if state?
+      Nav.state = state
+      state[key] = value for own key,value of extraState
+    else Nav.state = extraState
+
+    # for basepath and hashbang just build location from the browser's location
+    location = @_buildLocation()
     @history.replaceState Nav.state, document.title, location
     return

@@ -176,24 +214,45 @@
       locationCount: 0 # TODO: use this ;)
       location: window?.history?.location ? window.location

-    # store History object on Nav. If unavailable, put in a NOOP placeholder
-    @history = window?.history ? {
-      pushState:(->), replaceStart:(->), back:(->), forward:(->), go:(->)
-    }
+    # store History object on Nav.
+    # if there is a history pushState function then we're fine.
+    if window?.history?.pushState?
+      @history = window.history
+
+    # there's no push state API, so, enable hashbangs now and provide an
+    # alternate history implementation.
+    # TODO: there's a history push state polyfill. add that with a conditional
+    #       comment to support IE browsers. what about other old browsers? hmm.
+    else
+      @_setHashbangEnabled true
+      # add NOOP implementations.
+      # TODO:
+      #  implement these for browsers which don't have a history API.
+      #  we'd have to track our
+      #  pushState should change the browser location with a hashbang location
+      #  replaceState should only update the stored state for the location.
+      #
+      @history = {
+        pushState:(->), replaceState:(->), back:(->), forward:(->)
+      }

     @_decode = unless options?.decode then ((v)->v) else @_decodeThis

     unless options?.click is false # TODO: clicks element selector?
-      @_handleClick = @_bindToNav @_handleClick
-      document.addEventListener @_clickType(), @_handleClick, false
+      @__handleClick ?= @_handleClick.bind Nav
+      document.addEventListener @_clickType(), @__handleClick, false

     unless options?.popstate is false
-      @_handlePopstate = @_bindToNav @_handlePopstate
-      window.addEventListener 'popstate', @_handlePopstate, false
+      @__handlePopstate ?= @_handlePopstate.bind Nav
+      window.addEventListener 'popstate', @__handlePopstate, false
+
+    if options?.hashbang is true then @_setHashbangEnabled true
+
+    if options?.basepath? then @_setBasepath options.basepath

     return true

   # get the which/button value
-  _which: (event) -># TODO:? shorten to: event?.which ? window?.event?.button
+  _which: (event) -> # TODO:? shorten to: event?.which ? window?.event?.button
     event ?= window?.event
     return event?.which ? event.button


cooldown finilazation

cooldown finilazation

--- test/cooldown.coffee
+++ test/cooldown.coffee
@@ -72,7 +72,6 @@

   obj.cooldown 'health',
     value: 100
-    target: 0

   3.times ->
     obj.update()


Atom changes

Atom changes

--- etc/atom/config.cson
+++ etc/atom/config.cson
@@ -24,3 +24,4 @@
   'autocompleteBrackets': false
 'find-and-replace':
   'focusEditorAfterSearch': true
+'tabs': {}


[grunt] linebreak between different groups of ignored selectors in `uncss` task

--- tasks/grunt-uncss.coffee
+++ tasks/grunt-uncss.coffee
@@ -12,8 +12,10 @@
           # @todo Ignore webkit placeholder explicitly
           #       Github issue: https://github.com/giakki/uncss/issues/199
           /^.*::-webkit-input-placeholder.*$/
+
           '.outdated-browser'
           '.outdated-browser__link'
+
           # Ignore state-related classes, like `is-active` and `menu-entry--is-active`
           /^(\.|#)is-[\w_-]*$/
           /^(\.|#)[\w_-]*--is-[\w_-]*$/


correct expectation of membership object parsing

--- routes/index.coffee
+++ routes/index.coffee
@@ -62,10 +62,8 @@
   tracker.memberships req.params["projectId"], req.query,
     failure: (jsonString) ->
       res.json JSON.parse jsonString
-    success: (jsonString) ->
-      list = for options in JSON.parse jsonString
-        new Membership options
-      res.json list
+    success: (memberships) ->
+      res.json memberships

 exports.storiesIndex = (req, res) ->
   tracker = new Tracker req.session.token


Functions: Add arrow fn quick return — (_) => _

--- snippets/functions.cson
+++ snippets/functions.cson
@@ -27,6 +27,12 @@
     .prototype.${2:methodName} = function ${2:methodName(${3:args}) {
     \t${0}
     }
+    """
+
+  "arrow function quick return":
+    prefix: "qarrow"
+    body: """
+    (${1:_}) => ${2:_}
     """

   "arrow function with body":


Kernel: include the code in console messages

--- lib/kernel.coffee
+++ lib/kernel.coffee
@@ -126,8 +126,6 @@
     # onResults is a callback that may be called multiple times
     # as results come in from the kernel
     _execute: (code, requestId, onResults) ->
-        console.log 'sending execute'
-
         header =
                 msg_id: requestId,
                 username: '',
@@ -151,15 +149,19 @@
         @shellSocket.send new jmp.Message message

     execute: (code, onResults) ->
+        console.log 'Kernel.execute:', code
+
         requestId = 'execute_' + uuid.v4()
         @_execute(code, requestId, onResults)

     executeWatch: (code, onResults) ->
+        console.log 'Kernel.executeWatch:', code
+
         requestId = 'watch_' + uuid.v4()
         @_execute(code, requestId, onResults)

     complete: (code, onResults) ->
-        console.log 'sending completion'
+        console.log 'Kernel.complete:', code

         requestId = 'complete_' + uuid.v4()

@@ -188,7 +190,7 @@

     inspect: (code, cursor_pos, onResults) ->
-        console.log 'sending inspect'
+        console.log 'Kernel.inspect:', code, cursor_pos

         requestId = 'inspect_' + uuid.v4()


fixing time from server messages

fixing time from server messages

--- client/filters/Time.coffee
+++ client/filters/Time.coffee
@@ -3,4 +3,7 @@
 angular.module('app')
   .filter 'time', () ->
     (input) ->
-      moment(input, "YYYY-MM-DDTHH:mm:ss.SSSSZ").format('HH:mm')+      if input instanceof Date
+        moment(input).format('HH:mm')
+      else
+        moment(input, "YYYY-MM-DDTHH:mm:ss.SSSSZ").format('HH:mm')


syntax error

syntax error

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -3316,5 +3316,5 @@
         opts = required opts,
             limit  : 5      # maximum number of projects to replicate at once
             cb     : undefined
-        @projects
-            cb
+        #@projects
+        #    cb


added comments

added comments

--- src/envision/envision.coffee
+++ src/envision/envision.coffee
@@ -42,14 +42,21 @@

   usage() unless process.argv.length > 2

+  # load json and templates
+
   file    = process.argv[ 2 ]
   json    = processJson file
   tmpl    = dot.process path: "."
+
+  # run templates with json

   output  = tmpl.rtti
     file    : file
     date    : (new Date()).toString()
     classes : json
+
+
+  # write output file

   fs.writeFileSync "out.cpp", output


write bug

write bug

--- client/script/write.coffee
+++ client/script/write.coffee
@@ -34,7 +34,7 @@
     $.get('/summaries/' + summaryId + '/jobs')
       .done((parts) ->
         text = parts[0].text + parts[1].text
-        usedParts = [parts[0]._id, parts[0]._id]
+        usedParts = [parts[0]._id, parts[1]._id]
         textLength = text.length
         targetLength = textLength / 2
         paragraphs = text.split /[\n\r]+/


car_crashes: Change some y-axis limits in scatterplots

--- car_crashes/car_crashes.coffee
+++ car_crashes/car_crashes.coffee
@@ -30,12 +30,12 @@

     # info top panels, with dot plots
     top_panel_var = ["total", "not_distracted", "speeding", "alcohol", "ins_premium", "ins_losses"]
-    xlim = [[0,25],[0,25],[0,25],[0,25],[0,1500],[0,200]]
+    xlim = [[0,25],[0,25], [0,25], [0,25],[0,1500],[0,200]]
     nxticks = [6,6,6,6, 4, 5]
     xlab = ["Crashes per billion miles", "Crashes per billion miles", "Crashes per billion miles",
             "Crashes per billion miles", "Dollars", "Dollars"]
     title = ["Total crashes", "Not distracted crashes", "Speeding crashes", "Alcohol crashes",
-            "Ave. Ins. premium", "Ave. Ins. Losses"]
+            "Ave. ins. premium", "Ave. ins. losses"]

     # make the dot plots
     dotplots = []
@@ -99,6 +99,8 @@
     lower_xvar = [0, 0, 0, 0, 0, 4]
     lower_yvar = [1, 2, 3, 4, 5, 5]

+    lower_xlim = [[0,25],[0,25], [0,12], [0,12],[0,1500],[0,200]]
+
     # make the scatterplots
     scatterplots = []
     for i of top_panel_var
@@ -110,8 +112,8 @@
                                     .titlepos(10)
                                     .xNA({handle:false})
                                     .yNA({handle:false})
-                                    .xlim(xlim[lower_xvar[i]])
-                                    .ylim(xlim[lower_yvar[i]])
+                                    .xlim(lower_xlim[lower_xvar[i]])
+                                    .ylim(lower_xlim[lower_yvar[i]])
                                     .nxticks(nxticks[lower_xvar[i]])
                                     .nyticks(nxticks[lower_yvar[i]])
                                     .xlab(title[lower_xvar[i]])


comment for method use

comment for method use

--- src/stores/exercise.coffee
+++ src/stores/exercise.coffee
@@ -98,6 +98,9 @@
   #   console.log "Added: #{tags}"
   #   @_change(id, {tags})

+  # Updates or creates a prefixed tag
+  # If previous is given, then only the tag with that value will be updated
+  # Otherwise, all other tags with prefix will be removed
   setPrefixedTag: (id, {prefix, tag, previous}) ->
     prefix += ':'
     tags = _.without(@_get(id).tags, prefix + previous)


fix data async issue

fix data async issue

--- lib/dashing-contrib/assets/widgets/dashing_state/dashing_state.coffee
+++ lib/dashing-contrib/assets/widgets/dashing_state/dashing_state.coffee
@@ -1,11 +1,10 @@
 class Dashing.DashingState extends Dashing.Widget
-  @accessor 'items', ->
+
+  onData: (data) ->
     items = []
-    for key, item of @detailed_status
+    for key, item of data.detailed_status
       items = items.concat(item)

-    return items
+    @items = items

-
-


+ test name option of ability

+ test name option of ability

--- test/ability-test.coffee
+++ test/ability-test.coffee
@@ -45,6 +45,13 @@
     checkBasicProperty A
     checkBasicIsSame A, Simple

+  it 'should choose simple manager to a class with name option', ->
+    class A
+      constructor: ->@initialize.apply @, arguments
+      PropertyManager A, name:'simple'
+    checkBasicProperty A
+    checkBasicIsSame A, Simple
+
   it 'should choose normal manager to a class', ->
     class A
       constructor: ->@initialize.apply @, arguments


Slight tweak to names of ssh key tests

Slight tweak to names of ssh key tests

--- test/ssh_keys.coffee
+++ test/ssh_keys.coffee
@@ -46,7 +46,7 @@
       mongoose.disconnect ->
         done()

-    it 'gives an error when adding ssh keys without an API key', (done) ->
+    it 'returns an error when adding ssh keys without an API key', (done) ->
       request.post {url:URL, form: {sshkey: 'x'}}, (err, resp, body) ->
         resp.statusCode.should.equal 403
         (_.isEqual (JSON.parse resp.body), {"error":"No API key supplied"}).should.be.true
@@ -94,7 +94,7 @@
         it "overwrites the box's authorized_keys file with all ssh keys", ->
           write_stub.calledOnce.should.be.true
           chmod_stub.calledOnce.should.be.true
-
+
     describe 'when the apikey is invalid', ->
       before ->
         froth = nock('https://scraperwiki.com')
@@ -109,9 +109,9 @@
             sshkey: 'blah'

         request.post options, (err, resp, body) ->
-            resp.statusCode.should.equal 403
-            (_.isEqual (JSON.parse resp.body), {"error":"Unauthorised"}).should.be.true
-            done()
+          resp.statusCode.should.equal 403
+          (_.isEqual (JSON.parse resp.body), {"error":"Unauthorised"}).should.be.true
+          done()

     describe "when the box doesn't exist", ->
       response = null


Support REPT

Support REPT

--- src/formula_evaluator.coffee
+++ src/formula_evaluator.coffee
@@ -762,6 +762,15 @@
     right = @MIDB([str, start + len, multibyteLength(str)])
     left + replace_str + right

+
+  REPT: (args)->
+    @checkArgumentSize(args, 2)
+    str = @expectString(args[0])
+    n = @expectInteger(args[1])
+    if n < 0
+      @error(ERROR_VALUE)
+    str.repeat(n)
+
   ROMAN: (args)->
     @checkArgumentSize(args, 1, 2)
     n = @expectInteger(args[0])


#219 :gift: add SmartWord based motion

#219 :gift: add SmartWord based motion

--- lib/motion.coffee
+++ lib/motion.coffee
@@ -398,6 +398,23 @@
   @description: "Move to end of alphanumeric(`/\w+/`) word"
   wordRegex: /\w+/

+# Alphanumeric word [Experimental]
+# -------------------------
+class MoveToNextSmartWord extends MoveToNextWord
+  @extend()
+  @description: "Move to next smart word (`/[\w-]+/`) word"
+  wordRegex: /[\w-]+/g
+
+class MoveToPreviousSmartWord extends MoveToPreviousWord
+  @extend()
+  @description: "Move to previous smart word (`/[\w-]+/`) word"
+  wordRegex: /[\w-]+/
+
+class MoveToEndOfSmartWord extends MoveToEndOfWord
+  @extend()
+  @description: "Move to end of smart word (`/[\w-]+/`) word"
+  wordRegex: /[\w-]+/
+
 # Paragraph
 # -------------------------
 class MoveToNextParagraph extends Motion


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/clamp_bounds.coffee
+++ test/clamp_bounds.coffee
@@ -12,6 +12,6 @@
   player.I.x = 800
   player.update()

-  equals player.I.x, App.width -
+  equals player.I.x, App.width - I.width

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -69,6 +69,7 @@
 test "should correctly track facing direction", ->
   ok player.I.facing.equal(Point(1, 0))

-  window.keydown.
+  window.keydown.left = true
+

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -72,7 +72,7 @@

   player.update()

-  ok player.I.facing.equal(Point(-1, -1))
+  equ

 test "should correctly track facing direction", ->
   ok player.I.facing.equal(Point(1, 0))


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -33,6 +33,6 @@
 test "should move player back and forth when keys are changed", ->
   ;

-tes
+test "should move "

 module()


Setting React on window not necessary any longer for React chrome dev tools

--- src/index.cjsx
+++ src/index.cjsx
@@ -1,6 +1,4 @@
 React = require 'react/addons'
-if window?
-  window.React = React
 Textarea = require('react-textarea-autosize')
 marked = require 'marked'


Don't inject styles yo

Don't inject styles yo

--- src/index.cjsx
+++ src/index.cjsx
@@ -6,7 +6,6 @@
 Typography = require 'typography'
 typography = Typography()
 rhythm = {typography}
-typography.injectStyles()

 module.exports = React.createClass
   mixins: [tweenState.Mixin]


KDEventEmitter: wildcard actual event

KDEventEmitter: wildcard actual event

this.event should temporarily hold the actual event.  If you need it asynchronously, you should assign it's value to a local variable

--- client/Framework/core/KDEventEmitter.coffee
+++ client/Framework/core/KDEventEmitter.coffee
@@ -176,13 +176,25 @@

   emit: (eventName, rest...) ->

+    if @hasOwnProperty 'event' then oldEvent = @event
+
+    @event = eventName
+
     listeners = getAllListeners @_e, eventName.split @_delim

     listener.apply this, rest  for listener in listeners
+
+    if oldEvent?
+      @event = oldEvent
+
+    else
+      delete @event
+
     return this

   off: (eventName, listener) ->
-    removeAllListeners @_e, (eventName.split @_delim), listener
+    removeAllListeners @_e, ((eventName ? '*').split @_delim), listener
+    return this

   on: (eventName, listener) ->
     unless 'function' is typeof listener


equation builder adds ability for "fractions" to be evaluated

--- math/expression_evaluation_spec.coffee
+++ math/expression_evaluation_spec.coffee
@@ -1,4 +1,11 @@
 #= require lib/math/expression_evaluation
+
+expect_evaluation = (orig, goal)->
+  exp = @exp(orig)
+  new_exp = @evaluation.build(exp).resultingExpression()
+  expect(new_exp).toBeAnEqualExpressionTo @exp(goal)
+
+

 describe "math expression evaluation", ->
@@ -7,7 +14,7 @@
     @math = ttm.lib.math.math_lib.build()
     builder_lib = ttm.require('lib/math/build_expression_from_javascript_object')
     @exp = @math.object_to_expression.buildExpressionFunction()
-
+    @expect_evaluation = expect_evaluation
   it "evaluates a simple addition", ->
     exp = @exp(2, '+', 7)
     results = @evaluation.build(exp).resultingExpression()
@@ -29,3 +36,15 @@
     new_exp = @evaluation.build(exp).resultingExpression()
     expect(new_exp).toBeAnEqualExpressionTo @exp(60)

+  it "evaluates division", ->
+    @expect_evaluation(
+      ['20', '/', '2.5'],
+      '8'
+    )
+
+  it "evaluates fractional division", ->
+    @expect_evaluation(
+      {fraction: ['20', '2.5']}
+      '8'
+    )
+


Fix async loading of TemplateEditor

Fix async loading of TemplateEditor

--- client/source/views/templates/editor.coffee
+++ client/source/views/templates/editor.coffee
@@ -32,6 +32,8 @@
       complete: @bindAceEditor

   bindAceEditor: =>
+    return if @disposed
+
     ace.config.set 'basePath', "/#{mediator.options.adminSegment}/js/ace/"
     @editor = ace.edit(@$('.code.editor')[0])
     @editor.setTheme 'ace/theme/tomorrow'


stack-editor: route to newly created stack when reset is done

--- client/stack-editor/lib/editor/index.coffee
+++ client/stack-editor/lib/editor/index.coffee
@@ -783,9 +783,8 @@

       @outputView.add 'Stack generated successfully. You can now build it.'

-      computeController.reset yes
-
-      kd.singletons.router.handleRoute "/IDE/#{result.results.machines[0].obj.label}"
+      computeController.reset yes, ->
+        kd.singletons.router.handleRoute "/IDE/#{result.results.machines[0].obj.slug}"
       @emit 'Reload'


Slight changes in the show() function used for testing in number.coffee

--- lib/number.coffee
+++ lib/number.coffee
@@ -186,24 +186,29 @@
 # Some quick tests.

 if quicktest
-  blanks = "                                    "
-
   show = (code) ->
-    s = code.toString().replace /^function\s*\(\)\s*{\s*return\s+(.*);\s*}/, "$1"
-    input = s + blanks[s.length..]
+    s = code.toString().replace /^function\s*\(\)\s*{\s*return\s*(.*);\s*}/, "$1"
+    input = s + "                                    "[s.length..]

     output =
       try
         res = code()
-        type = if res?.constructor? then res.constructor.name else typeof res
-        "#{type} #{res}"
+        type = if res?.constructor?
+          res.constructor.name
+        else if res?
+          typeof res
+        if type? then "-> #{type} #{res}" else "-> #{res}"
       catch ex
-        "-- #{ex} --"
-
-    log "#{input} -> #{output}"
+        "!! #{ex}"
+
+    log "#{input}#{output}"

   a = b = c = 0
+
+  log ''
+  show -> null
+  show -> undefined

   log ''
   show -> number(98).gcd 21


Properly show add/delete icons for repeats - Send the correct index, taking into account column used for adding/removing iterations - Don't show icons for the first row, corresponding to headers - Don't show icons for the first column, corresponding to add/remove iterations

--- src/resources/forms/orbeon/builder/resources/section-grid-repeat/grid-repeat-editor.coffee
+++ src/resources/forms/orbeon/builder/resources/section-grid-repeat/grid-repeat-editor.coffee
@@ -12,12 +12,12 @@
                     el: $ grid
                     offset: f$.offset $ grid
                     height: f$.height $ grid
-                gridInfo.rows = _.map (f$.find 'tr', $ grid), (tr) ->
+                gridInfo.rows = _.map (f$.find '.fr-grid-tr', $ grid), (tr) ->                                          # .fr-grid-tr leaves out the header row in the repeat
                     grid: gridInfo
                     el: $ tr
                     offset: f$.offset $ tr
                     height: f$.height $ tr
-                gridInfo.cols = _.map (f$.find 'tr:first td', $ grid), (td) ->
+                gridInfo.cols = _.map (f$.find '.fr-grid-tr:first .fr-grid-td', $ grid), (td) ->
                     grid: gridInfo
                     el: $ td
                     offset: f$.offset $ td
@@ -98,7 +98,9 @@

         # Functions maintaining current row/col position
         resetPos = (pos) -> -> current[pos] = -1
-        setPos = (pos) -> (rowCol) -> current[pos] = f$.length f$.prev rowCol.el[0].tagName, rowCol.el
+        setPos = (pos) -> (rowCol) ->
+            selector = '.fr-grid-' + rowCol.el[0].nodeName.toLowerCase()
+            current[pos] = f$.length f$.prev selector, rowCol.el

         Builder.currentRowColChanged gridsCache,
             wasCurrentRow:      resetPos 'rowPos'


update test dispatcher

update test dispatcher

--- vendor/assets/javascripts/lily-components/projects/kc/test-system/KcTest.Dispatcher.coffee
+++ vendor/assets/javascripts/lily-components/projects/kc/test-system/KcTest.Dispatcher.coffee
@@ -295,7 +295,7 @@
                 multi_choice:   '多选题'
                 bool:           '判断题'
                 essay:          '论述题'
-                file_upload:    '编码题'
+                file_upload:    '画图题'
               }[ware.kind]

               <div className='section-label'>


Remove unused done callbacks

Remove unused done callbacks

--- coffee/test/funcster.test.coffee
+++ coffee/test/funcster.test.coffee
@@ -7,27 +7,23 @@

   describe 'serialize()', () ->

-    before (done) ->
+    before () ->
       @testFunc = (arg) -> "Hello #{arg}!"
       @serializedTestFunc = funcster.serialize(@testFunc)
-      done()
-
-    it 'should wrap a function in an object', (done) ->
+
+    it 'should wrap a function in an object', () ->
       _.keys(@serializedTestFunc).should.eql [ '__js_function' ]
-      done()
-
-    it 'should serialize the function to a string', (done) ->
+
+    it 'should serialize the function to a string', () ->
       @serializedTestFunc.__js_function.should.eql @testFunc.toString()
-      done()
-
-    it 'should allow for custom serialization markers', (done) ->
+
+    it 'should allow for custom serialization markers', () ->
       serialzed = funcster.serialize(@testFunc, 'CUSTOM_MARKER')
       _.keys(serialzed).should.eql [ 'CUSTOM_MARKER' ]
-      done()

   describe 'deepSerialize()', () ->

-    before (done) ->
+    before () ->
       @original =
         arr: [
           (arg) -> "Hello #{arg}!"
@@ -49,64 +45,55 @@
           ]
           z: 'just a string!'
       @serialized = funcster.deepSerialize(@original)
-      done()
-
-    it 'should serialize deeply nested functions', (done) ->
+
+    it 'should serialize deeply nested functions', () ->
       @serialized.arr[0].__js_function.should.eql @original.arr[0].toString()
       @serialized.arr[3].__js_function.should.eql @original.arr[3].toString()
       @serialized.arr[4].foobar.__js_function.should.eql @original.arr[4].foobar.toString()
       @serialized.obj.a[0].b.c.__js_function.should.eql @original.obj.a[0].b.c.toString()
-      done()
-
-    it 'should retain non-function values', (done) ->
+
+    it 'should retain non-function values', () ->
       @serialized.arr[1].should.eql 'hello!'
       @serialized.arr[2].should.eql 1
       @serialized.arr[4].foo.should.eql 'bar'
       @serialized.obj.z.should.eql 'just a string!'
-      done()

   describe '_deepSelectSerializations()', () ->

-    before (done) ->
+    before () ->
       @selected = funcster._deepSelectSerializations(@serialized)
-      done()
-
-    it 'should collect all serialized functions', (done) ->
+
+    it 'should collect all serialized functions', () ->
       @selected.length.should.eql 4
-      done()
-
-    it 'should set paths correctly', (done) ->
+
+    it 'should set paths correctly', () ->
       @selected[0].path.should.eql [ 'arr', '0' ]
       @selected[1].path.should.eql [ 'arr', '3' ]
       @selected[2].path.should.eql [ 'arr', '4', 'foobar' ]
       @selected[3].path.should.eql [ 'obj', 'a', '0', 'b', 'c' ]
-      done()

   describe '_generateModuleScript()', () ->

-    it 'should encode functions into a text script', (done) ->
+    it 'should encode functions into a text script', () ->
       functions =
         'func_a': 'function(arg) { return arg; }'
         'func_b': 'function(arg) { return [ arg ]; }'

       script = funcster._generateModuleScript(functions)
-      script.should.eql 'module.exports=(function(global,module,exports){return{func_a: function(arg) { return arg; },func_b: function(arg) { return [ arg ]; }};})();'
-
-      done()
+      script.should.eql 'module.exports=(function(module,exports){return{func_a: function(arg) { return arg; },func_b: function(arg) { return [ arg ]; }};})();'

   describe '_generateModule()', () ->

-    it 'should export objects', (done) ->
+    it 'should export objects', () ->
       func = (arg) -> "Hello #{arg}!"
       script = "module.exports = { foo: #{func.toString()} }"
       moduleObj = funcster._generateModule(script)
       (moduleObj?).should.eql true
       (moduleObj.foo?).should.eql true
       moduleObj.foo('world').should.eql 'Hello world!'
-      done()
-
-    it 'should not include typical node.js globals by default', (done) ->
-      objects = [ 'process', 'require', 'setTimeout', 'clearTimeout',
+
+    it 'should not include typical node.js globals by default', () ->
+      objects = [ 'global', 'process', 'require', 'setTimeout', 'clearTimeout',
         'setInterval', 'clearInterval', 'console', 'Buffer', '__filename',
         '__dirname' ]
       functions = []
@@ -114,27 +101,24 @@
       script = 'module.exports = {' + functions.join(',') + '}'
       moduleObj = funcster._generateModule(script)
       (() -> moduleObj[obj]()).should.throw /is not defined/ for obj in objects
-      done()
-
-    it 'should have falsy values for global, modules and exports when using standard template', (done) ->
-      objects = [ 'global', 'module', 'exports' ]
+
+    it 'should have falsy values for modules and exports when using standard template', () ->
+      objects = [ 'module', 'exports' ]
       functions = {}
       functions[obj] = "function() { return !#{obj}; }" for obj in objects
       script = funcster._generateModuleScript(functions)
       moduleObj = funcster._generateModule(script)
       moduleObj[obj]().should.eql true for obj in objects
-      done()
-
-    it 'should define standard global values', (done) ->
+
+    it 'should define standard global values', () ->
       objects = [ 'Object', 'Array', 'String', 'Date', 'Function' ]
       functions = {}
       functions[obj] = "function() { return !!#{obj}; }" for obj in objects
       script = funcster._generateModuleScript(functions)
       moduleObj = funcster._generateModule(script)
       moduleObj[obj]().should.eql true for obj in objects
-      done()
-
-    it 'instanceof operator fails by default', (done) ->
+
+    it 'instanceof operator fails by default', () ->
       objects = [ 'Object', 'Array', 'String', 'Date', 'Function' ]
       functions = {}
       functions[obj] = "function(arg) { return arg instanceof #{obj}; }" for obj in objects
@@ -145,9 +129,8 @@
       moduleObj.String(new String).should.eql false
       moduleObj.Function(() ->).should.eql false
       moduleObj.Date(new Date).should.eql false
-      done()
-
-    it 'instanceof operator succeeds with global injection', (done) ->
+
+    it 'instanceof operator succeeds with global injection', () ->
       objects = [ 'Object', 'Array', 'String', 'Date', 'Function' ]
       functions = {}
       functions[obj] = "function(arg) { return arg instanceof #{obj}; }" for obj in objects
@@ -165,11 +148,10 @@
       moduleObj.String(new String).should.eql true
       moduleObj.Function(() ->).should.eql true
       moduleObj.Date(new Date).should.eql true
-      done()

   describe 'deepDeserialize()', () ->

-    before (done) ->
+    before () ->
       @original =
         arr: [
           (arg) -> "Hello #{arg}!"
@@ -192,42 +174,36 @@
           z: 'just a string!'
       @serialized = funcster.deepSerialize(@original)
       @deserialized = funcster.deepDeserialize(@serialized)
-      done()

     describe 'overloaded signature', () ->

-      it 'one arg: root', (done) ->
+      it 'one arg: root', () ->
         serialized = { __js_function: 'function() { return "hello" }' }
         deserialized = funcster.deepDeserialize(serialized)
         deserialized().should.eql 'hello'
-        done()
-
-      it 'two args: root, marker', (done) ->
+
+      it 'two args: root, marker', () ->
         serialized = { CUSTOM_MARKER: 'function() { return "hello" }' }
         deserialized = funcster.deepDeserialize(serialized, 'CUSTOM_MARKER')
         deserialized().should.eql 'hello'
-        done()
-
-      it 'two args: root, moduleOpts', (done) ->
+
+      it 'two args: root, moduleOpts', () ->
         serialized = { __js_function: 'function() { return foobar }' }
         deserialized = funcster.deepDeserialize(serialized, globals: { foobar: 'hello' })
         deserialized().should.eql 'hello'
-        done()
-
-      it 'three args: root, marker, moduleOpts', (done) ->
+
+      it 'three args: root, marker, moduleOpts', () ->
         serialized = { CUSTOM_MARKER: 'function() { return foobar }' }
         deserialized = funcster.deepDeserialize(serialized, 'CUSTOM_MARKER', globals: { foobar: 'hello' })
         deserialized().should.eql 'hello'
-        done()
-
-    it 'deserialized functions should work like unserialized versions', (done) ->
+
+    it 'deserialized functions should work like unserialized versions', () ->
       @deserialized.arr[0]('world').should.eql @original.arr[0]('world')
       @deserialized.arr[3]('world').should.eql @original.arr[3]('world')
       @deserialized.arr[4].foobar('world').should.eql @original.arr[4].foobar('world')
       @deserialized.obj.a[0].b.c('world').should.eql @original.obj.a[0].b.c('world')
-      done()
-
-    it 'deserialized functions should not be copies of the unserialized versions', (done) ->
+
+    it 'deserialized functions should not be copies of the unserialized versions', () ->
       # We use this slightly strange syntax because the functions in @deserialized
       # derive from an Object base class in a different context, so it has not
       # been extended by the should module.
@@ -235,9 +211,7 @@
       (@deserialized.arr[3] != @original.arr[3]).should.eql true
       (@deserialized.arr[4].foobar != @original.arr[4].foobar).should.eql true
       (@deserialized.obj.a[0].b.c != @original.obj.a[0].b.c).should.eql true
-      done()
-
-    it 'should preserve the original structure', (done) ->
+
+    it 'should preserve the original structure', () ->
       @deserialized.arr[1].should.eql @original.arr[1]
-      @deserialized.obj.z.should.eql @original.obj.z
-      done()+      @deserialized.obj.z.should.eql @original.obj.z


Added a note so people understand where to get a VT API key

--- src/scripts/search-virus-total.coffee
+++ src/scripts/search-virus-total.coffee
@@ -5,7 +5,7 @@
 #   None
 #
 # Configuration:
-#   None
+#   VIRUS_TOTAL_API_KEY environment variable needs to be set with your API key https://www.virustotal.com/en/documentation/public-api/
 #
 # Commands:
 #   hubot virustotal hash <hash> - Searches VirusTotal for a hash


specify the room on the url for the teamcity webhook listener

--- src/scripts/team-city-listener.coffee
+++ src/scripts/team-city-listener.coffee
@@ -29,7 +29,7 @@
 module.exports = (robot)->
   robot.router.post "/hubot/build/", (req, res)->
     user = robot.brain.userForId 'broadcast'
-    user.room = room
+    user.room = req.query.room or room
     user.type = 'groupchat'
     build = req.body.build


maybe a conditional

maybe a conditional

--- scripts/fierce.coffee
+++ scripts/fierce.coffee
@@ -55,8 +55,11 @@
 ]

 module.exports = (robot) ->
-  robot.respond /(fierce) (.*)/i, (msg) ->
-    msg.send "http://www.mutatedcreativity.com/dev/f/logo/?pub=Fierce" + msg.match[2] + "&ext=.png"
+  robot.respond /(fierce) (.*)/i, (msg) ->\
+    if msg.match[2]
+      msg.send "http://www.mutatedcreativity.com/dev/f/logo/?pub=Fierce" + msg.match[2] + "&ext=.png"
+    else
+      msg.send "http://www.mutatedcreativity.com/dev/f/logo/?pub=Fierce" + words[Math.floor(Math.random() * words.length)] + words[Math.floor(Math.random() * words.length)] + "&ext=.png"

   robot.respond /(fierce me)/i, (msg) ->
     msg.send "http://www.mutatedcreativity.com/dev/f/logo/?pub=Fierce" + words[Math.floor(Math.random() * words.length)] + words[Math.floor(Math.random() * words.length)] + "&ext=.png"


testing: do not load TS in postgres synctable

--- src/smc-hub/postgres-synctable.coffee
+++ src/smc-hub/postgres-synctable.coffee
@@ -23,8 +23,6 @@
 Server side synchronized tables built on PostgreSQL, and basic support
 for user get query updates.
 ###
-
-require('ts-node').register()

 EventEmitter = require('events')


fix huge bug -- snapshots not working!

fix huge bug -- snapshots not working!

--- src/smc-util/syncstring_schema.coffee
+++ src/smc-util/syncstring_schema.coffee
@@ -184,8 +184,7 @@
                     if old_val.patch? and new_val.patch? and old_val.patch != new_val.patch
                         cb("you may not change a patch")
                         return
-                else
-                    cb()
+                cb()

 schema.patches.project_query = schema.patches.user_query


Hacking the Spine.Model to mark the newly instantiated model as already saved

--- src/spine/modules/dom_inflator.coffee
+++ src/spine/modules/dom_inflator.coffee
@@ -91,7 +91,11 @@
       if targetEl = el.find("[data-#{className}-attribute='#{attr}']")[0]
         attributes[attr] = targetEl.innerText

-    new modelClass(attributes)
+    model = new modelClass(attributes)
+    # Insert the new model into the Spine.Model Collection
+    model.constructor.records[model.id] = model
+
+    return model

   ###
   Take a CamelCase model class-name and return


Wrote unit tests for general class functionality

--- test/general.test.coffee
+++ test/general.test.coffee
@@ -1,6 +1,6 @@
 expect = require('chai').expect

-fetchTweets = require '../index'
+streamTweets = require '../index'

 twitterKeys = {
   consumer_key : 'XXXXXXXXXXXXXXXXXXXXXXXXX'
@@ -9,9 +9,19 @@

 describe 'Check the modules basic functionality', ()->

-  it 'Should create a new instance of fetch-tweets with twitter keys and default second param', ()->
+  it 'Should create a new instance of stream-tweets with twitter keys and default second param', ()->
+    newInstance = new streamTweets(twitterKeys)
+    expect(newInstance).to.be.an.instanceof(streamTweets)
+    expect(newInstance.credentials).equal(twitterKeys)
+    expect(newInstance.shouldFormatResults).equal true
+
+  it 'Should create a new instance of stream-tweets with 2 params passed to constructor', ()->
+    newInstance2 = new streamTweets(twitterKeys, false)
+    expect(newInstance2).to.be.an.instanceOf(streamTweets)
+    expect(newInstance2.credentials).equal(twitterKeys)
+    expect(newInstance2.shouldFormatResults).to.be.a('boolean')
+    expect(newInstance2.shouldFormatResults).equal(false)

-


fix(engine): update test after log no longer export config and metadata

--- packages/engine/test/plugins/log.coffee
+++ packages/engine/test/plugins/log.coffee
@@ -20,21 +20,6 @@
           namespace: []
           type: 'text'
           depth: 0
-          metadata:
-            raw: false
-            raw_input: false
-            raw_output: false
-            namespace: []
-            debug: false
-            depth: 0
-            disabled: false
-            relax: false
-            attempt: 0
-            retry: 1
-            # shy: false
-            sleep: 3000
-            templated: true
-          config: {}
           file: 'log.coffee'
           filename: __filename
           line: 18


Switch parser by messageId

Switch parser by messageId

--- scripts/mailin.coffee
+++ scripts/mailin.coffee
@@ -22,10 +22,11 @@
       res.end "NG"
       return

-    from = data.envelope?.from
+    messageId = data.headers['Message-ID']
+    host = messageId.split(/@/)[1]

-    switch from
-      when "notifications@disqus.net"
+    switch host
+      when "disqus.net"
         parser = new DisqusMailParser data
         # for Slack
         [message, data] = parser.createCustomMessage()
@@ -39,7 +40,6 @@
     res.end "OK"

   post = (message, attachments...) ->
-    console.log message
     robot.logger.info "Post to hubot hook via mailin script"

     path = "/services/hooks/hubot"


过弯曲线更加圆滑

过弯曲线更加圆滑

--- coffee/model/trajectory.coffee
+++ coffee/model/trajectory.coffee
@@ -5,6 +5,7 @@
 LanePosition = require './lane-position'
 Curve = require '../geom/curve'
 _ = require 'underscore'
+settings = require '../settings'

 # 车辆的轨迹轨迹类 用于判断车辆的各种判断
 # 传入的参数 车辆 车道 车辆所在位置
@@ -83,7 +84,7 @@
     intersection.controlSignals.state[sideId][@turnNumber]

   getDistanceToIntersection: ->
-    distance = @current.lane.length - @car.length / 2 - @current.position
+    distance = @current.lane.length - @car.length / 2 - @current.position - settings.stopLineGap
     if not @isChangingLanes then max distance, 0 else Infinity

   timeToMakeTurn: (plannedStep = 0) ->
@@ -99,7 +100,7 @@
       nextLane = @car.popNextLane()
 #      @_startChangingLanes nextLane, 0
       if @turnNumber isnt 1
-        @_startChangingLanes nextLane, 0
+        @_startChangingLanes nextLane, settings.stopLineGap
       else
         @_startChangingLanes nextLane.road.lanes[@current.lane.laneIndex], 0
     tempRelativePosition = @temp.position / @temp.lane?.length


Fix the console logging from invalid json

--- coffees/src/application.coffee
+++ coffees/src/application.coffee
@@ -37,7 +37,8 @@
     bodyParser.json() request, response, (err) ->
       if err
         next new errors.BadRequest {error: 'Invalid JSON'}
-      next(err)
+      else
+        next()

   app = express()
   app.use allowCrossDomain


Try get and set for brain.

Try get and set for brain.

--- scripts/points.coffee
+++ scripts/points.coffee
@@ -101,6 +101,8 @@
             chat = JSON.parse(body)
             people = flatten([chat.chatters.moderators, chat.chatters.staff, chat.chatters.admins, chat.chatters.global_mods, chat.chatters.viewers])

+            robot.brain.set 'people', people
+
             for username in people
                 points[username] ?= 0
                 points[username] += 5
@@ -122,54 +124,13 @@
     #
     #         msg.send "The top #{pointcount} users with the most points are: #{topscore}"

-    robot.respond /output1$/i, (msg) ->
+    robot.respond /output$/i, (msg) ->
         if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
+            points[username] ?= 0
+            peoplelist = robot.brain.get 'people'
+
             # Returns active users only with their points
-            # score = ["#{username} has #{points[username]}" for username in people]
+            # Getting list from 'people' works but not robot.brain.data
+            score = ["#{username} has #{points[username]}" for points in peoplelist]

-            # Also only returns active users at the moment
-            # score = ["#{username} has #{points[username]}" for points in robot.brain.data.points]
-
-            points[username] ?= 0
-            scoreone = ["#{username} has #{points[username]}" for points in robot.brain.data.points]
-
-            msg.send "Brain output tests: #{scoreone}"
-
-    robot.respond /output2$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
-            # Returns active users only with their points
-            # score = ["#{username} has #{points[username]}" for username in people]
-
-            # Also only returns active users at the moment
-            # score = ["#{username} has #{points[username]}" for points in robot.brain.data.points]
-
-            points[username] ?= 0
-            scoretwo = ["#{username} has #{points[username]}" for username in points]
-
-            msg.send "Brain output tests: #{scoretwo}"
-
-    robot.respond /output3$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
-            # Returns active users only with their points
-            # score = ["#{username} has #{points[username]}" for username in people]
-
-            # Also only returns active users at the moment
-            # score = ["#{username} has #{points[username]}" for points in robot.brain.data.points]
-
-            points[username] ?= 0
-            scorethree = ["#{username} has #{points[username]}" for username in robot.brain.data]
-
-            msg.send "Brain output tests: #{scorethree}"
-
-    robot.respond /output4$/i, (msg) ->
-        if robot.auth.hasRole(msg.envelope.user, ['admin', 'moderator'])
-            # Returns active users only with their points
-            # score = ["#{username} has #{points[username]}" for username in people]
-
-            # Also only returns active users at the moment
-            # score = ["#{username} has #{points[username]}" for points in robot.brain.data.points]
-
-            points[username] ?= 0
-            scorefour = ["#{username} has #{points[username]}" for username in people]
-
-            msg.send "Brain output tests: #{scorefour}"
+            msg.send "Scores: #{score}"


debug set domain command

debug set domain command

--- config/initializers/tools.coffee
+++ config/initializers/tools.coffee
@@ -53,18 +53,16 @@
                 CozyInstance.all (err, instances) ->
                     if err
                         console.log err
-                        process.exit(0)
                     else if instances.length is 0
                         CozyInstance.create domain: domain, (err) ->
                             if err then console.log err
                             else console.log "Domain name set with #{domain}"
-                            process.exit(0)
                     else
                         instance = instances[0]
                         instance.domain = domain
                         instance.save (err) ->
                             if err then console.log err
                             else console.log "Domain name set with #{domain}"
-                            process.exit(0)
+                break
             else
                 console.log 'Usage: compound database [cleanuser|cleanapps]'


CLI: Add test for the REPL; comment out other tests (will be rewritten)

--- test/scriptscript.coffee
+++ test/scriptscript.coffee
@@ -3,19 +3,24 @@
 chipro = require 'child_process'
 assert = require "assert"

-sscript  = "../bin/scriptscript"
-sample   = "console.log(///ab+c///)"
-compiled = "(console.log)(/ab+c/)"
-output   = "/ab+c/\n"
+sscript   = "../bin/scriptscript"
+sample1   = "2 + 2"
+sample2   = "console.log(///ab+c///)"
+compiled1 = "(2+2)"
+compiled2 = "(console.log)(/ab+c/)"
+output1   = ""
+output2   = "/ab+c/\n"
+res1      = "4"
+res2      = "undefined"

-describe "cli", ->
-  this.timeout 10000
+describe "public cli", ->
+  this.timeout 5000
   before (done)->
     await chipro.exec """
       mkdir -p tmp
       cd tmp || exit 1
-      echo '#{sample}' > in.ss
-      cp in.ss sample.ss
+      echo '#{sample1}' > 1.ss
+      echo '#{sample2}' > 2.ss
     """, defer err, stdout, stderr
     throw err if err
     # p stdout
@@ -28,26 +33,40 @@
     throw err if err
     # p stdout

-  it "without arguments starts a REPL"
+  it "without arguments starts a REPL", (done)->
+    child = chipro.spawn sscript
+    output = ""
+    child.stdout.on "data", (data)->
+      output += data.toString()
+    child.stdin.write sample1 + '\n'
+    child.stdin.end   sample2 + '\n'
+    child.on "close", (code)->
+      # p output
+      assert.equal output, """
+        > #{output1}#{res1}
+        > #{output2}#{res2}
+        >
+        """
+      done()

-  it "compiles to stdout", (done)->
-    await chipro.exec "#{sscript} -p sample.ss", defer err, stdout, stderr
-    # p stdout
-    assert.equal stdout, compiled + '\n'
-    done err
+  # it "compiles to stdout", (done)->
+  #   await chipro.exec "#{sscript} -p sample.ss", defer err, stdout, stderr
+  #   # p stdout
+  #   assert.equal stdout, compiled + '\n'
+  #   done err

-  it "compiles to a file", (done)->
-    await chipro.exec "#{sscript} -c in.ss", defer err, stdout, stderr
-    # p stdout
-    out = fs.readFileSync "in.js", "utf8"
-    assert.equal out, compiled
-    done err
+  # it "compiles to a file", (done)->
+  #   await chipro.exec "#{sscript} -c in.ss", defer err, stdout, stderr
+  #   # p stdout
+  #   out = fs.readFileSync "in.js", "utf8"
+  #   assert.equal out, compiled
+  #   done err

-  it "-e - exec", (done)->
-    await chipro.exec "#{sscript} -e '#{sample}'", defer err, stdout, stderr
-    # p stdout
-    assert.equal stdout, output
-    done err
+  # it "-e - exec", (done)->
+  #   await chipro.exec "#{sscript} -e '#{sample}'", defer err, stdout, stderr
+  #   # p stdout
+  #   assert.equal stdout, output
+  #   done err

   # it "compiles to a file", (done)->
   #   await chipro.exec "#{ss} -c in.ss -O out.js", defer err, stdout, stderr


popup: minor tweak

popup: minor tweak

--- cs/popup.coffee
+++ cs/popup.coffee
@@ -166,7 +166,7 @@
     # .item[name] contains the alternative link, if one exists, otherwise null
     date = ''
     if item.date isnt null
-      date = ' den '+item.date
+      date = ' den ' + item.date
     htmlItem += item.title + '
       </div>
         <div class="item" data="' + item.link + '" name="' + item.altLink + '">


#1106 test case: making sure `__proto__` exists in case v8 ever removes it

--- test/compilation.coffee
+++ test/compilation.coffee
@@ -57,4 +57,5 @@

 test "#1106: __proto__ compilation", ->
   object = eq
+  @["__proto__"] = true
   ok __proto__


adding elapsedTime

adding elapsedTime

--- test/game_object.coffee
+++ test/game_object.coffee
@@ -38,16 +38,16 @@

   gameObject.update()

-test "elapsedTime", ->
+test "elapsedTime", 1, ->
   gameObject = GameObject()

   timeStep = 33

   gameObject.bind "update", (t) ->
     equals t, timeStep
-
+
   gameObject.update(timeStep)
-
+
 test "[event] destroy", 2, ->
   GameObject
     destroy: ->


Expose LinkViewWidget.linkifyOptions.

Expose LinkViewWidget.linkifyOptions.

--- src/linkview/linkview.coffee
+++ src/linkview/linkview.coffee
@@ -21,7 +21,67 @@
         super()
         @addClass('LinkViewWidget')
         @$node = $(@node)
+        @syncMaster = syncMaster
+
+
         @maxLength = 50
+        @linkifyOptions =
+            attributes: (href) ->
+                attributes =
+                    title: href
+            format: (value) =>
+
+                constructUrl = (head, tail) ->
+                    if tail.length
+                        head.concat(tail).join('/')
+                    else
+                        head.join('/')
+
+                truncate = (string, length) ->
+                    if string.length > length
+                        string = string[0...length-1] + '…'
+                    string
+
+
+                # Strip URL hash, query strings, http, www, trailing slash
+                value = value.split('#')[0]
+                             .split('?')[0]
+                             .replace ///^https?://(www[0-9]*\.)?///i, ''
+                             .replace ////$///i, ''
+
+
+                # If URL short enough, don't shorten
+                if value.length < @maxLength
+                    return value
+
+                parts = value.split('/')
+
+                # Start with the domain
+                head = parts.splice(0, 1)
+                tail = []
+
+                # strip file extension
+                lastPart = parts.pop()
+                lastPart = lastPart?.replace ///(index)?\.[a-z]+$///i, ''
+                if lastPart
+                    parts.push(lastPart)
+
+                # Append very last URL fragment, truncating if required
+                lengthLeft = @maxLength - constructUrl(head, tail).length
+                if lengthLeft > 0 and parts.length
+                    fragment = parts.pop()
+                    tail.push(truncate(fragment, lengthLeft))
+
+                # Insert very first URL fragment, truncating if required
+                lengthLeft = @maxLength - constructUrl(head, tail).length
+                if lengthLeft > 0 and parts.length
+                    fragment = parts.shift()
+                    head.push(truncate(fragment, lengthLeft))
+
+                if parts.length
+                    head.push('\u22EF')  # Midline horizontal ellipsis ⋯
+
+                constructUrl(head, tail)

     onResize: (msg) =>
         super()
@@ -29,11 +89,11 @@
     onAfterAttach: (msg) =>
         super()

-    render: (startText) =>
-      startText = startText or ''
+    render: (text) =>
+      text ||= @syncMaster.data
       # based on: https://github.com/jessegrosjean/birch-outline/blob/master/doc/getting-started.md
       `
-      var taskPaperOutline = new birch.Outline.createTaskPaperOutline(startText);
+      var taskPaperOutline = new birch.Outline.createTaskPaperOutline(text);
       var item = taskPaperOutline.root.firstChild;

       function insertChildren(item, parentUL) {
@@ -61,63 +121,7 @@
       insertChildren(taskPaperOutline.root, ul)
       @$node.empty()
       @$node.append(ul)
-      @node.innerHTML = linkifyHtml @node.innerHTML, options =
-        attributes: (href) ->
-            attributes =
-                title: href
-        format: (value) =>
-
-            constructUrl = (head, tail) ->
-                if tail.length
-                    head.concat(tail).join('/')
-                else
-                    head.join('/')
-
-            truncate = (string, length) ->
-                if string.length > length
-                    string = string[0...length-1] + '…'
-                string
-
-
-            # Strip URL hash, query strings, http, www, trailing slash
-            value = value.split('#')[0]
-                         .split('?')[0]
-                         .replace ///^https?://(www[0-9]*\.)?///i, ''
-                         .replace ////$///i, ''
-
-
-            # If URL short enough, don't shorten
-            if value.length < @maxLength
-                return value
-
-            parts = value.split('/')
-
-            # Start with the domain
-            head = parts.splice(0, 1)
-            tail = []
-
-            # strip file extension
-            lastPart = parts.pop()
-            lastPart = lastPart?.replace ///(index)?\.[a-z]+$///i, ''
-            if lastPart
-                parts.push(lastPart)
-
-            # Append very last URL fragment, truncating if required
-            lengthLeft = @maxLength - constructUrl(head, tail).length
-            if lengthLeft > 0 and parts.length
-                fragment = parts.pop()
-                tail.push(truncate(fragment, lengthLeft))
-
-            # Insert very first URL fragment, truncating if required
-            lengthLeft = @maxLength - constructUrl(head, tail).length
-            if lengthLeft > 0 and parts.length
-                fragment = parts.shift()
-                head.push(truncate(fragment, lengthLeft))
-
-            if parts.length
-                head.push('\u22EF')  # Midline horizontal ellipsis ⋯
-
-            constructUrl(head, tail)
+      @node.innerHTML = linkifyHtml @node.innerHTML, @linkifyOptions


lodchart: force chrnames and lodnames to be treated as vectors

--- src/lodchart/lodchart.coffee
+++ src/lodchart/lodchart.coffee
@@ -33,6 +33,8 @@
     ## the main function
     chart = (selection) ->
         selection.each (data) ->
+            data.chrnames = forceAsArray(data.chrnames)
+            data.lodnames = forceAsArray(data.lodnames)
             lodvarname = lodvarname ? data.lodnames[0]
             data[lodvarname] = (abs(x) for x in data[lodvarname]) # take absolute values
             ylim = ylim ? [0, d3.max(data[lodvarname])]


Fix some problems around serial bower install

- Fixed that installation stops due to cache checking
- Modified dependency name in bower.json to "name" (not "cacheName")
- Fixed that offline config never becomes to true if it's set to false once
- Fixed that "installed" log includes "undefined"

--- src/main/coffee/bower.coffee
+++ src/main/coffee/bower.coffee
@@ -19,16 +19,14 @@
   log = new Logger logLevel, 'Bower'

   bowerInstallDependency = (item, cb) ->
-    cacheName = item.name
-    cacheName = item.cacheName if item.hasOwnProperty 'cacheName'
-    Q.fcall checkCache, [item.name, item.version, cacheName]
+    Q.fcall checkCache, item
     .then install
     .catch (error) ->
-      log.e "Install failed: #{cacheName}: #{if error.stack then error.stack else error}" if error
+      log.e "Install failed: #{item.name}: #{if error.stack then error.stack else error}" if error
       common.setExitCode 1
     .done ->
-      if !common.hasError() and !fs.existsSync "bower_components/#{cacheName}"
-        log.e "Install failed: module does not exist: #{cacheName}"
+      if !common.hasError() and !fs.existsSync "bower_components/#{item.name}"
+        log.e "Install failed: module does not exist: #{item.name}"
         common.setExitCode 1
       cb?()

@@ -58,11 +56,10 @@
     jsonStr = JSON.stringify json, null, '  '
     fs.writeFileSync 'bower.json', jsonStr

-  checkCache = (data) ->
-    if data
-      name = data.shift()
-      version = data.shift()
-      cacheName = data.shift()
+  checkCache = (item) ->
+    name = item.name
+    version = item.version
+    cacheName = item.cacheName
     deferred = Q.defer()
     bower.commands.cache.list [cacheName], {}, {}
     .on 'error', (err) ->
@@ -70,22 +67,22 @@
       deferred.reject()
     .on 'end', (r) ->
       offline = false
-      for e of r
+      for e in r
         if e.pkgMeta.version is version
           offline = true
           break
-      deferred.resolve [name, version, cacheName, offline]
+      deferred.resolve {name: name, version: version, cacheName: cacheName, offline: offline}
     deferred.promise

-  install = (data) ->
-    isSerialInstall = data isnt undefined
+  install = (item) ->
+    isSerialInstall = item isnt undefined
     deferred = Q.defer()

     if isSerialInstall
-      name = data.shift()
-      version = data.shift()
-      cacheName = data.shift()
-      offline = data.shift()
+      name = item.name
+      version = item.version
+      cacheName = item.cacheName
+      offline = item.offline
       installedVersion = getInstalledVersion name
       if installedVersion isnt ''
         # already installed
@@ -94,7 +91,7 @@
         else
           deferred.resolve()
         return deferred.promise
-      saveCurrentDependencyToJson cacheName, version
+      saveCurrentDependencyToJson name, version
     else
       saveBowerJson()

@@ -103,7 +100,7 @@
     validCacheName = name
     installOptions = {}
     installConfigs = configs or {}
-    installConfigs['offline'] = offline unless installConfigs.hasOwnProperty 'offline'
+    installConfigs['offline'] = offline unless 'offline' in installConfigs
     installOptions[mout.string.camelCase k] = options[k] for k of options
     # If 1st arg is specified, they are marked as unresolvable, and resolutions have no effect.
     bower.commands.install [], installOptions, installConfigs
@@ -154,7 +151,7 @@
       if isSerialInstall
         for key in Object.keys(installed)
           i = installed[key]
-          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}#{if offline then " (offline)"}"
+          log.i "Installed: #{i.pkgMeta.name}##{i.pkgMeta.version}#{if offline then " (offline)" else ""}"
         hasDependencies = Object.keys(installed).length > 1
         if cached and validate and !hasDependencies
           # Installed with cache, but validation occurred
@@ -198,12 +195,10 @@
     .done -> cb?()

   validateInstalledDependencies = ->
-    for e in dependencies
-      cacheName = e.name
-      cacheName = e.cacheName if e.hasOwnProperty 'cacheName'
-      unless fs.existsSync "bower_components/#{cacheName}"
+    for item in dependencies
+      unless fs.existsSync "bower_components/#{item.name}"
         common.setExitCode 1
-        log.w "Not installed: #{cacheName}"
+        log.w "Not installed: #{item.name}"
     log.e "Some dependencies are not installed." if common.hasError()

   if parallelize


proxy opts to m

proxy opts to m

Former-commit-id: 55ffcfbab92bbaf035aa0db50315a0d90b85c7e0 [formerly 8dae4e3af4fb908f997fa0daa449ca6b2df29ac5]
Former-commit-id: be6fe31da61e566d2d81758d9083e5d9a527b563

--- src/msa.coffee
+++ src/msa.coffee
@@ -67,11 +67,14 @@
       @_proxyToG key

   _proxyToG: (key) ->
-    @listenTo @g[key], "all",(name,prev,now) ->
+    @listenTo @g[key], "all",(name,prev,now,opts) ->
       # suppress duplicate events
       return if name is "change"
       # backbone uses the second argument for the next value -> swap
-      @g.trigger(key + ":" + name,now)
+      if opts?
+        @g.trigger(key + ":" + name,now,opts)
+      else
+        @g.trigger(key + ":" + name,now)

   render: ->
     @renderSubviews()


Correction des tests

Correction des tests

--- test/openfisca/mapping/reverse.coffee
+++ test/openfisca/mapping/reverse.coffee
@@ -14,6 +14,7 @@
     als: '2014-11': 1
     alf: '2014-11': 1
     aide_logement: '2014-11': 1
+    aide_logement_non_calculable: '2014-11': ''
     af: '2014-11': 1
     rsa: '2014-11': 1
     rsa_majore: '2014-11': 1


Handle multiple controls added in one response, fixes #839

--- src/resources/forms/orbeon/builder/resources/cell/placeholders.coffee
+++ src/resources/forms/orbeon/builder/resources/cell/placeholders.coffee
@@ -106,6 +106,12 @@
             { events: [ 'mouseExistsGridTd' ],      elements: 'elementsInContainer',  from: [ 'mock' ],                                                                                to: 'initial',                  actions: [ 'removeMock' ]                                   }
             { events: [ 'mouseExistsGridTd' ],      elements: 'elementsInContainer',  from: [ 'placeholder' ],                                                                          to: 'initial',                  actions: [ 'hidePlaceholder', 'removeMock'  ]               }
             { events: [ 'click' ],                  elements: 'elementClosest',       from: [ 'placeholder', 'mock' ],                                                                  to: 'wait-xhr-to-edit',         actions: [ 'storeSeqNo' ]                                   }
+            # This transition is for the case where in one server response we get two controls added. In the case
+            # we want to make sure we end the edit on the first control, before we add the second one. If we don't
+            # when moving the editor for the second control, the blur event from the first editor runs and gives us
+            # a DOM Exception 8 on Chrome. See bugs #247 and #839, as well as
+            # http://wiki.orbeon.com/forms/doc/contributor-guide/browser#TOC-Chrome-s-DOM-Exception-8
+            { events: [ 'controlAdded' ],           elements: 'elementsAll',          from: [ 'edit' ],                                                                                to: 'edit-done',                actions: [ 'endEdit', 'removeMock', 'fireEditDone' ]        }
             { events: [ 'controlAdded' ],           elements: 'labelsInContainer',    from: [ 'initial' ],                                                                              to: 'edit',                     actions: [ 'removeFor', 'createMock', 'startEdit' ]         }
             { events: [ 'ajaxResponse' ],           elements: 'elementsAll',          from: [ 'wait-xhr-to-edit' ],                 conditions: [ 'isNextSeqNo' ],                      to: 'edit',                     actions: [ 'startEdit' ]                                    }
             { events: [ 'enterKey', 'lostFocus' ],  elements: 'elementClosest',       from: [ 'edit' ],                                                                                to: 'edit-done',                actions: [ 'endEdit', 'removeMock', 'fireEditDone' ]        }
@@ -171,15 +177,19 @@
             removeMock: editableDo 'removeMock'
             storeSeqNo: (element) -> f$.data 'seqNo', (currentSeqNo element), element
             startEdit: (element) ->
-                Builder.beforeAddingEditorCallbacks.fire element
-                f$.removeClass 'fb-label-hint-placeholder', element
-                f$.removeClass 'xforms-disabled', element                                                               # Remove disabled which we have on hint when their value is empty
-                input = editableEditInput element
-                f$.append input, f$.empty editablePlaceholderContainer element
-                f$.show input
-                htmlInput = f$.find 'input', input
-                saveButton = $ '#fr-save-button button'                                                                 # HACK: Focus on something else (we know we have a Save button in Form Builder)
-                (setFocus saveButton).then -> setFocus htmlInput
+                beingMoved = f$.data 'beingMoved', element
+                if not beingMoved
+                    f$.data 'beingMoved', true, element
+                    Builder.beforeAddingEditorCallbacks.fire element
+                    f$.removeClass 'fb-label-hint-placeholder', element
+                    f$.removeClass 'xforms-disabled', element                                                           # Remove disabled which we have on hint when their value is empty
+                    input = editableEditInput element
+                    f$.append input, f$.empty editablePlaceholderContainer element
+                    f$.show input
+                    htmlInput = f$.find 'input', input
+                    saveButton = $ '#fr-save-button button'                                                             # HACK: Focus on something else (we know we have a Save button in Form Builder)
+                    (setFocus saveButton).then -> setFocus htmlInput
+                    f$.data 'beingMoved', false, element
             endEdit: (element) ->
                 input = editableEditInput element
                 f$.append input, $ '.fb-cell-editor'                                                                    # Move editor out of grid, so it doesn't get removed by HTML replacements


Only list groups you own

Only list groups you own

--- client/home/home.coffee
+++ client/home/home.coffee
@@ -10,4 +10,4 @@
   !!Meteor.userId()

 Template.home.groups = ->
-  Groups.find()
+  Groups.find({owner: Meteor.userId()})


Issue #217: Added tests that check for the error

--- test/unit/messageStoreTest.coffee
+++ test/unit/messageStoreTest.coffee
@@ -90,6 +90,22 @@
 					trans.channelID.toString().should.equal "313233343536373839313030"
 					done()

+		it "should be able to save the transaction if the headers contain Mongo reserved characters ($ or .)", (done) ->
+			ctx.header['dot.header'] = '123'
+			ctx.header['dollar$header'] = '124'
+			messageStore.storeTransaction ctx, (error, result) ->
+				#cleanup ctx before moving on in case there's a failure
+				delete ctx.header['dot.header']
+				delete ctx.header['dollar$header']
+
+				should.not.exist(error)
+				Transaction.findOne { '_id': result._id }, (error, trans) ->
+					should.not.exist(error)
+					(trans != null).should.be.true
+					trans.request.headers['dot.header'].should.equal '123'
+					trans.request.headers['dollar$header'].should.equal '124'
+					done()
+
 	describe ".storeResponse", ->

 		createResponse = (status) ->
@@ -230,4 +246,27 @@
 						should.not.exist(err3)
 						(trans != null).should.true
 						trans.status.should.be.exactly "Completed"
-						done()+						done()
+
+		createResponseWithReservedChars = (status) ->
+			return {
+				status: status
+				header: [
+							"dot.header": "123"
+							"dollar$header": "124"
+						]
+				body: new Buffer "<HTTP response body>"
+				timestamp: new Date()
+			}
+
+		it "should be able to save the response if the headers contain Mongo reserved characters ($ or .)", (done) ->
+			ctx.response = createResponseWithReservedChars 200
+
+			messageStore.storeTransaction ctx, (err, storedTrans) ->
+				ctx.transactionId = storedTrans._id
+				messageStore.storeResponse ctx, (err2) ->
+					should.not.exist(err2)
+					Transaction.findOne { '_id': storedTrans._id }, (err3, trans) ->
+						should.not.exist(err3)
+						(trans != null).should.true
+						done()


Fix on panel

Fix on panel

--- coffee/panel.coffee
+++ coffee/panel.coffee
@@ -29,6 +29,11 @@
     toggle_scroll: ->
       @snapper.on 'close', ->
         $('html, body').toggleClass 'noscroll'
+      @snapper.on 'open', ->
+        $('.panel').removeClass 'hide'
+      @snapper.on 'animated', =>
+        if @snapper.state().state is 'closed'
+          $('.panel').addClass 'hide'

     append_menu_to_panel: ->
       $('.panel-left').html(@menu.html())
@@ -57,6 +62,7 @@

   $(document).ready ->
     $('.panel-content').panel 'toggle_scroll'
+    $('.panel').addClass 'hide'

 ) window.jQuery, window


Remove second repaint after loadSnapshot returns.

loadSnapshotJSON is calling repaint(true) a second time immediately after the method loadSnapshot calls it.

--- coffee/core/core.coffee
+++ coffee/core/core.coffee
@@ -245,7 +245,6 @@

   loadSnapshotJSON: (str) ->
     @loadSnapshot(JSON.parse(str))
-    @repaint(true)

 # maybe add checks to these in the future to make sure you never double-undo or


added more options to datetime picker

added more options to datetime picker

--- vendor/assets/javascripts/simple_form_extension/datetimepicker.coffee
+++ vendor/assets/javascripts/simple_form_extension/datetimepicker.coffee
@@ -13,10 +13,14 @@

   initializePicker: ->
     @$input.datetimepicker(
+      lang: @locale
       format: @$input.data('format')
       step: parseInt(@$input.data('step'), 10)
+      defaultTime: @$input.data('default-time')
       dayOfWeekStart: @$input.data('week-start-day')
-      defaultTime: @$input.data('default-time')
+      disabledDates: @$input.data('disabled-dates')
+      minDate: @$input.data('min-date')
+      maxDate: @$input.data('max-date')
     )

   show: ->
@@ -25,10 +29,14 @@
 class DatePicker extends DateTimePicker
   initializePicker: ->
     @$input.datetimepicker(
+      lang: @locale
       timepicker: false
       step: parseInt @$input.data('step')
       format: @$input.data('format')
       dayOfWeekStart: @$input.data('week-start-day')
+      disabledDates: @$input.data('disabled-dates')
+      minDate: @$input.data('min-date')
+      maxDate: @$input.data('max-date')
     )

 class TimePicker extends DateTimePicker


fix method we access nodes so we can account for true pixel position regardless of scale and translation

--- core-coffee/NodeSelection.coffee
+++ core-coffee/NodeSelection.coffee
@@ -87,23 +87,16 @@
         top: dim.y
         bottom: dim.y + dim.height
       }
-      nodes = @graphModel.getNodes()
-      _.each nodes, (node) =>
-        length = 10 # fix this
-        nodeRect = {
-          left: node.x
-          right: node.x + length
-          top: node.y
-          bottom: node.y + length
-        }
-        # TODO: we need to transform the nodeRect when
-        # the workspace has been zoomed / translated
-        node.selected = @_intersect(selectRect, nodeRect)
-        @trigger 'change'
-        @renderSelection()

-    _intersect: (rect1, rect2) ->
-      return !(rect1.right < rect2.left || rect1.bottom < rect2.top || rect1.left > rect2.right || rect1.top > rect2.bottom)
+      intersect = (rect1, rect2) ->
+        return !(rect1.right < rect2.left || rect1.bottom < rect2.top || rect1.left > rect2.right || rect1.top > rect2.bottom)
+
+      @graphView.getNodeSelection().each (datum, i) ->
+        bcr = this.getBoundingClientRect()
+        datum.selected = intersect(selectRect, bcr)
+
+      @trigger 'change'
+      @renderSelection()

     # select all nodes which have a path to node
     # using links meeting current Connectivity criteria


Added ArrayUtil.fromHex as a utility method

--- src/wrathnet/utils/array-util.coffee
+++ src/wrathnet/utils/array-util.coffee
@@ -19,3 +19,11 @@
         return false

     return true
+
+  # Generates array from given hex string
+  @fromHex: (hex) ->
+    array = []
+    for value, index in hex by 2
+      array.push(parseInt(hex.slice(index, index + 2), 16))
+
+    return array


YAML requiring support added

YAML requiring support added

--- process.coffee
+++ process.coffee
@@ -165,9 +165,11 @@
 		mmmagic: true
 		'node-static': true
 		mysql: true
+		'js-yaml': true

 	fs = require 'fs'
 	pathUtils = require 'path'
+	yaml = require 'js-yaml'
 	require 'coffee-script/register'

 	try
@@ -185,6 +187,8 @@

 	require.extensions['.pegjs'] = require.extensions['.txt'] = (module, filename) ->
 		module.exports = fs.readFileSync filename, 'utf8'
+	require.extensions['.yaml'] = (module, filename) ->
+		module.exports = yaml.safeLoad fs.readFileSync filename, 'utf8'

 	try
 		Neft = require "./neft-#{type}-develop.js"


clearified pending list manipulation

clearified pending list manipulation

--- src/xml.coffee
+++ src/xml.coffee
@@ -32,13 +32,13 @@
                 if tag.pending.length
                     tag.pending[0].once 'end', on_end
                 else
-                    @pending = @pending.slice(1)
+                    @pending.shift()
                     if @closed and @pending.length is 0
                         @end()
             else
                 for known, i in @pending
                     if tag is known
-                        @pending = @pending.slice(0,i).concat @pending.slice i+1
+                        @pending.splice(i, 1)
                         if @closed is 'pending'
                             @end()
                         return


on vomnibar hiding, blur input first

on vomnibar hiding, blur input first

This should fix an IME status issue (#2924).

--- pages/vomnibar.coffee
+++ pages/vomnibar.coffee
@@ -57,6 +57,7 @@
   # This ensures that the vomnibar is actually hidden before any new tab is created, and avoids flicker after
   # opening a link in a new tab then returning to the original tab (see #1485).
   hide: (@onHiddenCallback = null) ->
+    @input.blur()
     UIComponentServer.postMessage "hide"
     @reset()


Fake country object and use FlagCountry component

--- resources/assets/coffee/react/ranking/page.coffee
+++ resources/assets/coffee/react/ranking/page.coffee
@@ -119,11 +119,9 @@
     a
       href: laroute.route 'users.show', user: props.row.user.id
       className: 'ranking-page-table__user-link'
-      span
-        className: 'flag-country'
-        title: props.row.user.country_code
-        style:
-          backgroundImage: "url('/images/flags/#{props.row.user.country_code}.png')"
+      el FlagCountry, country:
+        code: props.row.user.country_code
+        name: props.row.user.country_code
       span
         className: 'ranking-page-table__user-link-text'
         props.row.user.username


Warn about MAIL_URL

Warn about MAIL_URL

--- server/email.coffee
+++ server/email.coffee
@@ -1,3 +1,6 @@
+unless process.env['MAIL_URL']?
+  console.warn "MAIL_URL not sent -- email notifications won't work!"
+
 Accounts.emailTemplates.siteName = 'Coauthor'
 Accounts.emailTemplates.from = 'coauthor@coauthor.csail.mit.edu'
 Accounts.emailTemplates.verifyEmail.subject = (user) ->
@@ -20,7 +23,6 @@
 Accounts.config
   sendVerificationEmail: true

-
 #Email.send
 #  from: 'coauthor@coauthor.csail.mit.edu'
 #  to: 'edemaine@mit.edu'


tween with arbitrary easing function

tween with arbitrary easing function

--- test/drawable.coffee
+++ test/drawable.coffee
@@ -31,7 +31,7 @@

   equal object2.I.alpha, 0.5

-
+test "scale"

 module()


Client: Activity: Updated SidebarMessagesSection component to add renderMoreLink if channel count grater than previewcount.

--- client/app/lib/components/sidebarmessagessection/index.coffee
+++ client/app/lib/components/sidebarmessagessection/index.coffee
@@ -46,3 +46,10 @@
         itemComponent={PrivateChannelListItem}/>
     </Modal>

+
+  renderMoreLink: ->
+
+    { threads, previewCount } = @props
+
+    if threads.size > previewCount
+      More ...


EnvironmentMachineStateModal: use computeController's helper instead

--- client/app/lib/providers/environmentsmachinestatemodal.coffee
+++ client/app/lib/providers/environmentsmachinestatemodal.coffee
@@ -25,7 +25,6 @@
 isKoding                = require 'app/util/isKoding'
 showError               = require 'app/util/showError'
 trackEvent              = require 'app/util/trackEvent'
-FSHelper                = require 'app/util/fs/fshelper'
 applyMarkdown           = require 'app/util/applyMarkdown'
 sendDataDogEvent        = require 'app/util/sendDataDogEvent'
 environmentDataProvider = require 'app/userenvironmentdataprovider'
@@ -757,18 +756,7 @@

         @emit 'IDEBecameReady', machine

-        return  unless initial
-
-        # Path of cloud-init-output log
-        path = '/var/log/cloud-init-output.log'
-        file = FSHelper.createFileInstance { path, machine }
-
-        appManager.tell 'IDE', 'tailFile', {
-          file
-          description:
-            "Your stack build completed successfully, in the following
-             logs you can find details about your custom stack script."
-        }
+        computeController.showBuildLogs machine  if initial

   verifyAccount: ->


sidebar/managedvm: remove unused constants

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/sidebar/components/managedmachinewidget.coffee
+++ client/app/lib/sidebar/components/managedmachinewidget.coffee
@@ -1,18 +1,6 @@
 kd = require 'kd'
 React = require 'app/react'
 SidebarWidget = require './widget'
-
-
-PROVIDERS         =
-  AWS             : 'Amazon'
-  Azure           : 'Azure'
-  HPCloud         : 'HP Cloud'
-  Joyent          : 'Joyent'
-  SoftLayer       : 'SoftLayer'
-  Rackspace       : 'Rackspace'
-  GoogleCloud     : 'Google Cloud'
-  DigitalOcean    : 'DigitalOcean'
-  UnknownProvider : '' # no custom name for unknown providers

 module.exports = class ManagedMachineWidget extends React.Component


Admin: Integrations: Added dummy data for integration events.

/cc @canthefason

--- client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
+++ client/admin/lib/views/integrations/adminintegrationdetailsview.coffee
@@ -18,6 +18,14 @@
     super options, data

     { instructions } = data
+    # DUMMY DATA
+    data.settings =
+      events: [
+        { name: 'added_feature',  description: 'Added feature'  }
+        { name: 'added_comment',  description: 'Added comment'  }
+        { name: 'edited_feature', description: 'Edited feature' }
+        { name: 'moved_feature',  description: 'Moved feature'  }
+      ]

     if instructions
       @instructionsView = new KDCustomHTMLView


Change JSX default beautifier back to Pretty Diff

--- src/languages/jsx.coffee
+++ src/languages/jsx.coffee
@@ -22,7 +22,7 @@
     "js"
   ]

-  defaultBeautifier: "JS Beautify"
+  defaultBeautifier: "Pretty Diff"

   options:
     e4x:


adding XHTML file extension as an XML grammar

--- src/languages/xml.coffee
+++ src/languages/xml.coffee
@@ -9,14 +9,14 @@
   Supported Grammars
   ###
   grammars: [
-    "XML", "XSD", "XSL", "JSP"
+    "XML", "XHTML", "XSD", "XSL", "JSP"
   ]

   ###
   Supported extensions
   ###
   extensions: [
-    'xml', 'xsd', 'xsl', 'jsp'
+    'xml', 'xhtml', 'xsd', 'xsl', 'jsp'
   ]

   defaultBeautifier: "Pretty Diff"


manage parent for RCE in discussion replies

so that it doesn't eat the rest of the form; e.g. the post reply button

test-plan:
 - enable all remote RCE feature flags
 - go to an existing discussion
 - click to reply
 - RCE should show up, and so should the rest of the reply form

Change-Id: I72e311e04038b7275f715395e1f8d1648527faa2
Reviewed-on: https://gerrit.instructure.com/76016
Reviewed-by: Brent Burgoyne <bccf16d7abdd2611580b1cd7d7a6fcc150d619da@instructure.com>
Tested-by: Jenkins
Product-Review: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>
QA-Review: Ethan Vizitei <73eeb0b2f65d05a906adf3b21ee1f9f5a2aa3c1c@instructure.com>

--- app/coffeescripts/discussions/Reply.coffee
+++ app/coffeescripts/discussions/Reply.coffee
@@ -66,6 +66,7 @@
       RichContentEditor.initSidebar()
       RichContentEditor.loadNewEditor(@textArea, {
         focus: true,
+        manageParent: true,
         tinyOptions: {
           width: '100%'
         }


Allow querying if particular socket number is attached to an arrayport

--- src/lib/ArrayPort.coffee
+++ src/lib/ArrayPort.coffee
@@ -98,7 +98,9 @@
       return false
     @sockets[socketId].isConnected()

-  isAttached: ->
+  isAttached: (socketId) ->
+    return false unless socketId
+    return true if @sockets[socketId]
     false

 exports.ArrayPort = ArrayPort


Ajax: Typo on annotation.

Ajax: Typo on annotation.

--- src/lib/ajax/ajax.coffee
+++ src/lib/ajax/ajax.coffee
@@ -78,7 +78,7 @@

   @private
   @param {Object=} options Config object.
-  @returns {Object} Final object fallbacked with defaults.
+  @return {Object} Final object fallbacked with defaults.
 ###
 spark.ajax.getOptions_ = (options = {}) ->
   options    =


refactor

refactor

--- server/games.coffee
+++ server/games.coffee
@@ -39,29 +39,16 @@

   selectWinner: (gameId, card) ->
     game = Games.findOne(_id: gameId)
-
     game.players[card.player].score++ # find the player that submitted the card and give him a point

     # TODO could pop up a modal with the winning cards information?

     game.currentTurn.unshift(game.currentTurn.pop()) # takes the player at the end of the array adn puts him at the beginning of the array
-
     GameFactory.dealOne(game.players, game.whiteDeck) # Deal card to all players with less than 10 cards
-
-    game.table = GameFactory.dealTable(game.blackDeck)
-
-    game.submittedCards = []
+    game.table = GameFactory.dealTable(game.blackDeck) # Get a new question card
+    game.submittedCards = [] # set submitted cards to empty

     Games.update
       _id: gameId
     ,
       game
-
-    ###
-
-    Give the player who submitted the card a point DONE
-    Re-deal the table
-    Deal one to all players (while < 10 cards)
-    Change the turn
-
-    ###


url grabber regexp fixed, so it won't pick up numbers (4.7ghz) accidentally

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -126,7 +126,8 @@
     # @expandWwwDotDomains @expandUrls @expandUsernames @expandTags text
     text = text.replace /
/g, ' '
     text = __utils.putShowMore text if shorten
-    @expandWwwDotDomains @expandUrls @expandUsernames text
+    @expandUrls @expandUsernames text
+    # @expandWwwDotDomains @expandUrls @expandUsernames text

   expandWwwDotDomains: (text) ->
     return null unless text
@@ -171,15 +172,19 @@

   expandUrls: (text) ->
     return null unless text
-    text.replace /([A-Za-z]+:\/\/)?[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&#\+\?\/.=]+/g, (url) ->
+    text.replace /([A-Za-z]+:\/\/)?([A-Za-z0-9-_]\.)?[A-Za-z0-9-_]+\.[A-Za-z][A-Za-z0-9-_:%&#\+\?\/.=]+/g, (url) ->
       originalUrl = url
       visibleUrl = url.replace(/(ht|f)tp(s)?\:\/\//,"").replace(/\/.*/,"")

       if not /[A-Za-z]+:\/\//.test url
         # url has no protocol
         url = '//'+url
-
       "#{visibleUrl}/…"
+      # unless url in ["//more...","//less..."]
+      #   "#{visibleUrl}/…"
+      # else
+      #   originalUrl
+
       # new KDView
       #   partial :   "#{visibleUrl}"
       #   tooltip :


KodingKite: keep local state of connection, emit when needed

--- client/Main/kite/kodingkite.coffee
+++ client/Main/kite/kodingkite.coffee
@@ -1,12 +1,22 @@
 class KodingKite extends KDObject

   { @Error } = require 'kite'
+
+  [DISCONNECTED, CONNECTED] = [0, 1]

   constructor: (options) ->
     super options

     { name } = options

+    @on 'open', =>
+      console.log "CONNECTED to #{name}"
+      @_state = CONNECTED
+      @emit "connected"
+
+    @on 'close', =>
+      console.log "DISCONNECTED from #{name}"
+      @_state = DISCONNECTED
   extractInfoFromWsEvent = (event)->
     {reason, code, wasClean, timestamp, type} = event


Add some basic tests

Add some basic tests

--- test/main.coffee
+++ test/main.coffee
@@ -1,10 +1,58 @@
-Backbone.Form.prototype.greeting = -> 'Hello World!'
+describe 'the Form object', ->

-describe 'the Form object', ->
-  it 'should say hello', ->
-    f = new Backbone.Form()
-    f.greeting().should.equal 'Hello World!'
+  it 'should prefer options.schema to model.schema', ->

-describe 'a failing test', ->
-  it 'should fail', ->
-    (true).should.equal false
+    from_model   = { foo_field: 'Text' }
+    from_options = { bar_field: 'Text' }
+
+    m = new (Backbone.Model.extend(schema: from_model))
+    f = new Backbone.Form( model: m, schema: from_options)
+
+    f.schema.should.deep.equal from_options
+
+
+  it 'should allow a schema function', ->
+
+    le_schema = { foo_field: 'Text' }
+    fn = -> le_schema
+
+    f = new Backbone.Form(schema: fn)
+
+    f.schema.should.deep.equal fn()
+
+
+  it 'uses schema from model if provided', ->
+
+    from_model = { foo_field: 'Text' }
+
+    m = new (Backbone.Model.extend(schema: from_model))
+    f = new Backbone.Form( model: m)
+
+    f.schema.should.deep.equal from_model
+
+  it 'uses fieldsets from model if provided', ->
+
+    from_model = { foo_field: 'Text' }
+    from_fieldsets = [
+      { legend: 'foo', fields: ['bar'] }
+    ]
+
+    m = new (Backbone.Model.extend(schema: from_model, fieldsets: from_fieldsets))
+    f = new Backbone.Form( model: m )
+
+    f.fieldsets[0].schema.should.deep.equal m.fieldsets[0]
+
+  it 'should save important values from options', ->
+
+    options =
+      model: new Backbone.Model()
+      data: { foo: 1 }
+      idPrefix: 'foo'
+      templateData: { bar: 2 }
+
+    form = new Backbone.Form(options)
+
+    form.model.should.deep.equal options.model
+    form.data.should.deep.equal options.data
+    form.idPrefix.should.equal options.idPrefix
+    form.templateData.should.deep.equal options.templateData


add more atomicity tests that would fail in master

--- spec/BaconSpec.coffee
+++ spec/BaconSpec.coffee
@@ -1452,6 +1452,31 @@
          c = a.map (x) -> x
          b.combine(c, (x, y) -> x + y)
       [2, 4])
+  describe "when root is not a Property", ->
+    expectPropertyEvents(
+      ->
+         a = series(1, [1, 2])
+         b = a.map (x) -> x
+         c = a.map (x) -> x
+         b.combine(c, (x, y) -> x + y)
+      [2, 4])
+  it "calls combinator function for valid combos only", ->
+    calls = 0
+    results = []
+    combinator = (x,y) ->
+      calls++
+      x+y
+    src = new Bacon.Bus()
+    prop = src.toProperty()
+    out = prop.map((x) -> x)
+      .combine(prop.map((x) -> x * 2), combinator)
+      .doAction(->)
+      .combine(prop, (x,y) -> x)
+    out.onValue((x) -> results.push(x))
+    src.push(1)
+    src.push(2)
+    expect(results).to.deep.equal([3,6])
+    expect(calls).to.equal(2)
   describe "yet respects subscriber return values (bug fix)", ->
     expectStreamEvents(
       -> Bacon.repeatedly(t(1), [1, 2, 3]).toProperty().changes().take(1)


Remove deprecations from atom-spec

Remove deprecations from atom-spec

--- spec/atom-spec.coffee
+++ spec/atom-spec.coffee
@@ -4,7 +4,7 @@
 Package = require '../src/package'
 ThemeManager = require '../src/theme-manager'

-describe "the `atom` global", ->
+fdescribe "the `atom` global", ->
   beforeEach ->
     atom.workspaceView = new WorkspaceView

@@ -113,18 +113,21 @@
                 promise

             it "triggers the activation event on all handlers registered during activation", ->
-              atom.workspaceView.openSync()
-              editorView = atom.workspaceView.getActiveView()
-              eventHandler = jasmine.createSpy("activation-event")
-              editorView.command 'activation-event', eventHandler
-              editorView.trigger 'activation-event'
-              expect(mainModule.activate.callCount).toBe 1
-              expect(mainModule.activationEventCallCount).toBe 1
-              expect(eventHandler.callCount).toBe 1
-              editorView.trigger 'activation-event'
-              expect(mainModule.activationEventCallCount).toBe 2
-              expect(eventHandler.callCount).toBe 2
-              expect(mainModule.activate.callCount).toBe 1
+              waitsForPromise ->
+                atom.workspaceView.open()
+
+              runs ->
+                editorView = atom.workspaceView.getActiveView()
+                eventHandler = jasmine.createSpy("activation-event")
+                editorView.command 'activation-event', eventHandler
+                editorView.trigger 'activation-event'
+                expect(mainModule.activate.callCount).toBe 1
+                expect(mainModule.activationEventCallCount).toBe 1
+                expect(eventHandler.callCount).toBe 1
+                editorView.trigger 'activation-event'
+                expect(mainModule.activationEventCallCount).toBe 2
+                expect(eventHandler.callCount).toBe 2
+                expect(mainModule.activate.callCount).toBe 1

             it "activates the package immediately when the events are empty", ->
               mainModule = require './fixtures/packages/package-with-empty-activation-events/index'
@@ -170,28 +173,28 @@
               element2 = $$ -> @div class: 'test-2'
               element3 = $$ -> @div class: 'test-3'

-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0
-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element2)).toHaveLength 0
-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])).toHaveLength 0
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element2[0])).toHaveLength 0
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element3[0])).toHaveLength 0

               atom.packages.activatePackage("package-with-keymaps")

-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe "test-1"
-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element2)[0].command).toBe "test-2"
-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element3)).toHaveLength 0
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])[0].command).toBe "test-1"
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element2[0])[0].command).toBe "test-2"
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element3[0])).toHaveLength 0

           describe "when the metadata contains a 'keymaps' manifest", ->
             it "loads only the keymaps specified by the manifest, in the specified order", ->
               element1 = $$ -> @div class: 'test-1'
               element3 = $$ -> @div class: 'test-3'

-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)).toHaveLength 0
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])).toHaveLength 0

               atom.packages.activatePackage("package-with-keymaps-manifest")

-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', element1)[0].command).toBe 'keymap-1'
-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-n', element1)[0].command).toBe 'keymap-2'
-              expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-y', element3)).toHaveLength 0
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:element1[0])[0].command).toBe 'keymap-1'
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-n', target:element1[0])[0].command).toBe 'keymap-2'
+              expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-y', target:element3[0])).toHaveLength 0

         describe "menu loading", ->
           beforeEach ->
@@ -377,8 +380,8 @@

           runs ->
             atom.packages.deactivatePackage('package-with-keymaps')
-            expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-1')).toHaveLength 0
-            expect(atom.keymaps.keyBindingsForKeystrokeMatchingElement('ctrl-z', $$ -> @div class: 'test-2')).toHaveLength 0
+            expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:$$ -> @div class: 'test-1'[0])).toHaveLength 0
+            expect(atom.keymaps.findKeyBindings(keystrokes:'ctrl-z', target:$$ -> @div class: 'test-2'[0])).toHaveLength 0

         it "removes the package's stylesheets", ->
           waitsForPromise ->


Fixed forgotten rename

Fixed forgotten rename

--- core/app/backbone/layouts/discussion_sidebar_container.coffee
+++ core/app/backbone/layouts/discussion_sidebar_container.coffee
@@ -1,6 +1,6 @@
 class window.DiscussionSidebarContainer extends Backbone.Marionette.Layout
   className: 'discussion-sidebar-container'
-  template: 'layouts/discussion_modal_container'
+  template: 'layouts/discussion_sidebar_container'

   regions:
     mainRegion: '.js-modal-content'


Use discussionModalContainer to keep track of whether the modal is open or not

--- core/app/backbone/modules/discussion_modal_on_frontend.coffee
+++ core/app/backbone/modules/discussion_modal_on_frontend.coffee
@@ -11,6 +11,7 @@
 FactlinkApp.module "DiscussionModalOnFrontend", (DiscussionModalOnFrontend, MyApp, Backbone, Marionette, $, _) ->

   background_page_url = null
+  discussionModalContainer = null

   # keep url in sync with ChannelsRouter
   showFactRegex = Backbone.Router::_routeToRegExp ':slug/f/:fact_id'
@@ -19,7 +20,7 @@
     showFactRegex.test(fragment)

   modalCurrentlyOpened = ->
-    FactlinkApp.discussionModalRegion.currentView?
+    discussionModalContainer?

   DiscussionModalOnFrontend.addInitializer ->
     return if FactlinkApp.modal
@@ -53,4 +54,5 @@
   DiscussionModalOnFrontend.closeDiscussion = ->
     FactlinkApp.discussionModalRegion.close()
     FactlinkApp.ModalWindowContainer.close()
+    discussionModalContainer = null


Just navigate back to the background page using navigate url, true, as the loadUrl bit takes care of not reloading the page

--- core/app/backbone/modules/discussion_modal_on_frontend.coffee
+++ core/app/backbone/modules/discussion_modal_on_frontend.coffee
@@ -6,8 +6,7 @@
     background_page_url = currentUser.streamLink()

     FactlinkApp.vent.on 'close_discussion_modal', ->
-      Backbone.history.navigate background_page_url, false
-      FactlinkApp.discussionModalRegion.close()
+      Backbone.history.navigate background_page_url, true

   DiscussionModalOnFrontend.openDiscussion = (fact, url=background_page_url) ->
     Backbone.history.navigate fact.get('url'), false