fix: update initial time after player init

--- src/javascripts/player.coffee
+++ src/javascripts/player.coffee
@@ -17,6 +17,7 @@
     @attachEvents()
     @setInitialTime()
     @app.init(self)
+    @app.updateTime(@currentTimeInSeconds)

   jumpBackward: (seconds) =>
     seconds = seconds || @app.options.backwardSeconds
@@ -98,7 +99,6 @@
     if deeplink.startTime > 0
       @currentTimeInSeconds = deeplink.startTime
       @media.currentTime = deeplink.startTime
-      @app.updateTime(@currentTimeInSeconds)
     else
       @currentTimeInSeconds = 0
     @currentTime = Utils.secondsToHHMMSS(@currentTimeInSeconds)


match terminal width when narrow

match terminal width when narrow

--- src/apm-cli.coffee
+++ src/apm-cli.coffee
@@ -63,7 +63,7 @@
     commands[name] = commandClass

 parseOptions = (args=[]) ->
-  options = yargs(args).wrap(100)
+  options = yargs(args).wrap(Math.min(100, yargs.terminalWidth()))
   options.usage """

     apm - Atom Package Manager powered by https://atom.io


Find words of the language

Find words of the language

--- src/layout/test_layout.coffee
+++ src/layout/test_layout.coffee
@@ -60,5 +60,9 @@

   test "layout inspector", (t) ->
     t.ok inspector = sprite.get('inspector'), 'made inspector'
+    t.ok language = sprite.get('language'), 'language'
+    t.ok words = Object.keys(language), 'words'
+    t.ok "forward" in words, 'has a command'
+
     t.end()


Don't make IP objects out of connect/disconnect events

--- src/lib/InternalSocket.coffee
+++ src/lib/InternalSocket.coffee
@@ -67,11 +67,11 @@
   #         @readBuffer fd, position, size, buffer
   connect: ->
     @connected = true
-    @handleSocketEvent 'connect', null, false
+    @emitEvent 'connect', null

   disconnect: ->
     @connected = false
-    @handleSocketEvent 'disconnect', null, false
+    @emitEvent 'disconnect', null

   isConnected: -> @connected

@@ -183,12 +183,14 @@

     # Wrap legacy events into appropriate IP objects
     switch event
-      when 'connect', 'begingroup'
+      when 'begingroup'
         return new IP 'openBracket', payload
-      when 'disconnect', 'endgroup'
+      when 'endgroup'
         return new IP 'closeBracket'
+      when 'data'
+        return new IP 'data', payload
       else
-        return new IP 'data', payload
+        return null

   ipToLegacy: (ip) ->
     switch ip.type
@@ -208,6 +210,7 @@
   handleSocketEvent: (event, payload, autoConnect = true) ->
     isIP = event is 'ip' and IP.isIP payload
     ip = if isIP then payload else @legacyToIp event, payload
+    return unless ip

     if not @isConnected() and autoConnect and @brackets.length is 0
       # Connect before sending


Closes #37. Support upwards recursive .jsbeautifyrc fallback.

Priority hierarchy:
- Atom editor settings
- Atom Beautify package settings
- Home directory .jsbeautifyrc file configuration
- Closest EditorConfig file (see #68)
- Project options (from closest .jsbeautifyrc file to the beautified
  editor's file to root, "/")

--- lib/beautify.coffee
+++ lib/beautify.coffee
@@ -62,10 +62,11 @@
 @param {string} name filename to search for (e.g. .jshintrc)
 @param {string} dir directory to start search from (default:
 current working directory)
+@param {boolean} upwards should recurse upwards on failure? (default: true)

 @returns {string} normalized filename
 ###
-findFile = (name, dir) ->
+findFile = (name, dir, upwards=true) ->
   path ?= require("path")
   dir = dir or process.cwd()
   filename = path.normalize(path.join(dir, name))
@@ -77,7 +78,10 @@
   if dir is parent
     findFileResults[filename] = null
     return null
-  findFile name, parent
+  if upwards
+    findFile name, parent
+  else
+    return null

 ###
 Tries to find a configuration file in either project directory
@@ -86,14 +90,16 @@

 @param {string} config name of the configuration file
 @param {string} file path to the file to be linted
+@param {boolean} upwards should recurse upwards on failure? (default: true)
+
 @returns {string} a path to the config file
 ###
-findConfig = (config, file) ->
+findConfig = (config, file, upwards=true) ->
   path ?= require("path")
   dir = path.dirname(path.resolve(file))
   envs = getUserHome()
   home = path.normalize(path.join(envs, config))
-  proj = findFile(config, dir)
+  proj = findFile(config, dir, upwards)
   return proj if proj
   return home if verifyExists(home)
   null
@@ -119,6 +125,7 @@
   options

 beautify = ->
+  path ?= require("path")
   MessagePanelView ?= require('atom-message-panel').MessagePanelView
   PlainMessageView ?= require('atom-message-panel').PlainMessageView
   LoadingView ?= require "./loading-view"
@@ -128,12 +135,12 @@
   @loadingView.show()
   forceEntireFile = atom.config.get("atom-beautify.beautifyEntireFileOnSave")
   # Look for .jsbeautifierrc in file and home path, check env variables
-  getConfig = (startPath) ->
+  getConfig = (startPath, upwards=true) ->
     # Verify that startPath is a string
     startPath = (if (typeof startPath is "string") then startPath else "")
     return {} unless startPath
     # Get the path to the config file
-    configPath = findConfig(".jsbeautifyrc", startPath)
+    configPath = findConfig(".jsbeautifyrc", startPath, upwards)
     externalOptions = undefined
     if configPath
       fs ?= require("fs")
@@ -198,8 +205,10 @@
     @loadingView.hide()
     return
   # console.log 'Beautify time!'
-  text = undefined
+
+  # Get current editor
   editor = atom.workspace.getActiveEditor()
+  # Get current Atom editor configuration
   isSelection = !!editor.getSelectedText()
   softTabs = editor.softTabs
   tabLength = editor.getTabLength()
@@ -208,20 +217,51 @@
     indent_char: (if softTabs then " " else "\t")
     indent_with_tabs: not softTabs
   configOptions = getConfigOptionsFromSettings(languages)
+
+  # Get editor path and configurations for paths
   editedFilePath = editor.getPath()
-  projectOptions = getConfig(editedFilePath)
-  homeOptions = getConfig(getUserHome())
+
+  # Get configuration in User's Home directory
+  userHome = getUserHome()
+  # FAKEFILENAME forces `path` to treat as file path and it's parent directory
+  # is the userHome. See implementation of findConfig
+  # and how path.dirname(DIRECTORY) returns the parent directory of DIRECTORY
+  homeOptions = getConfig(path.join(userHome,"FAKEFILENAME"), false)
+
+  # TODO: See issue #68. EditorConfig options
+  editorConfigOptions = {};
+
+  # Get all options in configuration files from this directory upwards to root
+  projectOptions = []
+  p = path.dirname(editedFilePath)
+  # Check if p is root (top directory)
+  while p isnt "/"
+    # Get config for p
+    pf = path.join(p, "FAKEFILENAME")
+    pc = getConfig(pf, false)
+    # Add config for p to project's config options
+    projectOptions.push(pc)
+    # console.log p, pc
+    # Move upwards
+    p = path.resolve(p,"../")
+
+  # Combine all options together
+  allOptions = [
+    editorOptions
+    configOptions
+    homeOptions
+    editorConfigOptions
+  ]
+  allOptions = allOptions.concat(projectOptions)
+
+  # Get current editor's text
+  text = undefined
   if not forceEntireFile and isSelection
     text = editor.getSelectedText()
   else
     text = editor.getText()
   oldText = text
-  allOptions = [
-    editorOptions
-    configOptions
-    homeOptions
-    projectOptions
-  ]
+  # Get Grammar
   grammarName = editor.getGrammar().name
   # Finally, beautify!
   try


module export Comailer class

module export Comailer class

--- lib/comailer.coffee
+++ lib/comailer.coffee
@@ -2,7 +2,7 @@
 createTransport = (require 'nodemailer').createTransport
 Nodemailer = createTransport(yes).constructor

-class Comailer extends Nodemailer
+module.exports = class Comailer extends Nodemailer
   @Nodemailer = Nodemailer
   @createTransport = (transport) ->
     new Comailer transport
@@ -15,5 +15,3 @@
         do (name) =>
           @[name] = (args...) -> (callback) ->
               transport[name] args..., callback
-
-module.exports = new Comailer()


Add gulpfile.coffee

Add gulpfile.coffee


remember to pass the callback through

remember to pass the callback through

--- lib/packages.coffee
+++ lib/packages.coffee
@@ -39,7 +39,7 @@
   installAll: (pkgs, cb) ->
     if (pkg = pkgs.shift())?
       @install pkg, =>
-        @installAll pkgs
+        @installAll pkgs, cb
     else
       @setupnote.dismiss()
       atom.notifications.addSuccess "Juno: Success!",


Fix namespace generation

Fix namespace generation

Used - where there should be :

--- lib/protocol.coffee
+++ lib/protocol.coffee
@@ -5,12 +5,12 @@

 # Make namespace string.
 exports.makeNameSpace = (category) ->
-    version = @schema?.version || @device.schema.version
-    [ @schema?.prefix || @device?.schema.prefix
-      category
-      version.split('.')[0]
-      version.split('.')[1]
-    ].join '-'
+    version = @schema?.version or @device.schema.version
+    prefix  = @schema?.prefix or @device?.schema.prefix
+    prefix + ':' + [
+        category
+        version.split('.')[0]
+        version.split('.')[1] ].join '-'

 # Make Device/Service type string for SSDP messages
 # and Service/Device descriptions.


Tests

Tests

--- lib/provider.coffee
+++ lib/provider.coffee
@@ -96,6 +96,7 @@
   buildWordList: () =>
     possibleWords = []
     for citation in @references
+      console.log(citation)
       if citation.entryTags and citation.entryTags.title and (citation.entryTags.authors or citation.entryTags.author or citation.entryTags.editor)

         citation.entryTags.prettyTitle =
@@ -107,9 +108,10 @@
             citation.entryTags.authors =
               citation.entryTags.authors.concat @cleanAuthors citation.entryTags.author.split ' and '

-        if citation.entryTags.editor?
-          citation.entryTags.authors =
-            citation.entryTags.authors.concat @cleanAuthors citation.entryTags.editor.split ' and '
+        if not citation.entryTags.editors
+          if citation.entryTags.editor?
+            citation.entryTags.authors =
+              citation.entryTags.authors.concat @cleanAuthors citation.entryTags.editor.split ' and '

         citation.entryTags.prettyAuthors =
           @prettifyAuthors citation.entryTags.authors
@@ -135,25 +137,30 @@
     try
       references = []
       for file in referenceFiles
+        console.log(file)
         # What type of file is this?
         ftype = file.split('.')
         ftype = ftype[ftype.length - 1]
+        console.log(ftype)

         if fs.statSync(file).isFile()

           if ftype is "bib"
             parser = new bibtexParse(fs.readFileSync(file, 'utf-8'))
             references = references.concat parser.parse()
+            console.log(references)

           if ftype is "json"
             citeproc = JSON.parse fs.readFileSync(file, 'utf-8')
-            citeproc_refs = parseCiteproc citeproc
+            citeproc_refs = @parseCiteproc citeproc
             references = references.concat citeproc_refs
+            console.log(references)

           if ftype is "yaml"
             citeproc = yaml.load fs.readFileSync(file, 'utf-8')
-            citeproc_refs = parseCiteproc citeproc
+            citeproc_refs = @parseCiteproc citeproc
             references = references.concat citeproc_refs
+            console.log(references)

         else
           console.warn("'#{file}' does not appear to be a file, so autocomplete-bibtex will not try to parse it.")
@@ -166,6 +173,8 @@

   ###
   parseCiteproc: (cp) ->
+    console.log("Parsing citeproc")
+    console.log(cp)
     # Convert yaml to parsed bibtex format
     cp_references = []
     for ref in cp


Fix auth view rendering

Fix auth view rendering

--- scripts/views/workspace/menu/auth.coffee
+++ scripts/views/workspace/menu/auth.coffee
@@ -17,9 +17,10 @@
   #
   # This view updates when the login state changes
   return Marionette.ItemView.extend
-    template: signInOutTemplate
-      authenticated: session.authenticated()
-      user: session.user()
+    template: (serializedModel) ->
+      return signInOutTemplate
+        authenticated: session.authenticated()
+        user: session.user()

     events:
       'click #sign-out':      'signOut'
@@ -32,7 +33,7 @@
       beforeUnload = () =>
         return 'You have unsaved changes. Are you sure you want to leave this page?' if @hasChanged

-      $(window).on 'beforeunload', beforeUnload
+      $(window).on('beforeunload', beforeUnload)

       # TODO: Listen for changes to content and enable Save button


Löschen der Weg-Daten aus der Anzeige, wenn ein Weg deselektiert wurde, gefixt.

--- src/main/coffee/script.coffee
+++ src/main/coffee/script.coffee
@@ -39,9 +39,9 @@

   clearWayData = () ->
     $("#wayId").html("")
-    $("#wayLength").val("")
-    $("#wayMaxSpeed").val("")
-    $("#wayTripTime").val("")
+    $("#wayLength").html("")
+    $("#wayMaxSpeed").html("")
+    $("#wayTripTime").html("")
     $("#way-nodes").html("")

   deselectNode = () ->


0.22a

0.22a

-Improved Trash Issue
-Output cells enabled, press on cell 32 and information will popup


Add event listeners to settings, support load events

--- lib/settings.coffee
+++ lib/settings.coffee
@@ -31,6 +31,8 @@
         for own key, value of items
           Settings.storeAndPropagate key, value if @shouldSyncKey key
         Settings.isLoaded = true
+        unless isPreloaded
+          listener() while listener = Settings.eventListeners.load?.pop()

   # Asynchronous message from synced storage.
   handleStorageUpdate: (changes, area) ->
@@ -70,7 +72,13 @@
 root.Settings = Settings =
   isLoaded: isPreloaded
   cache: settingsCache
-  init: -> Sync.init()
+  eventListeners: {}
+
+  init: ->
+    Sync.init()
+    if isPreloaded
+      listener() while listener = Settings.eventListeners.load?.pop()
+
   get: (key) ->
     console.log "WARNING: Settings have not loaded yet; using the default value for #{key}." unless @isLoaded
     if (key of @cache) then JSON.parse(@cache[key]) else @defaults[key]
@@ -90,6 +98,9 @@
     Sync.clear key

   has: (key) -> key of @cache
+
+  addEventListener: (eventName, callback) ->
+    (@eventListeners[eventName] ||= []).push callback

   # For settings which require action when their value changes, add hooks to this object, to be called from
   # options/options.coffee (when the options page is saved), and by Settings.storeAndPropagate (when an


some explanations

some explanations

--- src/browser.coffee
+++ src/browser.coffee
@@ -18,11 +18,16 @@
     defaultfn[e] = -> throw new Error "no specific fn for #{e} defined" # dummy

+##
+# this contains the tag specific browser state from the user/developer event loop,
+#   which includes references to
+#   - state objects from the requestAnimationFrame event loop
 class BrowserState
     initialize: (prev) ->
         @manip ?= cancelable_and_retrivable_callbacks()
         @done  ?= deferred_callbacks()
         @manip.reset()
+        this

     mergeInto: (state) ->
         for key in SHARED
@@ -40,7 +45,11 @@
             delete manip
         this

-
+##
+# the main purpose of this is to sync two event loops:
+#   the event loop from which the template gets updated
+#                   with
+#   the requestAnimationFrame event loop
 class BrowserAdapter
     constructor: (@template, opts = {}) ->
         @builder = @template.xml ? @template


remove all jquery related stuff

remove all jquery related stuff

--- src/browser.coffee
+++ src/browser.coffee
@@ -1,8 +1,7 @@
 { Animation } = require 'animation'
 { singlton_callback, deferred_callbacks,
   cancelable_and_retrivable_callbacks,
-  defineJQueryAPI, $fyBuilder,
-  createSpaceholder, removed } = require './util'
+  removed } = require './util'
 { isArray } = Array

 EVENTS = [
@@ -25,12 +24,11 @@
         opts.execution        ?= '8ms' # half of 16ms (60 FPS), the other half is for the browser
         opts.timeout          ?= '120ms'
         opts.toggle           ?= on
-        @$ ?= opts.jquery ? opts.$ ? window?.$
         # init requestAnimationFrame handler
         @animation = new Animation(opts)
         @animation.start()
-        # init jquery functions
-        @fn = {}
+        # init browser manipulation functions
+        @fn ?= {}
         for n,f of defaultfn
             @fn[n] ?= f.bind(this)
         @initialize()
@@ -42,179 +40,152 @@
     initialize: () ->
         do @listen
         # prefill builder with state
-        @builder._jquery = @$([])
-        @builder._jquery_done = deferred_callbacks()
-        do @builder._jquery_done.callback() # builder is allways done
-        # override query
-        old_query = @builder.query
-        @builder.query = (type, tag, key) ->
-            return old_query.call(this, type, tag, key) unless tag._jquery?
-            if type is 'attr'
-                tag._jquery.attr(key)
-            else if type is 'text'
-                tag._jquery.text()
-            else if type is 'tag'
-#                 $(key).data('dt-jquery')
-                if key._jquery?
-                    key
-                else
-                    # assume this is allready a jquery object
-                    if (domel = key[0])?
-                        attrs = {}
-                        for attr in domel.attributes
-                            attrs[attr.name] = attr.value
-                        new @builder.Tag domel.nodeName.toLowerCase(), attrs, ->
-                            @_jquery = key
-                            @end()
-                    else
-                        old_query.call(this, type, tag, key)
-#                     $(key).data('dt-jquery') or {_jquery:key}
+        @make(@builder) # create a dom object
+        @builder._browser_done = deferred_callbacks()
+        do @builder._browser_done.callback() # builder is allways done
         # register ready handler
         @template.register 'ready', (tag, next) ->
             # when tag is already in the dom its fine,
             #  else wait until it is inserted into dom
-            if tag._jquery_ready is yes
+            if tag._browser_ready is yes
                 next(tag)
             else
-                tag._jquery_ready = ->
+                tag._browser_ready = ->
                     next(tag)
+        return this

     use: (plugin) ->
         plugin?.call(this, this)
         return this

     listen: () ->
-        EVENTS.forEach (event) =>
-            @template.on(event, this["on#{event}"].bind(this))
+        for event in EVENTS
+            if (listener = this["on#{event}"])?
+                @template.on(event, listener.bind(this))
+        return this
+
+    make: -> # create a dom object
+        throw new Error "Adapter::make not defined."

     # flow control : eventlisteners

     onadd: (parent, el) ->
         return if removed el
-        if el is el.builder
-            el._jquery ?= @$([], el.parent?._jquery)
-            $fyBuilder(el)
-        else
-            el._jquery ?= @$(el.toString(), el.parent._jquery)
-            defineJQueryAPI(el)
-
+        @make(el) # create a dom object
         that = this
-        el._jquery_manip    ?= cancelable_and_retrivable_callbacks()
-        el._jquery_done     ?= deferred_callbacks()
-        parent._jquery_done ?= deferred_callbacks()
-        el._jquery_manip.reset()
-        while (cb = el._jquery_manip.callbacks.shift())?
+        el._browser_manip    ?= cancelable_and_retrivable_callbacks()
+        el._browser_done     ?= deferred_callbacks()
+        parent._browser_done ?= deferred_callbacks()
+        el._browser_manip.reset()
+        while (cb = el._browser_manip.callbacks.shift())?
             @animation.push(cb)

-        ecb = el._jquery_done.callback()
-        pcb = parent._jquery_done.callback()
+        ecb = el._browser_done.callback()
+        pcb = parent._browser_done.callback()
         if el is el.builder then ecb() else el.ready(ecb)
         if parent is parent.builder then pcb() else parent.ready(pcb)

-        el._jquery_insert ?= singlton_callback el, ->
-            if @_jquery.length is 0
-                createSpaceholder.call(that, this, @parent._jquery)
-            that.fn.add(@parent, this)
-            if @parent._jquery_wrapped
-                @parent._jquery_wrapped = no
-                @parent._jquery = @parent._jquery.not(':first') # rm placeholder span
-            $fyBuilder(@parent) if @parent is @parent.builder
-            @_jquery_ready?()
-            @_jquery_ready = yes
-            @_jquery_insert = yes
-        el._jquery_insert.replace?(el)
+        el._browser_insert ?= singlton_callback el, ->
+            return if removed this
+            that.insert_callback(this)
+        el._browser_insert.replace?(el)

-        el._jquery_parent_done ?= singlton_callback el, ->
+        el._browser_parent_done ?= singlton_callback el, ->
             return if removed this
-            if @parent is @parent.builder
-                bool = (not @parent.parent? or
-                           (@parent.parent is @parent.parent?.builder and # FIXME recursive?
-                            @parent.parent?._jquery_insert is true))
-                if bool and @parent._jquery_insert is true
-                    that.animation.push(@_jquery_insert)
-                else
-                    @_jquery_insert?()
-            else
-                that.animation.push(@_jquery_insert)
-        el._jquery_parent_done.replace?(el)
-        parent._jquery_done(el._jquery_parent_done)
+            that.parent_done_callback(this)
+        el._browser_parent_done.replace(el)
+        parent._browser_done(el._browser_parent_done)

     onreplace: (oldtag, newtag) ->
         return if removed(oldtag) or removed(newtag)
-        newtag._jquery_parent_done ?= oldtag._jquery_parent_done
-        newtag._jquery_insert      ?= oldtag._jquery_insert
-        newtag._jquery_done        ?= oldtag._jquery_done
-        oldtag._jquery_parent_done  = null
-        oldtag._jquery_insert       = null
-        oldtag._jquery_done         = null
+        newtag._browser_parent_done ?= oldtag._browser_parent_done
+        newtag._browser_insert      ?= oldtag._browser_insert
+        newtag._browser_done        ?= oldtag._browser_done
+        oldtag._browser_parent_done  = null
+        oldtag._browser_insert       = null
+        oldtag._browser_done         = null

         @onadd(oldtag.parent, newtag)

-        oldtag._jquery_manip?.cancel?()
-        newtag._jquery_manip.reset()
+        oldtag._browser_manip?.cancel?()
+        newtag._browser_manip.reset()
         # if manip left from last time run them
-        while (cb = newtag._jquery_manip.callbacks.shift())?
+        while (cb = newtag._browser_manip.callbacks.shift())?
             @animation.push(cb)

-        if newtag._jquery_insert is true
+        if newtag._browser_insert is true
             that = this
-            newtag._jquery_replace ?= oldtag._jquery_replace
-            oldreplacerequest = newtag._jquery_replace?
-            newtag._jquery_replace ?= singlton_callback newtag, ->
-                if @_jquery.length is 0
-                    createSpaceholder.call(that, this, @parent._jquery)
-                that.fn.replace(oldtag, this)
-                $fyBuilder(this) if this is @builder
-            newtag._jquery_replace.replace?(newtag)
-            oldtag._jquery_replace = null
+            newtag._browser_replace ?= oldtag._browser_replace
+            oldreplacerequest = newtag._browser_replace?
+            newtag._browser_replace ?= singlton_callback newtag, ->
+                return if removed this
+                that.replace_callback(oldtag, this)
+            newtag._browser_replace.replace?(newtag)
+            oldtag._browser_replace = null
             unless oldreplacerequest
-                @animation.push(newtag._jquery_replace)
+                @animation.push(newtag._browser_replace)

     ontext: (el, text) ->
-        el._jquery_manip ?= cancelable_and_retrivable_callbacks(yes)
-        @animation.push el._jquery_manip =>
+        el._browser_manip ?= cancelable_and_retrivable_callbacks(yes)
+        @animation.push el._browser_manip =>
             @fn.text(el, text)

     onraw: (el, html) ->
-        el._jquery_manip ?= cancelable_and_retrivable_callbacks(yes)
-        @animation.push el._jquery_manip =>
+        el._browser_manip ?= cancelable_and_retrivable_callbacks(yes)
+        @animation.push el._browser_manip =>
             @fn.raw(el, html)

     onattr: (el, key, value) ->
-        el._jquery_manip ?= cancelable_and_retrivable_callbacks(yes)
-        @animation.push el._jquery_manip =>
+        el._browser_manip ?= cancelable_and_retrivable_callbacks(yes)
+        @animation.push el._browser_manip =>
             @fn.attr(el, key, value)

     onshow: (el) ->
-        @fn.show(el) if el._jquery?
+        @fn.show(el)

     onhide: (el) ->
-        @fn.hide(el) if el._jquery?
+        @fn.hide(el)

     onremove: (el, opts) ->
-        return unless el._jquery?
         @fn.remove(el, opts)
-        el._jquery_parent_done_done?.reset()
-        el._jquery_done?.reset()
-        el._jquery_manip?.cancel()
-        delete el._jquery_parent_done
-        delete el._jquery_replace
-        delete el._jquery_insert
+        el._browser_parent_done?.replace(null)
+        el._browser_done?.reset()
+        el._browser_manip?.cancel()
+        delete el._browser_parent_done
+        delete el._browser_replace
+        delete el._browser_insert
         unless opts.soft
-            delete el._jquery_manip
-            delete el._jquery_done
-            delete el._jquery
+            delete el._browser_manip
+            delete el._browser_done

-    onend: () ->
-        @template.jquery = @template._jquery = @builder._jquery
-        defineJQueryAPI(@template)
+    # ready callbacks

+    insert_callback: (el) ->
+        @fn.add(el.parent, el)
+        el._browser_ready?()
+        el._browser_ready = yes
+        el._browser_insert = yes
+
+    parent_done_callback: (el) ->
+        if el.parent is el.parent.builder
+            bool = (not el.parent.parent? or
+                       (el.parent.parent is el.parent.parent?.builder and # FIXME recursive?
+                        el.parent.parent?._browser_insert is true))
+            if bool and el.parent._browser_insert is true
+                @animation.push(el._browser_insert)
+            else
+                el._browser_insert?()
+        else
+            @animation.push(el._browser_insert)
+
+    replace_callback: (oldtag, newtag) ->
+        @fn.replace(oldtag, newtag)

 # exports

 module.exports = {
-    Adapter:BrowserAdapter
-    fn:defaultfn
+    Adapter:BrowserAdapter,
+    fn:defaultfn,
 }


Guard against no off

Guard against no off

--- lib/tab-view.coffee
+++ lib/tab-view.coffee
@@ -33,7 +33,7 @@
       #TODO Remove once old events are no longer supported
       @item.on('title-changed', titleChangedHandler)
       @titleSubscription = dispose: =>
-        @item.off('title-changed', titleChangedHandler)
+        @item.off?('title-changed', titleChangedHandler)

     iconChangedHandler = =>
       @updateIcon()
@@ -45,7 +45,7 @@
       #TODO Remove once old events are no longer supported
       @item.on('icon-changed', iconChangedHandler)
       @iconSubscription = dispose: =>
-        @item.off('icon-changed', iconChangedHandler)
+        @item.off?('icon-changed', iconChangedHandler)

     modifiedHandler = =>
       @updateModifiedStatus()
@@ -56,7 +56,7 @@
       #TODO Remove once old events are no longer supported
       @item.on('modified-status-changed', modifiedHandler)
       @modifiedSubscription = dispose: =>
-        @item.off('modified-status-changed', modifiedHandler)
+        @item.off?('modified-status-changed', modifiedHandler)

     @configSubscription = atom.config.observe 'tabs.showIcons', =>
       @updateIconVisibility()


:art: One line this

:art: One line this

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -35,8 +35,7 @@
     @ready = true

   updateCurrentEditor: (currentPaneItem) =>
-    return unless currentPaneItem?
-    return if currentPaneItem is @editor
+    return if !currentPaneItem? or currentPaneItem is @editor

     @editorSubscriptions?.dispose()
     @editorSubscriptions = null


correctly get file from current buffer in onDidSave event

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -46,7 +46,7 @@
         try
             buffer = editor.getBuffer()
             buffer.onDidSave (e) =>
-                file = e.file
+                file = buffer.file
                 if file? and file
                     sendHeartbeat(file, true)
             buffer.onDidChange (e) =>


Fix problem if number had comma in it. #6

--- data_object.coffee
+++ data_object.coffee
@@ -6,6 +6,8 @@
   if isNaN(val)
     # check for negative signs or parenthases.
     is_negative = if (val.match("-") || val.match(/\(.*\)/)) then -1 else 1
+    # remove any commas
+    val = val.replace(/,/g, "")
     # return just the number and make it negative if needed.
     +(val.match(/\d+.?\d*/)[0]) * is_negative
   else


use retrieve instead of recover

use retrieve instead of recover

--- server/controllers/authentication.coffee
+++ server/controllers/authentication.coffee
@@ -73,7 +73,7 @@
     counter = 0
     retrievePolyglot = (cb) =>
         if counter is 5
-            cb "Cannot recover polyglot"
+            cb "Cannot retrieve polyglot"
         else
             polyglot = localization.getPolyglot()
             if polyglot?.t?
@@ -81,7 +81,7 @@
             else
                 setTimeout () ->
                     counter += 1
-                    recoverPolyglot cb
+                    retrievePolyglot cb
                 , 500

     User.first (err, user) ->


Server: add app port

Server: add app port

--- components/Server.coffee
+++ components/Server.coffee
@@ -9,6 +9,7 @@
       listen: new noflo.Port 'number'
       close: new noflo.Port 'number'
     @outPorts =
+      app: new noflo.Port 'object'
       server: new noflo.Port 'object'
       error: new noflo.Port 'object'

@@ -28,10 +29,15 @@
     if @servers[port]
       @sendError("Port #{port} already in use")
       return
-    server = express()
     try
-      server.listen(port)
-      @servers[port] = server
+      app = express()
+      server = app.listen(port)
+      @servers[port] =
+        app: app
+        server: server
+      if @outPorts.app.isAttached()
+        @outPorts.app.send(app)
+        @outPorts.app.disconnect()
       if @outPorts.server.isAttached()
         @outPorts.server.send(server)
         @outPorts.server.disconnect()
@@ -42,7 +48,7 @@
     unless @servers[port]
       @sendError("Port #{port} not listened to")
       return
-    @servers[port].close()
+    @servers[port].server.close()
     delete @servers[port]

   shutdown: ->


Change name variable: records

Change name variable: records

--- source/class/molecule.coffee
+++ source/class/molecule.coffee
@@ -16,7 +16,7 @@

   @type = "Molecule"

-  _entities: []
+  _records: []

   constructor: (@attributes) ->
     super
@@ -35,17 +35,19 @@
       @_addAtomEntity entity for entity in entities

   # Entities
-  _addAtomEntity: (entity) =>
+  _addAtomEntity: (entity, record = true) =>
     attributes = entity: entity
-    for property in ["events", "callbacks"]
-      attributes[property] = @attributes.bind[property] if @attributes.bind[property]?
+    for property in ["events", "callbacks"] when @attributes.bind[property]?
+      attributes[property] = @attributes.bind[property]

     attributes = Atoms.Core.Helper.mix attributes, @default.children?[@attributes.entityAtom]
-    @_entities.push @appendChild "Atom.#{@attributes.bind.atom}", attributes
+    atom = @appendChild "Atom.#{@attributes.bind.atom}", attributes
+    @_records.push atom if record
+    atom

   _removeAtomsEntities: ->
-    entity.el.remove() for entity in @_entities
-    @_entities = []
+    entity.el.remove() for entity in @_records
+    @_records = []

   _bindEntityEvents : ->
     entity = Atoms.Entity[@attributes.bind.entity]


more house keeping

more house keeping

--- source/javascripts/master-map.coffee
+++ source/javascripts/master-map.coffee
@@ -20,7 +20,7 @@
       name = if /county/i.test(p.name) then p.name else "#{p.name} County"
       "
#{name}

#{p[starCount] || 0} athletes.
"
     else if d.weight?
-      "
#{d.stars} ☆ #{d.name} - #{d.year}

#{d.weight}lb #{d.position} from #{d.school} in #{d.location}"
+      "
#{d.stars} ★ #{d.name} - #{d.year}

#{d.weight}lb #{d.position} from #{d.school} in #{d.location}"
     else
       "
#{d.team}

#{d.stadium} in #{d.city}, #{d.state}
"

@@ -60,7 +60,7 @@
       .data(recruitFeatures, (d) -> "#{d.properties.name}:#{d.properties.school}")

     connections.enter().append('path')
-      .attr('class', 'connection')
+      .attr('class', (d) -> "connection stars#{d.properties.stars}")
       .attr('d', path)
       .style('stroke', (d) -> colors[d.properties.stars - 1])

@@ -72,7 +72,7 @@
     recruitNodes.enter().append('circle')
       .attr('cx', (d) -> d.coordinates[0])
       .attr('cy', (d) -> d.coordinates[1])
-      .attr('r', 2)
+      .attr('r', 4)
       .style('fill', (d) -> colors[d.stars - 1])
       .attr('class', 'recruit')
       .on('mouseover', tip.show)
@@ -80,10 +80,14 @@

     recruitNodes.exit().remove()

-  $.when($.ajax('/data/counties.json'),
-         $.ajax('/data/schools.csv'),
-         $.ajax('/data/recruits.csv')).then (r1, r2, r3) ->
-
+  # Draw the base map
+  #
+  # r1 - Nation, State and County polygons
+  # r2 - Schools
+  # r3 - Recruits
+  #
+  # Returns nothing
+  drawMap = (r1, r2, r3) ->
     usa      = r1[0]
     schools = d3.csv.parse r2[0]
     recruits = d3.csv.parse r3[0]
@@ -96,7 +100,7 @@
     nation   = topojson.mesh usa, usa.objects.nation

     # Set the fill domain based on the total number of recruits
-    fill.domain [0.1, d3.max(counties.features, (d) -> d.properties[starCount])]
+    fill.domain [0.2, d3.max(counties.features, (d) -> d.properties[starCount])]

     # Auto scale map based on bounds
     projection.scale(1).translate([0, 0])
@@ -128,28 +132,25 @@
         stars = d.properties[starCount] || 0
         if stars > 0 then fill(stars || 0) else '#333')

-    # Add states mesh
-    zoomGroup.append('path')
-      .datum(states)
-      .attr('class', 'states')
-      .attr('d', path)
-
-    # Add nation mesh
-    zoomGroup.append('path')
-      .datum(nation)
-      .attr('class', 'nation')
-      .attr('d', path)
+    # Add states and nation
+    zoomGroup.append('path').datum(states).attr('class', 'states').attr('d', path)
+    zoomGroup.append('path').datum(nation).attr('class', 'nation').attr('d', path)

     zoomGroup.selectAll('.schools')
       .data(schools)
-    .enter().append('circle')
-      .attr('cx', (d) -> d.position[0])
-      .attr('cy', (d) -> d.position[1])
-      .attr('r', 4)
+    .enter().append('rect')
+      .attr('x', (d) -> d.position[0])
+      .attr('y', (d) -> d.position[1])
+      .attr('width', 7)
+      .attr('height', 7)
       .attr('class', 'school')
       .classed('gt', (d) -> d.name == 'Georgia Tech')
       .on('mouseover', tip.show)
       .on('mouseout', tip.hide)
       .on('click', drawRecruitPathsToSchool)

+  $.when($.ajax('/data/counties.json'),
+         $.ajax('/data/schools.csv'),
+         $.ajax('/data/recruits.csv')).then(drawMap)
+
 $(render)


Search with select multiple fix

Search with select multiple fix

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -175,7 +175,9 @@

       value = $input.val()

-      if value.startsWith('"') && value.endsWith('"')
+      if Array.isArray(value)
+        # Nothing
+      else if value.startsWith('"') && value.endsWith('"')
         value = value.substring(1, value.length-1)
       else
         value = $.trim(value)


Also search on paste

Also search on paste

--- app/assets/javascripts/effective_datatables/initialize.js.coffee
+++ app/assets/javascripts/effective_datatables/initialize.js.coffee
@@ -161,6 +161,7 @@
           $input.on 'change', (event) -> dataTableSearch($(event.currentTarget))
         else if $input.is('input')
           $input.delayedChange ($input) -> dataTableSearch($input)
+          $input.on('paste', -> dataTableSearch($input))

     # Do the actual search
     dataTableSearch = ($input) ->   # This is the function called by a select or input to run the search


Default to chrome as browser

Default to chrome as browser

Because it's easier to get going without extra dependencies.


Minor improvement.

Minor improvement.

--- lib/game.coffee
+++ lib/game.coffee
@@ -30,7 +30,7 @@
 	return [] if str is null

 	ways = str.split '|'
-	for i in [0...ways.length]
+	for i of ways
 		s = ways[i].split '='
 		ways[i] = {
 			target: parseInt(s[1], 10)


destory Hole

destory Hole


ApiGenerator: support returns definition for static methods as well

--- scripts/api-generator.coffee
+++ scripts/api-generator.coffee
@@ -165,6 +165,11 @@

   for method, signatures of methods.statik

+    response      =
+      description : 'Request processed succesfully'
+      schema      :
+        $ref      : '#/definitions/DefaultResponse'
+
     if hasParams = signatures.length > 1 or signatures[0].split(',').length > 1
       parameters = [{ $ref: '#/parameters/bodyParam' }]
       examples = docs[name]['static'][method]?.examples ? []
@@ -178,6 +183,11 @@
             description: 'body of the request'
           }
         ]
+      if (returns = docs[name]['static'][method]?.returns) and Object.keys(returns).length
+        response =
+          description: returns.description
+          schema: { $ref: "#/definitions/#{returns.type}" }
+
     else
       parameters = null

@@ -189,9 +199,8 @@
         description: docs[name]['static'][method]?.description ? ''
         responses:
           '200':
-            description: 'Request processed succesfully'
-            schema:
-              $ref: '#/definitions/DefaultResponse'
+            description: response.description
+            schema: response.schema
           '401':
             description: 'Unauthorized request'
             schema:


Fix rebase

Fix rebase

--- lib/main.coffee
+++ lib/main.coffee
@@ -55,7 +55,7 @@
   # or the users globally installed sass-lint version
   findExecutable: ->
     {spawnSync} = require 'child_process'
-    {getPath} = require 'consistent-path'
+    consistentEnv = require 'consistent-env'
     if not @globalSassLint
       return require path.join(__dirname, '..', 'node_modules', 'sass-lint')
     if @globalPath is '' and prefixPath is null


Test for windows newlines.

Test for windows newlines.

--- spec/chunked-line-reader-spec.coffee
+++ spec/chunked-line-reader-spec.coffee
@@ -80,3 +80,37 @@
       expect(allLines.length).toEqual sample.length
       for line, i in allLines
         expect(line).toEqual sample[i]
+
+  it "works with windows newlines at the end", ->
+    rootPath = fs.realpathSync("spec/fixtures/many-files/sample-with-windows-line-endings.js")
+
+    reader = new ChunkedLineReader(rootPath)
+    reader.on 'end', endHandler = jasmine.createSpy('end handler')
+
+    allLines = []
+    reader.on 'data', (chunk) ->
+      allLines = allLines.concat(chunk.toString().split('\r\n'))
+
+    waitsFor ->
+      endHandler.callCount > 0
+
+    runs ->
+      sample = [
+        'var quicksort = function () {'
+        '  var sort = function(items) {'
+        '    if (items.length <= 1) return items;'
+        '    var pivot = items.shift(), current, left = [], right = [];'
+        '    while(items.length > 0) {'
+        '      current = items.shift();'
+        '      current < pivot ? left.push(current) : right.push(current);'
+        '    }'
+        '    return sort(left).concat(pivot).concat(sort(right));'
+        '  };'
+        ''
+        '  return sort(Array.apply(this, arguments));'
+        '};'
+      ]
+
+      expect(allLines.length).toEqual sample.length
+      for line, i in allLines
+        expect(line).toEqual sample[i]


Remove reference to considered but unused library

--- settings.coffee
+++ settings.coffee
@@ -23,7 +23,6 @@
             'bower_components/moment/moment.js'
             'bower_components/moment/min/moment-with-locales.js'
             'bower_components/moment/locale/*.js'
-            'bower_components/angular-bootstrap-nav-tree/dist/abn_tree_directive.js'
         ]
         APISecret: APISecret
         APIAddress: 'http://soul-weaver.tk'


strings are not bools #50

strings are not bools #50

--- src/command.coffee
+++ src/command.coffee
@@ -315,7 +315,8 @@
   return

 # The compile-time options to pass to the CoffeeScript compiler.
-compileOptions = (filename) -> {filename, bare: opts.bare, contracts: opts.contracts || process.env.CONTRACTS_COFFEE_ENABLED}
+enable_contracts = opts.contracts or (if process.env.CONTRACTS_COFFEE_ENABLED is "1" then true else false)
+compileOptions = (filename) -> {filename, bare: opts.bare, contracts: enable_contracts}

 # Start up a new Node.js instance with the arguments in `--nodejs` passed to
 # the `node` binary, preserving the other options.


Bugfix: handling of address number letters.

--- src/control.coffee
+++ src/control.coffee
@@ -361,6 +361,8 @@
                                 return letterMatch() or numberEndMatch()
                             if exactMatch.length != 1
                                 throw new Error 'Too many address matches'
+                            else
+                                position = exactMatch[0]

                         slug = position.slugifyAddress()
                         [originalMuni, originalStreet, originalNumber] = @_getWindowLocationPath()[-3..]


Support for `repeat: x` on schema-specs

Support for `repeat: x` on schema-specs

--- spec/schema/main-spec.coffee
+++ spec/schema/main-spec.coffee
@@ -6,6 +6,57 @@
   parts = key.split('.')
   obj = obj[parts.shift()] while parts.length
   obj
+
+tryTest = (test, refs, schema) ->
+  error = null
+
+  sample = try
+    jsf(schema, refs)
+  catch e
+    error = String(e)
+    throw e unless test.throws
+
+  if test.dump
+    console.log 'IN', JSON.stringify(schema, null, 2)
+    console.log 'OUT', JSON.stringify(sample, null, 2)
+    return
+
+  if test.hasNot
+    expect(JSON.stringify sample).not.toContain test.hasNot
+
+  if test.type
+    expect(sample).toHaveType test.type
+
+  if typeof test.throws is 'string'
+    if typeof error isnt 'string'
+      throw new Error """
+        THIS SHOULD NOT HAPPEN --- #{error}
+
+        #{JSON.stringify(schema, null, 2)}
+
+        #{JSON.stringify(sample, null, 2)}
+      """
+
+    expect(error).toMatch new RegExp(test.throws, 'i')
+
+  # strict checks
+  if test.nonEmpty
+    expect(sample).toHaveNonEmptyProps()
+
+  if test.valid
+    try
+      expect(sample).toHaveSchema [schema, refs]
+    catch e
+      throw new Error """
+        #{suite.description} (#{e})
+
+        #{JSON.stringify(sample, null, 2)}
+
+        #{JSON.stringify(schema, null, 2)}
+      """
+
+  if test.equal
+    expect(sample).toEqual test.equal

 glob.sync("#{__dirname}/**/*.json").forEach (file) ->
   suite = JSON.parse(fs.readFileSync(file))
@@ -36,52 +87,7 @@
             else
               ref

-          error = null
-
-          sample = try
-            jsf(schema, refs)
-          catch e
-            error = String(e)
-            throw e unless test.throws
-
-          if test.dump
-            console.log 'IN', JSON.stringify(schema, null, 2)
-            console.log 'OUT', JSON.stringify(sample, null, 2)
-            return
-
-          if test.hasNot
-            expect(JSON.stringify sample).not.toContain test.hasNot
-
-          if test.type
-            expect(sample).toHaveType test.type
-
-          if typeof test.throws is 'string'
-            if typeof error isnt 'string'
-              throw new Error """
-                THIS SHOULD NOT HAPPEN --- #{error}
-
-                #{JSON.stringify(schema, null, 2)}
-
-                #{JSON.stringify(sample, null, 2)}
-              """
-
-            expect(error).toMatch new RegExp(test.throws, 'i')
-
-          # strict checks
-          if test.nonEmpty
-            expect(sample).toHaveNonEmptyProps()
-
-          if test.valid
-            try
-              expect(sample).toHaveSchema [schema, refs]
-            catch e
-              throw new Error """
-                #{suite.description} (#{e})
-
-                #{JSON.stringify(sample, null, 2)}
-
-                #{JSON.stringify(schema, null, 2)}
-              """
-
-          if test.equal
-            expect(sample).toEqual test.equal
+          # support for "exhaustive" testing, increase or set in .json spec
+          # for detecting more bugs quickly by executing the same test N-times
+          nth = test.repeat or 1
+          tryTest(test, refs, schema) while nth--


Remove extra blank line

Remove extra blank line

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -3378,7 +3378,6 @@
             expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
             editor.insertText('foo')
             expect(editor.indentationForBufferRow(2)).toBe editor.indentationForBufferRow(1) + 1
-

       describe 'when scoped settings are used', ->
         coffeeEditor = null


Changed markdown loader

Changed markdown loader

--- lib/isomorphic/wrappers/md.cjsx
+++ lib/isomorphic/wrappers/md.cjsx
@@ -8,6 +8,6 @@
     post = @props.page.data

-
{post.attributes.title}

+
{post.title}


fix deprecated loadUrl

fix deprecated loadUrl

--- index.cjsx
+++ index.cjsx
@@ -26,7 +26,7 @@
     width: 820
     height: 650
     # forceMinimize: forceMinimize
-  window.akashicRecordsWindow.loadUrl "file://#{__dirname}/index.html"
+  window.akashicRecordsWindow.loadURL "file://#{__dirname}/index.html"
   if process.env.DEBUG?
     window.akashicRecordsWindow.openDevTools
       detach: true


Save changes to node properties

Save changes to node properties

--- graphedit/coffee/graphedit.coffee
+++ graphedit/coffee/graphedit.coffee
@@ -144,7 +144,7 @@
     @drawSelection()

   _propertyForm : (data) =>
-    str = '<form class="form-horizontal" role="form">'
+    str = '<form class="form-horizontal graphedit-property-form" role="form">'

     str += '
'
     for k,v of data
@@ -152,7 +152,7 @@

           #{k}

-            <input id="#{k}" class="form-control input-sm" value="#{v}">
+            <input id="#{k}" name="#{k}" class="form-control input-sm" value="#{v}">

       """
