chore: fixes webpackability

chore: fixes webpackability

Using webpack and typescript was not working due to problems with
the default import of underscore. It is not entirely clear why
it was not working, but adjusting underscore to not use `_()`
direct function calls fixes the issue.

Closes #233.

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -354,8 +354,8 @@
     attribute_name = get_attribute_value attribute, 'Name'
     throw new Error("Invalid attribute without name") unless attribute_name?
     attribute_values = attribute.getElementsByTagNameNS(XMLNS.SAML, 'AttributeValue')
-    assertion_attributes[attribute_name] = _(attribute_values).map (attribute_value) ->
-      attribute_value.childNodes[0]?.data or ''
+    assertion_attributes[attribute_name] = _.map(attribute_values, (attribute_value) ->
+      attribute_value.childNodes[0]?.data or '')
   assertion_attributes

 # Takes in an object containing SAML Assertion Attributes and returns an object with certain common attributes changed
@@ -382,8 +382,7 @@
     "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid": "primary_sid"
     "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname": "windows_account_name"

-  _(assertion_attributes)
-    .chain()
+  _.chain(assertion_attributes)
     .pairs()
     .filter(([k, v]) -> (claim_map[k]? and v.length > 0))
     .map(([k, v]) -> [claim_map[k], v[0]])
@@ -541,7 +540,7 @@
       @alt_private_keys = [].concat(@alt_private_keys or [])
       @alt_certs = [].concat(@alt_certs or [])

-      @shared_options = _(options).pick(
+      @shared_options = _.pick(options,
         "force_authn", "auth_context", "nameid_format", "sign_get_request", "allow_unencrypted_assertion", "audience", "notbefore_skew")

     # Returns:
@@ -563,7 +562,7 @@
           return cb ex
         delete uri.search # If you provide search and query search overrides query :/
         if options.sign_get_request
-          _(uri.query).extend sign_request(deflated.toString('base64'), @private_key, options.relay_state)
+          _.extend(uri.query, sign_request(deflated.toString('base64'), @private_key, options.relay_state))
         else
           uri.query.SAMLRequest = deflated.toString 'base64'
           uri.query.RelayState = options.relay_state if options.relay_state?


Oops!

Oops!

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -56,7 +56,7 @@
       '@xmlns:md': XMLNS.MD
       '@xmlns:ds': XMLNS.DS
       '@entityID': entity_id
-      '@validUntil': (new Date(Date.now + 1000*60*60)).toISOString()
+      '@validUntil': (new Date(Date.now() + 1000*60*60)).toISOString()
       'md:SPSSODescriptor': [
           '@protocolSupportEnumeration': 'urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol',
           { 'md:KeyDescriptor': certificate_to_keyinfo('signing', signing_certificate) },


Setup now uses a helper function to configure hostonly interfaces.

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -21,26 +21,8 @@
 	for n, c of config.network.hostonly
 		actions.push do (n, c) ->
 			(callback) ->
-				hostonly.list (err, ifs) ->
-					return callback err if err
-
-					i = ifs.narrow (previous, current) ->
-						return previous if previous and previous.Name == n
-						return current if current and current.Name == n
-
-					if not i
-						callee = arguments.callee
-
-						hostonly.create_if (err) ->
-							return callback err if err
-
-							hostonly.list callee
-					else
-						if i.IP != c.ip or s.i.NetworkMask != c.netmask
-							hostonly.configure_if n, c.ip, c.netmask, callback
-						else
-							return do callback if callback
-
+				hostonly.ensure_if n, c.ip, c.netmask, callback
+
 		if c.dhcp?
 			actions.push do (n, c) ->
 				(callback) ->


Added messages and exit functions

Added messages and exit functions

--- src/main/webapp/lib/views/PolygonDynamicView.coffee
+++ src/main/webapp/lib/views/PolygonDynamicView.coffee
@@ -11,9 +11,15 @@
         path: google.maps.SymbolPath.CIRCLE
         scale: 5
       }
+  gme = google.maps.event

   constructor: ->
     $a.map.setOptions({draggableCursor:'crosshair'})
+    gme = google.maps.event
+    gme.addDomListener(window, 'keydown', (e) => @exitPolygonMode(e))
+    modeMsg = 'Polygon Mode: Click the polygon to retrieve data or '
+    modeMsg += 'press \'Esc\' to exit.'
+    $a.broker.trigger('app:display_message:info', modeMsg)
     @drawPolygon()

   manageDraw : () ->
@@ -38,6 +44,7 @@
       fillOpacity : 0.2
       editable: true
       draggable: true
+      clickable: true
     })
     gme = google.maps.event
     gme.addListener($a.map, 'click', (e) =>
@@ -45,8 +52,15 @@
       @manageDraw()
     )
     gme.addListener(@poly, 'click', (e) =>
-      @vertices.splice(e.vertex, 1)
-      @manageDraw()
+      if !e.vertex?
+        $a.map.setOptions({draggableCursor:'hand'})
+        @vertices = []
+        @removePolygon()
+        $a.broker.trigger('app:show_message:success', 'Retrieving Pems Data')
+        $a.broker.trigger('app:display_message:info', 'Scenario Editing Mode')
+      else
+        @vertices.splice(e.vertex, 1)
+        @manageDraw()
     )

   removePolygon : ->
@@ -100,6 +114,13 @@
   removeLine: ->
     @line?.setMap(null)

+  exitPolygonMode: (e) ->
+    if e.keyCode == 27
+      @vertices = []
+      $a.map.setOptions({draggableCursor:'hand'})
+      @removePolygon()
+      $a.broker.trigger('app:display_message:info', 'Scenario Editing Mode')
+
   _getMarkerOpts : (color) ->
     @markerIconOpts.fillColor = color
     @markerIconOpts


Removed ambiguous test case

Removed ambiguous test case

--- test/columns.coffee
+++ test/columns.coffee
@@ -27,7 +27,7 @@
         "FIELD_6":"2050-11-27"
       ]
       next()
-
+
   it 'enforced by user if array', (next) ->
     parse """
     20322051544,1979,8.8017226E7,ABC,45,2000-01-01
@@ -50,7 +50,7 @@
         "FIELD_6":"2050-11-27"
       ]
       next()
-
+
   it 'validate options column length on first line', (next) ->
     parse """
     1,2,3
@@ -70,17 +70,6 @@
     , delimiter: ';', skip_empty_lines: true, (err, data) ->
       err.message.should.eql 'Number of columns is inconsistent on line 2'
       process.nextTick next
-
-  it 'don\'t emit single error when column count is invalid on multiple lines and relax_column_count is true', (next) ->
-    parse """
-    1;2
-    1
-    3;4
-    5;6;7
-    """
-    , delimiter: ';', relax_column_count: true, skip_empty_lines: true, (err, data) ->
-      return next err if err
-      process.nextTick next

   it 'validate options column length on last line', (next) ->
     parse """
@@ -90,7 +79,7 @@
     """, columns: ["a", "b", "c", "d"], (err, data) ->
       err.message.should.eql 'Number of columns on line 3 does not match header'
       next()
-
+
   it 'handles missing column if number of columns is inconsistent', (next) ->
     parse """
     20322051544,1979,8.8017226E7,ABC,45,2000-01-01
@@ -98,7 +87,7 @@
     """, (err, data) ->
       err.message.should.eql 'Number of columns is inconsistent on line 2'
       next()
