fix #23, refactor and improve Greenhorn class

* when the document's readyState reaches interactive the `Greenhorn` object will emit a `ready` event
* the `Greenhorn` object will emit an `update` event once every cycle
* `Greenhorn.isDown` has been changed from an array to a read-only function
* `Greenhorn.currentState` has been changed from a string to a read-only function
* added `SHIFT`, `CTRL`, and `ALT` to the `KEYS` object
* the canvas element is now directly accessible as `Greenhorn.canvas`
* relevant values from the `env` object have been moved to `Greenhorn.config` and `Greenhorn.buttonDefaults`
* general cleanup and refactoring in the interest of maintainability

--- src/greenhorn.coffee
+++ src/greenhorn.coffee
@@ -4,8 +4,14 @@
 sedabull@gmail.com
 ###

+#automatic initialization
+document.onreadystatechange = ->
+    if @readyState is 'interactive'
+        Greenhorn.emit 'ready'
+
 #keyboard value mapping object
 KEYS =
+    SHIFT: 16, CTRL: 17, ALT: 18
     ESC: 27, SPACE: 32, PGUP: 33
     PGDOWN: 34, END: 35, HOME: 36
     LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40
@@ -17,109 +23,111 @@
     T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90
 gh.KEYS = KEYS

-#automatic initialization
-document.onreadystatechange = ->
-    if @readyState is 'interactive'
-        (gh.init ? Greenhorn.start)()
-
-#listen for key events
-document.onkeydown = (e) ->
-    e.preventDefault()
-    Greenhorn.isDown[e.keyCode] = true
-document.onkeyup = (e) ->
-    e.preventDefault()
-    Greenhorn.isDown[e.keyCode] = false
-
-#asynchronous ID
-_masterID = null
-
-#draws on canvas once per frame
-_masterUpdate = ->
-    #clear previous frame
-    Greenhorn.clear()
-
-    #call custom update
-    gh.update?()
-
-    #draw all Sprites
-    Sprite._drawAll()
-
 #Engine class
 class Greenhorn extends EventEmitter
-    #keyboard input tracking array
-    @isDown = new Array 256
-    key = false for key in @isDown
-
-    #name of the current state
-    @currentState = 'STARTUP'
-
-    #create Engine elements
-    _elmnts =
-        main: document.createElement 'div'
-        title: document.createElement 'h1'
-        leftPanel: document.createElement 'div'
-        leftPanelHeader: document.createElement 'h3'
-        canvas: document.createElement 'canvas'
-        rightPanel: document.createElement 'div'
-        rightPanelHeader: document.createElement 'h3'
-        footer: document.createElement 'div'
+    #Greenhorn configuration object
+    @config:
+        frameRate: 25
+        title: 'GH-TITLE'
+        leftHeader: 'GH-LEFT-PANEL'
+        rightHeader: 'GH-RIGHT-PANEL'
+        footer: 'GH-FOOTER'
+        startUp:
+            size: 50
+            color: '#006400'
+            font: 'sans-serif'
+            text: 'CLICK HERE TO START!'
+
+    #Greenhorn button defaults
+    @buttonDefaults:
+        type: 'button'
+        onclick: undefined
+        parent: 'gh-right-panel'
+        label: 'Launch the Missiles!'
+
+    #asynchronous ID
+    _masterID = null
+    #draws on canvas once per frame
+    _masterUpdate = ->
+        Greenhorn.clear()
+        Greenhorn.emit 'update'
+        Sprite._drawAll()
+
+    #keyboard input tracking array (read only)
+    _isDown = new Array 128
+    key = false for key in _isDown
+
+    #listen for key events
+    document.onkeydown = (e) ->
+        e.preventDefault()
+        _isDown[e.keyCode] = true
+    document.onkeyup = (e) ->
+        e.preventDefault()
+        _isDown[e.keyCode] = false
+
+    #raw DOM manipulation
+    _main = document.createElement 'div'
+    _title = document.createElement 'h1'
+    _leftPanel = document.createElement 'div'
+    _leftPanelHeader = document.createElement 'h3'
+    @canvas = document.createElement 'canvas'
+    _rightPanel = document.createElement 'div'
+    _rightPanelHeader = document.createElement 'h3'
+    _footer = document.createElement 'div'
+    _context = @canvas.getContext '2d'

     #give id's to primary children
-    _elmnts.main.id = 'gh-main'
-    _elmnts.title.id = 'gh-title'
-    _elmnts.leftPanel.id = 'gh-left-panel'
-    _elmnts.canvas.id = 'gh-canvas'
-    _elmnts.rightPanel.id = 'gh-right-panel'
-    _elmnts.footer.id = 'gh-footer'
+    _main.id = 'gh-main'
+    _title.id = 'gh-title'
+    _leftPanel.id = 'gh-left-panel'
+    @canvas.id = 'gh-canvas'
+    _rightPanel.id = 'gh-right-panel'
+    _footer.id = 'gh-footer'

     #assign gh-h class to panelHeaders
-    _elmnts.leftPanelHeader.classList.add 'gh-h'
-    _elmnts.rightPanelHeader.classList.add 'gh-h'
+    _leftPanelHeader.classList.add 'gh-h'
+    _rightPanelHeader.classList.add 'gh-h'

     #append all primary children to main div
-    _elmnts.main.appendChild _elmnts.title
-    _elmnts.main.appendChild _elmnts.leftPanel
-    _elmnts.main.appendChild _elmnts.canvas
-    _elmnts.main.appendChild _elmnts.rightPanel
-    _elmnts.main.appendChild _elmnts.footer
+    _main.appendChild _title
+    _main.appendChild _leftPanel
+    _main.appendChild @canvas
+    _main.appendChild _rightPanel
+    _main.appendChild _footer

     #append headers to panels
-    _elmnts.leftPanel.appendChild _elmnts.leftPanelHeader
-    _elmnts.rightPanel.appendChild _elmnts.rightPanelHeader
+    _leftPanel.appendChild _leftPanelHeader
+    _rightPanel.appendChild _rightPanelHeader

     #keep track of mouse events over canvas
-    _elmnts.canvas.onmousemove = (e) ->
+    @canvas.onmousemove = (e) ->
         @mouseX = e.pageX
         @mouseY = e.pageY
-        Sprite.emitAll 'mouse:move'
-    _elmnts.canvas.onmousedown = (e) ->
-        Sprite.emitAll 'mouse:down'
-    _elmnts.canvas.onmouseup = (e) ->
-        Sprite.emitAll 'mouse:up'
-    _elmnts.canvas.ondblclick = (e) ->
-        Sprite.emitAll 'mouse:doubleClick'
-    _elmnts.canvas.oncontextmenu = (e) ->
+        Sprite.emitAll 'mouse:move', e
+    @canvas.onmousedown = (e) ->
+        Sprite.emitAll 'mouse:down', e
+    @canvas.onmouseup = (e) ->
+        Sprite.emitAll 'mouse:up', e
+    @canvas.ondblclick = (e) ->
+        Sprite.emitAll 'mouse:doubleClick', e
+    @canvas.oncontextmenu = (e) ->
         e.preventDefault()
-        Sprite.emitAll 'mouse:rightClick'
-
-    #start all asynchronous functions
-    _startEverything = ->
-        _masterID = setInterval _masterUpdate, 1000 / env.FRAME_RATE
-        Sprite._startAll()
-        Sound._playAll()
-        return
+        Sprite.emitAll 'mouse:rightClick', e
+
+    #keyPress tester
+    @isDown = (key) ->
+        _isDown[key]

     #mouse position getters
     @getMouseX = ->
-        _elmnts.canvas.mouseX - _elmnts.canvas.offsetLeft - _elmnts.canvas.width / 2
-
+        @canvas.mouseX - @canvas.offsetLeft - @canvas.width / 2
     @getMouseY = ->
-        -(_elmnts.canvas.mouseY - _elmnts.canvas.offsetTop - _elmnts.canvas.height / 2)
+        -@canvas.mouseY + @canvas.offsetTop + @canvas.height / 2

     #add button to one of the panels
     @addButton = (config = {}) ->
         #add missing keys to config
-        for own key, value of env.BUTTON_DEFAULT_CONFIG
+        for own key, value of @buttonDefaults
             config[key] ?= value

         #create element
@@ -132,17 +140,16 @@
         button.classList.add 'gh-button'

         #append to an element
-        _elmnts[config.parent].appendChild button
-
-    #change state
-    @changeState = (stateName) ->
-        @emit "state-change-from:#{@currentState}"
-        @emit 'state-change', @currentState, stateName
-        @emit "state-change-to:#{stateName}"
-        @currentState = stateName
-
-    #execute start-up logic only once
-    _firstTime = true
+        main.getElementById(config.parent).appendChild button
+
+    #game state functions + helper
+    _currentState = 'INITIALIZING'
+    @currentState = -> _currentState
+    @changeState = (newState) ->
+        @emit "state-change-from:#{_currentState}"
+        @emit 'state-change', _currentState, newState
+        @emit "state-change-to:#{newState}"
+        _currentState = newState

     #game control
     @isRunning = ->
@@ -153,64 +160,60 @@
         clearInterval _masterID
         _masterID = null
     @clear = ->
-        _elmnts.canvas
-        .getContext('2d')
-        .clearRect(
-            -_elmnts.canvas.width / 2,
-            -_elmnts.canvas.height / 2,
-            _elmnts.canvas.width,
-            _elmnts.canvas.height)
+        _context.clearRect -@canvas.width / 2, -@canvas.height / 2, @canvas.width, @canvas.height
+
+    #start function + helpers
+    _firstTime = true
+    _start = ->
+        _masterID = setInterval _masterUpdate, 1000 / @config.frameRate
+        Sprite._startAll()
+        Sound._playAll()
+        return
     @start = (stateName) =>
         #prevent starting without properly stopping first
         unless @isRunning()
             if _firstTime
                 #add engine to a user defined '#gh' div
-                mainDiv = document.querySelector '#gh'
-                if mainDiv?
+                if mainDiv = document.querySelector '#gh'
                     mainDiv.appendChild _elmnts.main
                 else
                     console.log "There is no '#gh' element!"

                 #set the innerHTML of each element
-                _elmnts.title.innerHTML = document.title
-                _elmnts.leftPanelHeader.innerHTML = env.ENGINE.leftHeader
-                _elmnts.rightPanelHeader.innerHTML = env.ENGINE.rightHeader
-                _elmnts.footer.innerHTML = env.ENGINE.footer
-                _elmnts.canvas.innerHTML = 'Your browser does not support the &ltcanvas&gt tag'
+                _title.innerHTML = @config.title
+                _leftPanelHeader.innerHTML = @config.leftHeader
+                _rightPanelHeader.innerHTML = @config.rightHeader
+                _footer.innerHTML = @config.footer
+                @canvas.innerHTML = 'Your browser does not support the &ltcanvas&gt tag'

                 #set the size of the canvas
-                _elmnts.canvas.width = _elmnts.canvas.clientWidth
-                _elmnts.canvas.height = _elmnts.canvas.clientHeight
+                @canvas.width = @canvas.clientWidth
+                @canvas.height = @canvas.clientHeight

                 #center the canvas origin point
-                _elmnts.canvas
-                .getContext('2d')
-                .translate(
-                    _elmnts.canvas.width / 2,
-                    _elmnts.canvas.height / 2)
+                _context.translate @canvas.width / 2, @canvas.height / 2

                 #draw the start screen
-                _ctx = _elmnts.canvas.getContext '2d'
-                _ctx.save()
-                _ctx.globalAlpha = 1.0
-                _ctx.textAlign = 'center'
-                _ctx.textBaseline = 'middle'
-                _ctx.font = "#{env.STARTUP.size}px #{env.STARTUP.font}"
-                _ctx.fillStyle = env.STARTUP.color
-                _ctx.fillText env.STARTUP.text, 0, 0
-                _ctx.restore()
+                _context.save()
+                _context.globalAlpha = 1.0
+                _context.textAlign = 'center'
+                _context.textBaseline = 'middle'
+                _context.font = "#{@config.startUp.size}px #{@config.startUp.font}"
+                _context.fillStyle = @config.startUp.color
+                _context.fillText @config.startUp.text, 0, 0
+                _context.restore()

                 #make the entire canvas a start button
-                _elmnts.canvas.onclick = ->
-                    _startEverything()
+                @canvas.onclick = ->
+                    _start()
                     Greenhorn.changeState stateName ? 'GREENHORN'
-                    _elmnts.canvas.onclick = (e) ->
-                        Sprite.emitAll 'mouse:click'
+                    @canvas.onclick = (e) ->
+                        Sprite.emitAll 'mouse:click', e

                 #don't do all this a second time
                 _firstTime = false
             else