@@ -177,6 +177,7 @@
   # display provided data, or whatever is currently selected
   displayData : (data) =>
     $('.add_property').off 'click'
+    $('.graphedit-property-form').off 'submit'

     if data
       @DATAVIEW.html(@_propertyForm(data.properties))
@@ -192,6 +193,7 @@
       @DATAVIEW.html('
Multiple items selected
')

     $('.add_property').on 'click', @clickNewProperty
+    $('.graphedit-property-form').on 'submit', @submitPropertyForm

   # display highlight on selected items
   drawSelection : () =>
@@ -414,7 +416,7 @@
   #add a new property to the form
   clickNewProperty: () =>
     str = """
-

+

           <input class="form-control input-sm">

@@ -424,6 +426,38 @@

     """
     @DATAVIEW.find('.properties').append(str)
+
+  submitPropertyForm: (e) =>
+    e.preventDefault();
+
+    data = {}
+
+    #extract existing properties (with name attrs)
+    form = $(e.target).serializeArray()
+
+    $.each form, (i, d) =>
+      data[d.name] = d.value
+
+    #add new properties
+    $(e.target).find('.graphedit-new-property').each (i, elm) =>
+      key = $(elm).find('input').first().val()
+      value = $(elm).find('input').last().val()
+      if key.length > 0
+        data[key] = value
+
+    #what's being edited?
+    target = null
+    if @selected_nodes.length == 1 and @selected_edges.length == 0
+      target = @selected_nodes[0]
+    else if @selected_nodes.length == 0 and @selected_edges.length == 1
+      target = @selected_edges[0]
+
+    if target == null
+      throw "Not sure what you were editing"
+
+    $.extend d3.select(target).data()[0].properties, data
+
+    @restart

 # GRAPHEDIT PLUGIN DEFINITION


rm i18n

rm i18n

--- index.cjsx
+++ index.cjsx
@@ -1,7 +1,6 @@
 {join} = require 'path-extra'
 {_, $, $$, React, ReactBootstrap, FontAwesome, layout} = window
 {Grid, Row, Col, Input, Panel, OverlayTrigger, Tooltip} = ReactBootstrap
-i18n = require './node_modules/i18n'
 _ = require 'underscore'
 _.mixin require 'underscore.inflection'


One more fix for duplicate comments

One more fix for duplicate comments

--- server/api/setOutcome.coffee
+++ server/api/setOutcome.coffee
@@ -56,16 +56,16 @@

 storeToDatabase = (req, res) ->
-    logger.info("Force-storing to database result #{req.params.submitId}")
     [fullSubmitId, runId, problemId] = req.params.submitId.match(/(\d+r\d+)(p\d+)/)
     submit = await Submit.findById(req.params.submitId)
     problem = await Problem.findById(problemId)
     if req.body.result in ["AC", "IG", "DQ"]
+        logger.info("Force-storing to database result #{req.params.submitId}")
         submit.outcome = req.body.result
         submit.force = true
     if req.body.comment
-        comment = entities.encode(req.body.comment)
-        if not (comment in submit.comments)
+        if not (req.body.comment in submit.comments.map(entities.decode))
+            comment = entities.encode(req.body.comment)
             logger.info("Force-storing to database comment for #{req.params.submitId}")
             rndId = Math.floor(Math.random() * 1000000)
             newComment = new SubmitComment


Avoid evaluating announcements twice when there are none

--- source/javascripts/index/schedule.coffee
+++ source/javascripts/index/schedule.coffee
@@ -33,6 +33,7 @@
             return "
#{announcement}
"
           when 2
             return "
#{announcement.join("

")}
"
+    false
   )()
   holidayTableWidths: (length) -> @_holidayTableWidths ?= switch length
     when 1