-
+
   it 'returned by user with the help of the first line', (next) ->
     parse """
     FIELD_1,FIELD_2,FIELD_3,FIELD_4,FIELD_5,FIELD_6


Detach popovers rather than hiding them, like bootstrap

--- src/scripts/helpers/handlers/popover-handler.coffee
+++ src/scripts/helpers/handlers/popover-handler.coffee
@@ -60,7 +60,7 @@
         popover = $owner.data('bs.popover')
         if popover.hoverState is 'in'
           $owner.popover('hide')
-          popover.$tip.hide() # HACK: Bootstrap is not properly hiding the popover dom element
+          popover.$tip.detach() # HACK: Bootstrap is not properly detaching the popover dom element

     @removePopover: ($el) ->
       @popovers = _.reject @popovers, ($popover) ->


Add tests for grammer matches and non-matches

--- test/grammer.coffee
+++ test/grammer.coffee
@@ -1,73 +1,192 @@
 vows = require 'vows'
 assert = require 'assert'

-{grammer:{
-  create
+{grammer} = require '../storyturtle'
+
+{ create
   die
   move
   pause
   say
   says
   go
-}} = require '../storyturtle'
+} = grammer
+
+matchesOnly = (line, topic) ->
+  assert.match line, topic.match
+
+  matchOther = null
+  matchTopic = null
+  for own key, check of grammer
+    match = check.match.exec line
+    if match
+      if check.match is topic.match
+        matchTopic = match
+      else
+        matchOther = match
+  assert.isNotNull matchTopic
+  assert.isNull matchOther
+
+  assert.equal line.trim(), matchTopic[0].trim()
+  matchTopic[1...]
+
+matchesNone = (line) ->
+  for own key, check of grammer
+    match = check.match.exec line
+    assert.isNull

 vows
   .describe('Grammer Test')
   .addBatch
     'when matching create':
-      topic: create
+      topic: create
       'it is defined': (topic)->
         assert.isObject topic
       'match is regexp': (topic)->
         assert.instanceOf topic.match, RegExp
       'handle is function': (topic)->
         assert.isFunction topic.handle
+      'matches "fred is a dog at 10 15"': (topic)->
+        line = "fred is a dog at 10 15"
+        [name, type, x, y] = matchesOnly line, topic
+        assert.equal name, "fred"
+        assert.equal type, "dog"
+        assert.equal x, "10"
+        assert.equal y, "15"
+      'matches " hank   is a fish  at 30  40 "': (topic)->
+        line = " hank   is a fish  at 30  40 "
+        [name, type, x, y] = matchesOnly line, topic
+        assert.equal name, "hank"
+        assert.equal type, "fish"
+        assert.equal x, "30"
+        assert.equal y, "40"
+      'matches "harry is an octopus at 100 300"': (topic)->
+        line = "harry is an octopus at 100 300"
+        [name, type, x, y] = matchesOnly line, topic
+        assert.equal name, "harry"
+        assert.equal type, "octopus"
+        assert.equal x, "100"
+      'does not match "megan is a cat at 10 1x"': (topic)->
+        line = "megan is a cat at 10 1x"
+        matchesNone line
+      'does not match "bob is a bug"': (topic)->
+        line = "bob is a bug"
+        matchesNone line
     'when matching die':
-      topic: die
+      topic: die
       'it is defined': (topic)->
         assert.isObject topic
       'match is regexp': (topic)->
         assert.instanceOf topic.match, RegExp
       'handle is function': (topic)->
         assert.isFunction topic.handle
+      'matches "ed dies"': (topic)->
+        line = "ed dies"
+        [name] = matchesOnly line, topic
+        assert.equal name, "ed"
+      'matches " wanda  dies "': (topic)->
+        line = " wanda  dies "
+        [name] = matchesOnly line, topic
+        assert.equal name, "wanda"
+      'does not match " willy die "': (topic)->
+        line = " willy die "
+        matchesNone line
     'when matching move':
-      topic: move
+      topic: move
       'it is defined': (topic)->
         assert.isObject topic
       'match is regexp': (topic)->
         assert.instanceOf topic.match, RegExp
       'handle is function': (topic)->
         assert.isFunction topic.handle
+      'matches "sally moves to 30 20"': (topic)->
+        line = "sally moves to 30 20"
+        [name, x, y] = matchesOnly line, topic
+        assert.equal name, "sally"
+        assert.equal x, "30"
+        assert.equal y, "20"
+      'matches "  fran    moves to 33   78  "': (topic)->
+        line = "  fran    moves to 33   78  "
+        [name, x, y] = matchesOnly line, topic
+        assert.equal name, "fran"
+        assert.equal x, "33"
+        assert.equal y, "78"
+      'does not match "luanne moves to 3z 10"': (topic)->
+        line = "luanne moves to 3z 10"
+        matchesNone line
+      'does not match " hank moves "': (topic)->
+        line = " hank moves "
+        matchesNone line
     'when matching pause':
-      topic: pause
+      topic: pause
       'it is defined': (topic)->
         assert.isObject topic
       'match is regexp': (topic)->
         assert.instanceOf topic.match, RegExp
       'handle is function': (topic)->
         assert.isFunction topic.handle
+      'matches "pause 20"': (topic)->
+        line = "pause 20"
+        [time] = matchesOnly line, topic
+        assert.equal time, "20"
+      'matches " pause  30 "': (topic)->
+        line = " pause  30 "
+        [time] = matchesOnly line, topic
+        assert.equal time, "30"
+      'does not match "pause f10"': (topic)->
+        matchesNone "pause f10"
     'when matching say':
-      topic: say
+      topic: say
       'it is defined': (topic)->
         assert.isObject topic
       'match is regexp': (topic)->
         assert.instanceOf topic.match, RegExp
       'handle is function': (topic)->
         assert.isFunction topic.handle
+      'matches "say hello world"': (topic)->
+        line = "say hello world"
+        [text] = matchesOnly line, topic
+        assert.equal "hello world", text
+      'matches " say   this is awesome "': (topic)->
+        line = " say   this is awesome "
+        [text] = matchesOnly line, topic
+        assert.equal "this is awesome ", text
+      'does not match "say"': (topic)->
+        matchesNone "say"
     'when matching says':
-      topic: says
+      topic: says
       'it is defined': (topic)->
         assert.isObject topic
       'match is regexp': (topic)->
         assert.instanceOf topic.match, RegExp
       'handle is function': (topic)->
         assert.isFunction topic.handle
+      'matches "pablo says awesome possum"': (topic)->
+        line = "pablo says awesome possum"
+        [name, text] = matchesOnly line, topic
+        assert.equal name, "pablo"
+        assert.equal text, "awesome possum"
+      'matches "  frank   says   cool beans"': (topic)->
+        line = "  frank   says   cool beans"
+        [name, text] = matchesOnly line, topic
+        assert.equal name, "frank"
+        assert.equal text, "cool beans"
+      'does not match "hope says"': (topic)->
+        matchesNone "hope says"
     'when matching go':
-      topic: go
+      topic: go
       'it is defined': (topic)->
         assert.isObject topic
       'match is regexp': (topic)->
         assert.instanceOf topic.match, RegExp
       'handle is function': (topic)->
         assert.isFunction topic.handle
+      'matches ""': (topic)->
+        matchesOnly "", topic
+      'matches "  "': (topic)->
+        matchesOnly "  ", topic
+      'matches "\\t \\t"': (topic)->
+        matchesOnly "\t \t", topic
+      'does not match "go"': (topic)->
+        matchesNone "go"
   .export(module)


Added mention of .js support into comments, along with a description of its behavior handling .js.  Also added info about what file types each of the *occo ports handles.

--- src/docco.coffee
+++ src/docco.coffee
@@ -1,6 +1,6 @@
 # **Docco** is a quick-and-dirty, hundred-line-long, literate-programming-style
-# documentation generator. It produces HTML that displays your comments
-# alongside your code. Comments are passed through
+# documentation generator for Javascript or CoffeeScript. It produces HTML
+# that displays your comments alongside your code. Comments are passed through
 # [Markdown](http://daringfireball.net/projects/markdown/syntax), and code is
 # passed through [Pygments](http://pygments.org/) syntax highlighting.
 # This page is the result of running Docco against its own source file.
@@ -8,6 +8,9 @@
 # If you install Docco, you can run it from the command-line:
 #
 #     docco src/*.coffee
+# or
+#
+#     docco src/*.js
 #
 # ...will generate linked HTML documentation for the named source files, saving
 # it into a `docs` folder.
@@ -22,13 +25,19 @@
 #
 #     sudo npm install docco
 #
+# When used to process JavaScript files, docco will only process end of line (`//`)
+# style comments, and only when they appear on a line by themselves.  Block comments
+# (`/* ... */`) and end of line comments on the same line as code (`var x = 1 // like this`)
+# are ignored by docco and will appear in your code.
+#
 # If **Node.js** doesn't run on your platform, or you'd prefer a more convenient
 # package, get [Rocco](http://rtomayko.github.com/rocco/), the Ruby port that's
-# available as a gem. If you're writing shell scripts, try
-# [Shocco](http://rtomayko.github.com/shocco/), a port for the **POSIX shell**.
+# available as a gem and processes `.rb` files. If you're writing shell scripts, try
+# [Shocco](http://rtomayko.github.com/shocco/), a port for the **POSIX shell** (`.sh`).
 # Both are by [Ryan Tomayko](http://github.com/rtomayko). If Python's more
 # your speed, take a look at [Nick Fitzgerald](http://github.com/fitzgen)'s
-# [Pycco](http://fitzgen.github.com/pycco/).
+# [Pycco](http://fitzgen.github.com/pycco/) which processes `.coffee`, `.js`, `.pl`,
+# `.sql`, `.c`, `.rb`, `.py`, `.scm`, `.lua`, and `.erl`, and can handle multi-line comments.

 #### Main Documentation Generation Functions


set just_listening

set just_listening

--- src/ector.coffee
+++ src/ector.coffee
@@ -41,7 +41,7 @@
     quiet = true

   robot.respond /just listen/i, (msg) ->
-    quiet = true
+    just_listening = true
     msg.reply "Now I'm just listening."

   robot.respond /speak/i, (msg) ->


Unignore manifest loader errors

Unignore manifest loader errors

--- src/lib/nodejs/ComponentLoader.coffee
+++ src/lib/nodejs/ComponentLoader.coffee
@@ -94,6 +94,7 @@

     @components = {}
     manifest.list.list @baseDir, manifestOptions, (err, modules) =>
+      return callback err if err
       @readModules modules, (err) =>
         @processing = false
         return callback err if err


Change adjustFontSizeTo function to fontSizeToPX and add fontSizeToMS

The later function is a convenience function used
 to set fontSize to particular modular scale.

--- src/index.coffee
+++ src/index.coffee
@@ -33,10 +33,14 @@
   vr = VerticalRhythm(options)

   return {
+    options: options
     GoogleFont: require('./components/GoogleFont')(options)
     TypographyStyle: require('./components/TypographyStyle')(vr, options)
     rhythm: vr.rhythm
-    adjustFontSizeTo: vr.adjustFontSizeTo
     styles: require('./utils/createStyles')(vr, options)
-    options: options
+    fontSizeToPx: vr.adjustFontSizeTo
+    fontSizeToMS: (scaler) ->
+      baseFont = options.baseFontSize.slice(0, -2)
+      newFontSize = ms(scaler, options.modularScale[0]) * baseFont + "px"
+      vr.adjustFontSizeTo(newFontSize)
   }


don't block all portal if serviceworker ready hang (older chrome)

--- src/index.coffee
+++ src/index.coffee
@@ -4,7 +4,8 @@

 RPCClient = require './rpc_client'

-DEFAULT_HANDSHAKE_TIMEOUT_MS = 10 * 1000 # 10 seconds
+DEFAULT_HANDSHAKE_TIMEOUT_MS = 10000 # 10 seconds
+SW_CONNECT_TIMEOUT_MS = 3000 # 3s

 selfWindow = if window? then window else self

@@ -43,24 +44,28 @@
     })

     useSw ?= navigator.serviceWorker and window.location.protocol isnt 'http:'
-
     if useSw
       # only use service workers if current page has one
-      @ready = navigator.serviceWorker.ready
-      .catch -> null
-      .then (registration) =>
-        worker = registration?.active
-        if worker
-          @sw = new RPCClient({
-            timeout: timeout
-            postMessage: (msg, origin) =>
-              swMessageChannel = new MessageChannel()
-              swMessageChannel?.port1.onmessage = (e) =>
-                @onMessage e, {isServiceWorker: true}
-              worker.postMessage(
-                msg, [swMessageChannel.port2]
-              )
-          })
+      @ready = new Promise (resolve, reject) ->
+        readyTimeout = setTimeout resolve, SW_CONNECT_TIMEOUT_MS
+
+        navigator.serviceWorker.ready
+        .catch -> null
+        .then (registration) =>
+          worker = registration?.active
+          if worker
+            @sw = new RPCClient({
+              timeout: timeout
+              postMessage: (msg, origin) =>
+                swMessageChannel = new MessageChannel()
+                swMessageChannel?.port1.onmessage = (e) =>
+                  @onMessage e, {isServiceWorker: true}
+                worker.postMessage(
+                  msg, [swMessageChannel.port2]
+                )
+            })
+          clearTimeout readyTimeout
+          resolve()
     else
       @ready = Promise.resolve true

@@ -99,7 +104,7 @@
       clearInterval @iabInterval

   replyInAppBrowserWindow: (data) =>
-    escapedData = data.replace(/'/g, "\\'")
+    escapedData = data.replace(/'/g, "\'")
     @iabWindow.executeScript {
       code: "
       if(window._portalOnMessage)


+ inject new languages

+ inject new languages

--- src/index.coffee
+++ src/index.coffee
@@ -1,3 +1,5 @@
+require './language'
+
 path        = require 'path'
 appRoot     = require 'app-root-path'
 loadConfig  = require './load-config'


rm requireAll

rm requireAll

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -1,4 +1,4 @@
-{LaterList, requireAll} = require('./dependencies')
+{LaterList} = require('./dependencies')

 {Flood, Relay} = LaterList

@@ -24,9 +24,5 @@
     Ctor[fnName] = ->
       throw new Error("#{Ctor.name}.prototype must implement #{fnName}.")

-filter = /(.+)\.coffee$/
-
-requireAll = (dirname) -> requireAll({dirname, filter})
-
 module.exports = { toFlood, reduceQueries, accumQuery , ctorMustImplement
-                 , protoMustImplement, requireAll }
+                 , protoMustImplement }


Added verbose messages

Added verbose messages

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -129,10 +129,18 @@
   @resetSchemas: (model_types, options, callback) ->
     [options, callback] = [{}, options] if arguments.length is 2

+    count = 0
     queue = new Queue()
     for model_type in model_types
-      do (model_type) -> queue.defer (callback) -> model_type.resetSchema(options, callback)
-    queue.await callback
+      do (model_type) -> queue.defer (callback) -> model_type.resetSchema options, (err) ->
+        if err
+          console.log "Error when dropping schema for #{model_type.model_name}. #{err}" if options.verbose
+        else
+          count++
+        callback()
+    queue.await (err) ->
+      console.log "#{count} schemas dropped." if options.verbose
+      callback(err)

   ##############################
   # Batch


Convert utils.coffee From JSDoc To TomDoc

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -1,15 +1,9 @@
 module.exports =

-  ###
-   * Returns a unique version of the array using ES6's Set
-   * implementation
-   *
-   * Damn, that shit is _fast_:
-   * http://jsperf.com/array-unique2/15
-   *
-   * @param  {Array} arr
-   * @return {Array}
-  ###
+  # Internal: De-duplicate an array
+  #
+  # arr - The {Array} to de-depulicate
+  # Returns {Array} that has no duplicate elements
   unique: (arr) ->
     out = []
     seen = new Set


Always URL decode javascript: URLs.

Always URL decode javascript: URLs.

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -38,19 +38,6 @@
   hasFullUrlPrefix: do ->
     urlPrefix = new RegExp "^[a-z]{3,}://."
     (url) -> urlPrefix.test url
-
-  # Apply heuristics to test whether a string (such as a "javascript:" URI) has been URL encoded.
-  isUrlEncoded: (string) ->
-    # If it doesn't contain "%", it's not URL encoded.
-    return false if -1 == string.indexOf "%"
-    # If it contains certain non-URL characters, then it's not URL encoded.
-    for char in [ " ", "{", "}", ";", '"', "'" ]
-      return false if 0 <= string.indexOf char
-    # If it contains any of these non-ascii characters, then it's not URL encoded.
-    return false if /[\u0000-\u001f\u00ff-\uffff]/.test string
-    # If every occurence of "%" is followed by two hexadecimal digits, then it's URL encoded (otherwise, it is
-    # not).
-    string.split(/%/).length == string.split(/%[0-9a-f][0-9a-f]/i).length

   # Completes a partial URL (without scheme)
   createFullUrl: (partialUrl) ->
@@ -124,8 +111,10 @@
     if Utils.hasChromePrefix string
       string
     else if Utils.hasJavascriptPrefix string
-      # In some workflows, Chrome URL encodes bookmarklets.  We URL decode them, if necessary.  See #1611.
-      if Utils.isUrlEncoded string then decodeURI string else string
+      # We blindly URL decode javascript: URLs.  That's what Chrome does when they're clicked, or entered into
+      # the omnibox.  However, Chrome does not URL decode such URLs in chrome.tabs.update.
+      # This is arguably a Chrome bug.  See https://code.google.com/p/chromium/issues/detail?id=483000.
+      decodeURI string
     else if Utils.isUrl string
       Utils.createFullUrl string
     else


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/number_extensions.coffee
+++ test/number_extensions.coffee
@@ -106,7 +106,7 @@
   equals 0.5.rotations, Math.TAU / 2

 test "#turns", ->
-  equals 1.rotation, Math.TAU
+  equals 1.turn, Math.TAU
   equals 0.5.rotations, Math.TAU / 2

 module undefined


ちょっとこれテスト方法どうしよう

ちょっとこれテスト方法どうしよう

--- test/spec/KazitoriSpec.coffee
+++ test/spec/KazitoriSpec.coffee
@@ -473,12 +473,54 @@
       router.change('/child/1')
       expect(childController.beforeAny).toHaveBeenCalled()

+
   describe "dynamic nest", ->
-    it 'should appended router from class name', ->
+
+    it 'should append router from constructor', ->
       spyOn(childController, 'index')
       router.appendRouter ChildAppend, '/appender'
       router.change('/appender')
       expect(childController.index).toHaveBeenCalled()
+
+    it 'should append router from instance', ->
+      spyOn(childController, 'index')
+      child = new ChildAppend({'isAutoStart': false})
+      router.appendRouter child, '/appender'
+      router.change('/appender')
+      expect(childController.index).toHaveBeenCalled()
+
+    notFoundHandler = jasmine.createSpy('NOT_FOUND Event')
+
+    it 'should remove router from constructor', ->
+      spyOn(childController, 'index')
+      router.appendRouter ChildAppend
+      router.change('/appender')
+      expect(childController.index).toHaveBeenCalled()
+
+      #jasmine を置き去りにした…！
+      # router.addEventListener KazitoriEvent.NOT_FOUND, notFoundHandler
+      # router.removeRouter ChildAppend
+      # router.change('/appender')
+
+      # expect(notFoundHandler).toHaveBeenCalled()
+      # expect(notFoundHandler.calls.length).toEqual(1)
+
+    it 'shuold remove router from instance', ->
+      spyOn(childController, 'index')
+      child = new ChildAppend({'isAutoStart': false})
+      router.appendRouter child
+      router.change('/appender')
+      expect(childController.index).toHaveBeenCalled()
+
+
+      router.addEventListener KazitoriEvent.NOT_FOUND, notFoundHandler
+      router.removeRouter childController
+      router.change('/appender')
+
+      # expect(notFoundHandler).toHaveBeenCalled()
+      # expect(notFoundHandler.calls.length).toEqual(1)
+
+


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -16,7 +16,7 @@
   equals "Math.TAU".constantize(), Math.TAU, "namespaced constants work too"

 test "#extension", ->
-  equials
+  equals "", ""

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"


Replace "’" with "'" when generating meme text.

--- src/memes.coffee
+++ src/memes.coffee
@@ -106,7 +106,7 @@

   robot.respond /meme me (\w+) ([\"\“\”][^\"\“\”]+[\"\“\”]) ([\"\“\”][^\"\“\”]+[\"\“\”])/i, (msg) ->
     meme = if checkCode(msg.match[1].toLowerCase(), memes) then msg.match[1].toLowerCase() else 'doge'
-    top    = msg.match[2].replace(/"|“|”/g, '').trim().replace(/\s+/g, '-')
-    bottom = msg.match[3].replace(/"|“|”/g, '').trim().replace(/\s+/g, '-')
+    top    = msg.match[2].replace(/"|“|”/g, '').trim().replace(/\s+/g, '-').replace(/\’/g, '\'')
+    bottom = msg.match[3].replace(/"|“|”/g, '').trim().replace(/\s+/g, '-').replace(/\’/g, '\'')

     msg.send "http://memegen.link/#{meme}/#{escape(top)}/#{escape(bottom)}.jpg"


slopes added

slopes added

--- src/neige.coffee
+++ src/neige.coffee
@@ -28,14 +28,16 @@
       intro: (name) -> '*** Bulletin pour ' + name + ' ***',
       state: (state) -> 'Enneigement bas - haut : ' + state,
       fall: (snowFall24h) -> 'Elle a tombé : ' + snowFall24h + ' de neige hier',
-      lifts: (lifts) -> 'Remontées: ' + lifts
+      lifts: (lifts) -> 'Remontées: ' + lifts,
+      slopes: (slopes) -> 'Pistes: ' + slopes
     },
     'snow': {
       url: 'http://www.onthesnow.com/united-states/skireport.html',
       intro: (name) -> '*** Snow report for ' + name + ' ***',
       state: (state) -> 'Snow report down - up : ' + state,
       fall: (snowFall24h) -> 'Snowfall !! : ' + snowFall24h + ' of snow yesterday',
-      lifts: (lifts) -> 'Lifts: ' + lifts
+      lifts: (lifts) -> 'Lifts: ' + lifts,
+      slopes: (slopes) -> 'Slopes: ' + slopes
     }
 }

@@ -91,6 +93,7 @@
         return $(this).text().match(/\d+\/\d+/)

      messages.push lang.lifts($(liftsAndSlopes[0]).text())
+     messages.push lang.slopes($(liftsAndSlopes[1]).text())

      delay = (s, i) ->
        setTimeout () ->


Shorter property names for ArrowType

Shorter property names for ArrowType

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -351,9 +351,9 @@
   children: ['type']

 exports.ArrowType = class ArrowType extends Base
-  constructor: (@arguments, @return) ->
-
-  children: ['arguments', 'return']
+  constructor: (@params, @ret) ->
+
+  children: ['params', 'ret']

 exports.StructType = class StructType extends Base
   constructor: (@fields) ->


Add support for arrow functions

Add support for arrow functions

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1368,14 +1368,6 @@
     if @bound and o.scope.method?.bound
       @context = o.scope.method.context

-    # Handle bound functions early.
-    if @bound and not @context
-      @context = '_this'
-      wrapper = new Code [new Param new Literal @context], new Block [this]
-      boundfunc = new Call(wrapper, [new Literal 'this'])
-      boundfunc.updateLocationDataIfMissing @locationData
-      return boundfunc.compileNode(o)
-
     o.scope         = del(o, 'classScope') or @makeScope o.scope
     o.scope.shared  = del(o, 'sharedScope')
     o.indent        += TAB
@@ -1405,19 +1397,38 @@
     @eachParamName (name, node) ->
       node.error "multiple parameters named #{name}" if name in uniqs
       uniqs.push name
-    @body.makeReturn() unless wasEmpty or @noReturn
-    code = 'function'
-    code += '*' if @isGenerator
-    code += ' ' + @name if @ctor
-    code += '('
+    @body.makeReturn() unless wasEmpty or @noReturn or (@bound and @body.expressions.length == 1)
+    if @bound
+      code = '('
+    else
+      code = 'function'
+      code += '*' if @isGenerator
+      code += ' ' + @name if @ctor
+      code += '('
     answer = [@makeCode(code)]
     for p, i in params
       if i then answer.push @makeCode ", "
+
       answer.push p...
-    answer.push @makeCode ') {'
-    answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
-    answer.push @makeCode '}'
-
+    if @bound
+      answer.push @makeCode ') =>'
+
+      answer.push @makeCode " {\n" if @body.expressions.length != 1
+
+      body = @body.compileWithDeclarations(o)
+      if @body.expressions.length == 1
+        # Remove not needed identation and change into a single space
+        body.shift()
+        body.unshift(@makeCode ' ')
+        # remove semi colon
+        body.pop()
+
+      answer = answer.concat(body)
+      answer.push @makeCode "\n#{@tab}" if @body.expressions.length != 1
+    else
+      answer.push @makeCode ') {'
+      answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
+      answer.push @makeCode '}'
     return [@makeCode(@tab), answer...] if @ctor
     if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer


fix(quoted-text): Guard call to children[0] Sentry 33642

--- src/services/quoted-html-transformer.coffee
+++ src/services/quoted-html-transformer.coffee
@@ -52,6 +52,8 @@

       @removeTrailingBr(doc)
       DOMUtils.Mutating.removeElements(quoteStringDetector(doc))
+
+      return "" unless doc.children[0]
       return doc.children[0].innerHTML

   # Finds any trailing BR tags and removes them in place
@@ -88,6 +90,9 @@
       text = "HTML Parser Error: #{error.toString()}"
       doc = domParser.parseFromString(text, "text/html")
       NylasEnv.reportError(error)
+
+    # As far as we can tell, when this succeeds, doc /always/ has at least
+    # one child: an  node.
     return doc

   _wholeBodyIsQuote: (doc, quoteElements) ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/point.coffee
+++ src/point.coffee
@@ -44,7 +44,7 @@

   Point:: =
     ###*
-    Constrain the magnitude
+    Constrain the magnitude of

     @name clamp
     @methodOf Point#


Remove route function on Robot

Remove route function on Robot

--- src/robot.coffee
+++ src/robot.coffee
@@ -50,15 +50,6 @@

     @documentation = {}

-  # Public: Specify a router and callback to register as Connect middleware.
-  #
-  # route    - A String of the route to match.
-  # callback - A Function that is called when the route is requested.
-  #
-  # Returns nothing.
-  route: (route, callback) ->
-    @router.get route, callback
-
   # Public: Adds a Listener that attempts to match incoming messages based on
   # a Regex.
   #


Make sure the undefined user has the correct room set before returning a user object.

--- src/robot.coffee
+++ src/robot.coffee
@@ -276,6 +276,11 @@
     unless user
       user = new User id, options
       @brain.data.users[id] = user
+
+    if options.room and user.room isnt options.room
+      user = new User id, options
+      @brain.data.users[id] = user
+
     user

   # Public: Get a User object given a name.


teamwork: fix error handling in `TeamworkImporter::downloadZip`

--- client/Teamwork/Views/teamworkimporter.coffee
+++ client/Teamwork/Views/teamworkimporter.coffee
@@ -54,8 +54,7 @@
     @notify "Downloading zip file...", "", 25000
     commands = commands.join(" && ")
     @vmController.run commands, (err, res) =>
-      err = err or res.stderr
-      return @handleError err  if err or res.exitStatus > 0
+      return @handleError err or res.stderr  if err or res.exitStatus > 0

       FSHelper.glob "#{@tempPath}/*", @vmName, (err, folders) =>
         return @handleError err  if err


Disable server-stack cog button for now

Disable server-stack cog button for now

--- client/app/MainApp/sidebar/sidebarview.coffee
+++ client/app/MainApp/sidebar/sidebarview.coffee
@@ -67,12 +67,12 @@
       cssClass     : "server-pin-button"
       iconOnly     : yes
       iconClass    : "cog"
-      callback     : =>
-        $('body').addClass 'server-stack'
-        @putOverlay
-          animated    : yes
-          isRemovable : no
-          color       : 'rgba(0,0,0,.3)'
+      # callback     : =>
+      #   $('body').addClass 'server-stack'
+      #   @putOverlay
+      #     animated    : yes
+      #     isRemovable : no
+      #     color       : 'rgba(0,0,0,.3)'

     @finderBottomControlPin = new KDToggleButton
       cssClass     : "finder-bottom-pin"


Fix bug causing error on user edit

Fix bug causing error on user edit

- Fixed a bug causing an error when editing a user as a non-admin with sign up disabled.
- Improved permission checks.
- Reformatting

--- src/users.coffee
+++ src/users.coffee
@@ -8,8 +8,6 @@
     isAdmin = AccountsUtil.isAdmin(@userId)
     config = AccountsUi.config()
     signUpAllowed = config.signUp.enabled
-    unless isAdmin || signUpAllowed
-      throw new Meteor.Error(403, 'SignUp not permitted.')

     modifier = Objects.flattenProperties(modifier)
     username = modifier.username
@@ -40,22 +38,25 @@
     @unblock()

     existingUser = Meteor.users.findOne(selector)
-    if existingUser? && !options.allowUpdate
+
+    if existingUser? and !options.allowUpdate
       throw new Meteor.Error(500, 'User already exists.')
-    else if !isAdmin && existingUser? && existingUser._id != @userId
+    else if !isAdmin and existingUser? and existingUser._id != @userId
       throw new Meteor.Error(403, 'Not authorized to update other users.')
-    unless existingUser
-      modifier['profile.signUp.date'] = new Date()
+    else if !isAdmin and !existingUser? and !signUpAllowed
+      throw new Meteor.Error(403, 'SignUp not permitted.')
+
+    unless existingUser then modifier['profile.signUp.date'] = new Date()

     enabled = modifier.enabled
-    if enabled? && !isAdmin
+    if enabled? and !isAdmin
       throw new Meteor.Error(403, 'Only admins can enable/disable users.')
     else if existingUser? and AccountsUtil.isAdmin(existingUser) and enabled?
       throw new Meteor.Error(403, 'Admins cannot be enabled/disabled')
     unless existingUser
       # For users without the `enabled` field, enable them if we don't require sign-up approval
       # or the admin user is creating the new user.
-      enabled ?= config.signUp.requireApproval == false || isAdmin
+      enabled ?= config.signUp.requireApproval == false or isAdmin
       modifier.enabled = enabled

     Meteor.users.upsert selector, $set: modifier
@@ -83,7 +84,7 @@
         html: '
A new user has signed up:
' + userTable

       # Send the user a new email to verify their email address.
-      if email? && email.verified != true
+      if email? and email.verified != true
         # If no password is set then the enrollment link will create a password for the user.
         # If the password is set, the user only needs to verify their email address.
         if password?
@@ -95,13 +96,13 @@

         Logger.info('Sent sign-up email to new user')

-    if existingUser? && existingUser.enabled != enabled
+    if existingUser? and existingUser.enabled != enabled
       action = if enabled then 'enabled' else 'disabled'
       Logger.info('User ' + action, user._id)
       AccountsUi.sendEmailToAdmin
         subject: 'User ' + Strings.toTitleCase(action)
         html: '
User has been ' + action + '
' + userTable
-      if enabled && !user.profile.activation?.date?
+      if enabled and !user.profile.activation?.date?
         Meteor.users.upsert selector, $set:
           'profile.activation.date': new Date()
         user = Meteor.users.findOne(selector)
@@ -131,8 +132,8 @@
   user = attempt.user
   config = AccountsUi.config()
   signUp = config.signUp
-  if (config.account.enabledByDefault && user.enabled != false) ||
-      AccountsUtil.isAdmin(user) ||
+  if (config.account.enabledByDefault and user.enabled != false) or
+      AccountsUtil.isAdmin(user) or
       user.enabled == true
     return true
   else


#93 - cosmetic

#93 - cosmetic

null is not required

--- src/utils.coffee
+++ src/utils.coffee
@@ -9,7 +9,6 @@
 try
     rbytes = require('rbytes')
 catch x
-    null

 exports.array_intersection = array_intersection = (arr_a, arr_b) ->
     r = []
@@ -34,7 +33,6 @@
         if array_intersection(origins, list_of_origins).length > 0
             return true
     catch x
-        null
     return false

 exports.escape_selected = (str, chars) ->


Fix bug of using mixpanel module as the mixpanel client

--- src/utils.coffee
+++ src/utils.coffee
@@ -11,7 +11,7 @@
 		obj = JSON.parse data
 		return obj.version

-mixpanel.init(config.mixpanelToken)
+mixpanelClient = mixpanel.init(config.mixpanelToken)

 exports.mixpanelProperties = mixpanelProperties =
 	username: require('/boot/config.json').username
@@ -20,4 +20,4 @@
 	# Mutation is bad, and it should feel bad
 	properties = _.assign(_.cloneDeep(properties), mixpanelProperties)

-	mixpanel.track(event, properties)
+	mixpanelClient.track(event, properties)


sort by id

sort by id

--- app/assets/javascripts/models/repository.js.coffee
+++ app/assets/javascripts/models/repository.js.coffee
@@ -10,4 +10,4 @@
     url: "/rest/repos"

     comparator: (repo) ->
-      repo.name
+      repo.id


Don't collide a player against themselves on a turn

--- src/walls.coffee
+++ src/walls.coffee
@@ -60,8 +60,12 @@
         orientation_to_search = VERTICAL
       else
         orientation_to_search = HORIZONTAL
+
       walls_to_search = @walls[orientation_to_search]
-      for wall in (w for w in walls_to_search when w.player != segment.player)
+
+      for wall in walls_to_search
+        console.log "Start: ", wall.endpoint
+        continue if wall.endpoint.x == segment.player.lastPos().x and wall.endpoint.y == segment.player.lastPos().y
         intersect = segment.intersection_with(wall)
         collisions.push(intersect) if intersect? and intersect != false
       collisions


remove plus step in tour

remove plus step in tour

--- app/assets/javascripts/tour/home-tour-fr.js.coffee
+++ app/assets/javascripts/tour/home-tour-fr.js.coffee
@@ -5,7 +5,6 @@
     onStart: -> $start.addClass "disabled", true
     onEnd: -> $start.removeClass "disabled", true
     backdrop: true
-#    debug: true
   )

   tour.addSteps [
@@ -28,20 +27,12 @@
       content: "Cliquez ici pour paramétrer votre compte :
 - renseigner vos données personnelles
 - ajouter la photo de votre profil
 - créer vos projets artistiques"
       fixed: true

-#campaigns-nav
-
     ,
       element: "#search-nav"
       placement: "right"
       title: "Recherche"
       content: "Atteignez en un clin d’œil à une personne ou une structure."
       fixed: true
-    ,
-      element: "#plusmenu-trigger"
-      placement: "left"
-      title: "Le bouton +"
-      content: "Le bouton plus vous permet à tout moment de créer une nouvelle personne, structure ou tâche"
-      fixed: true
     ,

       element: "#recent-nav"
@@ -50,7 +41,6 @@
       content: "Ici une liste des fiches consultées récemment."
       fixed: true
       onShown: (tour) -> clickerShow()
-#      onNext: (tour) -> clickerHide()
     ,
       element: "#opportunities-nav"
       placement: "right"
@@ -73,7 +63,7 @@
       content: "Easy is better, right? Easy like Bootstrap."
     ,
       title: "Ce tour est terminé"
-      content: "Un tour sera disponible pour la plupart des pages."
+      content: "Vous êtes maintenant prêt à utiliser Edgar ! Alors, on dit quoi ?"
       orphan: true

     ]
@@ -88,4 +78,4 @@
     tour.restart()
     $(".alert").alert "close"

-  $("html").smoothScroll()+  $("html").smoothScroll()


Redundant `setMachineUser` method call for permanent user

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -834,9 +834,11 @@

     @removeWorkspaceSnapshot()

-    @setMachineUser [nick()], no, (err) =>
-      throwError err  if err
-      @broadcastMessage type: 'ParticipantWantsToLeave'
+    unless @mountedMachine.isPermanent()
+      @setMachineUser [nick()], no, (err) =>
+        throwError err  if err
+
+    @broadcastMessage type: 'ParticipantWantsToLeave'

     callback()


collaboration: clarify stop process

collaboration: clarify stop process

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -711,28 +711,23 @@

     return callback msg : 'no social channel'  unless @socialChannel

-    {message} = kd.singletons.socialapi
-    nickname  = nick()
-
     if @amIHost
-      @broadcastMessages.push origin: nickname, type: 'SessionEnded'
-
-    options = { title: @getRealtimeFileName() }
-    @rtm.deleteFile options, (err) =>
+      @broadcastMessages.push origin: nick(), type: 'SessionEnded'
+
+    title = @getRealtimeFileName()
+    realtimeHelpers.deleteCollaborationFile @rtm, title, (err) =>
       return throwError err  if err
+      @setMachineSharingStatus off, (err) =>
+        return callback err  if err
+        @deletePrivateMessage (err) =>
+          return callback err  if err
+          @cleanupCollaboration { reinit: yes }
+          callback()

       @statusBar.emit 'CollaborationEnded'
       @stopChatSession()
       @modal?.destroy()

-      if @amIHost
-        @setMachineSharingStatus off, (err) =>
-          return callback err  if err
-          @deletePrivateMessage (err) =>
-            return callback err  if err
-            @cleanupCollaboration { reinit: yes }
-      else
-        callback null

     @mySnapshot.clear()


Fix Welcome Beta text, use overlay for beta text dialog

--- client/landing/app/CommonViews/headers.coffee
+++ client/landing/app/CommonViews/headers.coffee
@@ -54,6 +54,7 @@
             cssClass    : "what-you-should-know-modal"
             height      : "auto"
             width       : 500
+            overlay     : yes
             content     : response
             buttons     :
               Close     :


OHM-368

--- test/integration/e2eIntegrationTests.coffee
+++ test/integration/e2eIntegrationTests.coffee
@@ -1374,24 +1374,29 @@

   describe "Certificate Watcher", ->

+    afterEach (done) ->
+      server.stop ->
+        done()
+
     it "Should Listen for certificate changes and restart the server with new certificate", (done) ->
-
-      testUtils.setupTestKeystore (keystore) ->
-
-        # change the certificate file
-        fs.writeFile 'test/resources/certificate-watcher/test.crt', 'New certificate content. should trigger certificate reload', (err) ->
-          if err
-            return console.log(err)
-
-        setTimeout (->
-          # reset the certificate
-          fs.createReadStream('test/resources/certificate-watcher/testBackup.crt').pipe(fs.createWriteStream('test/resources/certificate-watcher/test.crt'));
-
-          Keystore.findOne {}, (err, keystore) ->
-            done(err) if err
-            keystore.cert.data.should.be.exactly 'New certificate content. should trigger certificate reload'
-            done()
-          return
-        ), 1000
+
+      server.start httpPort: 5001, httpsPort: 5000, ->
+        testUtils.setupTestKeystore (keystore) ->
+
+          # change the certificate file
+          fs.writeFile 'test/resources/certificate-watcher/test.crt', 'New certificate content. should trigger certificate reload', (err) ->
+            if err
+              return console.log(err)
+
+          setTimeout (->
+            # reset the certificate
+            fs.createReadStream('test/resources/certificate-watcher/testBackup.crt').pipe(fs.createWriteStream('test/resources/certificate-watcher/test.crt'));
+
+            Keystore.findOne {}, (err, keystore) ->
+              done(err) if err
+              keystore.cert.data.should.be.exactly 'New certificate content. should trigger certificate reload'
+              done()
+            return
+          ), 1500


fixing locations panel on settings panel cancel button

--- app/coffee/metamenu.coffee
+++ app/coffee/metamenu.coffee
@@ -139,7 +139,7 @@

     # setup saved locations
     $('.modifyLocationView .cancel').click( (event) ->
-      $(event.currentTarget).prev().show()
+      $(event.currentTarget).parent().prev().show()
       $(event.currentTarget).parent().hide()
     )


Fix infinite loop on trying to place pugicorn pets into level editor

--- app/lib/world/names.coffee
+++ app/lib/world/names.coffee
@@ -154,10 +154,10 @@
   'Raven Pet': ['Nevermore']
   'Cougar Pet': ['Kitty']
   'Frog Pet': ['Hypnotoad']
-  'Griffin Pet': ['']
-  'Pugicorn Pet': ['']
+  'Baby Griffin Pet': ['Griffin']
+  'Pugicorn Pet': ['Peggy']
   'Polar Bear Pet': ['Klondike']
-  'Wolf Pet': ['']
+  'Wolf Pup Pet': ['Wolf']
   'Horse': [
     # Animal
     'Abby'


Remove useless console.log's

Remove useless console.log's

--- src/public/scripts/models/game.coffee
+++ src/public/scripts/models/game.coffee
@@ -38,7 +38,6 @@

 			@node.onQuery
 				stats: ( callback, stats ) =>
-					console.log 'SOMEONE ASKING FOR OUR STATS WITH THESE STATS', stats
 					Stats.mergeStats(@world.getPlayers(), stats)
 					callback @stats

@@ -52,7 +51,7 @@
 				'player.kill': ( killerEntity ) =>
 					if killerEntity isnt @player
 						@node.broadcast('player.kill', killerEntity.id)
-					console.log 'We died. Incrementing deaths'
+
 					@player?.stats.incrementStat('deaths', 1)
 					killerEntity.die()
 				'stats.change': ( stats ) =>
@@ -77,7 +76,6 @@
 				'player.left': ( id ) =>
 					@world.removePlayer(id)
 				'player.died': ( id ) =>
-					console.log 'Player ' + id + ' died. Incrementing deaths'
 					player = @world.getPlayer(id)
 					player?.stats.incrementStat('deaths', 1)
 					player?.die()
@@ -88,12 +86,10 @@
 				'entity.die': ( id ) =>
 					@world.removeEntityByID(id)
 				'player.kill': ( killerEntityID ) =>
-					console.log 'We killed player ' + killerEntityID + '. Incrementing own kills'
 					@player.stats.incrementStat('kills', 1)
 					@node.broadcast('player.addKill', @node.id)
 					@world.removeEntityByID(killerEntityID)
 				'player.addKill': ( id ) =>
-					console.log 'We heard that player ' + id + ' killed someone. Incrementing kills'
 					player = @world.getPlayer(id)
 					player?.stats.incrementStat('kills', 1)

@@ -183,7 +179,6 @@
 		_onPlayerDied: ( interval ) =>
 			clearInterval(interval)
 			@node.broadcast('player.died', @player.id)
-			#@player.visible = false
 			@trigger('player.died')

 		# Returns the current network time.
@@ -194,9 +189,8 @@
 			return @node.time()

 		queryStats: ( ) =>
-			console.log 'REQUESTING STATS FROM NEIGHBOURS'
 			@node.queryTo('*', 2, 'stats', @stats, ( stats ) =>
-				console.log 'GOT STATS BACK', stats
-				Stats.mergeStats(@world.getPlayers(), stats)
+				if stats isnt null
+					Stats.mergeStats(@world.getPlayers(), stats)
 			)
 			return


change an error to a warning to avoid server uncaught exceptions...

--- src/smc-hub/postgres-synctable.coffee
+++ src/smc-hub/postgres-synctable.coffee
@@ -279,11 +279,11 @@

     # add and remove user from a project, maintaining our data structures (@_accounts, @_projects, @_collabs)
     _add_user_to_project: (account_id, project_id) =>
-        if not account_id? or not project_id?
+        dbg = @_dbg('_add_user_to_project')
+        if account_id?.length != 36 or project_id?.length != 36
             # nothing to do -- better than crashing the server...
-            return
-        if account_id?.length != 36 or project_id?.length != 36
-            throw Error("invalid account_id (='#{account_id}') or project_id (='#{project_id}')")
+            dbg("WARNING: invalid account_id (='#{account_id}') or project_id (='#{project_id}')")
+            return
         if @_projects[account_id]?[project_id]
             return
         @emit 'add_user_to_project', {account_id:account_id, project_id:project_id}


Logger: Clean up w3c file opening logic

Logger: Clean up w3c file opening logic

--- src/streammachine/logger/index.coffee
+++ src/streammachine/logger/index.coffee
@@ -124,9 +124,10 @@
             super(options)

             @options = options
-            @_opening = false
-
-            @queued = []
+
+            @_opening   = false
+            @_file      = null
+            @_queue = []

             process.addListener "SIGHUP", =>
                 # re-open our log file
@@ -139,24 +140,23 @@
             # unlike a normal logging endpoint, we only care about our request entries
             if level == @options.level
                 # for a valid w3c log, level should == "request", meta.
-                logline = "#{meta.ip} #{strftime(new Date(meta.time),"%F %T")} #{meta.path} 200 #{escape(meta.ua)} #{meta.bytes} #{meta.seconds}\n"
-
-                if @file && !@_opening
-                    # make sure there aren't any queued writes
-                    unless _u(@queued).isEmpty
-                        q = @queued
-                        @queued = []
-                        @file.write line for line in q
-
-                    # now write this line
-                    @file.write logline
-                    cb null, true
-
-                else
-                    @open (err) =>
-
-                    @queued.push logline
-                    cb null, true
+                logline = "#{meta.ip} #{strftime(new Date(meta.time),"%F %T")} #{meta.path} 200 #{escape(meta.ua)} #{meta.bytes} #{meta.seconds}"
+
+                @_queue.push logline
+                @_runQueue()
+
+        #----------
+
+        _runQueue:  ->
+            if @_file
+                # we're open, so do a write...
+                if @_queue.length > 0
+                    line = @_queue.shift()
+                    @_file.write line+"\n", "utf8", =>
+                        @_runQueue if @_queue.length > 0
+
+            else
+                @open (err) => @_runQueue()

         #----------

@@ -164,14 +164,19 @@
             if @_opening
                 console.log "W3C already opening... wait."
                 # we're already trying to open.  return an error so we queue the message
-                cb?(true)
-                return true
+                return false

             console.log "W3C opening log file."

-            # otherwise, open the file
-            @_opening = true
-
+            # note that we're opening, and also set a timeout to make sure
+            # we don't get stuck
+            @_opening = setTimeout =>
+                console.log "Failed to open w3c log within one second."
+                @_opening = false
+                @open(cb)
+            , 1000
+
+            # is this a new file or one that we're just re-opening?
             initFile = true
             if fs.existsSync(@options.filename)
                 # file exists...  see if there's anything in it
@@ -182,33 +187,36 @@
                     # start appending
                     initFile = false

-            @file = fs.createWriteStream @options.filename, flags:(if initFile then "w" else "r+")
-
-            @file.once "open", (err) =>
+            @_file = fs.createWriteStream @options.filename, flags:(if initFile then "w" else "r+")
+
+            @_file.once "open", (err) =>
                 console.log "w3c log open with ", err
+
+                _clear = =>
+                    console.log "w3c open complete"
+                    clearTimeout @_opening if @_opening
+                    @_opening = null
+                    cb?()
+
                 if initFile
-                    # write our initial w3c lines
-                    @file.write "#Software: StreamMachine\n#Version: 0.2.9\n#Fields: c-ip date time cs-uri-stem c-status cs(User-Agent) sc-bytes x-duration\n", "utf8", =>
-                      @_opening = false
-                      console.log "w3c open complete"
-                      cb?(false)
-
-                @flush()
+                    # write our initial w3c lines before we return
+                    @_file.write "#Software: StreamMachine\n#Version: 0.2.9\n#Fields: c-ip date time cs-uri-stem c-status cs(User-Agent) sc-bytes x-duration\n", "utf8", =>
+                        _clear()
+
+                else
+                    _clear()

         #----------

         close: (cb) ->
-            @file?.end null, null, =>
+            @_file?.end null, null, =>
               console.log "W3C log file closed."
-            @file = null
+            @_file = null

         #----------

         flush: ->
-            _u(@queued).each (l) => process.nextTick => @file.write(l)
-            @queued.length = 0
-            console.log "w3c finished flushing"
-            @file.once "drain", => @emit "flush"
+            @_runQueue()

     #----------


Fixed value_binding tests.

Fixed value_binding tests.

--- tests/batman/view/value_binding_test.coffee
+++ tests/batman/view/value_binding_test.coffee
@@ -1,6 +1,7 @@
 helpers = if typeof require is 'undefined' then window.viewHelpers else require './view_helper'

-QUnit.module 'Batman.View value bindings',
+QUnit.module 'Batman.View value bindings'
+
 asyncTest 'it should allow arbitrary attributes to be bound', 2, ->
   source = '

'
   helpers.render source,
@@ -31,19 +32,14 @@
     QUnit.start()

 asyncTest 'it should bind the input value and update the input when it changes', 2, ->
-  context = Batman
-    one: "qux"
-
-  helpers.render '<input data-bind="one" type="text" />', context, (node) ->
+  helpers.render '<input data-bind="one" type="text" />', {one: 'qux'}, (node, view) ->
     equal $(node[0]).val(), 'qux'
-    context.set('one', "bar")
+    view.set('one', "bar")
     equal $(node[0]).val(), 'bar'
     QUnit.start()

 asyncTest 'it should bind the input value but not update the window object if the input changes', 2, ->
-  context = Batman({})
-
-  helpers.render '<input data-bind="nonexistantKey" type="text" />', context, (node) ->
+  helpers.render '<input data-bind="nonexistantKey" type="text" />', {}, (node) ->
     equal node[0].value, ''
     node[0].value = 'foo'
     helpers.triggerChange(node[0])
@@ -51,9 +47,7 @@
     QUnit.start()

 asyncTest 'it should bind the input value but not update the window object if the input changes with a many segment keypath', 2, ->
-  context = Batman({})
-
-  helpers.render '<input data-bind="someKey.path" type="text" />', context, (node) ->
+  helpers.render '<input data-bind="someKey.path" type="text" />', {}, (node) ->
     equal node[0].value, ''
     node[0].value = 'foo'
     helpers.triggerChange(node[0])
@@ -61,67 +55,49 @@
       equal typeof window.someKey, 'undefined'

 asyncTest 'it should bind the input value of checkboxes and update the value when the object changes', 2, ->
-  context = Batman
-    one: true
-
-  helpers.render '<input type="checkbox" data-bind="one" />', context, (node) ->
+  helpers.render '<input type="checkbox" data-bind="one" />', {one: true}, (node, view) ->
     equal node[0].checked, true
-    context.set('one', false)
+    view.set('one', false)
     equal node[0].checked, false
     QUnit.start()

 asyncTest 'it should bind the input value of checkboxes and update the object when the value changes', 1, ->
-  context = Batman
-    one: true
-
-  helpers.render '<input type="checkbox" data-bind="one" />', context, (node) ->
+  helpers.render '<input type="checkbox" data-bind="one" />', {one: true}, (node, view) ->
     node[0].checked = false
     helpers.triggerChange(node[0])
-    equal context.get('one'), false
+    equal view.get('one'), false
     QUnit.start()

 asyncTest 'it should bind the input value and update the object when it changes', 1, ->
-  context = new Batman.Object
-    one: "qux"
-
-  helpers.render '<input data-bind="one" type="text" />', context, (node) ->
+  helpers.render '<input data-bind="one" type="text" />', {one: 'qux'}, (node, view) ->
     $(node[0]).val('bar')
     # Use DOM level 2 event dispatch, $().trigger doesn't seem to work
     helpers.triggerChange(node[0])
-    equal context.get('one'), 'bar'
+    equal view.get('one'), 'bar'
     QUnit.start()

 asyncTest 'it should bind the input value and update the object when it keyups', 1, ->
-  context = new Batman.Object
-    one: "qux"
-
-  helpers.render '<input data-bind="one" type="text" />', context, (node) ->
+  helpers.render '<input data-bind="one" type="text" />', {one: 'qux'}, (node, view) ->
     $(node[0]).val('bar')
     # Use DOM level 2 event dispatch, $().trigger doesn't seem to work
     helpers.triggerKey(node[0], 82) # 82 is r from "bar"
-    equal context.get('one'), 'bar'
+    equal view.get('one'), 'bar'
     QUnit.start()

 for type in ['text', 'search', 'tel', 'url', 'email', 'password']
   do (type) ->
     asyncTest "it should bind the input value on HTML5 input #{type} and update the object when it keyups", 1, ->
-      context = new Batman.Object
-        one: "qux"
-
-      helpers.render "<input data-bind=\"one\" type=\"#{type}\"></input>", context, (node) ->
+      helpers.render "<input data-bind=\"one\" type=\"#{type}\"></input>", {one: 'qux'}, (node, view) ->
         $(node[0]).val('bar')
         # Use DOM level 2 event dispatch, $().trigger doesn't seem to work
         helpers.triggerKey(node[0], 82) # 82 is r from "bar"
-        equal context.get('one'), 'bar'
+        equal view.get('one'), 'bar'
         QUnit.start()

 asyncTest 'it should bind the value of textareas', 2, ->
-  context = new Batman.Object
-    one: "qux"
-
-  helpers.render '<textarea data-bind="one"></textarea>', context, (node) ->
+  helpers.render '<textarea data-bind="one"></textarea>', {one: 'qux'}, (node, view) ->
     equal node.val(), 'qux'
-    context.set('one', "bar")
+    view.set('one', "bar")
     equal node.val(), 'bar'
     QUnit.start()

@@ -137,10 +113,7 @@
     QUnit.start()

 asyncTest 'it should bind the value of textareas and inputs simulatenously', ->
-  context = new Batman.Object
-    one: "qux"
-
-  helpers.render '<textarea data-bind="one"></textarea><input data-bind="one" type="text"/>', context, (node) ->
+  helpers.render '<textarea data-bind="one"></textarea><input data-bind="one" type="text"/>', {one: 'qux'}, (node) ->
     f = (v) =>
       equal $(node[0]).val(), v
       equal $(node[1]).val(), v
@@ -176,18 +149,18 @@
     [context, adapter] = getMockModel()
     ok !adapter.defaultRequestOptions.formData

-    helpers.render '<input type="file" data-bind="fileAttributes"></input>', false, context, (node) ->
+    helpers.render '<input type="file" data-bind="fileAttributes"></input>', false, context, (node, view) ->
       helpers.triggerChange(node.childNodes[0])
-      strictEqual context.fileAttributes, undefined
+      strictEqual view.fileAttributes, undefined
       QUnit.start()

   asyncTest 'it should bind the value of file type inputs with the "multiple" flag', 2, ->
     [context, adapter] = getMockModel()
     ok !adapter.defaultRequestOptions.formData

-    helpers.render '<input type="file" data-bind="fileAttributes" multiple="multiple"></input>', false, context, (node) ->
+    helpers.render '<input type="file" data-bind="fileAttributes" multiple="multiple"></input>', false, context, (node, view) ->
       helpers.triggerChange(node.childNodes[0])
-      deepEqual context.fileAttributes, []
+      deepEqual view.fileAttributes, []
       QUnit.start()

   asyncTest 'it should bind the value of file type inputs when they are proxied', 2, ->
@@ -196,27 +169,25 @@

     source = '<form data-formfor-foo="proxied"><input type="file" data-bind="foo.fileAttributes"></input></form>'

-    helpers.render source, false, {proxied: context}, (node) ->
+    helpers.render source, false, {proxied: context}, (node, view) ->
       helpers.triggerChange(node.childNodes[0].childNodes[0])
-      strictEqual context.fileAttributes, undefined
+      strictEqual view.fileAttributes, undefined
       QUnit.start()

 asyncTest 'should bind radio buttons to a value', ->
   source = '<input id="fixed" type="radio" data-bind="ad.sale_type" name="sale_type" value="fixed"/>
     <input id="free" type="radio" data-bind="ad.sale_type" name="sale_type" value="free"/>
     <input id="trade" type="radio" data-bind="ad.sale_type" name="sale_type" value="trade"/>'
-  context = Batman
-    ad: Batman
-      sale_type: 'free'
-
-  helpers.render source, context, (node) ->
+  context = ad: Batman(sale_type: 'free')
+
+  helpers.render source, context, (node, view) ->
     fixed = node[0]
     free = node[1]
     trade = node[2]

     ok (!fixed.checked and free.checked and !trade.checked)

-    context.set 'ad.sale_type', 'trade'
+    view.set('ad.sale_type', 'trade')
     ok (!fixed.checked and !free.checked and trade.checked)
     QUnit.start()

@@ -227,10 +198,7 @@
     <input id="trade" type="radio" data-bind="ad.sale_type" name="sale_type" value="trade" checked/>
   '''

-  context = Batman
-    ad: Batman()
-
-  helpers.render source, context, (node) ->
+  helpers.render source, {ad: Batman()}, (node, view) ->
     fixed = node[0]
     free = node[1]
     trade = node[2]
@@ -238,11 +206,11 @@
     ok !fixed.checked
     ok !free.checked
     ok trade.checked
-    equal context.get('ad.sale_type'), 'trade', 'checked attribute binds'
+    equal view.get('ad.sale_type'), 'trade', 'checked attribute binds'

     fixed.checked = true
     helpers.triggerChange(fixed)
-    equal context.get('ad.sale_type'), 'fixed'
+    equal view.get('ad.sale_type'), 'fixed'
     QUnit.start()

 asyncTest 'should bind to true and false values', ->
@@ -251,23 +219,20 @@
     <input type="radio" data-bind="ad.published" name="published" value="true"/>
   '''

-  context = Batman
-    ad: Batman()
-
-  helpers.render source, context, (node) ->
+  helpers.render source, {ad: Batman()}, (node, view) ->
     hidden = node[0]
     published = node[1]

     ok !published.checked
     ok hidden.checked

-    strictEqual context.get('ad.published'), false
-    context.set 'ad.published', true
+    strictEqual view.get('ad.published'), false
+    view.set 'ad.published', true
     ok published.checked
     ok !hidden.checked

     hidden.checked = true
     helpers.triggerChange hidden
-    equal context.get('ad.published'), false
-
-    QUnit.start()
+    equal view.get('ad.published'), false
+
+    QUnit.start()


Allow non-standard percentage shown in resolution.

--- views/components/settings/parts/display-config.cjsx
+++ views/components/settings/parts/display-config.cjsx
@@ -188,9 +188,10 @@
       <Col xs=4>
         <Input type="select"
          ref="webviewWidthRatio"
-         value={parseInt(@state.gameWidth / 400) * 400}
+         value={@state.gameWidth}
          onChange={@handleSetWebviewWidth.bind @, "webviewWidthRatio"}
          disabled={!@state.useFixedResolution} >
+          <option key={-1} value={@state.gameWidth} hidden>{Math.round(@state.gameWidth/8)}%</option>
           {
             i = 0
             while i < 4


CLI refactoring the source compiler and making it to not mangle with the exported class names

--- cli/source.coffee
+++ cli/source.coffee
@@ -6,6 +6,47 @@
 #
 fs   = require('fs')
 path = require('path')
+
+
+#
+# Preconverts coffe-script into javascript
+#
+# @param {String} CoffeeScript
+# @return {String} JavaScript
+#
+convert_from_coffee = (source)->
+  # hijacking the Coffee's class definitions and converting them in our classes
+  source = source.replace /(\n\s*)class\s+([^\s]+)(\s+extends\s+([^\s]+))?/g,
+    (match, start, Klass, smth, Super)->
+      if !Super then "#{start}#{Klass} = new Class"
+      else "#{start}#{Klass} = new Class #{Super},"
+
+  # replacing teh Coffee's 'super' calls with our own class calls
+  source = source.replace /([^\w$\.])super(\(|\s)(.*?)([\)\n;])/g,
+    (match, start, b1, params, b2)->
+      "#{start}this.$super(#{params});"
+
+  # building the basic scripts
+  source = require('coffee-script').compile(source, {bare: true})
+
+  # fixing coffee's void(0) hack back to `undefined`
+  source.replace /([^a-z0-9\_]+)void\s+0([^a-z0-9]+)/ig, '$1undefined$2'
+
+
+#
+# Adds function names to the constructors so that
+# they appeared correctly in the debugging console
+#
+# @param {String} original
+# @return {String} patched
+#
+add_constructor_names = (source)->
+  source.replace /([a-zA-Z0-9_$]+)(\s*=\s*new\s+Class\()((.|\n)+?constructor:\s*function\s*\()/mg,
+    (match, Klass, first, second)->
+      if second.indexOf('new Class') is -1 and second.match(/constructor:\s*function\s*\(/).length is 1
+        second = second.replace(/(constructor:\s*function)(\s*\()/, '$1 '+Klass.replace('.', '_')+'$2')
+        "#{Klass}#{first}#{second}"
+      else match.toString()

 #
 # Builds the actual source code of the current project
@@ -28,34 +69,10 @@
       .toString().replace(/($|\n)/g, '$1'+spaces) + "\n\n"

   # converting coffee into javascript if needed
-  if format is 'coffee'
-    # hijacking the Coffee's class definitions and converting them in our classes
-    source = source.replace /(\n\s*)class\s+([^\s]+)(\s+extends\s+([^\s]+))?/g,
-      (match, start, Klass, smth, Super)->
-        if !Super then "#{start}#{Klass} = new Class"
-        else "#{start}#{Klass} = new Class #{Super},"
-
-    # replacing teh Coffee's 'super' calls with our own class calls
-    source = source.replace /([^\w$\.])super(\(|\s)(.*?)([\)\n;])/g,
-      (match, start, b1, params, b2)->
-        "#{start}this.$super(#{params});"
-
-    # building the basic scripts
-    source = require('coffee-script').compile(source, {bare: true})
-
-    # fixing coffee's void(0) hack back to `undefined`
-    source = source.replace /([^a-z0-9\_]+)void\s+0([^a-z0-9]+)/ig, '$1undefined$2'
-
+  source = convert_from_coffee(source) if format is 'coffee'

   # adding the class names to the constructor functions
-  source = source.replace /([^\s]+)(\s*=\s*new\s+Class\()((.|\n)+?constructor:\s+function\s*\()/mg,
-    (match, Klass, first, second)->
-      if second.indexOf('new Class') is -1 and second.match(/constructor:\s+function\s*\(/).length is 1
-        second = second.replace(/(constructor:\s+function)(\s*\()/, '$1 '+Klass.replace('.', '_')+'$2')
-        "#{Klass}#{first}#{second}"
-      else match.toString()
-
-
+  source = add_constructor_names(source)

   # adding the package dependencies
   source = source.replace('%{version}', options.version)
@@ -119,13 +136,22 @@
   source = compile(directory)
   ugly   = require('uglify-js')
   build  = ugly.parser.parse(source)
-
-  build  = ugly.uglify.ast_mangle(build)
+  except = ['Class']
+
+  # extracting the exported class names so they didn't get mangled
+  if match = source.match(/((ext\(\s*exports\s*,)|(\n\s+exports\s*=\s*))[^;]+?;/mg)
+    for name in match[0].match(/[^a-zA-Z0-9_$][A-Z][a-zA-Z0-9_$]+/g) || []
+      except.push(name.substr(1)) if except.indexOf(name.substr(1)) is -1
+
+  build  = ugly.uglify.ast_mangle(build, except: except)
   build  = ugly.uglify.ast_squeeze(build)
   build  = ugly.uglify.gen_code(build)

   # fixing the ugly `== 'string'` fuckups back to `=== 'string'`
   build  = build.replace(/(typeof [a-z]+)==("[a-z]+")/g, '$1===$2')
+
+  # getting back the constructor names
+  build  = add_constructor_names(build, true)

   # copying the header over
   (source.match(/\/\*[\s\S]+?\*\/\s/m) || [''])[0] + build


Add stale image checking to thumbnail helper. This will do an earlier check than before when the image would be requested then checked and redirected.

--- client/app.coffee
+++ client/app.coffee
@@ -62,5 +62,10 @@
       file = 'presenter'
     when 'view-galicaster'
       file = 'screen'
-  url = "/image/#{roomId}/#{file}"
-  if timestamp then "#{url}?#{timestamp}" else url
+  room = Rooms.findOne roomId
+  image = room?.images?[file]
+  if image and image.timestamp < timestamp
+    "/images/no_image_available.png"
+  else
+    url = "/image/#{roomId}/#{file}"
+    if timestamp then "#{url}?#{timestamp}" else url


More concise coffee.

More concise coffee.

--- js/core/src/Runtime.coffee
+++ js/core/src/Runtime.coffee
@@ -1,8 +1,8 @@
-#     JEFRi Runtime.js 0.1.0
-#     (c) 2011-2012 David Souther
-#     JEFRi is freely distributable under the MIT license.
-#     For all details and documentation:
-#     http://jefri.org
+#   JEFRi Runtime.js 0.1.0
+#   (c) 2011-2012 David Souther
+#   JEFRi is freely distributable under the MIT license.
+#   For all details and documentation:
+#   http://jefri.org

 do (_=_) =>
 	root = @
@@ -28,7 +28,7 @@

 	# ### Runtime Constructor
 	root.JEFRi.Runtime = (contextUri, options, protos) ->
-		# Private variables we'll be using throughout the class.
+		# The ec reference for entity prototypes
 		ec = @

 		# Prepare a promise for completing context loading.
@@ -94,17 +94,17 @@
 				else ""

 		# Takes a "raw" context object and orders it into the internal _context
-		# storage.  Also builds constructors and prototypes for the context.
+		# storage. Also builds constructors and prototypes for the context.
 		_set_context = (context, protos) =>
 			# Save the attributes
-			_.extend ec._context.attributes, context.attributes
+			_.extend @_context.attributes, context.attributes

 			# Prepare each entity. Uses _.each to put (definition, type) in a closure.
-			_.each context.entities, (definition, type) ->
+			_.each context.entities, (definition, type) => # NO SERIOUSLY, definition MUST BE IN A CLOSURE!
 				# Keep the definition locally, modifying it directly with the ctor and prototypes.
-				ec._context.entities[type] = definition
+				@_context.entities[type] = definition
 				# Ready the instances bucket
-				ec._instances[type] = {}
+				@_instances[type] = {}

 				# Build an entity's constructor.
 				definition.Constructor = (proto) ->
@@ -141,7 +141,7 @@
 							_modified:
 								_count: 0
 						ec._modified.remove(@)
-					@
+					return @

 				#Set up the prototype for any of this entity.
 				_build_prototype(type, definition, (protos && protos[type]))
@@ -158,7 +158,7 @@

 				# Get this entity's ID.
 				id: (full) ->
-					return (full && "#{@_type()}/" || "") + @[definition.key]()
+					return (if full then "#{@_type()}/" else "") + @[definition.key]()

 				# Find the status of an entity.
 				_status: () ->
@@ -176,7 +176,7 @@
 				_persist: (transaction, callback) ->
 					deferred = _.Deferred().then(callback)
 					top = !transaction
-					transaction = top && new JEFRi.Transaction() || transaction
+					transaction = if top then new JEFRi.Transaction() else transaction
 					transaction.add(@)

 					#Call the on_persist handler
@@ -258,7 +258,7 @@
 			definition.Constructor.prototype[field] = (entity) ->
 				# Use arguments, since we might have a few things coming.
 				if arguments.length > 0
-					set = (relationship.type is "has_many") && "add" || "set"
+					set = if relationship.type is "has_many" then "add" else "set"
 					return @[field][set].apply(@, arguments)
 				else
 					return @[field].get.call(@)
@@ -271,7 +271,7 @@
 						# Lazy load
 						#if (longGet)
 							# This needs a bit of thought
-							#TODO
+							#TODO, leaving this comment in as a nag.
 						# Check if the field has ever been set
 						if !(field of @_relationships)
 							# The field hasn't been set, so we haven't ever gotten this relationship before.
@@ -341,7 +341,7 @@
 								#Add or set this to the remote entity
 								#Need to find the back relationship...
 								back_rel = ec.back_rel(@_type(), field, relationship)
-								back = ("has_many" is back_rel.type) && 'add' || 'set'
+								back = if ("has_many" is back_rel.type) then 'add' else 'set'
 								entity[back_rel.name][back].call(entity, @)

 						# Notify observers
@@ -353,17 +353,13 @@
 		if (options && options.debug)
 			# The context object was provided by the caller
 			_set_context(options.debug.context, protos)
-		else if (!@settings.contextUri)
-		else
-			_.get(@settings.contextUri, {
-				dataType: "application/json"
-			}).done(
-				(data) ->
-					if (!data) then throw { message: "Context loaded, but invalid." }
-					data = _.isString(data) && JSON.parse(data) || data
-					_set_context(data, protos)
-			)
-		@
+		else if @settings.contextUri?
+			_.get(@settings.contextUri, {dataType: "application/json"})
+			.done (data) ->
+				if (!data) then throw { message: "Context loaded, but invalid." }
+				data = if _.isString(data) then JSON.parse(data) else data
+				_set_context(data, protos)
+		return @

 	# #### Entity Array helper
 	pushResult = (entity) ->
@@ -382,8 +378,7 @@

 		# Get the definition of an entity type.
 		definition: (name) ->
-			name = (typeof name is "string") && name || name._type()
-
+			name = name._type?() || name
 			return @_context.entities[name]

 		# Find the relationship back to this entity, if it exists
@@ -404,19 +399,15 @@
 			if (@_context.entities[type])
 				_.extend @_context.entities[type].Constructor.prototype, extend.prototype

-
 		# Return the canonical memory reference of the entity.
 		intern: (entity, updateOnIntern) ->
 			updateOnIntern = !!updateOnIntern || @settings.updateOnIntern

 			if (entity.length && ! entity._type)
 				#Array-like
-				q = entity.length
-				for i in [0..q]
-					entity[i] = @intern(entity[i], updateOnIntern)
-				return entity
-
-			ret
+				entities = (@intern(ent, updateOnIntern) for ent in entity)
+				return entities
+
 			if (updateOnIntern)
 				#Merge the given entity into the stored entity.
 				ret = @_instances[entity._type()][entity.id()] || entity
@@ -467,7 +458,6 @@
 		# Prepare a new transaction
 		transaction: (spec) ->
 			spec = spec || []
-
 			return new JEFRi.Transaction(spec, @_store)

 		# Return an interned entity from the local instance matching spec.
@@ -496,7 +486,7 @@
 		# result array will have only the matching entities. If NONE matches, the
 		# result array will have one entity per spec.
 		get: (spec) ->
-			spec = _.isArray(spec) && spec || [spec]
+			spec = if _.isArray(spec) then spec else [spec]
 			results = {}
 			transaction = @transaction()
 			deferred = _.Deferred()
@@ -505,7 +495,7 @@

 			for _spec in spec
 				#Add the queries
-				_type = (_spec._type instanceof Function) && _spec._type() || _spec._type
+				_type = if (_spec._type instanceof Function) then _spec._type() else _spec._type
 				def = @definition(_type)
 				id = _spec[def.key]

@@ -517,23 +507,17 @@
 					#Otherwise, add to transaction
 					transaction.add(_spec)

-	#/*
-	#				TODO: If there are multiple specs, this will not work!
-	#				TODO: Need to figure out what a page means for multiple specs.
-	#				Page format: {on : 1, lines : 10, sort:[{'Type.field':order},{'Type.field':order}]}
-	#*/
-
 			# If transaction is not empty
 			if (transaction.entities.length > 0)
 				# Run the transaction
-				transaction.get().done((transaction) ->
+				transaction.get().done (transaction) ->
 					# Merge the result sets, adding `gotten` things to `had` things.
 					results.push(entity) for entity in transaction.entities
 					deferred.resolve(results, transaction.attributes)
-				)
 			else
 				# just resolve...
 				deferred.resolve(results, {})
+
 			return deferred.promise()

 		# Pass the spec to get, and just pop the first entity.
@@ -541,10 +525,9 @@
 			spec = (spec instanceof Array) ? spec : [spec]
 			d = _.Deferred()

-			@get(spec).then((data, meta) ->
-				_type = spec._type instanceof Function && spec._type() || spec._type
+			@get(spec).then (data, meta) ->
+				_type = if spec._type instanceof Function then spec._type() else spec._type
 				d.resolve(data[_type].pop(), meta)
-			)

 			return d.promise()

@@ -576,19 +559,4 @@

 		_save: (transaction, store) ->
 			store = store || @_store
-			return store.execute('persist', transaction).then(
-				_.bind(@expand, @)
-			)
-
-		# Returns transaction of all entities in local cache.
-		get_transaction_dump: () ->
-			transaction = @transaction()
-
-			#Add all entities to the transaction
-			# _instances is keyed by type...
-			for t, instance in @_instances
-				# ...and each key contains an object of entity instances
-				for k, entity in instance
-					transaction.add(entity)
-
-			return transaction+			return store.execute('persist', transaction).then( _.bind(@expand, @) )


selectively disable cmd-enter on tree-view

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -5,6 +5,9 @@
   # conflicts insert-mode ctrl-w and ctr-w in mini editor, so comment out by default
   # 'ctrl-w z': 'vim-mode-plus:maximize-pane'
   'cmd-enter': 'vim-mode-plus:maximize-pane'
+
+'atom-workspace .tree-view':
+  'cmd-enter': 'abort!'

 # all
 # -------------------------


:fire: I'm not prepared, not sure what happen

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -439,7 +439,6 @@
 # visual
 # -------------------------
 'atom-text-editor.vim-mode-plus.visual-mode':
-  '.': 'vim-mode-plus:repeat'
   'x': 'vim-mode-plus:delete'
   's': 'vim-mode-plus:change'
   'o': 'vim-mode-plus:reverse-selections'


leftover merge goofs

leftover merge goofs

--- static/coffee/common/gmap_plot.coffee
+++ static/coffee/common/gmap_plot.coffee
@@ -112,15 +112,6 @@
     )
     coll.add(pantool)
     @mget('tools').push(pantool.ref())
-
-    pantool = Collections('PanTool').create(
-      dataranges: [@x_range.ref(), @y_range.ref()]
-      dimensions: ['width', 'height']
-    )
-
-    @mset('tools', [pantool])
->>>>>>> master
-
     @requested_padding = {
       top: 0
       bottom: 0


Add updated atom configs

Add updated atom configs

--- .atom/config.cson
+++ .atom/config.cson
@@ -21,12 +21,17 @@
     ]
   editor:
     fontSize: 12
+    showIndentGuide: true
   "exception-reporting":
     userId: "eaf03499-4cbe-432b-8d82-002d5916ba8b"
   welcome:
     showOnStartup: false
   "xml-formatter":
     endLineCharacter: "LF (\\n)"
+".go.source":
+  editor:
+    tabLength: 8
+    tabType: "hard"
 ".scala.source":
   editor:
     autoIndentOnPaste: false


code style/readability

code style/readability

--- workers/social/lib/social/models/graph/graph.coffee
+++ workers/social/lib/social/models/graph/graph.coffee
@@ -37,8 +37,7 @@
       res.klass.all res.selector, (err, objects)->
         if err then callback err
         else
-          for o in objects
-            ret[o._id + '_' + res.modelName] = o
+          ret[o._id + '_' + res.modelName] = o  for o in objects
         fin()
     , -> sortThem null, ret

@@ -46,12 +45,11 @@
       ids = collections[modelName]
       klass = Base.constructors[modelName]
       selector = {
-        _id:
-          $in: ids.map (id)->
-            if 'string' is typeof id then ObjectId(id)
-            else id
+        _id: $in: ids.map (id)->
+          if 'string' is typeof id then ObjectId(id)
+          else id
       }
-      collectObjects({klass:klass, selector:selector, modelName:modelName})
+      collectObjects { klass, selector, modelName }

   # returns object ids from a result set as array
   # returns dict {colections: {'users':[id1,id2,id3]},


scriptblock: defensive control structure

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -23,8 +23,10 @@
     landingOptions =
       page         : landing

-    {delegate} = client.connection
-    landingOptions.username = delegate.profile.nickname if delegate?.type is "registered"
+    if client.connection?.delegate?
+      {connection: {delegate}} = client
+      {profile   : {nickname}} = delegate
+      landingOptions.username  = nickname if delegate.type is "registered"

     landingOptions = JSON.stringify landingOptions
     """