-                _startEverything()
+                _start()
                 if stateName then @changeState stateName

 #add to namespace object


legit bug fix

legit bug fix

--- src/driver/druidDriver.coffee
+++ src/driver/druidDriver.coffee
@@ -24,13 +24,18 @@
     else
       return { type: 'and', filters }

-rangeToDruidInterval = (interval) ->
-  return interval.map((d) -> d.toISOString().replace('Z', '')).join('/')
-

 class DruidQueryBuilder
   @ALL_DATA_CHUNKS = 10000
   @allTimeInterval = ["1000-01-01/3000-01-01"]
+
+  @dateToIntervalPart = (date) ->
+    return date.toISOString()
+      .replace('Z',    '') # remove Z
+      .replace('.000', '') # millis if 0
+      .replace(/:00$/, '') # remove seconds if 0
+      .replace(/:00$/, '') # remove minutes if 0
+      .replace(/T00$/, '') # remove hours if 0

   constructor: (@dataSource, @timeAttribute, @forceInterval, @approximate) ->
     throw new Error("must have a dataSource") unless typeof @dataSource is 'string'
@@ -43,14 +48,6 @@
     @nameIndex = 0
     @intervals = null
     @useCache = true
-
-  dateToIntervalPart: (date) ->
-    return date.toISOString()
-      .replace('Z',    '') # remove Z
-      .replace('.000', '') # millis if 0
-      .replace(/:00$/, '') # remove seconds if 0
-      .replace(/:00$/, '') # remove minutes if 0
-      .replace(/T00$/, '') # remove hours if 0

   unionIntervals: (intervals) ->
     null # ToDo
@@ -212,10 +209,11 @@
         throw new Error("filter type '#{filter.type}' not defined")

   addFilter: (filter) ->
+    dateToIntervalPart = DruidQueryBuilder.dateToIntervalPart
     return unless filter
     [@filter, intervals] = @filterToDruid(filter)
     if intervals
-      @intervals = intervals.map((({start, end}) -> "#{@dateToIntervalPart(start)}/#{@dateToIntervalPart(end)}"), this)
+      @intervals = intervals.map((({start, end}) -> "#{dateToIntervalPart(start)}/#{dateToIntervalPart(end)}"), this)
     return this

   addSplit: (split) ->
@@ -674,9 +672,7 @@

       queryObj = druidQuery.getQuery()
     catch e
-      callback({
-        detail: e.message
-      })
+      callback(e)
       return

     requester queryObj, (err, ds) ->


Return a valid current user id when testing