@@ -52,7 +53,7 @@
     widths = @holidayTableWidths(tables.length)
     html = for i in [0...(tables.length)]
       "
#{tables[i]}
"
-    $('.chagim-tables').html(html.join('') + (@announcementHtml() ? ''))
+    $('.chagim-tables').html(html.join('') + (@announcementHtml() || ''))
   writeSchedule: ->
     @writeWeekTable()
     @writeSedra()


Color

Color

--- site/app.coffee
+++ site/app.coffee
@@ -107,6 +107,113 @@
 NAMES = {"FL": "Florida", "cen4": "West North Central", "hhs9": "HHS Region 9", "MT": "Montana", "WV": "West Virginia", "RI": "Rhode Island", "AR": "Arkansas", "VA": "Virginia", "cen7": "West South Central", "IN": "Indiana", "NC": "North Carolina", "IA": "Iowa", "MN": "Minnesota", "cen2": "Middle Atlantic", "DE": "Delaware", "PA": "Pennsylvania", "hhs7": "HHS Region 7", "nat": "US National", "hhs10": "HHS Region 10", "LA": "Louisiana", "MD": "Maryland", "AK": "Alaska", "CO": "Colorado", "WI": "Wisconsin", "ID": "Idaho", "OK": "Oklahoma", "hhs3": "HHS Region 3", "hhs2": "HHS Region 2", "hhs1": "HHS Region 1", "cen1": "New England", "KY": "Kentucky", "ME": "Maine", "CA": "California", "cen5": "South Atlantic", "WY": "Wyoming", "ND": "North Dakota", "NY": "New York", "MA": "Massachusetts", "UT": "Utah", "DC": "District of Columbia", "MS": "Mississippi", "hhs6": "HHS Region 6", "GA": "Georgia", "AL": "Alabama", "HI": "Hawaii", "hhs4": "HHS Region 4", "AZ": "Arizona", "CT": "Connecticut", "KS": "Kansas", "NH": "New Hampshire", "cen8": "Mountain", "TX": "Texas", "NV": "Nevada", "TN": "Tennessee", "NJ": "New Jersey", "MI": "Michigan", "hhs8": "HHS Region 8", "NM": "New Mexico", "IL": "Illinois", "cen3": "East North Central", "VT": "Vermont", "WA": "Washington", "SD": "South Dakota", "NE": "Nebraska", "hhs5": "HHS Region 5", "SC": "South Carolina", "cen6": "East South Central", "OR": "Oregon", "cen9": "Pacific", "MO": "Missouri", "OH": "Ohio"}
 REGION2STATE = {"hhs1": ['ME', 'MA', 'NH', 'VT', 'RI', 'CT'], "hhs2": ['NY', 'NJ'], "hhs3": ['PA', 'DE', 'DC', 'MD', 'VA', 'WV'], "hhs4": ['NC', 'SC', 'GA', 'FL', 'KY', 'TN', 'MS', 'AL'], "hhs5": ['MI', 'IL', 'IN', 'OH', 'WI', 'MN'], "hhs6": ['LA', 'AR', 'OK', 'TX', 'NM'], "hhs7": ['IA', 'MO', 'NE', 'KS'], "hhs8": ['ND', 'SD', 'CO', 'WY', 'MT', 'UT'], "hhs9": ['NV', 'CA', 'HI', 'AZ'], "hhs10": ['WA', 'OR', 'AK', 'ID'], "cen1": ['ME', 'MA', 'NH', 'VT', 'RI', 'CT'], "cen2": ['PA', 'NY', 'NJ'], "cen3": ['WI', 'MI', 'IN', 'IL', 'OH'], "cen4": ['ND', 'SD', 'NE', 'KS', 'MN', 'IA', 'MO'], "cen5": ['DE','MD','DC','WV','VA','NC','SC','GA','FL'], "cen6": ['KY','TN', 'MS', 'AL'], "cen7": ['OK', 'AR', 'LA', 'TX'], "cen8": ['MT', 'ID', 'WY', 'CO', 'UT', 'NV', 'AZ', 'NM'], "cen9": ['WA', 'OR', 'CA', 'AK', 'HI']}