rollbar: add check to not logs more than 1 hr old

--- workers/social/lib/social/render/scriptblock.coffee
+++ workers/social/lib/social/render/scriptblock.coffee
@@ -57,11 +57,19 @@

     <!-- ROLLBAR -->
     <script>
+      var startTime = new Date().getTime();
       var _rollbarParams = {
         "server.environment": "production",
         "client.javascript.source_map_enabled": true,
         "client.javascript.code_version": "#{KONFIG.version}",
-        "client.javascript.guess_uncaught_frames": true
+        "client.javascript.guess_uncaught_frames": true,
+        checkIgnore: function(msg, file, line, col, err) {
+          if ((new Date().getTime() - startTime) > 1000*60*60) {
+            // ignore errors after the page has been open for 1hr
+            return true;
+          }
+          return false;
+        }
       };
       _rollbarParams["notifier.snippet_version"] = "2"; var _rollbar=["#{KONFIG.rollbar}", _rollbarParams]; var _ratchet=_rollbar;
       (function(w,d){w.onerror=function(e,u,l){_rollbar.push({_t:'uncaught',e:e,u:u,l:l});};var i=function(){var s=d.createElement("script");var


Revert "add tls settings"

Revert "add tls settings"

This reverts commit 72a4994cebab2731f99f0ada7a094c8a0acb3293.

--- services/document-updater/config/settings.defaults.coffee
+++ services/document-updater/config/settings.defaults.coffee
@@ -1,7 +1,6 @@
 Path = require('path')
 http = require('http')
 http.globalAgent.maxSockets = 300
-fs = require('fs')

 module.exports =
 	internal:
@@ -45,12 +44,6 @@
 			key_schema:
 				projectHistoryOps: ({project_id}) -> "ProjectHistory:Ops:{#{project_id}}"
 				projectHistoryFirstOpTimestamp: ({project_id}) -> "ProjectHistory:FirstOpTimestamp:{#{project_id}}"
-			tls: if process.env['REDIS_CA_CERT'] && process.env['REDIS_CLIENT_CERT'] && process.env['REDIS_CLIENT_KEY']
-				ca: fs.readFileSync(process.env['REDIS_CA_CERT']),
-				cert: fs.readFileSync(
-					process.env['REDIS_CLIENT_CERT']
-				),
-				key: fs.readFileSync(process.env['REDIS_CLIENT_KEY'])

 		new_project_history:
 			port: process.env["NEW_HISTORY_REDIS_PORT"] or "6379"
@@ -61,12 +54,6 @@
 				projectHistoryFirstOpTimestamp: ({project_id}) -> "ProjectHistory:FirstOpTimestamp:{#{project_id}}"
 				projectHistoryMigrationKey: ({project_id}) -> "ProjectHistory:MigrationKey:{#{project_id}}"
 			migration_phase: process.env["PROJECT_HISTORY_MIGRATION_PHASE"] or "prepare"
-			tls: if process.env['NEW_HISTORY_REDIS_CA_CERT'] && process.env['NEW_HISTORY_REDIS_CLIENT_CERT'] && process.env['NEW_HISTORY_REDIS_CLIENT_KEY']
-				ca: fs.readFileSync(process.env['NEW_HISTORY_REDIS_CA_CERT']),
-				cert: fs.readFileSync(
-					process.env['NEW_HISTORY_REDIS_CLIENT_CERT']
-				),
-				key: fs.readFileSync(process.env['NEW_HISTORY_REDIS_CLIENT_KEY'])
 			redisOptions:
 				keepAlive: 100


just finishing up ranges_slices_and_splices.coffee


Added Array::equals

Added Array::equals

--- engine/stdlib.coffee
+++ engine/stdlib.coffee
@@ -82,6 +82,32 @@
         i += step
     return r

+# Warn if overriding existing method
+if Array::equals
+    console.warn("Overriding existing Array.prototype.equals. Possible causes: New API defines the method, there's a framework conflict or you've got double inclusions in your code.");
+
+# attach the .equals method to Array's prototype to call it on any array
+Array::equals = (array)->
+    # if the other array is a falsy value, return
+    if not array
+        return false
+    # compare lengths - can save a lot of time
+    if @length != array.length
+        return false
+    for i in [0...@length]
+        # Check if we have nested arrays
+        if @[i] instanceof Array && array[i] instanceof Array
+            # recurse into the nested arrays
+            if not @[i].equals array[i]
+                return false
+        else if @[i] != array[i]
+            # Warning - two different object instances will never be equal: {x:20} != {x:20}
+            return false
+    return true
+
+# Hide method from for-in loops
+Object.defineProperty(Array.prototype, "equals", {enumerable: false});
+
 window.KEYS = {
     CANCEL: 3, HELP: 6, BACK_SPACE: 8, TAB: 9, CLEAR: 12,
     RETURN: 13, ENTER: 14, SHIFT: 16, CONTROL: 17, ALT: 18,


append load numbers to IAB test

append load numbers to IAB test

--- testApp/app/controllers/plugin.coffee
+++ testApp/app/controllers/plugin.coffee
@@ -590,9 +590,9 @@
     ref.removeEventListener 'exit', exit

     if loadNum > 1
-      steroids.logger.log "SUCCESS in IABRedirectTest"
-    else
-      steroids.logger.log "FAILURE in IABRedirectTest"
+      steroids.logger.log "SUCCESS in IABRedirectTest, loadstarts: #{loadNum}"
+    else
+      steroids.logger.log "FAILURE in IABRedirectTest, loadstarts: #{loadNum}"

     loadNum = 0


Make ctrl-k work first time

Make ctrl-k work first time

--- etc/atom/keymap.cson
+++ etc/atom/keymap.cson
@@ -25,3 +25,7 @@
   'ctrl-y': 'core:paste'
   'ctrl-\\': 'bracket-matcher:go-to-matching-bracket'
   'alt-q': 'autoflow:reflow-selection'
+
+'.workspace':
+  'ctrl-k ctrl-m': 'unset!'
+  'ctrl-k ctrl-s': 'unset!'


fixing weird disappearing save button

fixing weird disappearing save button

--- @client/admin.coffee
+++ @client/admin.coffee
@@ -551,12 +551,13 @@
               fontWeight: 600
             "Customizations for #{subdomain.name}.consider.it:"

-          CodeMirrorTextArea
-            id: 'customizations'
-            key: md5(subdomain.customizations) # update text area if subdomain.customizations changes elsewhere
-            default_value: subdomain.customizations or "\n\n\n\n\n\n\n"
-            onChange: (val) =>
-              @local.current_value = val
+          DIV null,
+            CodeMirrorTextArea
+              id: 'customizations'
+              key: md5(subdomain.customizations) # update text area if subdomain.customizations changes elsewhere
+              default_value: subdomain.customizations or "\n\n\n\n\n\n\n"
+              onChange: (val) =>
+                @local.current_value = val

           DIV
             className: 'input_group'
@@ -653,11 +654,12 @@

                   k

-                CodeMirrorTextArea
-                  key: "#{md5(subdomain.customizations)}-#{k}" # update text area if subdomain.customizations changes elsewhere
-                  default_value: js
-                  onChange: do (k,v) => (val) =>
-                    @local.property_changes[k] = val
+                DIV null,
+                  CodeMirrorTextArea
+                    key: "#{md5(subdomain.customizations)}-#{k}" # update text area if subdomain.customizations changes elsewhere
+                    default_value: js
+                    onChange: do (k,v) => (val) =>
+                      @local.property_changes[k] = val

                 DIV
                   className: 'input_group'


Just fixes to get things work

Just fixes to get things work

--- workers/social/lib/social/models/app.coffee
+++ workers/social/lib/social/models/app.coffee
@@ -11,7 +11,7 @@

   CActivity = require './activity'
   JAccount = require './account'
-  JReview = require './messages/review'
+  #JReview = require './messages/review'
   JTag = require './tag'

   @trait __dirname, '../traits/filterable'       # brings only static methods
@@ -78,22 +78,26 @@
         default     : 0

     relationships   :
-      creator       : JAccount
-      review        : JReview
+      creator       :
+        targetType  : "JAccount"
+        as          : "related"
+      # review        :
+      #   targetType  : "JReview"
+      #   as          : "review"
       activity      :
-        targetType  : CActivity
+        targetType  : "CActivity"
         as          : 'activity'
       follower      :
-        targetType  : JAccount
+        targetType  : "JAccount"
         as          : 'follower'
       likedBy       :
-        targetType  : JAccount
+        targetType  : "JAccount"
         as          : 'like'
       participant   :
-        targetType  : JAccount
+        targetType  : "JAccount"
         as          : ['author','reviewer','user']
       tag           :
-        targetType  : JTag
+        targetType  : "JTag"
         as          : 'tag'

   @create = secure (client, data, callback)->
@@ -303,7 +307,7 @@
       if err then callback err else @markInstalled client, _apps, (err, apps)=>
         @markFollowing client, apps, callback

-  @markInstalled = bongo.secure (client, apps, callback)->
+  @markInstalled = secure (client, apps, callback)->
     Relationship.all
       targetId  : client.connection.delegate.getId()
       as        : 'user'


Moderation: in case of tag removal instead of soft delete its type is updated as 'deleted'

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -292,7 +292,7 @@
           callback new KodingError 'Access denied'
         else
           tagId = @getId()
-          @remove (err)=>
+          @update {$set: type: "deleted"}, (err)=>
             if err
               callback err
             else


_onDataChannel() test for Remote.Peer

_onDataChannel() test for Remote.Peer

--- src/tests/spec/public/models/remote.peer.spec.coffee
+++ src/tests/spec/public/models/remote.peer.spec.coffee
@@ -357,3 +357,20 @@
 				waitsFor(->
 						return success
 					, 1000)
+
+		describe 'when receiving a data channel', ->
+			it 'should add it to itself', ->
+				fakeChannel = new FakeRTCDataChannel()
+				peer = new Peer(fakeController, '1', true, FakeRTCPeerConnection)
+				spyOn(peer, '_addChannel')
+
+				peer._onDataChannel({
+						channel: fakeChannel
+					})
+
+				expect(peer._addChannel).toHaveBeenCalled()
+
+				callArgs = peer._addChannel.mostRecentCall.args
+				expect(callArgs).toEqual([
+						fakeChannel
+					])


Make active only if 'os-teacher' class is present

--- src/components/reference-book/teacher-content-toggle.cjsx
+++ src/components/reference-book/teacher-content-toggle.cjsx
@@ -1,14 +1,42 @@
 React = require 'react'
 BS = require 'react-bootstrap'
+
+TEACHER_CONTENT_SELECTOR = '.os-teacher'

 TeacherContentToggle = React.createClass

   propTypes:
-    onChange: React.PropTypes.func.isRequired
-    isShowing: React.PropTypes.bool.isRequired
+    onChange:   React.PropTypes.func.isRequired
+    isShowing:  React.PropTypes.bool
+    section:    React.PropTypes.string
+    windowImpl: React.PropTypes.object
+
+  getDefaultProps: ->
+    windowImpl: window
+
+  getInitialState: ->
+    hasTeacherContent: false

   onClick: ->
-    @props.onChange(not @props.isShowing)
+    @props.onChange(not @props.isShowing) if @state.hasTeacherContent is true
+
+  componentDidMount:      -> @scheduleCheckForTeacherContent()
+  componentDidUpdate:     -> @scheduleCheckForTeacherContent()
+  componentWillUnmount:   -> clearTimeout(@state.pendingCheck) if @state.pendingCheck
+  scheduleCheckForTeacherContent: ->
+    return if @state.pendingCheck
+    @setState(pendingCheck: _.defer(@checkForTeacherContent))
+
+  checkForTeacherContent: ->
+    @setState(
+      pendingCheck: false
+      hasTeacherContent: !!@props.windowImpl.document.querySelector(TEACHER_CONTENT_SELECTOR)
+    )
+
+  renderNoContentTooltip: ->
+    <BS.Popover id='no-content'>
+      No teacher edition content is available for this page.
+    </BS.Popover>

   render: ->
     teacherLinkText = if @props.isShowing
@@ -16,8 +44,18 @@
     else
       'Show Teacher Edition'

+    content = if @state.hasTeacherContent
+      {teacherLinkText}
+    else
+      <BS.OverlayTrigger
+        placement='bottom' trigger='click' overlay={@renderNoContentTooltip()}
+      >
+        {teacherLinkText}
+      </BS.OverlayTrigger>
+
+
     <BS.NavItem className='teacher-edition' onClick={@onClick}>
-      {teacherLinkText}
+      {content}
     </BS.NavItem>

 module.exports = TeacherContentToggle


Backfill specs for not scrolling left when activating autocomplete

--- src/extensions/autocomplete/spec/autocomplete-spec.coffee
+++ src/extensions/autocomplete/spec/autocomplete-spec.coffee
@@ -139,6 +139,16 @@
         expect(editor.getCursorBufferPosition()).toEqual [10,11]
         expect(editor.getSelection().isEmpty()).toBeTruthy()
         expect(editor.find('.autocomplete')).not.toExist()
+
+    describe "when the editor is scrolled to the right", ->
+      it "does not scroll it to the left", ->
+        editor.width(300)
+        editor.height(300)
+        editor.attachToDom()
+        editor.setCursorBufferPosition([6, Infinity])
+        previousScrollLeft = editor.scrollView.scrollLeft()
+        autocomplete.attach()
+        expect(editor.scrollView.scrollLeft()).toBe previousScrollLeft

   describe 'core:confirm event', ->
     describe "where there are matches", ->


Replace deprecated #controllerFor with #needs.

--- app/assets/javascripts/all_aboard/controllers/boards_new_controller.js.coffee
+++ app/assets/javascripts/all_aboard/controllers/boards_new_controller.js.coffee
@@ -1,4 +1,6 @@
 App.BoardsNewController = Ember.ObjectController.extend
+  needs: [ "board" ]
+
   initializeNewBoard: ->
     if !@transaction
       @transaction = App.store.transaction()
@@ -12,4 +14,4 @@
   showRecord: ->
     newRecord = @get("model")
     newRecord.removeObserver("id", this, "showRecord")
-    @controllerFor("board").transitionTo("board", newRecord)
+    @get("controllers.board").transitionTo("board", newRecord)


Fix compile issues

Fix compile issues

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -1,54 +1,60 @@
 # Load all required libraries.
-gulp       = require 'gulp'
-less       = require 'gulp-less'
-coffee     = require 'gulp-coffee'
-yaml       = require 'gulp-yaml'
-eco        = require 'gulp-eco'
-del        = require 'del'
-sourcemaps = require 'gulp-sourcemaps'
-uglify     = require 'gulp-uglify'
-minifyCss  = require 'gulp-minify-css'
-changed    = require 'gulp-changed'
+gulp        = require 'gulp'
+less        = require 'gulp-less'
+coffee      = require 'gulp-coffee'
+yaml        = require 'gulp-yaml'
+eco         = require 'gulp-eco'
+del         = require 'del'
+sourcemaps  = require 'gulp-sourcemaps'
+uglify      = require 'gulp-uglify'
+minifyCss   = require 'gulp-minify-css'
+changed     = require 'gulp-changed'

-gulp.task 'less', ->
+gulp.task 'less', ['compile:clean'], ->
   gulp.src 'app/assets/css/**/*.less'
     .pipe changed 'build/assets/css'
     .pipe less()
     .pipe gulp.dest 'build/assets/css'

-gulp.task 'css', ->
+gulp.task 'css', ['compile:clean'], ->
   gulp.src 'app/assets/css/**/*.css'
     .pipe changed 'build/assets/css'
     .pipe gulp.dest 'build/assets/css'

-gulp.task 'images', ->
+gulp.task 'images', ['compile:clean'], ->
   gulp.src 'app/assets/images/**/*'
     .pipe changed 'build/assets/images/'
     .pipe gulp.dest 'build/assets/images/'

-gulp.task 'html', ->
+gulp.task 'html', ['compile:clean'], ->
   gulp.src 'app/assets/views/**/*.html'
     .pipe changed 'build/assets/views'
     .pipe gulp.dest 'build/assets/views'

-gulp.task 'eco', ->
+gulp.task 'eco', ['compile:clean'], ->
   gulp.src 'app/assets/views/**/*.ect'
     .pipe changed 'build/assets/views'
     .pipe eco()
     .pipe gulp.dest 'build/assets/views'

-gulp.task 'yml', ->
-  gulp.src 'app/**/*.yml'
+gulp.task 'yml', ['compile:clean'], ->
+  gulp.src 'app/manifest.yml'
     .pipe changed 'build/'
     .pipe yaml()
     .pipe gulp.dest 'build/'

-gulp.task 'js', ->
+gulp.task 'translate', ['compile:clean'], ->
+  gulp.src 'app/locales/*.yml'
+  .pipe changed 'build/'
+    .pipe yaml()
+      .pipe gulp.dest 'build/_locales'
+
+gulp.task 'js', ['compile:clean'], ->
   gulp.src 'app/**/*.js'
     .pipe changed 'build/'
     .pipe gulp.dest 'build/'

-gulp.task 'coffee-script', ->
+gulp.task 'coffee-script', ['compile:clean'], ->
   gulp.src 'app/**/*.coffee'
     .pipe changed 'build/'
     .pipe sourcemaps.init()
@@ -92,9 +98,11 @@
   watcher = gulp.watch('app/**/*', ['compile:assets', 'compile:sources'])

 # Default task call every tasks created so far.
-gulp.task 'compile:assets', ['less', 'css', 'eco', 'html', 'yml', 'images']
+gulp.task 'compile:assets', ['less', 'css', 'eco', 'html', 'yml', 'images', 'translate']
 gulp.task 'compile:sources', ['js', 'coffee-script']
 gulp.task 'compile', ['compile:clean', 'compile:assets', 'compile:sources']
 gulp.task 'default', ['compile']

+gulp.task 'clean', ['compile:clean', 'release:clean']
+
 gulp.task 'release', ['release:clean', 'release:uglify', 'release:minify', 'release:json', 'release:libs', 'release:images']