--- app/assets/javascripts/initializers/authentication.coffee
+++ app/assets/javascripts/initializers/authentication.coffee
@@ -40,6 +40,10 @@
     SimpleAuth.Session.reopen
       currentUser: (->
         userId = @get('user_id')
+
+        if Ember.isEmpty(userId) && Ember.testing
+          userId = 1
+
         if !Ember.isEmpty(userId)
           Ember.run ->
             Dashboard.__container__.lookup('store:main').find('user', userId)


Fix marker handling on a map view in edit mode. Leaflet doesn't properly set the reference to the map on marker, so we use the current polygon. Fixes #1186.

--- app/assets/javascripts/leaflet/reactive_measure.js.coffee
+++ app/assets/javascripts/leaflet/reactive_measure.js.coffee
@@ -207,7 +207,7 @@

     L.extend(L.Draw.Polyline.prototype.options, target: e.marker.getLatLng())

-    e.marker._map.reactiveMeasureControl.updateContent measure, {selection: true}
+    @_poly._map.reactiveMeasureControl.updateContent(measure, {selection: true}) if @_poly._map?

   addHooks: () ->


fix sorting

fix sorting

--- app/assets/javascripts/routes/posts/index_route.js.coffee
+++ app/assets/javascripts/routes/posts/index_route.js.coffee
@@ -2,7 +2,8 @@

   model: ()->
     @store.find('post').then (posts)->
-      posts.sortBy('id').reverse()
+      posts = posts.sortBy('id').reverse()
+      posts

   setupController: (controller, model)->
     searchVal = controller.get('searchVal')


Replace backbone default model with bam default model for collection.

--- src/collection.coffee
+++ src/collection.coffee
@@ -1,6 +1,8 @@
 Backbone = require('./backbone')
+Model = require('./model')

 class Collection extends Backbone.Collection
+    model: Model

     ###
     Returns the model at the index immediately before the passed in model


Simplify changepalette event.

Simplify changepalette event.

--- src/controller.coffee
+++ src/controller.coffee
@@ -115,7 +115,6 @@
     'redraw_main': []       # whenever we need to redraw the main canvas
     'redraw_palette': []    # repaint the graphics of the palette
     'rebuild_palette': []   # redraw the paltte, both graphics and elements
-    'set_palette': []       # whenever we switch palette categories

     'mousedown': []
     'mousemove': []
@@ -225,6 +224,9 @@
         ctx: @mainCtx
         draw: @draw

+      # Set up event bindings before creating a view
+      @bindings = {}
+
       # Instantiate an ICE editor view
       @view = new view.View extend_ @standardViewSettings, respectEphemeral: true
       @dragView = new view.View extend_ @standardViewSettings, respectEphemeral: false
@@ -1503,8 +1505,6 @@

       clickHandler = =>
         do updatePalette
-        for event in editorBindings.set_palette
-          event.call this

       paletteGroupHeader.addEventListener 'click', clickHandler
       paletteGroupHeader.addEventListener 'touchstart', clickHandler
@@ -1563,7 +1563,7 @@
     if @currentHighlightedPaletteBlock?
       @paletteHighlightPath.draw @paletteHighlightCtx

-  hook 'rebuild_palette', 0, ->
+  hook 'rebuild_palette', 1, ->
     # Remove the existent blocks
     @paletteScrollerStuffing.innerHTML = ''

@@ -3588,9 +3588,6 @@
   # PUBLIC EVENT BINDING HOOKS
   # ===============================

-  hook 'populate', 0, ->
-    @bindings = {}
-
   Editor::on = (event, handler) ->
     @bindings[event] = handler

@@ -3689,7 +3686,7 @@

   # PALETTE EVENT
   # =================================
-  hook 'set_palette', 0, ->
+  hook 'rebuild_palette', 0, ->
     @fireEvent 'changepalette', []

   # TOUCHSCREEN SUPPORT


Clicking the palette scrollbar shouldn't trigger a mousedown event

--- src/controller.coffee
+++ src/controller.coffee
@@ -245,8 +245,8 @@
     for eventName, elements of {
         keydown: [@dropletElement, @paletteElement]
         keyup: [@dropletElement, @paletteElement]
-        mousedown: [@dropletElement, @paletteElement, @dragCover]
-        dblclick: [@dropletElement, @paletteElement, @dragCover]
+        mousedown: [@dropletElement, @paletteHeader, @paletteScrollerStuffing, @dragCover]
+        dblclick: [@dropletElement, @paletteHeader, @paletteScrollerStuffing, @dragCover]
         mouseup: [window]
         mousemove: [window] } then do (eventName, elements) =>
       for element in elements


Switched time to use $timeout instead of setTimeout.

--- src/directives/time.coffee
+++ src/directives/time.coffee
@@ -13,9 +13,10 @@

 angular.module("Mac").directive "macTime", [
   "$filter"
+  "$timeout"
   "util"
   "keys"
-  ($filter, util, keys) ->
+  ($filter, $timeout, util, keys) ->
     restrict: "E"
     scope:
       model:    "=macTimeModel"
@@ -99,9 +100,9 @@
           $scope.model = $filter("date") $scope.time.getTime(), "hh:mm a"

           # Highlight hour on activating the time input
-          setTimeout (->
+          $timeout ->
             inputSelectAction start, end, highlighActions
-          ), 0
+          , 0

         #
         # @name $scope.updateScopeTime


Fix gray box issue.

Fix gray box issue.

There were race conditions because sb[/connection] was
not necessarily initialized when it was used in initializing
janus. This commit avoids using statebus for non-react code,
and thus avoiding problems with accidentally using state before
it is defined.

--- client/tawk.coffee
+++ client/tawk.coffee
@@ -1,5 +1,6 @@
 plugin_handle = null
 streams = {}
+janus_initialized = false

 ###############################################################################
 # Client Bus (all state prefixed with tawk/)
@@ -12,7 +13,6 @@

 window.statebus_ready or= []
 window.statebus_ready.push ->
-  sb['tawk/janus_initialized'] = false
   sb['tawk/space'] = undefined # Will be filled in dom.TAWK

   unsavable = (obj) ->
@@ -152,29 +152,16 @@
 is only supported on https sites.
 ###
 dom.TAWK = ->
-  sb['tawk/space'] = if @props.space? then @props.space else ''
-  if not sb['tawk/janus_initialized']
-    initialize_janus
-      audio: true
-      video: true
-    sb['tawk/janus_initialized'] = true
-
-  if not Janus.isWebrtcSupported() or Janus.webRTCAdapter.browserDetails.browser not in ['chrome', 'firefox']
-    return DIV {}, 'Tawk is only supported in Google Chrome or Mozilla Firefox'
-
-  if @props.height && @props.height != sb['tawk/height']
-    sb['tawk/height'] = @props.height
-  if @props.width && @props.width != sb['tawk/width']
-    sb['tawk/width'] = @props.width
-  sb['tawk/scratch_disabled'] = !!@props.scratch_disabled
-
   # Have to make sure we get all connections to choose
-  # whether to join the first group
+  # whether to join the first group. This state is not
+  # used directly, but is necessary to ensure we don't
+  # use it elsewhere in the widget until it is loaded.
   connections = sb[server + '/connections']
   me = sb[server + '/connection']
   if @loading()
     return DIV {}, 'Loading...'

+  sb['tawk/space'] = if @props.space? then @props.space else ''
   name = @props.name or random_name?() or 'Anonymous ' + random_numbers(4)
   video = if @props.video? then @props.video else true
   audio = if @props.audio? then @props.audio else true
@@ -189,6 +176,23 @@
     me.space = sb['tawk/space']
     me.video = video
     me.audio = audio
+
+  if not janus_initialized
+    initialize_janus
+      my_id: me.id
+      my_space: sb['tawk/space']
+      audio: true
+      video: true
+    janus_initialized = true
+
+  if not Janus.isWebrtcSupported() or Janus.webRTCAdapter.browserDetails.browser not in ['chrome', 'firefox']
+    return DIV {}, 'Tawk is only supported in Google Chrome or Mozilla Firefox'
+
+  if @props.height && @props.height != sb['tawk/height']
+    sb['tawk/height'] = @props.height
+  if @props.width && @props.width != sb['tawk/width']
+    sb['tawk/width'] = @props.width
+  sb['tawk/scratch_disabled'] = !!@props.scratch_disabled

   chats_served = sb['tawk/chats_served']
   if chats_served
@@ -588,6 +592,7 @@
 # Note that the hook when we get a local stream is actually
 # a separate callback to janus.attach
 window.publish_local_stream = ({audio, video}) ->
+  console.info 'publishing local stream'
   plugin_handle.createOffer
     media:
       audioRecv: false
@@ -595,6 +600,7 @@
       audioSend: audio
       videoSend: video
     success: (jsep) ->
+      console.info 'success, configuring local stream'
       plugin_handle.send
         message:
           request: "configure"
@@ -615,8 +621,21 @@
 to all remote streams in the same space,
 and by default automatically publish local stream (though this is configurable).

-State imported:
-sb['tawk/space'] (string, required) -> The room to subscribe to. Corresponds to <space-id>
+State imported: none
+
+State exported:
+sb['tawk/stream/' + id] -> {
+  url: A blob url for the stream. It can be used in audio or video tags with src: url
+  volume: On a scale from 0-100, how loud is the user speaking.
+      Volume tries to reflect human speech, and ignore static background noise.
+}
+There will be one piece of state exported per remote *and* local stream.
+
+Params:
+my_id (string, required): An identifier for the connection that must be unique
+    in the given space. It is used to export stream information
+    for every user.
+my_space (string, required): The room to subscribe to. Corresponds to <space-id>
     in https://tawk.space/<space-id>. Note that you probably
     don't want to have overlap between spaces used in dom.TAWK
     and directly in initialize_janus. This function subscribes
@@ -624,19 +643,6 @@
     that are in a groups. The end result is that if you call the lower
     level function you get all the streams, but using dom.TAWK you only
     get a subset of them.
-sb[server+'/connection'].id (string, required): An identifier for the connection that must be unique
-    in the given space. It is used to export stream information
-    for every user.
-
-State exported:
-sb['tawk/stream/' + id] -> {
-  url: A blob url for the stream. It can be used in audio or video tags with src: url
-  volume: On a scale from 0-100, how loud is the user speaking.
-      Volume tries to reflect human speech, and ignore static background noise.
-}
-There will be one piece of state exported per remote *and* local stream.
-
-Params:
 audio (boolean, default=true): Whether to subscribe to and publish audio
 video (boolean, default=true): Whether to subscribe to and publish video
 on_join (function({audio, video}), default=window.publish_local_stream):
@@ -648,8 +654,9 @@
     into initialize_janus, and represent whether the caller wants to publish
     audio and video.
 ###
-window.initialize_janus = ({audio = true, video = true, on_join = window.publish_local_stream}) ->
+window.initialize_janus = ({my_id, my_space, audio = true, video = true, on_join = window.publish_local_stream}) ->
   new_remote_feed = (janus, feed) ->
+    console.info 'new remote feed', feed.display
     remote_feed = null
     {id, space} = JSON.parse feed.display
     janus.attach
@@ -696,8 +703,9 @@
           janus.attach
             plugin: "janus.plugin.videoroom"
             error: console.error
-            onlocalstream: (stream) -> recieved_stream(stream, sb[server + '/connection'].id)
+            onlocalstream: (stream) -> recieved_stream(stream, my_id)
             success: (ph) ->
+              console.info 'joining as a publisher', my_id, my_space
               # Join plugin as a publisher (able to both send and receive streams)
               plugin_handle = ph
               plugin_handle.send
@@ -706,14 +714,14 @@
                   room: 1234
                   ptype: "publisher"
                   display: JSON.stringify
-                    id: sb[server + '/connection'].id
-                    space: sb['tawk/space']
+                    id: my_id
+                    space: my_space
             onmessage: (msg, jsep) ->
               # Janus is informing us of publishers we do not know about
               publishers = msg["publishers"] or []
               for feed in publishers
                 {id, space} = JSON.parse feed.display
-                if space == sb['tawk/space']
+                if space == my_space
                   new_remote_feed janus, feed

               # The plugin_handle.send call to join as a publisher succeeded.


Fix react15 warning about unknown props (#36)

--- src/index.cjsx
+++ src/index.cjsx
@@ -28,13 +28,18 @@
     unless @props.noFadeIn
       require '../css/fade-in.css'

+    props = Object.assign({}, @props)
+    delete props.spinnerName
+    delete props.noFadeIn
+    delete props.overrideSpinnerClassName
+
     switch @props.spinnerName

       when "three-bounce"
         require '../css/three-bounce.css'
         return (
           (
-

+

@@ -46,7 +51,7 @@
         require '../css/double-bounce.css'
         return (
           (
-

+

@@ -57,7 +62,7 @@
         require '../css/rotating-plane.css'
         return (
           (
-

+

           )
@@ -67,7 +72,7 @@
         require '../css/wave.css'
         return (
           (
-

+

@@ -81,7 +86,7 @@
         require '../css/wandering-cubes.css'
         return (
           (
-

+

@@ -92,7 +97,7 @@
         require '../css/pulse.css'
         return (
           (
-

+

           )
@@ -102,7 +107,7 @@
         require '../css/chasing-dots.css'
         return (
           (
-

+

@@ -115,7 +120,7 @@
         require '../css/circle.css'
         return (
           (
-

+

@@ -136,7 +141,7 @@
         require '../css/cube-grid.css'
         return (
           (
-

+

@@ -154,7 +159,7 @@
         require '../css/wordpress.css'
         return (
           (
-

+


label render removed

label render removed

--- lib/assets/javascripts/socmap_adf/modules/gmap/views/polygon.js.coffee
+++ lib/assets/javascripts/socmap_adf/modules/gmap/views/polygon.js.coffee
@@ -23,6 +23,7 @@
       @zone.setPolygonMap( @map )
       @onPolygonDrawCompleted() if allowDraw
       @setPolygonHandlers()
+
     @mapView.getMap().addOverlay(@)
     @setLabel() if @label
     @onZoneInitialized()
@@ -53,7 +54,6 @@
       @labelView.overlay.setPosition(point)
       @labelView.label = @label
       @labelView.content = @content
-      @labelView.render()

   startDrawing: =>
     return if @zone.readOnly


Some refactoring in interface.coffee

Some refactoring in interface.coffee

--- src/interface.coffee
+++ src/interface.coffee
@@ -9,25 +9,25 @@
   @it = ()->
     Jasmine.it.apply(
       this,
-      Wrapper.apply(this, [].slice.call(arguments)).it()
+      Wrapper.apply(this, arguments).it()
     )

   @iit = ()->
     Jasmine.iit.apply(
       this,
-      Wrapper.apply(this, [].slice.call(arguments)).it()
+      Wrapper.apply(this, arguments).it()
     )

   @fit = ()->
     Jasmine.fit.apply(
       this,
-      Wrapper.apply(this, [].slice.call(arguments)).it()
+      Wrapper.apply(this, arguments).it()
     )

   @xit = ()->
     Jasmine.xit.apply(
       this,
-      Wrapper.apply(this, [].slice.call(arguments)).it()
+      Wrapper.apply(this, arguments).it()
     )

   this


listen for 'identify' notice instead of 'register': more generic

--- src/irc.coffee
+++ src/irc.coffee
@@ -127,7 +127,7 @@
       identify_args += "#{options.nickpass}"

       bot.addListener 'notice', (from, to, text) ->
-        if from is 'NickServ' and text.indexOf('registered') isnt -1
+        if from is 'NickServ' and text.indexOf('identify') isnt -1
           bot.say 'NickServ', "identify #{identify_args}"
         else if options.nickpass and from is 'NickServ' and
                 (text.indexOf('Password accepted') isnt -1 or


calculate leave position correctly based on parent scale

--- components/dia/kddiaobject.coffee
+++ components/dia/kddiaobject.coffee
@@ -35,6 +35,9 @@

     bounds = @getBounds()
     joint  = null
+
+    bounds.w = bounds.w * @parent.scale
+    bounds.h = bounds.h * @parent.scale

     if e.pageX >= bounds.x + bounds.w  # means right joint
       joint = @joints['right']


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -1,7 +1,7 @@
 GameObject.Effect = (I={}, self) ->

   ###*
-  A convenient way to fade out this obje
+  A convenient way to fade out this object over time.

       player = GameObject()
       # this will fade the player object out over the next 2 seconds.


Outsource code in run() into separate functions

--- src/judge/run.coffee
+++ src/judge/run.coffee
@@ -1,30 +1,33 @@
 which = require 'which'
+
+originalExtensions = {}

 module.exports = (judgeCase, packagePath, command, args = [], callback) ->
 	which command, (err, commandPath) ->
 		callback err if err

-		originalExtensions = {}
-
-		# Override extension handlers.
-		for extension of require.extensions
-			do (extension) ->
-				# Delete any non-default extensions such as .coffee.
-				if ['.js', '.json', '.node'].indexOf(extension) is -1
-					delete require.extensions[extension]
-					return
-
-				unless originalExtensions[extension]
-					originalExtensions[extension] = require.extensions[extension]
-
-				if require.extensions[extension].length is 2
-					require.extensions[extension] = (module, filename) ->
-						originalExtensions[extension](module, filename)
-
-		# Clear module cache.
-		for module of require.cache
-			delete require.cache[module]
+		overrideExtensionHandlers()
+		clearModuleCache()

 		process.argv = ['node', commandPath].concat args

 		require commandPath
+
+overrideExtensionHandlers = ->
+	for extension of require.extensions
+		do (extension) ->
+			# Delete any non-default extensions such as .coffee.
+			if ['.js', '.json', '.node'].indexOf(extension) is -1
+				delete require.extensions[extension]
+				return
+
+			unless originalExtensions[extension]
+				originalExtensions[extension] = require.extensions[extension]
+
+			if require.extensions[extension].length is 2
+				require.extensions[extension] = (module, filename) ->
+					originalExtensions[extension](module, filename)
+
+clearModuleCache = ->
+	for module of require.cache
+		delete require.cache[module]


Remove stale code

Remove stale code

--- src/add/steps.coffee
+++ src/add/steps.coffee
@@ -13,7 +13,6 @@
   directum.init()

 step 'Подключение к серверу Directum', ->
-  z = require './validate'
   directum.connect validate.s, validate.d

 step 'Проверка поддержки фотографий', ->


:lipstick: de-linting

:lipstick: de-linting

--- src/scripts/modules/media/body/body.coffee
+++ src/scripts/modules/media/body/body.coffee
@@ -47,7 +47,9 @@
       @$el.children('[data-type="abstract"]').remove()

       # Wrap title and content elements in header and section elements, respectively
-      @$el.find('.example, .exercise, .note, [data-type="example"], [data-type="exercise"], [data-type="note"]').each (index, el) ->
+      @$el.find('.example, .exercise, .note,
+                [data-type="example"], [data-type="exercise"], [data-type="note"]').each (index, el) ->
+
         $el = $(el)
         $contents = $el.contents().filter (i, node) ->
           return !$(node).is('.title, [data-type="title"]')


Show the offending port name

Show the offending port name

--- src/lib/Ports.coffee
+++ src/lib/Ports.coffee
@@ -21,7 +21,7 @@
       throw new Error 'Add and remove are restricted port names'

     unless name.match /^[a-z0-9_\.]+$/
-      throw new Error 'Port names can only contain lowercase alphanumeric characters and underscores'
+      throw new Error "Port names can only contain lowercase alphanumeric characters and underscores. '#{name}' not allowed"

     # Remove previous implementation
     @remove name if @ports[name]


Use RootView.eachEditor from StatusBar package

--- src/status-bar.coffee
+++ src/status-bar.coffee
@@ -5,11 +5,8 @@
 module.exports =
 class StatusBar extends View
   @activate: (rootView) ->
-    for editor in rootView.getEditors()
-      @appendToEditorPane(rootView, editor) if rootView.parents('html').length
-
-    rootView.on 'editor:attached', (e, editor) =>
-      @appendToEditorPane(rootView, editor)
+    rootView.eachEditor (editor) =>
+      @appendToEditorPane(rootView, editor) if editor.attached

   @appendToEditorPane: (rootView, editor) ->
     if pane = editor.pane()


Only compute Git status flags once

Only compute Git status flags once

Previously the status was fetched twice, once for
if modified and once for if new.

Now the flags are fetched once and Git now provides
helpers to check the status flags directly for
modified and new status.

--- src/status-bar.coffee
+++ src/status-bar.coffee
@@ -76,7 +76,10 @@

     @gitStatusIcon.addClass('git-status octicons')
     git = @buffer.getRepo()
-    if git?.isPathModified(path)
+    return unless git
+
+    status = git.getPathStatus(path)
+    if git.isStatusModified(status)
       @gitStatusIcon.addClass('modified-status-icon')
       stats = git.getDiffStats(path)
       if stats.added and stats.deleted
@@ -87,7 +90,7 @@
         @gitStatusIcon.text("-#{stats.deleted}")
       else
         @gitStatusIcon.text('')
-    else if git?.isPathNew(path)
+    else if git.isStatusNew(status)
       @gitStatusIcon.addClass('new-status-icon')
       @gitStatusIcon.text("+#{@buffer.getLineCount()}")


Use meaningful names for helper processes

Summary: See diff title

Test Plan: Run locally, check in htop for new titles

Reviewers: evan, juan

Reviewed By: juan

Differential Revision: https://phab.nylas.com/D3778

--- src/nylas-env.coffee
+++ src/nylas-env.coffee
@@ -201,6 +201,9 @@
     # We initialize all of the stores loaded into the StoreRegistry once
     # the window starts loading.
     require('nylas-exports')
+
+    process.title = "Nylas Mail #{@getWindowType()}"
+    @onWindowPropsReceived(-> process.title = "Nylas Mail #{@getWindowType}")

   # This ties window.onerror and Promise.onPossiblyUnhandledRejection to
   # the publically callable `reportError` method. This will take care of


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -61,7 +61,7 @@
   ###
   each: (selector, fn) ->
     self.find(selector).each (obj, index) ->
-      fn(obj,)
+      fn(obj, index)

   find: (selector) ->
     results = []


Refactor fetchGlobalStatusMetrics

Refactor fetchGlobalStatusMetrics

--- src/api/statsd.coffee
+++ src/api/statsd.coffee
@@ -11,6 +11,8 @@
 metrics = require "../metrics"
 logger = require "winston"
 co = require "co"
+
+statusArray = ['Processing', 'Failed', 'Completed', 'Successful', 'Completed with error(s)']

 # Overall Metrics

@@ -30,39 +32,33 @@
 # Retrives Global Status Metrics from the StatsD API

 exports.fetchGlobalStatusMetrics = `function *(allowedIds) {
-  var filtersObject = this.request.query;
-  var userRequesting = this.authenticated,
-    path = '',
-    results = {},
-    final = {};
-  allowedIds = allowedIds.length > 0 ? allowedIds : yield metrics.getAllowedChannelIDs(userRequesting)
-  var data = []
-  var status_array = ['Processing', 'Failed', 'Completed', 'Successful', 'Completed with error(s)']
+  allowedIds = allowedIds.length > 0 ? allowedIds : yield metrics.getAllowedChannelIDs(this.authenticated);

-  for (var j = 0; j < allowedIds.length; j++) {
-    var render_url = "/render?target=transformNull(summarize(stats.counters." + domain + ".channels." + allowedIds[j]
-    for (i = 0; i < status_array.length; i++) {
-      path = render_url + ".statuses." + status_array[i] + ".count,'1day'))&format=json";
-      results[status_array[i]] = yield exports.fetchData(path)
-      if (results[status_array[i]]) {
-        final[status_array[i]] = 'data' in results[status_array[i]] ? results[status_array[i]].data[1][0] : 0;
+  var data = allowedIds.map(function(channelId) {
+    var total = {};
+    var renderUrl = '/render?target=transformNull(summarize(stats.counters.' + domain + '.channels.' + channelId;
+
+    statusArray.forEach(co.wrap(function*(statusType) {
+      var path = renderUrl + '.statuses.' + statusType + '.count,\'1day\'))&format=json';
+      var result = yield exports.fetchData(path);
+      if (result && result.data) {
+        total[statusType] = result.data[1][0];
       } else {
-        final[status_array[i]] = 0 ;
+        total[statusType] = 0;
       }
-    }
+    }));

-    data.push({
-      _id: {"channelID": allowedIds[j]},
-      failed: final.Failed,
-      successful: final.Successful,
-      processing: final.Processing,
-      completed: final.Completed,
-      completedWErrors: final['Completed with error(s)']
-    });
-  }
+    return {
+      _id: {channelID: channelId},
+      failed: total.Failed,
+      successful: total.Successful,
+      processing: total.Processing,
+      completed: total.Completed,
+      completedWErrors: total['Completed with error(s)']
+    };
+  });

-  this.body = data
-  return data;
+  return this.body = data;
 }`

 exports.retrieveAverageLoadTimePerHour = `function *(type) {
@@ -75,7 +71,6 @@
 }`

 exports.retrieveChannelMetrics = `function *(type, channelId) {
-  var statusArray = ['Processing', 'Failed', 'Completed', 'Successful', 'Completed with error(s)'];
   var renderUrl = "/render?target=transformNull(summarize(stats.counters." + domain + ".channels." + channelId
   var path = '';
   var total = {};


fix: add the query property to the requestOptionsChanged observer list

--- src/grapp-iron-ajax.coffee
+++ src/grapp-iron-ajax.coffee
@@ -39,7 +39,7 @@
     query: {type: String, value: null}

   observers: [
-    '_requestOptionsChanged(baseUrl, url, path, method, params, headers,contentType, body, sync, handleAs, withCredentials, auto)'
+    '_requestOptionsChanged(baseUrl, url, path, query, method, params, headers,contentType, body, sync, handleAs, withCredentials, auto)'
   ]

 # grappendorf: removed the creation of a query string from the params hash


start without parameters restarts the last timer

--- src/scripts/harvest.coffee
+++ src/scripts/harvest.coffee
@@ -11,12 +11,13 @@
 #
 #   hubot remember my harvest account <email> with password <password> - Make hubot remember your Harvest credentials
 #   hubot forget my harvest account - Make hubot forget your Harvest credentials again
+#   hubot start harvest - Restart the last timer.
 #   hubot start harvest at <project>/<task>: <notes> - Start a Harvest timer at a given project-task combination
 #   hubot stop harvest [at project/task] - Stop the most recent Harvest timer or the one for the given project-task combination.
 #   hubot daily harvest [of <user>] [at yyyy-mm-dd] - Show a user's Harvest timers for today (or yours, if noone is specified) or a specific day
 #   hubot list harvest tasks [of <user>] - Show the Harvest project-task combinations available to a user (or you, if noone is specified)
 #   hubot is harvest down/up - Check if the Harvest API is reachable.
-#
+#
 # Notes:
 #   All commands and command arguments are case-insenitive. If you work
 #   on a project "FooBar", hubot will unterstand "foobar" as well. This
@@ -39,6 +40,10 @@
 #   hubot forget my harvest account
 #     Deletes your account credentials from Hubt's memory.
 #
+#  hubot start harvest
+#    Examines the list of timers for today and creates a new timer with
+#    the same properties as the most recent one.
+#
 #   hubot start harvest at <project>/<task>: <notes>
 #     Starts a timer for a task at a project (both of which may
 #     be abbreviated, Hubot will ask you if your input is
@@ -63,7 +68,7 @@
 #     This is the subdomain you access the Harvest service with, e.g.
 #     if you have the Harvest URL http://yourcompany.harvestapp.com
 #     you should set this to "yourcompany" (without the quotes).
-#
+#
 # Author:
 #   Quintus @ Asquera
 #
@@ -239,6 +244,23 @@
     catch error
       msg.reply "Failed to start timer: fatal error: #{error}"

+  robot.respond /start harvest/i, (msg) ->
+    unless user = check_user(robot, msg)
+      return
+
+    harvest = new HarvestService(user.harvest_account)
+
+    try
+      harvest.restart msg, (status, body) ->
+        if 200 <= status <= 299
+          if body.hours_for_previously_running_timer?
+            msg.reply "Previously running timer stopped at #{body.hours_for_previously_running_timer}h."
+          msg.reply "OK, I started tracking you on #{body.project}/#{body.task}."
+        else
+          msg.reply "Failed to start timer: request failed with status #{status}."
+    catch error
+      msg.reply "Failed to start timer: fatal error: #{error}"
+
   # Stops the timer running for a project/task combination,
   # if any. If no combination is given, stops the first
   # active timer available.
@@ -324,6 +346,25 @@
         callback res.statusCode, JSON.parse(body)
       else
         callback res.statusCode, null
+
+  restart: (msg, callback) ->
+    this.daily msg, (status, body) =>
+      if 200 <= status <= 299
+        if body.day_entries.length == 0
+          msg.reply "No last entry to restart, sorry."
+        else
+          last_entry = body.day_entries.pop()
+          data =
+            notes: last_entry.notes
+            project_id: last_entry.project_id
+            task_id: last_entry.task_id
+          this.request(msg).path("/daily/add").post(JSON.stringify(data)) (err, res, body) ->
+            if 200 <= res.statusCode <= 299
+              callback res.statusCode, JSON.parse(body)
+            else
+              callback res.statusCode, null
+      else
+        callback status, null

   # Issues /daily/add to the Harvest API to add a new timer
   # starting from now.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/rectangle.coffee
+++ src/rectangle.coffee
@@ -33,81 +33,3 @@

   (exports ? this)["Rectangle"] = Rectangle
 )()
-
-(->
-  Rectangle = ({x, y, halfWidth, halfHeight}) ->
-
-    return {
-      __proto__: Rectangle::
-
-      x: x || 0
-      y: y || 0
-
-      halfWidth: halfWidth || 0
-      halfHeight: halfHeight || 0
-    }
-
-  Rectangle:: =
-    contains: (rectangle) ->
-      rectangle.top >= @top and
-      rectangle.bottom <= @bottom and
-      rectangle.left >= @left and
-      rectangle.right <= @right
-
-    center: ->
-      Point(@x, @y)
-
-    equal: (other) ->
-      @x is other.x and
-      @y is other.y and
-      @halfWidth is other.halfWidth and
-      @halfWidth is other.halfHeight
-
-    grow: (x, y) ->
-      amount = x unless y?
-
-      if amount
-        return Rectangle
-          x: @x
-          y: @y
-          halfWidth: @halfWidth + amount
-          halfHeight: @halfHeight + amount
-      else
-        return Rectangle
-          x: @x
-          y: @y
-          halfWidth: @halfWidth + x
-          halfHeight: @halfHeight + y
-
-    shrink: (x, y) ->
-      if y?
-        @grow(-x, -y)
-      else
-        @grow(-x, -x)
-
-    overlaps: (rectangle) ->
-      @top < rectangle.bottom and
-      @right > rectangle.left and
-      @bottom > rectangle.top and
-      @left < rectangle.right
-
-    outsideOf: (rectangle) ->
-      not @contains(rectangle) and
-      not @overlaps(rectangle)
-
-  Rectangle::__defineGetter__ 'left', ->
-    @x - @halfWidth
-
-  Rectangle::__defineGetter__ 'right', ->
-    @x + @halfWidth
-
-  Rectangle::__defineGetter__ 'top', ->
-    @y - @halfHeight
-
-  Rectangle::__defineGetter__ 'bottom', ->
-    @y + @halfHeight
-
-  (exports ? this)["Rectangle"] = Rectangle
-)()
-
-


collision with regular bounds

collision with regular bounds

--- src/collision.coffee
+++ src/collision.coffee
@@ -217,6 +217,7 @@
     rayRectangle: (source, direction, target) ->
       unless target.xw? and target.yw?
         if target.width? and target.height?
+          x
           return Collision.rayRectangle source, direction,
             xw: target.width/2
             yw: target.height/2


fix order of objects passed to deep-extend

--- lib/from-files.coffee
+++ lib/from-files.coffee
@@ -15,5 +15,5 @@
         object = require file
       else
         object = ini.parse fs.readFileSync file, 'utf8'
-      the.values = deepExtend object, the.values
+      the.values = deepExtend the.values, object
   return the


backend rethinkdb: add a backend version of synctable, so I don't have to write a bunch of buggy tricky .changes() queries

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -254,7 +254,6 @@
                     opts = {}
                 if not opts?
                     opts = {}
-                opts.includeInitial = false  # accidentally in rethinkdb 2.1.1 and breaks badly
                 that2 = @  # needed to call run_native properly on the object below.
                 query_string = "#{that2}"
                 if query_string.length > 200
@@ -361,6 +360,16 @@

     table: (name, opts={readMode:'outdated'}) =>
         @db.table(name, opts)
+
+    # Server-side changefeed-updated table, which automatically restart changefeed
+    # on error, etc.  See SyncTable docs where the class is defined.
+    synctable: (opts) =>
+        opts = defaults opts,
+            query : required
+            primary_key : undefined  # if not given, will use one for whole table -- value *must* be a string
+            cb    : required
+        new SyncTable(opts.query, opts.primary_key, @, opts.cb)
+        return

     # Wait until the query results in at least one result obj, and
     # calls cb(undefined, obj).
@@ -3826,3 +3835,132 @@

 exports.rethinkdb = (opts) -> new RethinkDB(opts)
+
+
+###
+SyncTable
+
+Methods:
+  - get():     Current value of the query, as an immutable.js Map from
+               the primary key to the records, which are also immutable.js Maps.
+  - get(key):  The record with given key, as an immutable Map; if key=undefined, returns full immutable.js map
+  - getIn([key1,key2,...]):  as with immutable.js
+  - has(key):  Whether or not record with this primary key exists.
+  - close():   Frees up resources, stops syncing, don't use object further
+
+Events:
+  - 'change', primary_key : fired any time the value of the query result
+        *changes* after initialization; where change includes deleting a record.
+
+###
+
+immutable = require('immutable')
+EventEmitter = require('events')
+
+# hackishly try to determine the most likely table of  query
+# (some queries span tables, e.g., joins, so not always possible, right).
+query_table = (query) ->
+    s = query.toString()
+    tbl = '.table("'
+    i = s.indexOf(tbl)
+    if i == -1
+        return
+    s = s.slice(i+tbl.length)
+    i = s.indexOf('"')
+    if i == -1
+        return
+    return s.slice(0,i)
+
+class SyncTable extends EventEmitter
+    constructor: (@_query, @_primary_key, @_db, cb) ->
+        @_table = query_table(@_query)   # undefined if can't determine
+        dbg = @_dbg('constructor')
+        @_primary_key ?= SCHEMA[@_table]?.primary_key ? 'id'
+        @_value = immutable.Map()
+        dbg("initializing changefeed")
+        @_init_changefeed (err) =>
+            cb(err, @)
+
+    get: (key) =>
+        if not key?
+            return @_value
+        else
+            return @_value.get(key)
+
+    getIn: (x...) =>
+        return @_value.getIn(x)
+
+    has: (key) =>
+        return @_value.has(key)
+
+    close: () =>
+        @_dbg('close')()
+        @removeAllListeners()
+        @_closed = true
+        @_feed.close()
+        delete @_value
+        delete @_primary_key
+        delete @_table
+        delete @_query
+        delete @_db
+
+    _dbg: (f) =>
+        return (m) => winston.debug("SyncTable(table='#{@_table}').#{f}: #{m}")
+
+    _init_changefeed: (cb) =>
+        if @_closed
+            return
+        dbg = @_dbg("_init_changefeed")
+        dbg()
+        @_query.changes(includeInitial:true, includeStates:true).run (err, feed) =>
+            if err
+                dbg("failed to start changefeed: #{err}")
+                if err.name == 'ReqlQueryLogicError'
+                    dbg('terminating')
+                    cb?(err)
+                else
+                    dbg('will try again')
+                    setTimeout(@_init_changefeed, 10000*Math.random()+5000)
+            else
+                value = immutable.Map()
+                @_feed = feed
+                feed.each (err, change) =>
+                    if err
+                        feed.close()
+                        dbg("error -- will recreate changefeed in a few seconds: #{err}")
+                        setTimeout(@_init_changefeed, 10000*Math.random()+5000)
+                    else
+                        if change.state?
+                            @_state = change.state
+                            if @_state == 'ready'
+                                if @_value?
+                                    # this is a reconnect so fire change event for all records that changed
+                                    # keys that were there before and have changed:
+                                    @_value.map (v, k) =>
+                                        if not immutable.is(v, value.get(k))
+                                            @emit('change', k)
+                                    # new keys - not there before
+                                    value.map (v, k) =>
+                                        if not @_value.has(k)
+                                            @emit('change', k)
+                                @_value = value
+                                cb?()  # initialized!
+                                cb = undefined
+                        if change.old_val? and not change.new_val?
+                            k = change.old_val[@_primary_key]
+                            value = value.delete(k)
+                            if @_state == 'ready'
+                                @_value = value
+                                # WARNING!!!!! This process.nextTick is necessary; if you don't put it here
+                                # the feed just stops after the first thing is emitted!!!!!  This would, of
+                                # course, lead to very subtle bugs.  I don't understand this, but it is probably
+                                # some subtle bug/issue involving bluebird promises and node's eventemitter...
+                                process.nextTick(=>@emit('change', k))
+                        if change.new_val?
+                            k = change.new_val[@_primary_key]
+                            value = value.set(k, immutable.fromJS(change.new_val))
+                            if @_state == 'ready'
+                                @_value = value
+                                process.nextTick(=>@emit('change', k))   # WARNING: don't remove this promise.nextTick! See above.
+
+


Fix for when the array is empty

Fix for when the array is empty

`$$.fn.hasClass` and `$$.fn.style` will both throw exceptions if the current selection of elements is empty.

--- src/quo.style.coffee
+++ src/quo.style.coffee
@@ -24,12 +24,12 @@
                 @className = @className.trim()

     $$.fn.hasClass = (name) ->
-        _existsClass name, this[0].className
+        this.length > 0 and _existsClass name, this[0].className

     $$.fn.style = (property, value) ->
         if value
             @each -> @style[property] = value
-        else
+        else if this.length > 0
             this[0].style[property] or _computedStyle(this[0], property)

     $$.fn.css = (property, value) -> @style property, value


no more top level app resize notifications

--- src/react/app.coffee
+++ src/react/app.coffee
@@ -5,16 +5,6 @@
 MessageList = require "./message_list"

 App = React.createClass
-  updateDimensions: ->
-    console.log "WINDOWS: width: #{$(window).width()}, height: #{$(window).height()}"
-    this.setState({width: $(window).width(), height: $(window).height()})
-  componentWillMount: ->
-    this.updateDimensions()
-  componentDidMount: ->
-    window.addEventListener("resize", this.updateDimensions)
-  componentWillUnmount: ->
-    window.removeEventListener("resize", this.updateDimensions)
-
   getInitialState: ->
     tokenFile = "#{process.env.HOME}/.peonies.json"
     config = new Config(tokenFile)


Fix row style bug

Fix row style bug

--- src/row.coffee
+++ src/row.coffee
@@ -65,7 +65,9 @@
 			}
 			c:[]
 		}
-		obj.$.s = @_.styleMixin.style.id
+		styleId = @_.styleMixin.style.id
+		obj.$.s = styleId
+		obj.$.customFormat = 1 if styleId != 0

 		if @_.heightCustomized
 			obj.$.ht = @_.height


API update appendModelJSON -> appendJSON

--- lib/json_utils.coffee
+++ lib/json_utils.coffee
@@ -83,7 +83,7 @@
         return callback(err) if err
         callback(null, result)

-  @appendModelJSON = (json, related_model, attribute_name, template, options, callback) ->
+  @appendJSON = (json, related_model, attribute_name, template, options, callback) ->
     (callback = options; options = {}) if arguments.length is 5

     # empty


Revert "Fixed linting errors"

Revert "Fixed linting errors"

This reverts commit c40ef86f80e6b31bbb3f665cad5266eaa5c3adde.

--- lib/label-view.coffee
+++ lib/label-view.coffee
@@ -23,7 +23,7 @@
     while(match = texRootRex.exec(@editor.getText()))
       absolutFilePath = FindLabels.getAbsolutePath(activePaneItemPath,match[4])
       basePath = pathModule.dirname(absolutFilePath)
-      labels = ListLabels.fromDir(basePath, /\.tex$/)
+      labels = ListLabels.fromDir(basePath, /\.tex$/);
     @setItems(labels)
     @panel ?= atom.workspace.addModalPanel(item: this)
     @panel.show()
@@ -40,7 +40,7 @@
     "label"

   viewForItem: ({label}) ->
-    "
#{label}
"
+     "
#{label}
"

   confirmed: ({label}) ->
     @editor.insertText label


Passed arguments into mapEntity()

Passed arguments into mapEntity()


Fixed cannot get detectEncoding from undefined

--- src/lib/models/file.coffee
+++ src/lib/models/file.coffee
@@ -348,7 +348,7 @@
 	# Actions

 	# Initialize
-	initialize: (attrs,opts) ->
+	initialize: (attrs,opts={}) ->
 		# Prepare
 		{outDirPath, detectEncoding, stat, data, buffer, meta} = opts


find models in dir method

find models in dir method

--- lib/node_utils.coffee
+++ lib/node_utils.coffee
@@ -5,38 +5,58 @@
 Backbone = require 'backbone'
 Queue = require 'queue-async'

-resetFiles = (directory, options, callback) ->
-  fs.readdir directory, (err, files) ->
-    return callback(err) if err
-    return callback() unless files
+module.exports = class NodeUtils

-    queue = new Queue(1)
+  @findModels = (directory, options, callback) ->

-    for file in files
-      do (file) -> queue.defer (callback) ->
-        pathed_file = path.join(directory, file)
-        fs.stat pathed_file, (err, stat) ->
-          return callback(err) if err
+    model_types = []
+    findModelsInDirectory = (directory, options, callback) ->
+      fs.readdir directory, (err, files) ->

-          return resetFiles(pathed_file, options, callback) if stat.isDirectory() # a directory
-          extension = path.extname(pathed_file)
-          return callback() unless (extension is '.js' or extension is '.coffee')
+        return callback(err) if err
+        return callback(null, model_types) unless files

-          try
-            model_type = require(path.join(directory, file))
-            return callback() unless (model_type and _.isFunction(model_type) and ((new model_type()) instanceof Backbone.Model) and model_type.resetSchema)
-            model_type.resetSchema options, callback
+        queue = new Queue(1)

-          catch err
-            console.log "resetSchemasByDirectory: skipping: #{err}" if options.verbose
-            callback()
+        for file in files
+          do (file) -> queue.defer (callback) ->
+            pathed_file = path.join(directory, file)
+            fs.stat pathed_file, (err, stat) ->
+              return callback(err) if err

-    queue.await callback
+              return findModelsInDirectory(pathed_file, options, callback) if stat.isDirectory() # a directory
+              extension = path.extname(pathed_file)
+              return callback() unless (extension is '.js' or extension is '.coffee')

-module.exports = class NodeUtils
-  @resetSchemasByDirectory: (directory, options, callback) ->
+              try
+                model_path = path.join(directory, file)
+                model_type = require(model_path)
+                return callback() unless (model_type and _.isFunction(model_type) and ((new model_type()) instanceof Backbone.Model) and model_type.resetSchema)
+                model_type.path = model_path if options.append_path
+                model_types.push(model_type)
+                callback()
+
+              catch err
+                console.log "resetSchemasByDirectory: skipping: #{err}" if options.verbose
+                callback()
+
+        queue.await (err) ->
+          callback(err) if err
+          callback(null, model_types)
+
+    findModelsInDirectory(directory, options, callback)
+
+  @resetSchemasByDirectory: (directory, options, callback) =>
     [options, callback] = [{}, options] if arguments.length is 2

-    resetFiles directory, options, (err) ->
-      console.log "resetSchemasByDirectory: failed to reset schemas: #{err}" if err
-      callback(err)
+    @findModels directory, options, (err, model_types) ->
+
+      queue = new Queue(1)
+
+      for model_type in model_types
+        do (model_type) -> queue.defer (callback) ->
+          model_type.resetSchema options, callback
+
+      queue.await (err) ->
+        console.log "resetSchemasByDirectory: failed to reset schemas: #{err}" if err
+        callback(err)


:bug: Empty previous buffer on render in panel-view

--- lib/panel-view.coffee
+++ lib/panel-view.coffee
@@ -4,6 +4,7 @@
   createdCallback: ->
     this.id = 'linter-panel'
   render: (Messages)->
+    @innerHTML = ''
     Messages.forEach (Message)=>
       @appendChild @Linter.View.messageLine Message, @Model.Type is 'project'
 module.exports = PanelView = document.registerElement('linter-panel-view', {prototype: PanelView.prototype})


Fix broken Processor.

Fix broken Processor.

- The `process` method wasn’t part of the class, due to
  syntax -- inconsistent indentation. Standardize on 4 spaces.

- The default params weren’t proper code references. Fixed.

--- lib/processor._coffee
+++ lib/processor._coffee
@@ -3,15 +3,18 @@

 # Public API
 module.exports = class Processor
-# Defaults
+    # Defaults
     @DEFAULT_TILE_SIZE = 254
     @DEFAULT_TILE_OVERLAP = 1
     @DEFAULT_FORMAT = 'jpg'

-  constructor: (@path) ->
-  process: (source, _) ->
-    destination = path.join @path, path.basename source
-    destination += '.dzi'
-    DeepZoomImage.create _, source, destination, DEFAULT_TILE_SIZE,
-      DEFAULT_TILE_OVERLAP, DEFAULT_FORMAT
-    destination
+    constructor: (@path) ->
+
+    process: (source, _) ->
+        destination = path.join @path, path.basename source
+        destination += '.dzi'
+        DeepZoomImage.create _, source, destination,
+            Processor.DEFAULT_TILE_SIZE,
+            Processor.DEFAULT_TILE_OVERLAP,
+            Processor.DEFAULT_FORMAT
+        destination


Changing default settings to refresh the repl on start and to automatically pretty print REPL results. I believe these are more likely to be what most people want and friendly to beginners.

--- lib/proto-repl.coffee
+++ lib/proto-repl.coffee
@@ -60,11 +60,11 @@
     autoPrettyPrint:
       description: "Configures whether the REPL automatically pretty prints values."
       type: "boolean"
-      default: false
+      default: true
     refreshOnReplStart:
       description: "Configures whether the REPL should automatically refresh code when it starts."
       type: "boolean"
-      default: false
+      default: true
     refreshBeforeRunningTestFile:
       description: "Configures whether the REPL should automatically refresh code before running all the tests in a file."
       type: "boolean"


adding SC.Helper.JSON.parse

adding SC.Helper.JSON.parse

--- src/sdk.coffee
+++ src/sdk.coffee
@@ -251,7 +251,14 @@
         removeItem: (key) ->
           delete this._store.key
       }
-
+    JSON:
+      parse: (string) ->
+        if string[0] != "{" && string[0] != "["
+          return null
+        else if window.JSON?
+          window.JSON.parse(string)
+        else
+          eval(string)
     JSONP:
       callbacks: {}
       randomCallbackName: ->


stop hitting yourself

stop hitting yourself


Cleanup: Remove commented out code

Cleanup: Remove commented out code

--- src/marbles/http.js.coffee
+++ src/marbles/http.js.coffee
@@ -72,7 +72,6 @@
       @multipart = true

       @buildMultipartRequestBody =>
-        # @setHeader("Content-Length", @body.length)
         @runRequestMiddleware()
         @finalizeSendRequest()
     else


:confused: Forgot to replace most references to clipboardMetadata

Never attempt to fix a PR right before going to sleep!

--- src/selection.coffee
+++ src/selection.coffee
@@ -514,8 +514,8 @@
     if maintainClipboard
       {text: clipboardText, metadata} = atom.clipboard.readWithMetadata()

-      if clipboardMetadata? and clipboardMetadata.selections?
-        clipboardMetadata.selections.push(text)
+      if metadata? and metadata.selections?
+        metadata.selections.push(text)
       else
         metadata = { selections: [clipboardText, text] }


fix possible nil bug with using getLeaf

fix possible nil bug with using getLeaf

--- src/selection.coffee
+++ src/selection.coffee
@@ -35,14 +35,16 @@
     else if end == start
       # TODO can we remove DOM manipulation here? Could cause issues with rest of app
       @editor.doSilently( =>
-        leaf = @range.end.getLeaf()
-        clone = Tandem.Utils.cloneAncestors(leaf.node, leaf.line.node)
-        [left, right] = leaf.line.splitContents(leaf.getLineOffset() + @range.end.offset)
-        leaf.line.node.insertBefore(clone, right)
+        leafNode = @range.end.leafNode
+        line = @editor.doc.findLine(leafNode)
+        clone = Tandem.Utils.cloneAncestors(leafNode, line.node)
+        lineOffset = Tandem.Position.getIndex(leafNode, 0, line.node) + @range.end.offset
+        [left, right] = line.splitContents(lineOffset)
+        line.node.insertBefore(clone, right)
         clone = Tandem.Utils.removeAttributeFromSubtree(clone, attribute)
         if clone == null
-          clone = leaf.node.ownerDocument.createElement('span')
-          leaf.line.node.insertBefore(clone, right)
+          clone = line.node.ownerDocument.createElement('span')
+          line.node.insertBefore(clone, right)
         if value and Tandem.Utils.getAttributeDefault(attribute) != value
           attrNode = Tandem.Utils.createContainerForAttribute(clone.ownerDocument, attribute, value)
           clone = Tandem.Utils.wrap(attrNode, clone)
@@ -102,16 +104,6 @@
     rangy.restoreSelection(savedSel, false)
     this.update(true)

-  save: ->
-    savedSel = rangy.saveSelection(@editor.contentWindow)
-    _.each(savedSel.rangeInfos, (rangeInfo) ->
-      _.each([rangeInfo.startMarkerId, rangeInfo.endMarkerId, rangeInfo.markerId], (markerId) ->
-        marker = rangeInfo.document.getElementById(markerId)
-        marker.classList.add(Tandem.Constants.SPECIAL_CLASSES.EXTERNAL) if marker?.classList?
-      )
-    )
-    return savedSel
-
   setRange: (@range, silent = false) ->
     rangySel = rangy.getSelection(@editor.contentWindow)
     if @range?
@@ -128,6 +120,16 @@
     range.setEnd(nativeSel.focusNode, nativeSel.focusOffset)
     rangySel.setSingleRange(range)

+  save: ->
+    savedSel = rangy.saveSelection(@editor.contentWindow)
+    _.each(savedSel.rangeInfos, (rangeInfo) ->
+      _.each([rangeInfo.startMarkerId, rangeInfo.endMarkerId, rangeInfo.markerId], (markerId) ->
+        marker = rangeInfo.document.getElementById(markerId)
+        marker.classList.add(Tandem.Constants.SPECIAL_CLASSES.EXTERNAL) if marker?.classList?
+      )
+    )
+    return savedSel
+
   update: (silent = false) ->
     range = this.getRange()
     unless (range == @range) || (@range?.equals(range))


missed conflict

missed conflict

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -6,17 +6,11 @@
     _.each(@container.querySelectorAll('.active'), (button) =>
       Scribe.DOM.removeClass(button, 'active')
     )
-<<<<<<< HEAD
-    return unless selection?
-    formats = selection.getFormats()
-    _.each(formats, (value, key) =>
-=======
     _.each(@container.querySelectorAll('select'), (select) =>
       select.querySelector('option[selected]').selected = true
     )
     return unless selection?
     _.each(selection.getFormats(), (value, key) =>
->>>>>>> link
       if value?
         elem = @container.querySelector(".#{key}")
         return unless elem?


Whitelist 'Via' and 'X-Real-Ip' headers.

--- src/transport.coffee
+++ src/transport.coffee
@@ -116,7 +116,7 @@

         headers = {}
         for key in ['referer', 'x-client-ip', 'x-forwarded-for', \
-                    'x-cluster-client-ip']
+                    'x-cluster-client-ip', 'via', 'x-real-ip']
             headers[key] = req.headers[key] if req.headers[key]
         if headers
             @connection.headers = headers


shorter coffee

shorter coffee

--- stretch.coffee
+++ stretch.coffee
@@ -20,7 +20,7 @@
       shadow.className = "textmetrics"
       shadow = $(shadow)
       properties = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'word-wrap', "width"]
-      for property in  properties
+      for property in properties
         value = @css elm, property
         shadow.css property, value
       shadow.css "height", @css(shadow, "line-height")
@@ -33,7 +33,7 @@
   capitalize: (name) ->
     while (indice = name.indexOf "-") > -1
       name = name.replace "-", ""
-      suffixe = name.slice(indice)
+      suffixe = name.slice indice
       suffixe = suffixe.replace suffixe[0], suffixe[0].toUpperCase()
       name = name.replace name.slice(indice), suffixe
     name
@@ -41,10 +41,8 @@
   measure: =>
     shadow = @shadow()
     shadow.val "#{@el().val()}..."
-    {
-      width: shadow.get(0).scrollWidth
-      height: shadow.get(0).scrollHeight
-    }
+    width: shadow.get(0).scrollWidth
+    height: shadow.get(0).scrollHeight

   destroy: =>
     document.body.removeChild @_shadow
@@ -56,4 +54,4 @@
     height = this.measure().height
     height = Math.max @min, height
     height = Math.min height, @max if @max
-    @el().css "height", height+    @el().css "height", height


Stub out `Decoration::destroy` and `Decoration::update`

--- src/decoration.coffee
+++ src/decoration.coffee
@@ -18,6 +18,12 @@
     @id = nextId()
     @params.id = @id
     @flashQueue = null
+
+  destroy: ->
+    # TODO: implement
+
+  update: (newParams) ->
+    # TODO: implement

   getParams: ->
     @params


Return from adjust_image_position unless sized

--- src/jquery.superslides.coffee
+++ src/jquery.superslides.coffee
@@ -35,6 +35,7 @@
       $img.data('original-height', image.height).removeAttr('height')
       $img.data('original-width', image.width).removeAttr('width')
       adjust_image_position $img
+    return

   if height < $img.data('original-height')
     $img.css


good fix for Issue [#33](https://github.com/atom/asar/issues/33)

--- src/filesystem.coffee
+++ src/filesystem.coffee
@@ -18,8 +18,9 @@
     p = path.relative @src, p
     return @header if not p
     name = path.basename p
-    node = @searchNodeFromDirectory(path.dirname(p))
-    node.files[name] = {} if not node.files[name]
+    node = @searchNodeFromDirectory path.dirname p
+    node.files ?= {}
+    node.files[name] ?= {}
     node.files[name]

   insertDirectory: (p, shouldUnpack) ->


Change default homeRoute to /

Change default homeRoute to /

--- client/entry.coffee
+++ client/entry.coffee
@@ -1,7 +1,7 @@
 AccountsEntry =
   settings:
     wrapLinks: true
-    homeRoute: '/home'
+    homeRoute: '/'
     dashboardRoute: '/dashboard'
     passwordSignupFields: 'EMAIL_ONLY'
     emailToLower: true


reverse relation saving checks own type rather than its reverse_relations

--- lib/relations/relation.coffee
+++ lib/relations/relation.coffee
@@ -35,7 +35,7 @@
     return reverse_relation

   _saveRelated: (model, related_models, callback) ->
-    return callback() if @embed or not @reverse_relation or (@reverse_relation.type is 'hasOne') # no foriegn key, no save required
+    return callback() if @embed or not @reverse_relation or (@type is 'belongsTo') # no foriegn key, no save required
     return callback() if @isVirtual() # skip virtual attributes

     @cursor(model, @key).toJSON (err, json) =>


refactor(connector): renamed Connector to RESTConnector and removed type param

this class will never handle more than one type, other types require other connector classes

--- src/connector.coffee
+++ src/connector.coffee
@@ -1,5 +1,5 @@
-class Connector
-  constructor: (base = '/api/', @type = 'jquery.rest', settings = {}) ->
+class RESTConnector
+  constructor: (base = '/api/', settings = {}) ->
     default_settings =
       stripTrailingSlash: true
       methodOverride:     true
@@ -8,15 +8,13 @@

     @connector = new $.RestClient base, settings

-  get: (main_api) =>
-    return @connector[main_api]
+  get: (base_uri) =>
+    return @connector[base_uri]

-  init: (resource) =>
+  init: (resource, sub_resources = []) =>
     @connector.add resource unless @connector[resource]?

-  add_apis_to: (sub_apis, main_api) =>
-    for name, data of sub_apis
-      @connector[main_api].add name + 's' unless @connector[main_api][name + 's']
-
+    for name, data of sub_resources
+      @connector[resource].add name + 's' unless @connector[resource][name + 's']

 module.exports = Connector if module?


Added autoload for autoload-dev

Added autoload for autoload-dev

--- lib/services/namespace.coffee
+++ lib/services/namespace.coffee
@@ -24,6 +24,14 @@
                     namespace = namespace.substr(0, namespace.length-1)

                 autoloaders[src] = namespace
+
+        if composer["autoload-dev"]
+            for psr, autoload of composer["autoload-dev"]
+                for namespace, src of autoload
+                    if namespace.endsWith("\\")
+                        namespace = namespace.substr(0, namespace.length-1)
+
+                    autoloaders[src] = namespace

         # Get the current path of the file
         path = editor.getPath()


feat(ViewCtrl): no initial transition

feat(ViewCtrl): no initial transition

--- modules/controllers/view/index.coffee
+++ modules/controllers/view/index.coffee
@@ -24,12 +24,14 @@
     toState
     toParams
     fromState
-    fromParams ) ->
+    fromParams
+    ) ->

-    $scope = event.currentScope
-    if $scope.getDirection() is 'reverse'
-      $scope.setTransition fromState.transition
+    event.currentScope.setTransition if fromState.name is ''
+      ''
+    else if $scope.getDirection() is 'reverse'
+      fromState.transition
     else
-      $scope.setTransition toState.transition
+      toState.transition

 angular.module('bp.controllers').controller 'bpViewCtrl', viewCtrl


Lines-yardstick has different measurements on win32

--- spec/lines-yardstick-spec.coffee
+++ spec/lines-yardstick-spec.coffee
@@ -78,9 +78,10 @@
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 0))).toEqual({left: 0, top: 0})
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 1))).toEqual({left: 7, top: 0})
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 5))).toEqual({left: 38, top: 0})
-      expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
-      expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
-      expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28})
+      if process.platform is 'darwin' # One pixel off on left on Win32
+        expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 6))).toEqual({left: 43, top: 14})
+        expect(linesYardstick.pixelPositionForScreenPosition(Point(1, 9))).toEqual({left: 72, top: 14})
+        expect(linesYardstick.pixelPositionForScreenPosition(Point(2, Infinity))).toEqual({left: 287.859375, top: 28})

     it "reuses already computed pixel positions unless it is invalidated", ->
       atom.styles.addStyleSheet """
@@ -133,6 +134,7 @@

       editor.setText(text)

+      return unless process.platform is 'darwin' # These numbers are 15 higher on win32 and always integer
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 35)).left).toBe 230.90625
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 36)).left).toBe 237.5
       expect(linesYardstick.pixelPositionForScreenPosition(Point(0, 37)).left).toBe 244.09375
@@ -155,8 +157,10 @@
         expect(linesYardstick.screenPositionForPixelPosition({top: 32, left: 24.3})).toEqual([2, 3])
         expect(linesYardstick.screenPositionForPixelPosition({top: 46, left: 66.5})).toEqual([3, 9])
         expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 99.9})).toEqual([5, 14])
+        expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
+
+        return unless process.platform is 'darwin' # Following tests are 1 pixel off on Win32
         expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 224.2365234375})).toEqual([5, 29])
-        expect(linesYardstick.screenPositionForPixelPosition({top: 70, left: 225})).toEqual([5, 30])
         expect(linesYardstick.screenPositionForPixelPosition({top: 84, left: 247.1})).toEqual([6, 33])

       it "overshoots to the nearest character when text nodes are not spatially contiguous", ->


:construction: More logs in tests

:construction: More logs in tests

--- spec/minimap-element-spec.coffee
+++ spec/minimap-element-spec.coffee
@@ -881,7 +881,7 @@

           describe 'reaching a separator', ->
             beforeEach ->
-              console.log quickSettingsElement.querySelectorAll('li')
+              console.log Array::map.call quickSettingsElement.querySelectorAll('li'), (el) -> el.outerHTML
               atom.commands.dispatch quickSettingsElement, 'core:move-down'

             it 'moves past the separator', ->
@@ -896,7 +896,7 @@

         describe 'core:move-up', ->
           beforeEach ->
-            console.log quickSettingsElement.querySelectorAll('li')
+            console.log Array::map.call quickSettingsElement.querySelectorAll('li'), (el) -> el.outerHTML
             atom.commands.dispatch quickSettingsElement, 'core:move-up'

           it 'selects the last item', ->


conflict resolution

conflict resolution

--- src/core/line.coffee
+++ src/core/line.coffee
@@ -160,7 +160,11 @@
     @delta = new Delta()
     @leaves.toArray().forEach((leaf) =>
       @length += leaf.length
-      @delta.insert(leaf.text, leaf.formats)
+      # TODO use constant for embed type
+      if dom.EMBED_TAGS[leaf.node.tagName]?
+        @delta.insert(1, leaf.formats)
+      else
+        @delta.insert(leaf.text, leaf.formats)
     )
     @delta.insert('\n', @formats)


remove unused tests

remove unused tests

--- src/layout/test_layout.coffee
+++ src/layout/test_layout.coffee
@@ -59,11 +59,9 @@
     t.end()

   test "sprite state", (t) ->
-    t.ok sprite.get('words'), 'sprite words'
-    t.ok sprite.get('state'), 'sprite state'
+    # t.ok sprite.get('state'), 'sprite state'
     t.end()

   test "layout inspector", (t) ->
-    t.ok sprite.get('state'), 'sprite state'
     t.end()


KDView: Removed duplicated detach method.

--- src/core/view.coffee
+++ src/core/view.coffee
@@ -402,9 +402,6 @@
 # #
 # ADD/DESTROY VIEW INSTANCES
 # #
-  detach: ->
-    @parent.getElement().removeChild @getElement()
-
   attach:(view) ->
     @getElement().appendChild view.getElement()


home/billing: fix credit card value pass

--- client/home/lib/billing/components/creditcard/view.coffee
+++ client/home/lib/billing/components/creditcard/view.coffee
@@ -69,7 +69,7 @@
       <SelectBox
         options={options[type]}
         placeholder={placeholders[type]}
-        onChange={onChange ? noop}
+        onChange={(e) -> onChange?(e.value)}
         value={value} />
     </div>


PlusStatus -> GPlusStatus

PlusStatus -> GPlusStatus

--- src/googleplus.coffee
+++ src/googleplus.coffee
@@ -1,6 +1,6 @@
 {Timeline, Status, Subscriber, extend, jsonp} = Chorus = @Chorus

-class PlusStatus extends Status
+class GPlusStatus extends Status
     constructor: (id, username, avatar, date, text, raw, @url, @streamUrl) ->
         super id, username, avatar, date, text, raw

@@ -11,7 +11,7 @@
     @from: (data) ->
         {actor, url, published, object, id} = data
         {displayName, image, stream}  = actor
-        new PlusStatus id, displayName, image.url, published, object.content, data, url, stream
+        new GPlusStatus id, displayName, image.url, published, object.content, data, url, stream

 class GPlusTimeline extends Timeline
     constructor: (@id, options) -> super options
@@ -27,6 +27,6 @@

         callback: (data) => @update data

-    statusesFromData: (data) -> PlusStatus.from item for item in  data.items
+    statusesFromData: (data) -> GPlusStatus.from item for item in  data.items

 Timeline.shorthands.unshift pattern: /^\+(\d+)$/, fun: (_, id) -> new GPlusTimeline(id)


added entries for replace mode

added entries for replace mode

--- lib/status-bar-manager.coffee
+++ lib/status-bar-manager.coffee
@@ -3,11 +3,12 @@
 # mode names can be combined with submode names with the `$` character
 # the order matters - the last matching entry wins
 ContentsByMode =
-  insert:  ["status-bar-vim-mode-insert", "Insert"]
-  command: ["status-bar-vim-mode-command", "Command"]
-  visual:  ["status-bar-vim-mode-visual", "Visual"]
-  visual$linewise:      ["status-bar-vim-mode-visual", "Visual Line"]
-  visual$blockwise:     ["status-bar-vim-mode-visual", "Visual Block"]
+  insert:           ["status-bar-vim-mode-insert", "Insert"]
+  insert$replace:   ["status-bar-vim-mode-insert", "Replace"]
+  command:          ["status-bar-vim-mode-command", "Command"]
+  visual:           ["status-bar-vim-mode-visual", "Visual"]
+  visual$linewise:  ["status-bar-vim-mode-visual", "Visual Line"]
+  visual$blockwise: ["status-bar-vim-mode-visual", "Visual Block"]

 module.exports =
 class StatusBarManager


added formatter

added formatter

--- lib/views/results-pane.coffee
+++ lib/views/results-pane.coffee
@@ -5,6 +5,7 @@
 ControlsView = require './controls-view'
 FileFinderUtil = require '../file-finder-util'
 {$} = require 'space-pen'
+parseInterval = require 'postgres-interval'
 window.jQuery = require 'jquery'

 class ResultsPaneView extends DockPaneView
@@ -16,6 +17,23 @@

     reset: () ->
         @empty()
+
+    formatter: (row, cell, value, columnDef, dataContext) ->
+        if value == null
+            return "NULL";
+        if columnDef.type == "boolean"
+            if value == true
+                return "true";
+            else
+                return "false";
+        if columnDef.type in ['timestamp with time zone', 'timestamp without time zone']
+            return new Date(value).toLocaleString(atom.config.get('pg-hoff.locale'))
+        if columnDef.type in ['time with time zone', 'time without time zone']
+            return new Date('2000-01-01 ' + value).toLocaleTimeString(atom.config.get('pg-hoff.locale'))
+        if columnDef.type == 'json'
+            return JSON.stringify(JSON.parse(value), null, '   ')
+        return value
+
     render: (resultset) ->
         return unless resultset.complete and resultset.columns
         options =
@@ -34,6 +52,7 @@
             c["rerenderOnResize"] = true
             c["id"] = c["field"]
             c["width"] = 200
+            c["formatter"] = @formatter
             max = 0
             for d in resultset.rows
                 ## console.log 'hej->', c["field"], d[c["field"]].length, max


Update Atom config

Update Atom config

--- dotfiles/.atom/config.cson
+++ dotfiles/.atom/config.cson
@@ -36,7 +36,6 @@
       "language-toml"
       "metrics"
       "styleguide"
-      "tree-view"
       "welcome"
       "linter"
     ]
@@ -68,14 +67,22 @@
     fixOnSave: true
   "linter-flow": {}
   nuclide:
+    "nuclide-file-tree":
+      allowKeyboardPrefixNavigation: false
+      hideIgnoredNames: false
+      showOpenFiles: false
     "nuclide-flow":
       canUseFlowBin: true
     "nuclide-home":
       showHome: false
+    use:
+      "nuclide-file-tree": false
+      "nuclide-side-bar": false
   tabs:
     showIcons: false
   "tree-view":
-    showOnRightSide: true
+    autoReveal: true
+    squashDirectoryNames: true
   welcome:
     showOnStartup: false
   whitespace:


More play state refactoring.

More play state refactoring.

remainingMinutes, nextStateLabel, boardInstructions all retrieved from
state instance.

--- js/token-poker/reverse-hold-em.coffee
+++ js/token-poker/reverse-hold-em.coffee
@@ -10,7 +10,7 @@
     super(@store, @round)
     @playerStore = @store.playerStore ||= []
     @round ||= new Rounds.TimedRound(2)
-    @playState = new HandsPlayState
+    @playState = new HandsPlayState(this)
     @pot = new Pot(1)
     @betDuration = 1 # minute

@@ -89,7 +89,7 @@
     @round.setAlarm(0, this, this.finishRound)

   startBetting: ->
-    @playState = new BetPlayState
+    @playState = new BetPlayState(this)
     this.pushStatus("Hands are locked. Time to bet. Type 'bet' and a number.")
     this.pushStatus("Type 'call' to stay in by matching highest bet.")
     this.pushStatus("Type 'fold' to fold and forfeit anything bet already.")
@@ -102,7 +102,7 @@
     @pot.settleUp()
     @pot.goesTo(@winningHandResult.player) if @winningHandResult
     this.pushStatus(this.showBoard())
-    @playState = new HandsPlayState
+    @playState = new HandsPlayState(this)

   applyHoleDigits: ->
     for playerName, handResult of @boardStore
@@ -119,8 +119,7 @@
   showBoard: ->
     # TODO: trim player name
     width = 56
-    remaining = @round.minutesLeft()
-    remaining = remaining - @betDuration if @playState.name == 'play'
+    remaining = @playState.remainingMinutes()
     remainingText = if (remaining >= 1) then "#{remaining} min" else "soon"

     holeDigits = if @round.isOver() then @holeDigits.join(' ') else 'X X'
@@ -129,18 +128,12 @@
     status = if @round.isOver()
       "Winner: #{@winningHandResult.playerName}".rjust(width - title.length)
     else
-      action = if @playState.name == 'play' then 'Bet' else 'Flop'
-      # TODO: Time to bet shows total time, not total minus 1
+      action = @playState.nextStateLabel()
       "Time to #{action}: #{remainingText}".rjust(width - title.length)
-
-    inst = if @playState.name == 'bet'
-      'bet [xx] | call | fold  ||  ** auto-call in effect **'
-    else
-      ''

     header = [
       "#{title}#{status}",
-      inst.center(width),
+      @playState.boardInstructions().center(width),
       "                                                 POT/ALL",
       ''
     ]
@@ -163,9 +156,27 @@
     @playerHand = @playerHand.replace(/\s+/g, '').replace(/\d\d\d/, "$& ")

 class HandsPlayState
-  constructor: ->
+  constructor: (@game) ->
     @name = 'play'

+  remainingMinutes: ->
+    @game.round.minutesLeft() - @game.betDuration
+
+  nextStateLabel: ->
+    'Bet'
+
+  boardInstructions: ->
+    ''
+
 class BetPlayState
-  constructor: ->
-    @name = 'bet'+  constructor: (@game) ->
+    @name = 'bet'
+
+  remainingMinutes: ->
+    @game.round.minutesLeft()
+
+  nextStateLabel: ->
+    'Flop'
+
+  boardInstructions: ->
+    'bet [xx] | call | fold  ||  ** auto-call in effect **'


roll back async method for moment

roll back async method for moment

--- timeAsyncMethod.coffee
+++ timeAsyncMethod.coffee
@@ -5,15 +5,14 @@
 	if typeof obj[methodName] != 'function'
 		throw new Error("[Metrics] expected object property '#{methodName}' to be a function")

+	key = "#{prefix}.#{methodName}"
+
 	realMethod = obj[methodName]
-	keys = prefix.split(".")
-	key = keys[0].toLowerCase()
+	startPrefix = prefix.split(".")[0]

-
-	if keys[1]?
-		methodName = "#{keys[1]}_#{methodName}"
-
-	console.log "Async method", keys, methodName
+	endPrefix = prefix.split(".")[1]
+	modifedMethodName = "#{endPrefix}_methodName"
+	console.log "Async method", prefix, key, methodName, modifedMethodName
 	obj[methodName] = (originalArgs...) ->

 		[firstArgs..., callback] = originalArgs
@@ -23,8 +22,11 @@
 				logger.log "[Metrics] expected wrapped method '#{methodName}' to be invoked with a callback"
 			return realMethod.apply this, originalArgs

-		console.log("creating timer for async method")
-		timer = new metrics.Timer(key, null, {method: methodName})
+		console.log("creating timer for async method", prefix, startPrefix, modifedMethodName)
+
+
+		timer = new metrics.Timer(prefix, null, {method: methodName})
+		console.log("changed timer for async method", prefix, methodName, key)

 		realMethod.call this, firstArgs..., (callbackArgs...) ->
 			elapsedTime = timer.done()


add continue at end of review

add continue at end of review

--- src/task/review.cjsx
+++ src/task/review.cjsx
@@ -52,10 +52,16 @@

You're done.

         <ContinueToBookLink moduleUUID={moduleUUID} taskId={taskId}/>

+      completedEnd = <ContinueToBookLink
+        className='review-continue-to-book'
+        moduleUUID={moduleUUID}
+        taskId={taskId}
+      />

       {completedMessage}
       {completeStepsReview}
+      {completedEnd}

 module.exports = {TaskReview}


Slight listening simplification

Slight listening simplification

--- web.coffee
+++ web.coffee
@@ -1,7 +1,6 @@
 # Main web server definition

 io = require 'socket.io'
-http = require 'http'
 express = require 'express'
 app = express()
 port = process.env.PORT or 3000
@@ -21,7 +20,6 @@
 rest = require './rest'
 rest.init(app, api)

-server = http.createServer(app)
-server.listen(port)
+app.listen(port)

 console.log "voting server started on port #{port}"


added request rejected recognition

added request rejected recognition

--- src/lib/requestfactory.coffee
+++ src/lib/requestfactory.coffee
@@ -204,7 +204,10 @@
                 process.exit 0
                 return false

-            return new Error 'Unknown server response'
+            if body.indexOf('but your computer or network may be sending automated queries') > -1
+                return new Error 'request rejected'
+
+            return new Error 'unknown server response'

         return null


added empty response detection

added empty response detection

--- src/lib/requestfactory.coffee
+++ src/lib/requestfactory.coffee
@@ -189,6 +189,9 @@
             if body.indexOf('but your computer or network may be sending automated queries') > -1
                 return new Error 'request rejected'

+            if body.trim().length is 0
+                return new Error 'Empty server response'
+
             return new Error 'unknown server response'

         return null


removing answer sorting

removing answer sorting

--- src/stores/question.coffee
+++ src/stores/question.coffee
@@ -19,9 +19,6 @@

 QuestionConfig = {
   _loaded: (obj, id) ->
-    obj.answers = _.sortBy obj?.answers, (answer) ->
-      parseInt(answer.id)
-
     for answer in obj?.answers
       AnswerActions.loaded(answer, answer.id)


Fix LED symbol in GOST style

Fix LED symbol in GOST style

--- src/symbol/gost/led.coffee
+++ src/symbol/gost/led.coffee
@@ -55,7 +55,7 @@
     .polyline -width/2, -height/2, width/2, 0, -width/2, height/2, -width/2, -height/2
     .line width/2, -height/2, width/2, height/2
     .line -r, 0, r, 0
-    .circle 0, 0, r
+    .circle 0, 0, r, settings.fill
     .line x1, y1, x1 + len, y1 - len
     .arrow x1 + len/2, y1 - len/2, x1 + len, y1 - len, arrowWidth
     .line x2, y2, x2 + len, y2 - len


Remove unnecessary caching of StyleGuideView class

--- lib/styleguide.coffee
+++ lib/styleguide.coffee
@@ -1,8 +1,7 @@
-StyleguideView = null
 styleguideUri = 'atom://styleguide'

 createStyleguideView = (state) ->
-  StyleguideView ?= require './styleguide-view'
+  StyleguideView = require './styleguide-view'
   new StyleguideView(state)

 atom.deserializers.add


Stylistic change

Stylistic change

--- src/dahelpers.coffee
+++ src/dahelpers.coffee
@@ -394,8 +394,8 @@
     create: (parent, mixins...) ->
       Child = () ->
         h.extend.apply null, [this].concat mixins
-        this.__super__ = parent
-        this
+        @__super__ = parent
+        @
       Child.prototype = parent
       new Child()


Correct indentation of a block comment.

Correct indentation of a block comment.

Upgrade to latest version of coffeescript detected incorrect indentation.

--- src/data-type.coffee
+++ src/data-type.coffee
@@ -98,24 +98,24 @@
   0x3B:
     type: 'FLT4'
     name: 'Real'
-    # Had some weired rounding issues not sure what was going on
-    ###
-    declaration: (parameter) ->
-      'real'
-    writeParameterData: (buffer, parameter) ->
-      # ParamMetaData (TYPE_INFO)
-
-      # Some issues with rounding
-
-      buffer.writeUInt8(typeByName.FloatN.id)
-      buffer.writeUInt8(4)
-
-      # ParamLenData
-      if parameter.value?
+      # Had some weired rounding issues not sure what was going on
+      ###
+      declaration: (parameter) ->
+        'real'
+      writeParameterData: (buffer, parameter) ->
+        # ParamMetaData (TYPE_INFO)
+
+        # Some issues with rounding
+
+        buffer.writeUInt8(typeByName.FloatN.id)
         buffer.writeUInt8(4)
-        buffer.writeFloatLE(parseFloat(parameter.value))
-      else
-        buffer.writeUInt8(0)
+
+        # ParamLenData
+        if parameter.value?
+          buffer.writeUInt8(4)
+          buffer.writeFloatLE(parseFloat(parameter.value))
+        else
+          buffer.writeUInt8(0)
       ###
   0x3C:
     type: 'MONEY'
@@ -210,14 +210,14 @@
         length = parameter.value.toString().length
       else
         length = -1
-
+
       # ParamMetaData (TYPE_INFO)
       buffer.writeUInt8(typeByName.Text.id)
       buffer.writeInt32LE(length)
-
+
       # Collation
       buffer.writeBuffer(new Buffer([0x00, 0x00, 0x00, 0x00, 0x00]))
-
+
       # ParamLenData
       if parameter.value?
         buffer.writeInt32LE(length)


Unsubscribe from editors when they are destroyed

Fixes #29

--- lib/whitespace.coffee
+++ lib/whitespace.coffee
@@ -25,8 +25,9 @@
         if atom.config.get('whitespace.ensureSingleTrailingNewline')
           @ensureSingleTrailingNewline(editor)

-    @subscribe editor, 'destroyed', ->
+    @subscribe editor, 'destroyed', =>
       bufferSavedSubscription.off()
+      @unsubscribe(editor)

     @subscribe buffer, 'destroyed', =>
       @unsubscribe(buffer)


Use private functions instead of public methods

--- lib/y-richtext.coffee
+++ lib/y-richtext.coffee
@@ -199,7 +199,7 @@
   # @param position [Integer] start position for the delta, default: 0
   #
   # @return [Integer] the position of the cursor after parsing the delta
-  deltaHelper : (delta, position = 0) ->
+  deltaHelper= (delta, position = 0) =>
     if delta?
       selections = (@_get "selections")
       delta_unselections = []
@@ -234,7 +234,7 @@
         return position + retain
       throw new Error "This part of code must not be reached!"

-  insertHelper : (position, content) ->
+  insertHelper= (position, content) =>
     as_array =
       if typeof content == "string"
         content.split("")
@@ -243,7 +243,7 @@
     if as_array?
       @_get("characters").insertContents position, as_array

-  deleteHelper : (position, length = 1) ->
+  deleteHelper= (position, length = 1) =>
     console.log "deleteHelper"
     (@_get "characters").delete position, length


Renaming of subtree nodes

Renaming of subtree nodes

--- src/treeLoader.coffee
+++ src/treeLoader.coffee
@@ -390,7 +390,11 @@
 		# take names from behaviors
 		behavior = activeChief.getBehavior cNode.getBehaviorId()
 		if behavior
-			unless behavior.getType() is 'SUBTREE'
+			if behavior.getType() is 'SUBTREE'
+				subtreeId = cNode.getBehaviorConfig('treeId').treeId
+				subTree = activeChief.getTree(subtreeId)
+				cNode.setTitle subTree.getName()
+			else
 				cNode.setTitle behavior.getName()
 		else
 			alertify.error('Behavior is missing, please remove node: ' + cNode.getTitle())
@@ -402,7 +406,7 @@
 	for cNode in cNodes
 		cParentNodeId = cNode.getParentId()
 		tNode = nodeMap[cNode.getId()]
-		if cParentNodeId.indexOf('Tree') == -1
+		if cParentNodeId != cActiveTree.getId()
 			tParentNode = nodeMap[cParentNodeId]
 			tNode.parent = tParentNode


Make additional metadata optional

Make additional metadata optional

--- modules/player/commands.coffee
+++ modules/player/commands.coffee
@@ -45,7 +45,7 @@
           @hud.addPlaylist(m.member, info, m.channel)
           info.on 'done', =>
             info.items.forEach (item, i)=>
-              vid = await @util.getAdditionalMetadata(item)
+              try vid = await @util.getAdditionalMetadata(item)
               return if time > vid.duration or time < 0
               vid.startAt = time
               vid.filters = filters


minors in the plumbs

minors in the plumbs

--- library/nucleus/plumbs.coffee
+++ library/nucleus/plumbs.coffee
@@ -156,9 +156,9 @@
 module.exports.redirect = (kernel) ->
     (request, response, next) ->
         response.redirect = (url, status) ->
-            relocated = status or 302
-            codes = http.STATUS_CODES
-            message = codes[relocated]
+            assert relocated = status or 302
+            assert codes = http.STATUS_CODES
+            assert message = codes[relocated]
             response.setHeader "Location", url
             response.setHeader "Content-Length", 0
             response.writeHead relocated, message


uses the server url for external app ddp

--- packages/rocketchat-external/external.coffee
+++ packages/rocketchat-external/external.coffee
@@ -13,7 +13,7 @@
 			<script type="text/javascript">
 				__meteor_runtime_config__ = {
 					"meteorRelease": "METEOR@1.1.0.2",
-					"ROOT_URL": "http://localhost:3000/",
+					"ROOT_URL": "#{Meteor.absoluteUrl()}",
 					"ROOT_URL_PATH_PREFIX": "",
 					"autoupdateVersion": "#{Autoupdate.autoupdateVersion}",
 					"autoupdateVersionRefreshable": "#{Autoupdate.autoupdateVersionRefreshable}",
@@ -30,4 +30,4 @@
 	"""

 	res.write html
-	res.end()+	res.end()


tweaks to locations, etc.

tweaks to locations, etc.

--- src/coffee/app.coffee
+++ src/coffee/app.coffee
@@ -1,6 +1,6 @@
 controllers = require './assets/js/star/controllers'
 debug = (require 'debug')('app')
-nedb  = require 'nedb'
+nedb = require 'nedb'
 { gui } = global

 angular.module('starCore', [])
@@ -44,18 +44,22 @@
 	)

 angular.module('star', ['starCore'])
+	.config(['$locationProvider', ($locationProvider) ->
+		$locationProvider.html5Mode false
+		$locationProvider.hashPrefix '!'
+	])
 	.config(['$routeProvider', ($routeProvider) ->
 		# define routes
 		$routeProvider
-			.when('/404', { templateUrl: 'templates/404.html' })
+			.when('/404', { templateUrl: './assets/templates/404.html' })
 			.when('/image', { redirectTo: '/list' })
-			.when('/', { templateUrl: 'templates/ImageList.html', controller: controllers.ImageList })
-			.when('/list', { templateUrl: 'templates/ImageList.html', controller: controllers.ImageList })
-			.when('/list/:page', { templateUrl: 'templates/ImageList.html', controller: controllers.ImageList })
+			.when('/', { templateUrl: './assets/templates/ImageList.html', controller: controllers.ImageList })
+			.when('/list', { templateUrl: './assets/templates/ImageList.html', controller: controllers.ImageList })
+			.when('/list/:page', { templateUrl: './assets/templates/ImageList.html', controller: controllers.ImageList })

-			#.when('/image/new', { templateUrl: 'templates/latest.html', controller: controllers.ImageNew })
-			#.when('/image/import', { templateUrl: 'templates/latest.html', controller: controllers.ImageImport })
-			.when('/image/:imageId', { templateUrl: 'templates/ImageView.html', controller: controllers.ImageView })
+			#.when('/image/new', { templateUrl: './assets/templates/latest.html', controller: controllers.ImageNew })
+			#.when('/image/import', { templateUrl: './assets/templates/latest.html', controller: controllers.ImageImport })
+			.when('/image/:imageId', { templateUrl: './assets/templates/ImageView.html', controller: controllers.ImageView })
 			#.when('/image/:imageId/edit', { templateUrl: '', controller: controllers.ImageEdit })
 			.otherwise({redirectTo: '/404'}) # todo change?
 	])


Get rid of unnecessary quotes

Get rid of unnecessary quotes

--- vendor/assets/javascripts/gmaps4rails/yandex/objects/marker.coffee
+++ vendor/assets/javascripts/gmaps4rails/yandex/objects/marker.coffee
@@ -13,7 +13,7 @@
     markerLatLng = @createLatLng(args.lat, args.lng)

     @serviceObject = new ymaps.Placemark(markerLatLng, {
-        balloonContent: '"' + @description + '"',
+        balloonContent: @description,
         iconContent: @iconContent
         }
     );


set value to 0.5

set value to 0.5

--- src/valueHand.coffee
+++ src/valueHand.coffee
@@ -59,8 +59,8 @@
   if hand.community.length == 0
     if hand.hole[0].suit == hand.hole[1].suit
       value = 1
-    # if detectStraight hand.all, 2
-    #   value = 0.5
+    if detectStraight hand.all, 2
+      value = 0.5

   if hand.community.length < 3 or value > 0
     highCards = _.filter hand.hole, (card) -> card.rank > 10