+POPULATION = {
+  'AK':   731449, 'AL':  4822023, 'AR':  2949131, 'AZ':  6553255,
+  'CA': 38041430, 'CO':  5187582, 'CT':  3590347, 'DC':   632323,
+  'DE':   917092, 'FL': 19317568, 'GA':  9919945, 'HI':  1392313,
+  'IA':  3074186, 'ID':  1595728, 'IL': 12875255, 'IN':  6537334,
+  'KS':  2885905, 'KY':  4380415, 'LA':  4601893, 'MA':  6646144,
+  'MD':  5884563, 'ME':  1329192, 'MI':  9883360, 'MN':  5379139,
+  'MO':  6021988, 'MS':  2984926, 'MT':  1005141, 'NC':  9752073,
+  'ND':   699628, 'NE':  1855525, 'NH':  1320718, 'NJ':  8864590,
+  'NM':  2085538, 'NV':  2758931, 'NY': 19570261, 'OH': 11544225,
+  'OK':  3814820, 'OR':  3899353, 'PA': 12763536, 'RI':  1050292,
+  'SC':  4723723, 'SD':   833354, 'TN':  6456243, 'TX': 26059203,
+  'UT':  2855287, 'VA':  8185867, 'VT':   626011, 'WA':  6897012,
+  'WI':  5726398, 'WV':  1855413, 'WY':   576412,
+}
+
+# Non-Influennza Week Calculation
+NON_INFLUENZA_WEEK_SEASON = 2015
+
+calculateMean = (values) ->
+  sum = 0
+  for v in values
+    sum += v
+  return sum/values.length
+
+calculateStdev = (values, mean) ->
+  sum = 0
+  for v in values
+    sum += (v - mean)**2
+  return (sum/(values.length-1))**0.5
+
+calculateNonInfluenzaData = (epidata) ->
+  NonInfluenzaData = {}
+  mappedData = {}
+  for loc in LOCATIONS
+    mappedData[loc] = {}
+  for row in epidata
+    wk = row.epiweek%100
+    mappedData[row.location][wk] = row.value
+  for region in HHS_REGIONS
+    weeks = nonInfluenzaWeekData[region]
+    for state in REGION2STATE[region]
+      values = []
+      for week in weeks
+        values.push(mappedData[state][week])
+      mean = calculateMean(values)
+      stdev = calculateStdev(values, mean)
+      NonInfluenzaData[state] = [mean, stdev]
+  return NonInfluenzaData
+
+calculateColor = (NonInfluenzaData, epidata, ep) ->
+  mappedData = {}
+  levelData = {}
+  colorData = {}
+  for row in epidata
+    if row.epiweek = ep
+      mappedData[row.location] = row.value
+  for state in STATES
+    [mean, stdev] = NonInfluenzaData[state]
+    ili = mappedData[state]
+    level = activity_level(ili, mean, stdev)
+    colorData[state] = level2Color(level)
+    levelData[state] = level
+  for region in REGIONS
+    pop_total = 0
+    for state in REGION2STATE[region]
+      pop_total += POPULATION[state]
+    level = 0
+    for state in REGION2STATE[region]
+      level += (POPULATION[state]/pop_total) * levelData[state]
+    colorData[region] = level2Color(level)
+    levelData[region] = level
+  pop_total = 0
+  for state in STATES
+    pop_total += POPULATION[state]
+  level = 0
+  for state in STATES
+    level += (POPULATION[state]/pop_total) * levelData[state]
+  colorData['nat'] = level2Color(level)
+  levelData['nat'] = level
+  return colorData
+
+activity_level = (ili, mean, stdev) ->
+  l = (ili-mean)/stdev
+  if l < 1.1
+    return 1
+  if l < 2.5
+    return 2
+  if l < 4.0
+    return 3
+  if l < 4.9
+    return 4
+  if l < 6.0
+    return 5
+  if l < 7.6
+    return 6
+  if l < 8.0
+    return 7
+  if l < 9.5
+    return 8
+  if l < 12
+    return 9
+  return 10
+
+level2Color = (level) ->
+  return Math.max(0, Math.min(10, level)) / 10
+
 Date.prototype.getWeek = () ->
   stdDate = new Date(2016, 0, 3)
   rst = (Math.ceil((((this - stdDate) / 86400000) + stdDate.getDay() + 1) / 7)%52)
@@ -165,12 +272,9 @@
     Epidata.nowcast(handler, locations, Epidata.range(epiweek1, epiweek2))
   else
     fakeData = []
-    for i in [0...3]
-      for loc in locations
-        row = getFakeRow(loc, i)
-        if i == 0
-          row.epiweek = epiweek2
-        fakeData.push(row)
+    for location in locations
+      for i in [0...280]
+        fakeData.push(getFakeRow(location, i))
     callback = () -> handler(1, 'debug', fakeData)
     delay = 250 + Math.round(Math.random() * 500)
     window.setTimeout(callback, delay)
@@ -338,19 +442,24 @@
       date.setDate(date.getDate() + 6)
       datestr = datestr + "-" + date2String(date) + ")"
       @dataTimeline.html("Nowcasting epi-week " + epiweek2%100 + " " + datestr)
-      callback = (epidata) =>
-        @colors = {}
-        @mapData = {}
-        for row in epidata
-          if row.epiweek == epiweek2
-            ili = row.value
-            v = Math.max(0, Math.min(5, ili)) / 5
-            c = ('0' + Math.round(0x3f + v * 0xc0).toString(16)).slice(-2)
-            @colors[row.location] = '#' + c + '4040'
-            @mapData[row.location] = row
-        @renderMap()
-      handler = getEpidataHander(callback)
-      Epidata_nowcast_multi(handler, LOCATIONS, epiweek1, epiweek2)
+      callback1 = (epidata) =>
+        NonInfluenzaData = calculateNonInfluenzaData(epidata)
+        callback2 = (epidata) =>
+          @colors = {}
+          @mapData = {}
+          colorData = calculateColor(NonInfluenzaData, epidata, epiweek2)
+          for row in epidata
+            if row.epiweek == epiweek2
+              ili = row.value
+              v = colorData[row.location]
+              c = ('0' + Math.round(0x3f + v * 0xc0).toString(16)).slice(-2)
+              @colors[row.location] = '#' + c + '4040'
+              @mapData[row.location] = row
+          @renderMap()
+        handler = getEpidataHander(callback2)
+        Epidata_nowcast_multi(handler, LOCATIONS, epiweek1, epiweek2)
+      handler = getEpidataHander(callback1)
+      Epidata_nowcast_multi(handler, STATES, NON_INFLUENZA_WEEK_SEASON*100+40, (NON_INFLUENZA_WEEK_SEASON+1)*100+20)
     handler = getEpidataHander(callback)
     Epidata_nowcast_single(handler, 'nat')

@@ -798,4 +907,16 @@

   onFluviewReceived: (ilidata) ->
     @truthData = ilidata
-    @resizeCanvas()+    @resizeCanvas()
+
+
+
+
+
+
+
+
+
+
+
+


Labeling regions on map

Labeling regions on map

--- site/app.coffee
+++ site/app.coffee
@@ -23,7 +23,6 @@
   return (x, y, z) -> [w2 + x * a + y * b, h2 + x * d + y * e + z * f]

 #Pre-process geodata
-
 # Move Hawaii and Alaska closer to Mainland, also shrink Alaska
 indexes = []
 for poly in geodata.locations['HI'].paths
@@ -50,8 +49,6 @@
   [lat, lon] = geodata.points[i]
   [newlat, newlon] = [(centerY+lat)/2, (centerX+lon)/2]
   geodata.points[i] = [newlat, newlon]
-
-

 for i in [0...geodata.points.length]
   [lat, lon] = geodata.points[i]
@@ -313,7 +310,7 @@
     }
     @ecef2ortho = get_ecef2ortho(@dlat, @dlon, @zoom, w, h)
     for loc in @locations
-      #ctx.fillStyle = '#' + ('00000' + Math.floor(Math.random() * 0x1000000).toString(16)).slice(-6)
+      [cX, cY] = @locCenterOnMap(loc)
       for poly in geodata.locations[loc].paths
         ctx.beginPath()
         ln = 0
@@ -324,6 +321,9 @@
         ctx.fillStyle = @colors?[loc] ? '#ccc'
         ctx.fill()
         ctx.stroke()
+      ctx.font = 24 * Math.min(1, w / 3000) + 'px sans-serif'
+      ctx.fillStyle = 'Cyan'
+      ctx.fillText(loc, cX, cY)
     return 0

   renderChart: () ->
@@ -418,6 +418,15 @@
           return loc
     return null

+  locCenterOnMap: (loc) ->
+    [centerX, centerY, k] = [0, 0, 0]
+    for poly in geodata.locations[loc].paths
+      for idx in [1..poly.length]
+        [x, y] = @ecef2ortho(geodata.points[poly[idx % poly.length]]...)
+        [centerX, centerY] = [centerX + x, centerY + y]
+        k++
+    return [centerX/k, centerY/k]
+
   fetchNowcast: (loc) ->
     @chartData = null
     callback = (epidata) => @onNowcastReceived(epidata)


menu/spawn: Add Spawn...

menu/spawn: Add Spawn...

--- Oghma/menu/spawn.coffee
+++ Oghma/menu/spawn.coffee
@@ -24,6 +24,33 @@
 # @author Christopher Alfeld (calfeld@calfeld.net)
 # @copyright 2013 Christopher Alfeld
 Oghma.Menu.spawn = ( O ) ->
+  spawn_dialog = ->
+    dialog = Ext.create( 'Oghma.Ext.EditObject',
+      object:
+        name:      'Example'
+        index:     ''
+        size:      1
+        owner:     O.me().gets( 'name' )
+        group:     'undetermined'
+        category:  'undetermined'
+        fill:      O.me().gets( 'primary' )
+        stroke:    O.me().gets( 'secondary' )
+      types:
+        fill:   'color'
+        stroke: 'color'
+        index:  'string-blank'
+      title: 'Spawn Avatar'
+      onSave: ( avatarinfo ) =>
+        O.table.load_dropper( ( x, y, e) ->
+          avatarinfo.x = x
+          avatarinfo.y = y
+          O.tableverse.create( 'avatar', avatarinfo )
+        )
+        dialog.close()
+      onCancel: ->
+        dialog.close()
+    )
+
   calculate_index = ( name ) ->
     max_existing_index = 0
     O.tableverse.avatar.each( ( avatar ) ->
@@ -35,7 +62,7 @@
     items: [
       {
         text: 'Spawn Example Avatar'
-        handler: ( item, e ) ->
+        handler: ->
           O.table.load_dropper( ( x, y, e ) ->
             O.tableverse.create( 'avatar',
               name: 'Example'
@@ -44,6 +71,11 @@
               y: y
             )
           )
+      },
+      {
+        text: 'Spawn...'
+        handler: ->
+          spawn_dialog().show()
       }
     ]
   )


Add more expectations for multiple roles

--- spec/javascripts/role_jquery_spec.coffee
+++ spec/javascripts/role_jquery_spec.coffee
@@ -21,6 +21,16 @@
     ).toBe(
       $('#element_two').get(0)
     )
+    expect(
+      $('@role_one').get(0)
+    ).toBe(
+      $('#element_two').get(0)
+    )
+    expect(
+      $('@role_two').get(0)
+    ).toBe(
+      $('#element_two').get(0)
+    )

   it 'should allow to combine roles with id and classes', ->
     expect(


Adding mixin comment.

Adding mixin comment.

--- src/element.coffee
+++ src/element.coffee
@@ -129,7 +129,7 @@
     destroy : () ->
         @.trigger('destroyed', this)

-
+# Mix events into the element class.
 timber.extend(timber.Element::, timber.Events)


Added settings page

Added settings page

Round time and reset function added. Does not sync with database

--- client.coffee
+++ client.coffee
@@ -8,6 +8,8 @@
 Ui = require 'ui'
 CSS = require 'css'
 Geoloc = require 'geoloc'
+Form = require 'form'
+Num = require 'num'

 exports.render = ->

@@ -29,6 +31,7 @@
         logContent()
 	if !Geoloc.isSubscribed()
 		Geoloc.subscribe()
+
 	if Geoloc.isSubscribed()
 		state = Geoloc.track()
 		location = state.get('latlong');
@@ -39,7 +42,26 @@
 			marker.addTo(window.map)
 		else
 			log 'location could not be found'
-
+
+
+
+exports.renderSettings = !->
+	Dom.div !->
+		Dom.style Box: "middle", padding: '12px 40px 12px 8px'
+		Dom.div !->
+			Dom.style Flex: 'true'
+			Dom.text tr "Round time in hours "
+		Num.render
+			name: 'time'
+			value: 0||7*24
+	if Db.shared
+		Form.check
+			name: 'restart'
+			text: tr 'Restart'
+			sub: tr 'Check this to destroy the current game and start a new one.'
+
+
+

 # Load the javascript necessary for the map
 loadMap = ->


Removing the utterly pointless splice in scope for the much more reasonable assignment.

--- scope.coffee
+++ scope.coffee
@@ -29,7 +29,7 @@
   # Adds a new variable or overrides an existing one.
   setVar: (name, type) ->
     if @positions.hasOwnProperty name
-      @variables.splice @positions[name], 1, {name, type}
+      @variables[@positions[name]].type = type
     else
       @positions[name] = @variables.length
       @variables.push {name, type}


Remove unneeded variable

Remove unneeded variable

--- src/opac/search_result.coffee
+++ src/opac/search_result.coffee
@@ -173,7 +173,9 @@
 							depth: request.depth
 						, (cns) -> show_callnumber.call @, cns, x.ou_tree
 					) title_id, n
-				$first_title = $('.title:first', $summary_info).focus()
+
+				# Focus on the first title in the result list.
+				$('.title:first', $summary_info).focus()
 				return

 		# Handle keyups for title or author links.


Firewall: Updated country json key.

Firewall: Updated country json key.

--- client/Environments/views/firewall/firewallfilterformview.coffee
+++ client/Environments/views/firewall/firewallfilterformview.coffee
@@ -72,7 +72,7 @@
     selectbox.removeSelectOptions()
     countryList = []
     for country in KD.utils.countries
-      countryList.push { title: country.name, value: country.cca3 }
+      countryList.push { title: country.name, value: country.code }
     selectbox.setSelectOptions countryList

     selectbox.setValue @match  if @type is 'country'


Ace: Fixed saving localfile of ace.

Ace: Fixed saving localfile of ace.

--- client/landing/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Ace.kdapplication/AppView.coffee
@@ -163,7 +163,7 @@
               @getDelegate().openFile FSHelper.createFileFromPath "#{parent.path}/#{name}", yes
               @utils.defer =>
                 newIndex = tabView.getPaneIndex tabView.getActivePane()
-                tabView.removePane tabView.getPaneByIndex newIndex - 1
+                tabView.removePane_ tabView.getPaneByIndex newIndex - 1
                 {ace} = tabView.getActivePane().getOptions().aceView
                 ace.on "ace.ready", =>
                   ace.editor.moveCursorTo oldCursorPosition.row, oldCursorPosition.column


only redraw on sync

only redraw on sync

--- lib/assets/javascripts/stark/model_binding.js.coffee
+++ lib/assets/javascripts/stark/model_binding.js.coffee
@@ -46,10 +46,10 @@
     #   @log "redrawing view: ", @
     #   @redraw()

-    @onR view, "destroy", ->
-      @log "redraw handler fired (destroy) due to model binding on: ", model
-      @log "redrawing view: ", @
-      @redraw()
+    # @onR view, "destroy", ->
+    #   @log "redraw handler fired (destroy) due to model binding on: ", model
+    #   @log "redrawing view: ", @
+    #   @redraw()

   unbind_view: (view) ->
     @bound_views = _.reject(@bound_views, (v) -> v == view)


add missing newline at eof

add missing newline at eof

--- app/assets/javascripts/angular/controllers/shared_folder_list_controller.js.coffee
+++ app/assets/javascripts/angular/controllers/shared_folder_list_controller.js.coffee
@@ -14,4 +14,5 @@
     $location.path("/shared_folders/" + id)

   $scope.addNew = () ->
-    $location.path("/shared_folders/new")+    $location.path("/shared_folders/new")
+


FileFinder: Minor review fixes.

FileFinder: Minor review fixes.

--- client/ide/views/filefinder/idefilefinder.coffee
+++ client/ide/views/filefinder/idefilefinder.coffee
@@ -9,7 +9,6 @@
     @addSubView @input = input = new KDInputView
       type         : 'text'
       placeholder  : 'Type file name to search'
-      callback     : @bound 'search'
       keyup        :
         'esc'      : @bound 'destroy'
         'enter'    : @bound 'handleEnterKey'
@@ -18,7 +17,7 @@

     input.on 'keyup', KD.utils.debounce 300, @bound 'handleKeyUp'
     input.on 'keyup', =>
-      if input.getValue() is '' then input.unsetClass 'has-text'
+      if   input.getValue() is '' then input.unsetClass 'has-text'
       else input.setClass 'has-text'

     @addSubView new KDCustomHTMLView cssClass: 'icon'


keyboard: s/super\+/meta\+/g

keyboard: s/super\+/meta\+/g

--- client/landing/Chat/Views/chatinputwidget.coffee
+++ client/landing/Chat/Views/chatinputwidget.coffee
@@ -7,8 +7,8 @@
       keyup             :
         "up"            : => @emit 'goUpRequested'
         "down"          : => @emit 'goDownRequested'
-        "super+up"      : => @emit 'moveUpRequested'
-        "super+down"    : => @emit 'moveDownRequested'
+        "meta+up"      : => @emit 'moveUpRequested'
+        "meta+down"    : => @emit 'moveDownRequested'
       callback          : ->
         @emit 'messageSent', @getValue()  if @getValue() isnt ''
         @setValue ''


Add library existsence check in the cakefile

--- library/nucleus/cakefile.coffee
+++ library/nucleus/cakefile.coffee
@@ -23,12 +23,16 @@
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ###

+
 _ = require "lodash"
 assert = require "assert"
 colors = require "colors"
 logger = require "winston"
+paths = require "path"
+fs = require "fs"
+
+{rmdirSyncRecursive} = require "wrench"
 {spawn} = require "child_process"
-{rmdirSyncRecursive} = require "wrench"

 # Here follows the definition of a number of constants that define
 # the defaults for some of the options, generally for the ones that
@@ -72,7 +76,10 @@
         process.env["NODE_ENV"] = scoping.toString()
         process.env["log:level"] = logging.toString()
         granite = require "#{__dirname}/../../index"
+        assert resolved = paths.resolve library
+        missingLibrary = "missing library: #{resolved}"
         assert _.isObject(granite), "framework failed"
+        assert fs.existsSync(library), missingLibrary
         compiled = granite.collectPackages no, library
         assert _.isObject(compiled), "invalid library"
         granite.cachedKernel(library).bootstrap()


potential fix for term agreement

potential fix for term agreement

--- @client/auth.coffee
+++ @client/auth.coffee
@@ -944,7 +944,7 @@
     # Note that we don't have server side validation because
     # the questions are all defined on the client.
     @local.errors = []
-    if auth.ask_questions # && auth.form in ['create account', 'create account via invitation']
+    if auth.ask_questions
       questions = customization('auth_questions')
       for question in questions
         if question.required
@@ -968,9 +968,8 @@

       save @local

-    if auth.form in ['create account', 'create account via invitation']
-      if !current_user.tags['considerit_terms.editable']
-        @local.errors.push translator('auth.validation.agree_to_terms', "To proceed, you must agree to the terms")
+    if auth.ask_questions && !current_user.tags['considerit_terms.editable']
+      @local.errors.push translator('auth.validation.agree_to_terms', "To proceed, you must agree to the terms")

     if @local.errors.length == 0
       @local.submitting = true
@@ -984,7 +983,7 @@
           ensureCurrentUserAvatar()

         if auth.form in ['edit profile']
-          @local.saved_successfully = current_user.errors.length == 0 && (@local.errors or []).length == 0
+          @local.saved_successfully = current_user.errors.length + @local.errors.length == 0

         # Once the user logs in, we will stop showing the log-in screen
         else if current_user.logged_in
@@ -1005,6 +1004,8 @@
           data:
             authenticity_token: current_user.csrf
             trying_to: 'update_avatar_hack'
+    else
+      save @local

   componentDidMount : ->
     writeToLog {what: 'accessed authentication'}


Fix permissions bug that prevented the "Login to submit a new idea" button from showing up

--- @client/list.coffee
+++ @client/list.coffee
@@ -92,6 +92,8 @@
           list_key: list_key

       else if !is_collapsed && !@props.fresh
+        permitted = permit('create proposal', list_key)
+
         DIV null,
           UL null,
             for proposal,idx in proposals
@@ -104,7 +106,7 @@
                 category_color: if @props.combines_these_lists then hsv2rgb(colors["list/#{(proposal.cluster or 'Proposals')}"], .9, .8)

             if  (list_state.show_all_proposals || proposals.length <= list_state.show_first_num_items) && \
-               ((@props.combines_these_lists && lists_current_user_can_add_to(@props.combines_these_lists).length > 0) || permit('create proposal', list_key) > 0) && \
+               ((@props.combines_these_lists && lists_current_user_can_add_to(@props.combines_these_lists).length > 0) || (permitted > 0 || permitted == Permission.NOT_LOGGED_IN) ) && \
                 !edit_list.editing

               LI
@@ -1139,6 +1141,7 @@
     permitted = permit('create proposal', list_key)
     add_new &&= permitted > 0 || permitted == Permission.NOT_LOGGED_IN

+
   DIV
     className: 'list_actions'
     style:


Query getModifierState for AltGraph code path

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -120,7 +120,7 @@
   if isNonCharacterKey
     key = NON_CHARACTER_KEY_NAMES_BY_KEYBOARD_EVENT_KEY[key] ? key.toLowerCase()
   else
-    if altKey
+    if event.getModifierState('AltGraph')
       # All macOS layouts have an alt-modified character variant for every
       # single key. Therefore, if we always favored the alt variant, it would
       # become impossible to bind `alt-*` to anything. Since `alt-*` bindings
@@ -139,7 +139,7 @@
       # keystroke, it likely to be the intended character, and we always
       # interpret it as such rather than favoring a `ctrl-alt-*` binding
       # intepretation.
-      else if process.platform is 'win32' and ctrlKey and event.code
+      else if process.platform is 'win32' and event.code
         nonAltModifiedKey = nonAltModifiedKeyForKeyboardEvent(event)
         if metaKey
           key = nonAltModifiedKey


:art:

:art:

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -127,7 +127,7 @@
         charCode = event.keyCode

     if charCode?
-      if process.platform is 'linux' or process.platform is 'win32'
+      if process.platform in ['linux', 'win32']
         charCode = translateCharCodeForWindowsAndLinuxChromiumBug(charCode, event.shiftKey)

       if event.location is KeyboardEvent.DOM_KEY_LOCATION_NUMPAD


better glob, ditch create, watch, serve

better glob, ditch create, watch, serve

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -1,9 +1,20 @@
-import {parse as _parse, relative, join} from  "path"
+import {parse as _parse, join, resolve} from  "path"
 import _transform from "jstransformer"
+import {map} from "panda-river"
 import {curry, binary, tee} from "panda-garden"
 import {include} from "panda-parchment"
-import {glob, read as _read, write as _write, mkdirp} from "panda-quill"
+import {read as _read, write as _write, mkdirp, cp} from "panda-quill"
 import chokidar from "chokidar"
+
+import http from "http"
+import connect from "connect"
+import logger from "morgan"
+import finish from "finalhandler"
+import rewrite from "connect-history-api-fallback"
+
+import files from "serve-static"
+import {green, red} from "colors/safe"
+import _glob from "fast-glob"

 parse = (path) ->
   {dir, name, ext} = _parse path
@@ -12,44 +23,74 @@
   name: name
   extension: ext

-create = curry (_directory, _path) ->
-  {path, directory, name, extension} = parse _path
-  path: relative _directory, (join directory, name)
-  name: name
-  source: {path, directory, name, extension}
-  target: {}
-  data: {}
+create = curry (path, rpath) ->
+  include (parse rpath),
+    source: parse resolve path, rpath
+    target: {}
+    data: {}
+
+glob = (pattern, path) ->
+  map (create path), (await _glob pattern, cwd: path)
+
+complete = (directory, context) ->
+  {target} = context
+  if target.path?
+    context.target = parse target.path
+  else
+    defaults = parse resolve directory, context.path
+    target.directory ?= defaults.directory
+    target.name ?= defaults.name
+    target.extension ?= defaults.extension
+    {directory, name, extension} = target
+    target.path = join directory, "#{name}#{extension}"
+  context

 read = tee ({source}) ->
   source.content = await _read source.path

-write = curry binary tee (directory, {path, target, source}) ->
+write = curry binary (directory, context) ->
+  {target} = context
   if target.content?
-    if !target.path?
-      extension = if target.extension?
-        target.extension
-      else if source.extension?
-        source.extension
-      else ""
-      include target,
-        parse (join directory, "#{path}#{extension}")
-    await mkdirp "0777", (target.directory)
-    _write target.path, target.content
+    complete directory, context
+    await mkdirp "0777", target.directory
+    await _write target.path, target.content
+    context
+  else
+    console.error red "[warning] p9k/write: attempt to write with no content."

 extension = (extension) -> tee ({target}) -> target.extension = extension

-copy = tee ({source, target}) -> cp source.path, target.path
+copy = curry binary (directory, context) ->
+  {source, target} = complete directory, context
+  await mkdirp "0777", target.directory
+  cp source.path, target.path
+  context

 transform = (transformer, options) ->
   adapter = _transform transformer
   tee ({source, target}) ->
     source.content ?= await read source.path
     options.filename = source.path
-    target.content = adapter.render source.content, options
+    result = await adapter.renderAsync source.content, options
+    target.content = result.body ? ""

-watch = (path, tasks) ->
+watch = (path, handler) ->
   ->
-    chokidar.watch path
-    .on "all", -> run task for task in tasks
+    chokidar.watch path, ignoreInitial: true
+    .on "all", handler

-export {create, read, write, extension, copy, transform, watch}
+serve = (path, options) ->
+  ->
+    {port} = options
+    handler = connect()
+    if options.logger?
+      handler.use logger options.logger
+    if options.rewrite?
+      handler.use if options.rewrite then rewrite() else rewrite options.rewrite
+    handler.use files "./build", options.files
+    handler.use finish
+    http.createServer handler
+    .listen port, ->
+      console.log green "p9k: server listening on port #{port}"
+
+export {glob, read, write, extension, copy, transform, watch, serve}


Replace instead of extend overriden objects under files[type] such as joinTo

Closes gh-693

--- src/helpers.coffee
+++ src/helpers.coffee
@@ -29,12 +29,13 @@
     recursiveExtend config, config.overrides?[override] or {}
   config

-recursiveExtend = (object, properties) ->
+recursiveExtend = (object, properties, parentKeys=[]) ->
   Object.keys(properties).forEach (key) ->
     value = properties[key]
-    if typeof value is 'object' and value?
+    replaceObj = parentKeys.length is 2 and parentKeys[0] is 'files'
+    if typeof value is 'object' and value? and not replaceObj
       object[key] ?= {}
-      recursiveExtend object[key], value
+      recursiveExtend object[key], value, parentKeys.concat [key]
     else
       object[key] = value
   object


Moving credentials to their own variable

--- scripts/jira-linkifier.coffee
+++ scripts/jira-linkifier.coffee
@@ -60,8 +60,8 @@
 watch = (robot, project) ->
   robot.hear new RegExp(project + "-([0-9]*)", "i"), (mention) ->
     ticket_number = mention.match[1]
-    credentials
-    auth_header = 'Basic ' + new Buffer(process.env.HUBOT_JIRA_READER_USERNAME + ":" + process.env.HUBOT_JIRA_READER_PASSWORD).toString('base64')
+    credentials = process.env.HUBOT_JIRA_READER_USERNAME + ":" + process.env.HUBOT_JIRA_READER_PASSWORD
+    auth_header = 'Basic ' + new Buffer(credentials).toString('base64')

     robot.http(process.env.HUBOT_JIRA_INSTANCE_URL + "/rest/api/2/issue/" + project + "-" + ticket_number)
       .header('Authorization', )


Percent escaping OUI, serial number, product class in device ID string

--- common.coffee
+++ common.coffee
@@ -23,11 +23,20 @@

 exports.getDeviceId = (deviceIdStruct) ->
+  # Percent escaping function. Escapes everything except alphanumerics and underscore
+  esc = (str) ->
+    str.replace(/[^A-Za-z0-9_]/g, (chr) ->
+      buf = new require('buffer').Buffer(chr)
+      rep = ''
+      rep += "%#{b.toString(16).toUpperCase()}" for b in buf
+      return rep
+    )
+
   # Guaranteeing globally unique id as defined in TR-069
   if deviceIdStruct['ProductClass']
-    return "#{deviceIdStruct['OUI']}-#{deviceIdStruct['ProductClass']}-#{deviceIdStruct['SerialNumber']}"
+    return "#{esc(deviceIdStruct['OUI'])}-#{esc(deviceIdStruct['ProductClass'])}-#{esc(deviceIdStruct['SerialNumber'])}"

-  return "#{deviceIdStruct['OUI']}-#{deviceIdStruct['SerialNumber']}"
+  return "#{esc(deviceIdStruct['OUI'])}-#{esc(deviceIdStruct['SerialNumber'])}"

 exports.extend = (obj, mixin) ->


Adds verbose console.log in download task

--- tasks/download.coffee
+++ tasks/download.coffee
@@ -3,6 +3,7 @@
 {platform} = require './utils'
 electronDownloader = require 'gulp-electron-downloader'
 manifest = require '../src/package.json'
+args = require './args'

 # Flags to keep track of downloads
 downloaded =
@@ -23,6 +24,8 @@
   gulp.task 'download:' + dist, ['kill:' + dist], (done) ->
     # Skip if already downloaded to speed up auto-reload
     if downloaded[dist]
+      if args.verbose
+        console.log 'already downloaded, skipping'
       return done()

     electronDownloader


Update LandingExplain.coffee

Update LandingExplain.coffee

--- src/components/LandingExplain.coffee
+++ src/components/LandingExplain.coffee
@@ -116,8 +116,8 @@
           div className: "circle green",
             img className: "teamImage", src: require "../img/team/Lisa.jpg"
           div className: "team-member-name", "Lisa Stehr"
-          div null, "Psychologin"
-          div null, "Funding & Strategy"
+          div null, "Politikwissenschaftlerin"
+          div null, "Strategie und Wirkung "
         div className: "teamMember",
           div className: "circle green",
            a href:"http://martenroebel.de/",


Support for updated *Buffer* API on node >= 6.*

--- files.coffee
+++ files.coffee
@@ -792,7 +792,10 @@
                   if request.headers['x-eof'] is '1'
                     opts.eof = true
                   else
-                    opts.binData = new Buffer body, 'base64'
+                    if typeof Buffer.from == 'function'
+                      opts.binData = Buffer.from body, 'base64'
+                    else
+                      opts.binData = new Buffer body, 'base64'
                     opts.chunkId = parseInt request.headers['x-chunkid']

                   _continueUpload = self._continueUpload opts.fileId


don't convert blobs to hex strings before storing in DB -- it is insanely slow and now deprecated in cassandra 2

--- salvus/cassandra.coffee
+++ salvus/cassandra.coffee
@@ -271,17 +271,13 @@
         @_from_db = from_json

 class UUIDBlobStore extends UUIDStore
-    # c = new (require("cassandra").Salvus)(keyspace:'test'); s = c.uuid_blob_store(name:'test')
+    # c = new (require("cassandra").Salvus)(keyspace:'salvus'); s = c.uuid_blob_store(name:'test')
     # b = new Buffer("hi\u0000there"); uuid = s.set(value:b, ttl:300, cb:console.log)
     # s.get(uuid: uuid, cb:(e,r) -> console.log(r))
     constructor: (@cassandra, opts={}) ->
         @opts     = defaults(opts, name:required)
         @_table   = 'uuid_blob'
-        @_to_db   = (x) ->
-            winston.debug("converting object of length #{x.length} to hex")
-            s = x.toString('hex')
-            winston.debug('converted, now storing')
-            return s
+        @_to_db   = (x) -> x
         @_from_db = (x) -> new Buffer(x, 'hex')

 class KeyValueStore


add a trivial comment clarifying a bugfix.

--- salvus/misc_node.coffee
+++ salvus/misc_node.coffee
@@ -187,7 +187,7 @@
         m = "misc_node: timed out trying to connect to locked socket on port #{port}"
         winston.debug(m)
         cb?(m)
-        cb = undefined  # NOTE: everywhere below we set cb to undefined after calling it, and only call it if defined, since the event and timer callback stuff is very hard to do right here without calling cb more than once (which is VERY bad to do).
+        cb = undefined  # NOTE: here and everywhere below we set cb to undefined after calling it, and only call it if defined, since the event and timer callback stuff is very hard to do right here without calling cb more than once (which is VERY bad to do).
         socket.end()
         timer = undefined


Favor position over id

Favor position over id

--- components/auction_artworks/view.coffee
+++ components/auction_artworks/view.coffee
@@ -20,7 +20,7 @@
   sorts: (artwork) ->
     { saleArtwork, artist } = artwork.related()

-    default: saleArtwork.get('lot_number') or saleArtwork.id
+    default: saleArtwork.get('lot_number') or saleArtwork.get('position') or saleArtwork.id
     name_asc: artist.get('sortable_id')
     bids_desc: -(saleArtwork.get('bidder_positions_count'))
     bids_asc: saleArtwork.get('bidder_positions_count')


tools: Fix rsync command

tools: Fix rsync command

--- server/code/model/tool.coffee
+++ server/code/model/tool.coffee
@@ -34,7 +34,7 @@
   @dbClass: zDbTool

   rsync: (boxServer, callback) =>
-    child_process.exec "rsync -avz -e 'ssh -i /root/tools_rsa' /opt/tools/#{@name} tools@#{boxServer}:", callback
+    child_process.exec "rsync --delete -avz -e 'ssh -i /etc/custard/tools_rsa' /opt/tools/#{@name} tools@#{boxServer}:", callback

   gitCloneOrPull: (options, callback) ->
     {Box} = require 'model/box'


fix optimist better

fix optimist better

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -16,15 +16,16 @@
   run: ->
     argv = optimist
       .usage('mongoose [options] <mongo url>')
-      .options 'schemas',
-        alias: 's'
-        describe: 'Path to a module that exports schema definitions'
-      .options 'host',
-        alias: 'h'
-        describe: 'Host to connect to'
-      .options 'version',
-        alias: 'v'
-        describe: 'List version'
+      .options
+        schemas:
+          alias: 's'
+          describe: 'Path to a module that exports schema definitions'
+        host:
+          alias: 'h'
+          describe: 'Host to connect to'
+        version:
+          alias: 'v'
+          describe: 'List version'
       .argv
     if argv.version
       console.log require("#{__dirname}/../package.json").version


Added --help and --version options

Added --help and --version options

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -1,8 +1,8 @@
 _ = require "underscore"
 hintFiles = require("./lib-js/hint")
-argv = require("optimist")
+{argv, help} = require("optimist")
   .usage('$0 [options] filename.coffee ...')
-  .options(
+  .options
     options:
       alias: 'o'
       describe: 'comma separated list of JSHint options to turn on'
@@ -16,23 +16,33 @@
       alias: 'v'
       type: 'boolean'
       describe: 'print more detailed output'
-  ).argv
+    version:
+      type: 'boolean'
+      describe: 'print the version'
+    help:
+      alias: 'h'
+      type: 'boolean'
+      describe: 'print usage info'

-splitArgs = (strList) -> strList?.split(',') ? []
+switch
+  when argv.version then console.log require("./package.json").version
+  when argv.help then console.log help()
+  else
+    splitArgs = (strList) -> strList?.split(',') ? []

-# Filter out non-coffee paths
-{coffee, other} = _(argv._).groupBy (path) ->
-  if /.+\.coffee$/.test path then "coffee" else "other"
-if argv.verbose and other?.length > 0
-  console.log "Skipping files that don't end in .coffee:\n" + other.join('\n')
+    # Filter out non-coffee paths
+    {coffee, other} = _(argv._).groupBy (path) ->
+      if /.+\.coffee$/.test path then "coffee" else "other"
+    if argv.verbose and other?.length > 0
+      console.log "Skipping files that don't end in .coffee:\n" + other.join('\n')

-errors = hintFiles(coffee,
-  options: splitArgs argv.options
-  withDefaults: (not argv['default-options-off'])
-  globals: splitArgs argv.globals
-  verbose: argv.verbose
-, true)
-if _.flatten(errors).length is 0
-  process.exit 0
-else
-  process.exit 1
+    errors = hintFiles(coffee,
+      options: splitArgs argv.options
+      withDefaults: (not argv['default-options-off'])
+      globals: splitArgs argv.globals
+      verbose: argv.verbose
+    , true)
+    if _.flatten(errors).length is 0
+      process.exit 0
+    else
+      process.exit 1


Eval Tweak

Eval Tweak

--- modules/eval.coffee
+++ modules/eval.coffee
@@ -24,7 +24,7 @@
       j = (obj, length = 2)->
         pruned = prune obj, length
         p "\`\`\`json\n#{format(JSON.parse(pruned),formatSettings)}\n\`\`\`"
-      eval "(async function () {#{args}})()"
+      eval "return (async function () {#{args}})()"
     # coffeelint: enable=max_line_length

 module.exports = EvalModule


Add deleteSlide to User

Add deleteSlide to User

--- slide.coffee
+++ slide.coffee
@@ -2,6 +2,9 @@

 class Slide
   constructor: (data) ->
+    if data.duration < 0
+      data.duration = 10
+
     @data = _.assign({
       type: null
       name: null


Displaying non-instructions in decimal and binary as well

--- src/disassembler/disassembler.coffee
+++ src/disassembler/disassembler.coffee
@@ -109,7 +109,7 @@
     else if X?
       "#{type} #{registerNames[X]}"
     else if type == 'dw'
-      "dw 0x#{(word.toString 16).toUpperCase()}"
+      "dw 0x#{(word.toString 16).toUpperCase()} ; #{word} ; 0b#{word.toString 2}"
     else
       type


Remove unnecessary code

Remove unnecessary code

--- src/jakucho.coffee
+++ src/jakucho.coffee
@@ -13,7 +13,6 @@
 resources = require './data/resources.json'
 key = 'jakucho'
 resetTime = process.env.HUBOT_JAKUCHO_RESET_TIME or 24 * 60 * 60 * 1000
-youtubeUrl = 'youtube'

 module.exports = (robot) ->


removed duplicate service

removed duplicate service

--- src/app/rounds/rounds.coffee
+++ src/app/rounds/rounds.coffee
@@ -21,15 +21,6 @@

 roundsConfig.$inject = ['$stateProvider']
 rounds.config roundsConfig
-
-rounds.service "TagsService", ->
-  random_tag: (type) ->
-    all_tags = {
-      'series': ["adventure time", "supernatural", "doctor who", "lotr", "harry potter"],
-      'characters': ["hannibal lecter", "mako mori", "katniss everdeen"]
-    }
-    tags = all_tags[type]
-    return tags[Math.floor(Math.random() * tags.length)]

 rounds.factory "RoundsRes", [
   "$resource"


Draggable

Draggable

- Fixed overwriting helper_set_pos in FlexHandle.

--- src/base/DragDropSelect/Draggable.coffee
+++ src/base/DragDropSelect/Draggable.coffee
@@ -461,9 +461,8 @@

 		# console.debug "limitRect", dump(pos), dim_contain

-		helper_pos =
-			top: pos.y
-			left: pos.x
+		helper_pos.top = pos.y
+		helper_pos.left = pos.x

 		helper_pos.dragDiff =
 			x: helper_pos.left - globalDrag.helperNodeStart.left


Remove unused code

Remove unused code

--- content_scripts/mode_key_handler.coffee
+++ content_scripts/mode_key_handler.coffee
@@ -27,8 +27,6 @@

     super extend options,
       keydown: @onKeydown.bind this
-      # We cannot track keyup events if we lose the focus.
-      blur: (event) => @alwaysContinueBubbling => @keydownEvents = {} if event.target == window

     if options.exitOnEscape
       # If we're part way through a command's key sequence, then a first Escape should reset the key state,


renamed resize.main function to o.resize

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -44,45 +44,44 @@
         metadata = ($ el).data "#{ pluginName.toLowerCase() }-options"
         stateKey = "#{ pluginName.toLowerCase() }-state"

-        resize =
-            main: (e) ->
-                current      = $ this
-                optimalWidth = o.el.data "#{ pluginName.toLowerCase() }-optimalwidth"
-
-                crumbHeights = o.el
-                    .children('li')
-                    .map( ->
-                        return ($ this).outerHeight true
-                    )
-                    .get()
-
-                optimalCrumbHeight = _.max crumbHeights
-
-                if typeof optimalWidth is "undefined"
-                    if optimalCrumbHeight isnt o.el.height()
+        o.resize = (e) ->
+            current      = $ this
+            optimalWidth = o.el.data "#{ pluginName.toLowerCase() }-optimalwidth"
+
+            crumbHeights = o.el
+                .children('li')
+                .map( ->
+                    return ($ this).outerHeight true
+                )
+                .get()
+
+            optimalCrumbHeight = _.max crumbHeights
+
+            if typeof optimalWidth is "undefined"
+                if optimalCrumbHeight isnt o.el.height()
+                    o.opts.onBeforeCompress(_this) if $.isFunction(o.opts.onBeforeCompress)
+
+                    o.el
+                        .data("#{ pluginName.toLowerCase() }-optimalwidth", (current.width() + o.opts.allowance))
+                        .addClass(o.opts.wrappedClass)
+                        .trigger "compress.#{ pluginName }"
+            else
+                if current.width() >= optimalWidth
+                    if _this.isCompressed()
+                        o.opts.onBeforeDecompress(_this) if $.isFunction(o.opts.onBeforeDecompress)
+
+                        o.el
+                            .removeClass(o.opts.wrappedClass)
+                            .trigger "decompress.#{ pluginName }"
+                else
+                    if not _this.isCompressed()
                         o.opts.onBeforeCompress(_this) if $.isFunction(o.opts.onBeforeCompress)

                         o.el
-                            .data("#{ pluginName.toLowerCase() }-optimalwidth", (current.width() + o.opts.allowance))
                             .addClass(o.opts.wrappedClass)
                             .trigger "compress.#{ pluginName }"
-                else
-                    if current.width() >= optimalWidth
-                        if _this.isCompressed()
-                            o.opts.onBeforeDecompress(_this) if $.isFunction(o.opts.onBeforeDecompress)
-
-                            o.el
-                                .removeClass(o.opts.wrappedClass)
-                                .trigger "decompress.#{ pluginName }"
-                    else
-                        if not _this.isCompressed()
-                            o.opts.onBeforeCompress(_this) if $.isFunction(o.opts.onBeforeCompress)
-
-                            o.el
-                                .addClass(o.opts.wrappedClass)
-                                .trigger "compress.#{ pluginName }"
-
-                return null
+
+            return null

         o.debug = ->
             # Skip browsers w/o firebug or console
@@ -200,7 +199,7 @@

                 console.log 'decompress'

-            o.browserWindow.on "resize.#{ pluginName }", resize.main
+            o.browserWindow.on "resize.#{ pluginName }", o.resize

             # execute custom code after the plugin has loaded
             o.opts.onAfterLoad(_this) if $.isFunction(o.opts.onAfterLoad)


fixed condition in o.resize wherein incorrect variable was used

--- src/coffee/responsive.breadcrumbs.coffee
+++ src/coffee/responsive.breadcrumbs.coffee
@@ -204,7 +204,7 @@
             o.compress() if o.optimalCrumbHeight isnt o.el.height()

             if o.windowWidth isnt o.browserWindow.width()
-                if o.browserWindow.width() < o.windowWidth and optimalCrumbHeight isnt o.el.height()
+                if o.browserWindow.width() < o.windowWidth and o.optimalCrumbHeight isnt o.el.height()
                     console.log 'decreasing'

                     o.compress()


Added 'global' to sandbox to keep Ember > 2 happy

--- src/ember-handlebars-compiler.coffee
+++ src/ember-handlebars-compiler.coffee
@@ -46,6 +46,8 @@
   # window
   sandbox.window = sandbox

+  sandbox.global = sandbox
+
   # create a context for the vm using the sandbox data
   context = vm.createContext sandbox


fixes trying to load without cnxId as props on ref book page

--- src/components/reference-book/index.cjsx
+++ src/components/reference-book/index.cjsx
@@ -26,7 +26,7 @@

   renderLoading: (previousPageProps, currentProps) ->
     (refreshButton) ->
-      if previousPageProps? and not _.isEqual(previousPageProps, currentProps)
+      if previousPageProps? and previousPageProps.cnxId? and not _.isEqual(previousPageProps, currentProps)
         loading = <ReferenceBookPage
           {...previousPageProps}
           className='page-loading loadable is-loading'>


fix(account): fix initial token loading in account store

--- src/flux/stores/account-store.coffee
+++ src/flux/stores/account-store.coffee
@@ -53,7 +53,10 @@
       @_accounts.push((new Account).fromJSON(json))

     index = atom.config.get(saveIndexKey) || 0
-    @_selectAccountByIndex(index)
+    @_index = Math.min(@_accounts.length - 1, Math.max(0, index))
+
+    @_tokens = atom.config.get(saveTokensKey) || {}
+    @trigger()

   _save: =>
     atom.config.set(saveObjectsKey, @_accounts)


Added GameOfLife and MacroCell class stubs

--- lib/gol.coffee
+++ lib/gol.coffee
@@ -12,3 +12,30 @@
   is to be considered a private implementation detail.
 ###

+###
+  A macro-cell is an analogue of a quad-tree node in the HashLife algorithm.
+  A macro-cell size is a power of two. Given a macro-cell of size 2^n, we say
+  it's level is n.
+
+  Aside from the four (n-1) child-macro-cells (as in a quad-tree), we also make
+  use of a fifth (n-1) child located at the center of the macro-cell. This
+  child contains the state of the simulation after 2^(n-2) steps.
+###
+class _MacroCell
+  constructor: (@_level, @_nw, @_ne, @_sw, @_se) ->
+
+  _step: ->
+
+###
+  Simulates an infinite cell grid running Conway's Game of Life (GOL).
+  In GOL, a cell can be either dead or alive. The simulation proceeds in
+  discrete time steps and cells evolve according to the following rules:
+    1. A living cell with fewer than 2 living neighbours dies from isolation.
+    2. A living cell with more than 3 living neighbours dies from overcrowding.
+    3. A dead cell with with exactly 3 living neighbours comes alive due to
+       reproduction.
+###
+class GameOfLife
+  constructor: ->
+
+


Added faceCamera but tentative

Added faceCamera but tentative

--- src/Node.coffee
+++ src/Node.coffee
@@ -96,6 +96,39 @@
   scale: (scaleX, scaleY, scaleZ) ->
     @_transform.scale scaleX, scaleY, scaleZ
     @style[DivSugar._transform] = @_transform.toCSSTransform()
+    return @
+
+  faceCamera: (rotateZ = 0) ->
+    vec = DivSugar._Node._tmpVec1
+    mat = DivSugar._Node._tmpMat1
+
+    mat.set DivSugar.Matrix.UNIT
+    parent = @parentNode
+
+    while parent? and parent._transform?
+      mat.toGlobal_noTrans parent._transform
+      parent = parent.parentNode
+
+    vec.set(mat.xAxis).add(mat.yAxis).add(mat.zAxis)
+    scaleX = mat.xAxis.norm() #vec.dot DivSugar.Vector.X_UNIT
+    scaleY = mat.yAxis.norm() #vec.dot DivSugar.Vector.Y_UNIT
+    scaleZ = mat.zAxis.norm() #vec.dot DivSugar.Vector.Z_UNIT
+
+    mat.xAxis.mul 1 / scaleX
+    mat.yAxis.mul 1 / scaleY
+    mat.zAxis.mul 1 / scaleZ
+    #mat.scale 1 / scaleX, 1 / scaleY, 1 / scaleZ
+
+    #sqSclZ = mat.zAxis.sqNorm()
+    #scaleZ = if Math.abs(sqSclZ - 1) < DivSugar.EPSILON then 1 else Math.sqrt sqSclZ
+
+    vec.set @_transform.trans
+    @_transform.set DivSugar.Matrix.UNIT
+    @_transform.toLocal_noTrans mat
+    @_transform.trans.set vec
+    @_transform.rotate(0, 0, rotateZ)
+    @setTransform @_transform
+
     return @

   playAnimation: (animation) ->
@@ -221,4 +254,5 @@
       @_animTasks.shift().destroy()
     return @

+DivSugar._Node._tmpVec1 = new DivSugar.Vector()
 DivSugar._Node._tmpMat1 = new DivSugar.Matrix()


Correct function call https.request.end()

--- scripts/watsonqa.coffee
+++ scripts/watsonqa.coffee
@@ -70,5 +70,4 @@
     }

     watson_req.write JSON.stringify(questionData)
-    watson_req.end
-
+    watson_req.end()