Reduce and regroup first half of tip docs. :hocho:

--- src/js/jquery.hlf.tip.coffee
+++ src/js/jquery.hlf.tip.coffee
@@ -3,9 +3,7 @@
 =====================
 ###

-# __See__: [tests](../../tests/js/tip.html).
-
-# §
+# [Tests](../../tests/js/tip.html)

 # The base `tip` plugin does several things. It does basic parsing of trigger
 # element attributes for the tip content. It can anchor itself to a trigger by
@@ -22,77 +20,105 @@
 # axis. For example, snapping to the x-axis will only allow the tip to shift
 # along the y-axis. The x will remain constant.

-# §
-
-# Export. Support AMD, CommonJS (Browserify), and browser globals.
-((root, factory) ->
+# __UMD__
+# - When AMD, register the attacher as an anonymous module.
+# - When Node or Browserify, set module exports to the attach result.
+# - When browser globals (root is window), Just run the attach function.
+((root, attach) ->
   if typeof define is 'function' and define.amd?
-    # - AMD. Register as an anonymous module.
     define [
       'jquery'
       'underscore'
       'hlf/jquery.extension.hlf.core'
       'hlf/jquery.extension.hlf.event'
-    ], factory
-  else if typeof exports is 'object'
-    # - Node. Does not work with strict CommonJS, but only CommonJS-like
-    #   environments that support module.exports, like Node.
-    module.exports = factory(
+    ], attach
+  else if typeof module is 'object' && module.exports?
+    module.exports = attach(
       require 'jquery',
       require 'underscore',
       require 'hlf/jquery.extension.hlf.core',
       require 'hlf/jquery.extension.hlf.event'
     )
   else
-    # - Browser globals (root is window). No globals needed.
-    factory jQuery, _, jQuery.hlf
+    attach jQuery, _, jQuery.hlf
+  return
 )(@, ($, _, hlf) ->

   #- Cache common references internally.
   _requestAnimationFrame = window.requireAnimationFrame or
     window.Modernizr?.prefixed 'requestAnimationFrame', window

+  # Namespace
+  # ---------
+
   # It takes some more boilerplate to write the plugins. Any of this additional
-  # support API is put into a plugin specific namespace under `$.hlf`.
+  # support API is put into a plugin specific namespace under `$.hlf`, which in
+  # this case is __$.hlf.tip__.
+  #
+  # - __debug__ toggles debug logging for all instances of a plugin.
+  # - __toString__ helps to namespace when extending any jQuery API.
+  #
+  # The plugin's __defaults__ are available as reference. Also note that _the
+  # plugin instance gets extended with the options_.
+  #
+  # - __$viewport__ is the element in which the tip must fit. It is _not_ the
+  #   context element, which stores the tip instance and by convention contains
+  #   the triggers.
+  #
+  # - __autoDirection__ automatically changes the direction to better fit the
+  #   tip inside the viewport.
+  #
+  # - __cursorHeight__ is the browser's cursor height. We need to know this to
+  #   properly offset the tip to avoid cases of cursor-tip-stem overlap.
+  #
+  # - __defaultDirection__ is used as a tie-breaker when selecting the best
+  #   direction. Note that the direction data structure must be an array of
+  #   `components`, and conventionally with top/bottom first.
+  #
+  # - __fireEvents__ can allow events __show.hlf.tip__ and __shown.hlf.tip__,
+  #   __hide.hlf.tip__ and __hidden.hlf.tip__ to be triggered from the trigger
+  #   elements. This is off by default to improve performance.
+  #
+  # - __tipTemplate__ should return interpolated html when given the additional
+  #   container class list. Its context is the plugin instance.
+  #
+  # - __triggerContent__ can be the name of the trigger element's attribute or a
+  #   function that provides custom content when given the trigger element.
+  #
+  # - NOTE: For these tip plugins, the majority of presentation state logic is
+  #   in the plugin stylesheet. We update the presentation state by using
+  #   namespaced __classNames__ generated in a closure.
+  #
+  #   - __classNames.stem__ - Empty string to remove the stem.
+  #   - __classNames.follow__ - Empty string to disable cursor following.
+  #
+  # - __animator__ by default uses the stock jQuery animation system. If you
+  #   want a more performant system, like GreenSock or Velocity, set it here.
+  #   See the visual tests for an example.
+  #
+  # - __animations__ are very configurable. Individual animations can be
+  #   customized and will default to the base animation settings as needed.
+  #
+  # - __followUsingTransform__ will automatically check CSS transforms support
+  #   via Modernizr. So you'll need to use Modernizr or manually enable this
+  #   option to use the more performant transforms.
+  #
   hlf.tip =
-    # To toggle debug logging for all instances of a plugin, use the `debug` flag.
     debug: off
-    # To namespace when extending any jQuery API, we use custom `toString` helpers.
     toString: _.memoize (context) ->
       switch context
         when 'event'  then '.hlf.tip'
         when 'data'   then 'hlf-tip'
         when 'class'  then 'js-tips'
         else 'hlf.tip'
-    # The plugin's default options is available as reference for values like sizes.
-
-    # §
-
-    # Tip Options
-    # -----------
-    #- NOTE: The plugin instance gets extended with the options.
+
     defaults: do (pre = 'js-tip-') ->

-      # - `$viewport` is the element in which the tip must fit into. It is _not_
-      #   the context, which stores the tip instance and by convention contains
-      #   the triggers.
       $viewport: $ 'body'
-      # - `autoDirection` automatically changes the direction so the tip can
-      #   better fit inside the viewport.
       autoDirection: on
-      # - `cursorHeight` is the browser's cursor height. We need to know this to
-      #   properly offset the tip to avoid cases of cursor-tip-stem overlap.
       cursorHeight: 12
-      # - `defaultDirection` is used as a tie-breaker when selecting the best
-      #   direction. Note that the direction data structure must be an array of
-      #   `components`, and conventionally with top/bottom first.
       defaultDirection: ['bottom', 'right']
-      # - `fireEvents` can be enabled to allow `show.hlf.tip` and `shown.hlf.tip`,
-      #   `hide.hlf.tip` and `hidden.hlf.tip` events to be triggered from the
-      #   trigger elements. This is off by default to improve performance.
       fireEvents: on
-      # - `tipTemplate` should return interpolated html when given the
-      #   additional container class list. Its context is the plugin instance.
       tipTemplate: (containerClass) ->
         stemHtml = "

" if @doStem is on
         """
@@ -103,29 +129,16 @@

         """
-      # - `triggerContent` can be the name of the trigger element's attribute or a
-      #   function that provides custom content when given the trigger element.
       triggerContent: null
-      # NOTE: For these tip plugins, the majority of presentation state logic is
-      # in the plugin stylesheet. We update the presentation state by using
-      # namespaced `classNames` generated in a closure.
-      #
-      # - `classNames.stem` - Empty string to remove the stem.
-      # - `classNames.follow` - Empty string to disable cursor following.
       classNames: do ->
         classNames = {}
         keys = ['inner', 'content', 'stem', 'top', 'right', 'bottom', 'left', 'follow', 'trigger']
         (classNames[key] = "#{pre}#{key}") for key in keys
         classNames.tip = 'js-tip'
         classNames
-      # - `animator` by default uses the stock jQuery animation system. If you
-      #   want a more performant system, like GreenSock or Velocity, replace
-      #   this with a custom implementation.
       animator:
         show: ($el, options) -> $el.stop().fadeIn options
         hide: ($el, options) -> $el.stop().fadeOut options
-      # - `animations` are very configurable. Individual animations can be
-      #   customized and will default to the base animation settings as needed.
       animations:
         base:
           delay: 0
@@ -138,11 +151,19 @@
         resize:
           delay: 300
           easing: 'ease-in-out'
-      # - `followUsingTransform` will automatically check CSS transforms support
-      #   via Modernizr. So you'll need to use Modernizr or manually enable this
-      #   option to use the more performant transforms.
       followUsingTransform: window.Modernizr?.csstransforms

+  # Within `$.hlf.tip` is a subnamespace for the `snapTip` plugin
+  # __$.hlf.tip.snap__.
+  #
+  # Its __defaults__ extend `tip`'s defaults.
+  #
+  # - __snap.toXAxis__ is the switch for snapping along x-axis and only
+  #   following along y-axis. Off by default.
+  # - __snap.toYAxis__ is the switch for snapping along y-axis and only
+  #   following along x-axis. Off by default.
+  # - __snap.toTrigger__ is the switch for snapping to trigger built on
+  #   axis-snapping. On by default.
   hlf.tip.snap =
     debug: off
     toString: _.memoize (context) ->
@@ -152,21 +173,8 @@
         when 'class'  then 'js-snap-tips'
         else 'hlf.tip.snap'

-    # SnapTip Options
-    # ---------------
-    #- NOTE: The plugin instance gets extended with the options.
     defaults: do (pre = 'js-snap-tip-') ->
-      # These options extend the tip options.
       $.extend (deep = yes), {}, hlf.tip.defaults,
-        # NOTE: For each snapping option, the plugin adds its class to the tip
-        # if the option is on.
-        #
-        # - `snap.toXAxis` is the switch for snapping along x-axis and only
-        #   following along y-axis. Off by default.
-        # - `snap.toYAxis` is the switch for snapping along y-axis and only
-        #   following along x-axis. Off by default.
-        # - `snap.toTrigger` is the switch for snapping to trigger built on
-        #   axis-snapping. On by default.
         snap:
           toTrigger: on
           toXAxis: off
@@ -182,50 +190,54 @@
           classNames.tip = 'js-tip js-snap-tip'
           classNames

-  # §
-
-  # Tip Implementation
-  # ------------------
-  # Read on to learn about implementation details.
+  # Tip
+  # ---
   class Tip

-    # ƒ `constructor` keeps `$triggers` and `$context` as properties. `options`
+    # __constructor__ keeps `$triggers` and `$context` as properties. `options`
     # is further normalized.
     constructor: (@$triggers, options, @$context) ->
       for own name, animation of options.animations when name isnt 'base'
         _.defaults animation, options.animations.base

-    # ƒ `init` offloads non-trivial setup to other subroutines.
+    # __init__ offloads non-trivial setup to other subroutines. Otherwise it will:
+    # 1. Store the tip element.
+    # 2. Infer `doStem` and `doFollow` flags from respective `classNames` entries.
+    # 3. Initialize state, which is either: `awake`, `asleep`, `waking`, `sleeping`;
+    #    and which will respectively show or hide the tip.
+    # 4. Initialize timers. The tip should remain visible and `awake` as long as
+    #    there is a high enough frequency of relevant mouse activity. In addition
+    #    to using `hoverIntent`, this is achieved with a basic implementation
+    #    around timers `_sleepCountdown` and `_wakeCountdown`, and an extra
+    #    reference to `_$currentTrigger`.
+    # 5. Initialize tip. Note the initial render.
+    # 6. Initialize context.
+    # 7. Initialize triggers. Note the initial processing.
     init: ->
       _.bindAll @, '_onTriggerMouseMove', '_setBounds'
-      # - Initialize tip element.
+
       @_setTip $ '
'
-      # - Infer `doStem` and `doFollow` flags from respective `classNames` entries.
+
       @doStem = @classNames.stem isnt ''
       @doFollow = @classNames.follow isnt ''
-      # - Initialize state, which is either: `awake`, `asleep`, `waking`,
-      # `sleeping`; respectively show, hide.
+
       @_setState 'asleep'
-      # - The tip should remain visible and `awake` as long as there is a high
-      # enough frequency of relevant mouse activity. In addition to using
-      # `hoverIntent`, this is achieved with a simple base implementation around
-      # timers `_sleepCountdown` and `_wakeCountdown` and an extra reference to
-      # `_$currentTrigger`.
+
       @_wakeCountdown = null
       @_sleepCountdown = null
       @_$currentTrigger = null
-      # - Initialize tip. Note the initial render.
+
       @_render()
       @_bind()
-      # - Initialize context.
+
       @_bindContext()
-      # - Initialize triggers. Note the initial processing.
+
       @_processTriggers()
       @_bindTriggers()

-    # ### Accessors
-
-    # ƒ `_defaultHtml` provides a basic html structure for tip content. It can be
+    # __Accessors__
+
+    # ___defaultHtml__ provides a basic html structure for tip content. It can be
     # customized via the `tipTemplate` external option, or by subclasses using
     # the `htmlOnRender` hook.
     _defaultHtml: ->
@@ -237,28 +249,26 @@
       containerClass = $.trim [@classNames.tip, @classNames.follow, directionClass].join ' '
       html = @tipTemplate containerClass

-    # ƒ `_isDirection` is a helper to deduce if `$tip` currently has the given
-    # `directionComponent`. The tip is considered to have the same direction as
-    # the given `$trigger` if it has the classes or if there is no trigger or
-    # saved direction value and the directionComponent is part of
-    # `defaultDirection`. Note that this latter check is placed last for
-    # performance savings.
+    # ___isDirection__ deduces if `$tip` has the given `directionComponent`,
+    # which is true  if it has the classes or if there is no given `$trigger` or
+    # saved direction value, and `directionComponent` is part of
+    # `defaultDirection`.
     _isDirection: (directionComponent, $trigger) ->
       @$tip.hasClass(@classNames[directionComponent]) or (
         (not $trigger? or not $trigger.data(@attr('direction'))) and
         _.include(@defaultDirection, directionComponent)
       )

-    # ƒ `_offsetForTrigger` is a helper to return the proper trigger offset for
-    # proper tip attachment. Fixed-position triggers need to fall back to the
-    # fool-proof `$.fn.offset`. This is despite `$.fn.position` being de-facto
-    # for elements sharing a parent positioning context.
+    # ___offsetForTrigger__ computes the proper trigger offset for proper tip
+    # attachment. Fixed-position triggers need to fall back to the fool-proof
+    # `$.fn.offset`. This is despite `$.fn.position` being de-facto for elements
+    # sharing a parent positioning context.
     _offsetForTrigger: ($trigger) ->
       if $trigger.css('position') is 'fixed' then $trigger.offset()
       else $trigger.position()

-    # ƒ `_setCurrentTrigger` is a simple setter that updates a trigger and makes
-    # it current, but only if it isn't already.
+    # ___setCurrentTrigger__ also updates a trigger and makes it current, but
+    # only if it isn't already.
     _setCurrentTrigger: ($trigger) ->
       @_triggerChanged = not $trigger.is @_$currentTrigger
       return no unless @_triggerChanged
@@ -266,8 +276,8 @@
       @_$currentTrigger = $trigger

-    # ƒ `_setState` is a simple setter that returns false if state doesn't
-    # change. It also handles minor tasks when switching to a new state.
+    # ___setState__ also performs minor tasks when switching to a new state, if
+    # it did not first return `false` from no state change.
     _setState: (state, data) ->
       return no if state is @_state
       #- previous = @_state
@@ -293,11 +303,11 @@
           clearTimeout @_sleepCountdown
           @_triggerChanged = yes

-    # ƒ `_setTip` aliases the conventional `$el` property to `$tip` for clarity.
+    # ___setTip__ aliases the conventional `$el` property to `$tip` for clarity.
     _setTip: ($tip) => @$tip = @$el = $tip

-    # ƒ `_sizeForTrigger` does a stealth render via `_wrapStealthRender` to find tip
-    # size. It will return saved data if possible before doing a measure. The
+    # ___sizeForTrigger__ does a stealth render via `_wrapStealthRender` to find
+    # tip size. It returns saved data if possible before doing a measure. The
     # measures, used by `_updateDirectionByTrigger`, are stored on the trigger
     # as namespaced, `width` and `height` jQuery data values. If on,
     # `contentOnly` will factor in content padding into the size value for the
@@ -325,7 +335,7 @@

       size

-    # ƒ `_stemSize` does a stealth render via `_wrapStealthRender` to find stem
+    # ___stemSize__ does a stealth render via `_wrapStealthRender` to find stem
     # size. The stem layout styles will add offset to the tip content based on
     # the tip direction. Knowing the size helps operations like overall tip
     # positioning.


requestMetaKeys is called with an up.Request, not with a URL string

--- lib/assets/javascripts/unpoly/classes/request.coffee
+++ lib/assets/javascripts/unpoly/classes/request.coffee
@@ -344,7 +344,7 @@
   # (2) become part of our @cacheKey().
   metaProps: ->
     props = {}
-    for key in up.proxy.config.requestMetaKeys(@url)
+    for key in up.proxy.config.requestMetaKeys(@)
       value = this[key]
       if u.isGiven(value)
         props[key] = value


Turn a unit test into an acceptance test

--- services/document-updater/test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee
+++ services/document-updater/test/acceptance/coffee/ApplyingUpdatesToProjectStructureTests.coffee
@@ -4,11 +4,8 @@
 Settings = require('settings-sharelatex')
 rclient_history = require("redis-sharelatex").createClient(Settings.redis.history)
 ProjectHistoryKeys = Settings.redis.project_history.key_schema
-SandboxedModule = require('sandboxed-module')
-HistoryManagerModulePath = require('path').join __dirname, '../../../app/js/HistoryManager.js'
-ProjectManagerModulePath = require('path').join __dirname, '../../../app/js/ProjectManager.js'
-RedisManagerModulePath = require('path').join __dirname, '../../../app/js/RedisManager.js'

+MockProjectHistoryApi = require "./helpers/MockProjectHistoryApi"
 MockWebApi = require "./helpers/MockWebApi"
 DocUpdaterClient = require "./helpers/DocUpdaterClient"

@@ -153,22 +150,6 @@

 	describe "with enough updates to flush to the history service", ->
 		before (done) ->
-			@RedisManager = SandboxedModule.require RedisManagerModulePath, requires:
-				"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
-			@HistoryManager = SandboxedModule.require HistoryManagerModulePath, requires:
-				"request": {}
-				"settings-sharelatex": {}
-				"logger-sharelatex": @logger
-				"./HistoryRedisManager": {}
-			@HistoryManager.flushProjectChangesAsync = sinon.stub()
-			@ProjectManager = SandboxedModule.require ProjectManagerModulePath, requires:
-				'./HistoryManager': @HistoryManager
-				'logger-sharelatex' : @logger
-				'./Metrics': @Metrics = {}
-				'./RedisManager': @RedisManager
-			@Metrics.Timer = class Timer
-				done: sinon.stub()
-
 			@project_id = DocUpdaterClient.randomId()
 			@user_id = DocUpdaterClient.randomId()

@@ -179,12 +160,19 @@
 					pathname: '/file-' + v
 					docLines: 'a\nb'

+			sinon.spy MockProjectHistoryApi, "flushProject"
+
 			# Send updates in chunks to causes multiple flushes
-			@ProjectManager.updateProjectWithLocks @project_id, @user_id, updates.slice(0, 250), [], (error) =>
+			projectId = @project_id
+			userId = @project_id
+			DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(0, 250), [], (error) ->
 				throw error if error?
-				@ProjectManager.updateProjectWithLocks @project_id, @user_id, updates.slice(250), [], (error) =>
+				DocUpdaterClient.sendProjectUpdate projectId, userId, updates.slice(250), [], (error) ->
 					throw error if error?
 					setTimeout done, 2000

+		after ->
+			MockProjectHistoryApi.flushProject.restore()
+
 		it "should flush project history", ->
-			@HistoryManager.flushProjectChangesAsync.calledWith(@project_id).should.equal true
+			MockProjectHistoryApi.flushProject.calledWith(@project_id).should.equal true


load webiew asynchronously to avoid electron crash

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -232,7 +232,13 @@
       @webview = webview
       @append $ webview

-    @webview.src = @tmpPath
+    # load url in webview asynchronously to avoid crash.
+    webview = @webview
+    srcPath = @tmpPath
+    setTimeout ( ->
+      webview.setAttribute( 'src', srcPath )
+      ), 20
+
     try
       @find('.show-error').hide()
       @find('.show-loading').hide()


Moved saving of buffer to temp file, to not overwrite users' files or interrupt the editor (avoids stuff like dismissing the auto-suggest)

--- lib/atom-html-preview-view.coffee
+++ lib/atom-html-preview-view.coffee
@@ -1,4 +1,5 @@
 path                  = require 'path'
+fs                    = require 'fs'
 {CompositeDisposable, Disposable} = require 'atom'
 {$, $$$, ScrollView}  = require 'atom-space-pen-views'
 _                     = require 'underscore-plus'
@@ -91,13 +92,21 @@
     if @editor?
       @renderHTMLCode()

+  save: ->
+    outPath = path.resolve "/tmp/" + @editor.getTitle() + ".tmp"
+    # console.log outPath
+    out = @editor.getText()
+    fs.writeFileSync outPath, out
+    return outPath
+
   renderHTMLCode: (text) ->
-    if not atom.config.get("atom-html-preview.triggerOnSave") and @editor.getPath()? then @editor.save()
+    outPath = @getPath()
+    if not atom.config.get("atom-html-preview.triggerOnSave") and @editor.getPath()? then outPath = @save() # @editor.save()
     iframe = document.createElement("iframe")
     # Fix from @kwaak (https://github.com/webBoxio/atom-html-preview/issues/1/#issuecomment-49639162)
     # Allows for the use of relative resources (scripts, styles)
     iframe.setAttribute("sandbox", "allow-scripts allow-same-origin")
-    iframe.src = @getPath()
+    iframe.src = outPath
     @html $ iframe
     # @trigger('atom-html-preview:html-changed')
     atom.commands.dispatch 'atom-html-preview', 'html-changed'


SendGraphChanges: batch node metadata changes

Node metadata includes positions on the graph. The UI emits changes on
these for each event receive on the canvas, resulting as many changes
in the logs of the SendGraphChanges component.

Given that each metadata object contains all the information we need,
we can batch these.

--- components/SendGraphChanges.coffee
+++ components/SendGraphChanges.coffee
@@ -7,6 +7,7 @@
     @runtime = null
     @graph = null
     @changes = []
+    @changesStates = {}
     @subscribed = false
     @inPorts = new noflo.InPorts
       runtime:
@@ -93,6 +94,12 @@
       topic: topic
       payload: payload
     @outPorts.queued.send @changes.length
+    return @changes.length - 1
+
+  replaceChange: (offset, topic, payload) =>
+    @changes[offset] =
+      topic: topic
+      payload: payload

   addNode: (node) =>
     @registerChange 'addnode',
@@ -113,10 +120,15 @@
       graph: @graph.properties.id

   changeNode: (node) =>
-    @registerChange 'changenode',
+    key = 'changenode- ' + node.id
+    metadata =
       id: node.id
       metadata: node.metadata
       graph: @graph.properties.id
+    if @changesStates[key] is undefined
+      @changesStates[key] = @registerChange 'changenode', metadata
+    else
+      @replaceChange @changesStates[key], 'changenode', metadata

   addEdge: (edge) =>
     @registerChange 'addedge',
@@ -244,6 +256,7 @@
     @outPorts.sent.send true
     @outPorts.sent.endGroup() if @graph
     @outPorts.queued.send @changes.length
+    @changesStates = {}

   shutdown: ->
     do @unsubscribe


collaboration: use env.updateWorkspace helper

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -50,7 +50,7 @@
       return callback err  if err

       @setSocialChannel channel
-      @updateWorkspace { channelId : channel.id }
+      envHelpers.updateWorkspace @workspaceData, { channelId : channel.id }
         .then =>
           @workspaceData.channelId = channel.id
           callback null, channel
@@ -827,11 +827,6 @@
       sinkrow.dash queue, callback

-  updateWorkspace: (options = {}) ->
-
-    return remote.api.JWorkspace.update @workspaceData._id, { $set : options }
-
-
   # collab related modals (should be its own mixin)


Add defensive check.

Add defensive check.

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -925,6 +925,10 @@

   showChat: ->

+    # Show this message while "@stateMachine" is preparing when a session is over just now.
+    # It will be ready in a few seconds.
+    return showError 'Please wait a few seconds.'  unless @stateMachine
+
     switch @stateMachine.state
       when 'Active'     then @showChatPane()
       when 'Prepared'   then @chat.show()


hide private partners from exihibitors list

--- apps/fair/components/booths/view.coffee
+++ apps/fair/components/booths/view.coffee
@@ -18,7 +18,7 @@

     # Set up a @params model to maintain the query param state for the @shows collection
     # attched to /api/v1/fair/:id/shows
-    @params = new Backbone.Model sort: '-featured'
+    @params = new Backbone.Model sort: '-featured', private_partner: false
     @shows = new FeedItems
     @shows.url = "#{@fair.url()}/shows"


I no longer care

I no longer care

--- components/filter/artworks/test/view.coffee
+++ components/filter/artworks/test/view.coffee
@@ -70,7 +70,7 @@

   describe '#reset', ->

-    it 'fetches the correct counts', ->
+    xit 'fetches the correct counts', ->
       @view.params.set related_gene: 'photography', medium: 'digital-print'
       @view.reset()
       Backbone.sync.args[1][2].data.should.eql


Add a proper description for the package

--- package.cson
+++ package.cson
@@ -1,2 +1,3 @@
 'main': './lib/vim-mode'
+'description': 'Provides vim modal control of Atom.'
 'activationEvents': ['vim-mode:toggle']


Add a description for each package

Add a description for each package

--- package.cson
+++ package.cson
@@ -1,3 +1,4 @@
-'main': 'lib/autocomplete'
+'main': './lib/autocomplete'
+'description': 'Display possible completions from the current editor with `ctrl-space`.'
 'activationEvents':
-  'autocomplete:attach': '.editor'+  'autocomplete:attach': '.editor'


adding legacy product lookup fn

adding legacy product lookup fn

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -331,6 +331,34 @@
   return product_db

+
+updateLegacyProductIds_ = (ids) ->
+  # list of product ids
+  map =
+    world_none: 'pupil_wnone'
+    world_hr: 'pupil_w30'
+    world_hs: 'pupil_w120'
+    eye_none: 'enone'
+    eye_30hz: 'e30'
+    eye_120hz: 'e120'
+    eye_120hz_binocular: 'e120b'
+    product_eye_120hz: 'e120upgrade'
+    product_oculus_mono: 'oculusdk2m'
+    product_oculus_bi: 'oculusdk2b'
+    product_epson_bi: 'epsonbt200b'
+    product_support_6: 'support6'
+    product_support_12: 'support12'
+    license_academic: 'edu'
+    product_usb_c: 'usbcupgrade'
+    product_htc_vive_bi: 'htcviveb'
+  res = []
+  i = 0
+  while i < ids.length
+    res.push map[ids[i]]
+    i++
+  res.join '_'
+
+
 get_config_images = ->
   db = get_product_database()
   return (db[k]['img'] for k of db when k.split("_").length == 3)


do not call toString on uncompressed response

--- src/middleware/router.coffee
+++ src/middleware/router.coffee
@@ -135,7 +135,7 @@
 						callback()
 				)
 			else
-				responseDst.body = Buffer.concat(bufs).toString charset
+				responseDst.body = Buffer.concat(bufs)
 				callback()

 	routeReq.on "error", (err) ->


url Guesser now only shortens URLS with a protocol

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -181,7 +181,7 @@

     urlGrabber = ///
     (?!\s)                                                      # leading spaces
-    ([a-zA-Z]+://|)                                             # protocol
+    ([a-zA-Z]+://)                                             # protocol
     (\w+:\w+@|[\w|\d]+@|)                                       # username:password@
     ((?:[a-zA-Z\d]+(?:-[a-zA-Z\d]+)*\.)*)                       # subdomains
     ([a-zA-Z\d]+(?:[a-zA-Z\d]|-(?=[a-zA-Z\d]))*[a-zA-Z\d])      # domain


test fix

test fix


primeFactors

primeFactors

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -160,6 +160,47 @@
   n = this / resolution; 1/1; # This is to fix broken regex in doc parser
   n.floor() * resolution

+###*
+In number theory, integer factorization or prime factorization is the
+breaking down of a composite number into smaller non-trivial divisors,
+which when multiplied together equal the original integer.
+
+Floors the number for purposes of factorization.
+
+@name primeFactors
+@methodOf Number#
+
+@returns An array containing the factorization of this number.
+@type Array
+###
+Number::primeFactors = ->
+  factors = []
+
+  n = Math.floor(this)
+
+  if n == 0
+    return undefined
+
+  if n < 0
+    factors.push(-1)
+    n /= -1
+
+  i = 2
+  iSquared = i * i
+
+  while iSquared < n
+    while (n % i) == 0
+        factors.push i
+        n /= i
+
+    i += 1
+    iSquared = i * i
+
+  if n != 1
+    factors.push n
+
+  return factors
+
 Number::toColorPart = ->
   s = parseInt(this.clamp(0, 255), 10).toString(16)


dance the lattice better

dance the lattice better

--- echoes/midground/lattice/main.coffee
+++ echoes/midground/lattice/main.coffee
@@ -3,7 +3,7 @@
     @flipped = [yes, no].random()
     @doubled = [yes, no].random()

-    @spinSpeed = THREE.Math.randFloatSpread(180).degToRad
+    @spinSpeed = THREE.Math.randFloatSpread(60).degToRad

     @offsetTime = 10

@@ -70,9 +70,7 @@
     @flippedDir = if @flipped then -1 else 1

     @rotDirection   = source.rotDirection   || [1, -1].random()
-    @rotSpeedTarget = source.rotSpeedTarget || THREE.Math.randFloat(20, 50).degToRad
-    @rotSpeedDecay  = source.rotSpeedDecay  || @rotSpeedTarget * @rotDirection * 8
-    @rotSpeed       = 0
+    @rotSpeed       = source.rotSpeedTarget || THREE.Math.randFloat(30, 90).degToRad

     @push (new Strut(this, i/@qty, @flipped) for i in [0...@qty])...

@@ -84,15 +82,14 @@

   update: (elapsed) ->
     super
+    @rotation.z += @rotSpeed * @rotDirection * elapsed

-    @rotSpeed -= @rotSpeedDecay * @rotDirection * @flippedDir * elapsed / stage.song.bps
-    @rotSpeed  = @rotSpeedTarget  * 3 if @rotSpeed >  @rotSpeedTarget * 3
-    @rotSpeed  = -@rotSpeedTarget * 3 if @rotSpeed < -@rotSpeedTarget * 3
+  onBeat: (beat) ->
+    new TWEEN.Tween(this)
+      .to({rotSpeed: -@rotSpeed}, beat.duration.ms)
+      .easing(TWEEN.Easing.Sinusoidal.InOut)
+      .start()

-    @rotation.z += @rotSpeed      * @rotDirection * elapsed / stage.song.bps
-
-  onBeat: ->
-    @rotSpeed = @rotSpeedTarget

 class Strut extends Echotron.Echo
   uniformAttrs:


Introduce a find function to find a particular slide.

--- coffee/presentation.coffee
+++ coffee/presentation.coffee
@@ -107,10 +107,17 @@
   hasSteps: ->
     @$steps? and @$steps.length isnt 0

+  find: (slide) ->
+    if slide instanceof $
+      slide
+    else
+      $section = $("##{slide}")
+      if $section.length is 0
+        $section = $('section').eq(parseInt(slide) - 1)
+      $section
+
   go: (slide = 1) ->
-    $section = if (slide instanceof $) then slide else $("##{slide}")
-    if $section.length is 0
-      $section = $('section').eq(parseInt(slide) - 1)
+    $section = @find(slide)

     if $section.length
       @checkSteps($section)


Fixing undefined issue with previous obstacles, and also making squad dirty when obstacles choice change

--- coffeescripts/xwing.coffee
+++ coffeescripts/xwing.coffee
@@ -111,7 +111,7 @@

         @collection = null

-        @current_obstacles = null
+        @current_obstacles = []

         @setupUI()
         @setupEventHandlers()
@@ -163,7 +163,7 @@
         @squad_name_input.val 'New Squadron'
         @removeAllShips()
         @addShip()
-        @current_obstacles = null
+        @current_obstacles = []
         @resetCurrentSquad()
         @notes.val ''

@@ -809,13 +809,18 @@
             if @obstacles_select.val().length > 3
                 @obstacles_select.val(@current_squad.additional_data.obstacles)
             else
-                @previous_obstacles = @current_squad.additional_data.obstacles
+                previous_obstacles = @current_squad.additional_data.obstacles
                 @current_obstacles = (o for o in @obstacles_select.val())
-                @new_selection = @current_obstacles.filter((element) => return @previous_obstacles.indexOf(element) == -1)
-                if @new_selection.length > 0
-                    @showChooseObstaclesSelectImage(@new_selection[0])
+                if (previous_obstacles?)
+                    new_selection = @current_obstacles.filter((element) => return previous_obstacles.indexOf(element) == -1)
+                else
+                    new_selection = @current_obstacles
+                if new_selection.length > 0
+                    @showChooseObstaclesSelectImage(new_selection[0])
                 @current_squad.additional_data.obstacles = @current_obstacles
                 @updatePermaLink()
+                @current_squad.dirty = true
+                @container.trigger 'xwing-backend:squadDirtinessChanged'

         @view_list_button.click (e) =>
             e.preventDefault()


Disable the autopoll for now.

Disable the autopoll for now.

--- jobs/index.coffee
+++ jobs/index.coffee
@@ -34,4 +34,4 @@
     done()

   # Create initial poll
-  jobs.create('poll').save()
+  #jobs.create('poll').save()


Include trailing newline in .json files

Include trailing newline in .json files

--- src/package-converter.coffee
+++ src/package-converter.coffee
@@ -109,7 +109,8 @@
         contents = @convertPreferences(contents)
         return unless contents

-      fs.writeFileSync(destinationPath, JSON.stringify(contents, null, 2))
+      json = "#{JSON.stringify(contents, null, 2)}\n"
+      fs.writeFileSync(destinationPath, json)

   normalizeFilenames: (directoryPath) ->
     return unless fs.isDirectorySync(directoryPath)


Forgive trailing .git in bundle URL

Forgive trailing .git in bundle URL

--- src/package-converter.coffee
+++ src/package-converter.coffee
@@ -45,6 +45,7 @@

   getDownloadUrl: ->
     downloadUrl = @sourcePath
+    downloadUrl = downloadUrl.replace(/\.git\/?$/, '')
     downloadUrl += '/' unless downloadUrl[downloadUrl.length - 1] is '/'
     downloadUrl += 'archive/master.tar.gz'


cleanup console msgs during render

cleanup console msgs during render

--- manager/lib/assets/javascripts/stark/view.js.coffee
+++ manager/lib/assets/javascripts/stark/view.js.coffee
@@ -79,7 +79,7 @@
   jst: (tpl) ->
     tpl ||= @template
     filename = @app.template_root + tpl
-    @log "template: ", filename
+    @log "render ", @, "file: ", filename
     JST[filename]

   # Create a Template object for the configured @template
@@ -100,8 +100,6 @@
   # Custom rendering should usually call super() before any additional
   # rendering.
   render: ->
-    @log "render", @
-
     @$el.html(@render_html())

     @attach_link_events()


Sim: Disable chat limiter for now.

Sim: Disable chat limiter for now.

--- server/index.coffee
+++ server/index.coffee
@@ -93,21 +93,13 @@

     'sendChat': (user, message) ->
       return  unless typeof message == "string" && message.trim().length > 0
-      server.limit user, 'chat', max: 5, duration: 1500, (err, limit) ->
-        if !limit.remaining
-          auth.getMuteTTL user.id, (err, ttl) ->
-            if ttl == -2
-              server.mute(user.id, "[AUTOMATED] Triggered chat rate-limit.", 3 * 60)
-              lobby.message("#{user.id} was muted because they were spamming.")
-            else
-              user.message("You are muted for another #{ttl} seconds!")
-        else if message[0] == '/'
-          [ command, args... ] = message.split(/\s+/)
-          command = command.substr(1)
-          args = args.join(' ').split(/,/g)
-          commands.executeCommand(server, user, lobby, command, args...)
-        else
-          server.userMessage(lobby, user, message)
+      if message[0] == '/'
+        [ command, args... ] = message.split(/\s+/)
+        command = command.substr(1)
+        args = args.join(' ').split(/,/g)
+        commands.executeCommand(server, user, lobby, command, args...)
+      else
+        server.userMessage(lobby, user, message)

     'sendBattleChat': (user, battleId, message) ->
       return  unless message?.replace(/\s+/, '').length > 0


no exiting on error, instead upload subject based on successful subject locations

--- lib/panoptes-subject-uploader.iced
+++ lib/panoptes-subject-uploader.iced
@@ -155,29 +155,28 @@
       imageURLs = findImagesURLs metadata

       if imageURLs.length == 0
-        log "!!! Couldn't find an media files for row #{i + 1}"
-        process.exit 0
+        log "!!! Couldn't find an media urls for row #{i + 1}"

+      locationSuccessCount = 0
       for url, index in imageURLs
         await request url, defer error, response

         if error?
-          log "!!! Error requesting URL for row #{i + 1}:", error
-          process.exit 0
+          log "!!! Error requesting URL for #{url} on row #{i + 1}:", error

         if response?
           if response.statusCode is 200
             mimeType = mime.lookup url
-            subject.locations.push locationCreator(mimeType, url)
-
+            subject.locations.push locationCreator(mimeType, url)
+            locationSuccessCount++

           else
             log "!!! Error: Unexpected response code:", response.statusCode
-            process.exit 0
-
-      newSubject = apiClient.type('subjects').create(subject)
-      await newSubject.save().then(defer _).catch(console.error.bind console)
-      log "Saved subject #{newSubject.id}"
+
+      if locationSuccessCount == imageURLs.length
+        newSubject = apiClient.type('subjects').create(subject)
+        await newSubject.save().then(defer _).catch(console.error.bind console)
+        log "Saved subject #{newSubject.id}"

       if newSubject?
         newSubjectIDs.push newSubject.id


On load set any remaining images srcs

On load set any remaining images srcs

--- src/assets/javascripts/preloader.coffee
+++ src/assets/javascripts/preloader.coffee
@@ -9,6 +9,13 @@
       .attr 'data-application', @application

     if @application is 'mobile' then @kill.web() else @kill.mobile()
+
+    @setImages()
+
+  setImages: ->
+    $('img[data-src]').each ->
+      src = ($img = $(this)).data('src')
+      $img.attr 'src', src

   kill:
     mobile: -> $('#mobile').remove()


activity: reorder getter methods

activity: reorder getter methods

--- client/activity/lib/components/createprivatechannelmodal/index.coffee
+++ client/activity/lib/components/createprivatechannelmodal/index.coffee
@@ -51,6 +51,22 @@
   getDefaultPlaceholder: -> 'type a @username and hit enter'

+  getParticipantsWrapperClassnames: -> classnames
+    'delete-mode'                       : @state.deleteMode
+    'CreateChannel-participantsWrapper' : yes
+
+
+  getNameFieldClassnames: -> classnames
+    'Reactivity-formfield' : yes
+    'invalid'              : @state.invalidName
+
+
+  getDropboxFieldClassnames: -> classnames
+    'Reactivity-formfield' : yes
+    'dropdown'             : yes
+    'invalid'              : @state.invalidParticipants
+
+
   setName: (event) ->

     value = event.target.value
@@ -228,11 +244,6 @@
           </div>
         </ProfileLinkContainer>
       </div>
-
-
-  getParticipantsWrapperClassnames: -> classnames
-    'delete-mode'                       : @state.deleteMode
-    'CreateChannel-participantsWrapper' : yes

   renderAddParticipantInput: ->
@@ -268,17 +279,6 @@
       selectedIndex   = { @state.selectedIndex }
       onItemConfirmed = { @bound 'onDropdownItemConfirmed' }
     />
-
-
-  getNameFieldClassnames: -> classnames
-    'Reactivity-formfield' : yes
-    'invalid'              : @state.invalidName
-
-
-  getDropboxFieldClassnames: -> classnames
-    'Reactivity-formfield' : yes
-    'dropdown'             : yes
-    'invalid'              : @state.invalidParticipants

   render: ->


Admin: Integrations: Created loader view.

--- client/admin/lib/views/integrations/adminintegrationparentview.coffee
+++ client/admin/lib/views/integrations/adminintegrationparentview.coffee
@@ -19,6 +19,7 @@
     @identifier = identifier

     @mainView?.destroy()
+    @createLoadingView()

     if action is 'Add' then @handleAdd() else @handleConfigure()

@@ -29,6 +30,7 @@
       return @handleError err  if err

       @addSubView @mainView = new AdminIntegrationSetupView {}, data
+      @loader.destroy()

   handleConfigure: ->
@@ -39,6 +41,7 @@
       return @handleError err  if err

       @addSubView @mainView = new AdminIntegrationDetailsView {}, data
+      @loader.destroy()

   handleError: (err) ->
@@ -51,3 +54,11 @@
       partial = 'There is no integration related with this identifier.'

     @addSubView @mainView = new kd.CustomHTMLView { partial, cssClass: 'error-view' }
+
+
+  createLoadingView: ->
+
+    @addSubView @loader = new kd.LoaderView
+      size       : width : 26
+      cssClass   : 'action-container'
+      showLoader : yes


Workspace: Changed CodeMirror editor mode and theme.

--- client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
+++ client/app/CommonViews/workspace/panes/collaborativeeditorpane.coffee
@@ -1,4 +1,6 @@
 class CollaborativeEditorPane extends CollaborativePane
+
+  cdnRoot = "https://fatihacet.kd.io/cdn/codemirror/latest"

   constructor: (options = {}, data) ->

@@ -9,12 +11,7 @@
     @container = new KDView

     @container.on "viewAppended", =>
-      @codeMirrorEditor = CodeMirror @container.getDomElement()[0],
-        lineNumbers     : yes
-        mode            : "javascript"
-        extraKeys       :
-          "Cmd-S"       : @bound "save"
-
+      @createEditor()
       @panel      = @getDelegate()
       @workspace  = @panel.getDelegate()
       @sessionKey = @getOptions().sessionKey or @createSessionKey()
@@ -57,6 +54,48 @@
       log "client wants to save a file"
       @ref.child("WaitingSaveRequest").set yes

+  createEditor: ->
+    @codeMirrorEditor = CodeMirror @container.getDomElement()[0],
+      lineNumbers     : yes
+      mode            : "javascript"
+      extraKeys       :
+        "Cmd-S"       : @bound "save"
+
+    @setEditorTheme()
+    @setEditorMode()
+
+  setEditorTheme: ->
+    unless document.getElementById "codemirror-ambiance-style"
+      link       = document.createElement "link"
+      link.rel   = "stylesheet"
+      link.type  = "text/css"
+      link.href  = "#{cdnRoot}/theme/ambiance.css"
+      document.head.appendChild link
+      @codeMirrorEditor.setOption "theme", "ambiance"
+
+  setEditorMode: ->
+    {file} = @getOptions()
+
+    return  unless file
+
+    CodeMirror.modeURL = "#{cdnRoot}/mode/%N/%N.js" # TODO: fatihacet - it should be publicly available. should change the cdn url.
+    fileExtension      = file.getExtension()
+    syntaxHandler      = __aceSettings.syntaxAssociations[fileExtension]
+    modeName           = null
+    corrections        =
+      html             : "xml"
+      json             : "javascript"
+      js               : "javascript"
+
+    if corrections[fileExtension]
+      modeName = corrections[fileExtension]
+    else if syntaxHandler
+      modeName = syntaxHandler[0].toLowerCase()
+
+    if modeName
+      @codeMirrorEditor.setOption "mode", modeName
+      CodeMirror.autoLoadMode @codeMirrorEditor, modeName
+
   pistachio: ->
     """
       {{> @header}}


daily

daily

--- lib/cli/generators/fsharp.coffee
+++ lib/cli/generators/fsharp.coffee
@@ -29,13 +29,13 @@

 params = (args) ->
-  sb = []
+  s = []
   for arg in args
     name = arg.split(':')[0]
     type = getType(arg.split(':')[1])
-    sb.push "#{name}"
-
-  sb.join(', ')
+    s.push "#{name}"
+
+  s.join(', ')

 module.exports =
@@ -47,6 +47,7 @@
   run: (flags...) ->

     sb = []
+    s0 = []
     ext = {}
     sys = {}
     s1 = ext['Entity'] = []
@@ -55,17 +56,31 @@

     sb.push "namespace #{config.namespace}"
     sb.push "(**"
-    sb.push " * Entitas Generated Components for #{config.namespace}"
+    sb.push " * Entitas Global Functions for #{config.namespace}"
     sb.push " *"
     sb.push " * do not edit this file"
     sb.push " *)"
     sb.push "[<AutoOpen>]"
-    sb.push "module ComponentExtensions ="
+    sb.push "module GlobalFunctions ="
     sb.push ""
-    sb.push "    open Entitas"
-    sb.push "    open System"
-    sb.push "    open System.Collections.Generic"
+    sb.push "    let isNull x = match x with null -> true | _ -> false"
+    sb.push "    let notNull x = match x with null -> false | _ -> true"
+    sb.push "    let rnd = new System.Random()"
     sb.push ""
+
+    s0.push "namespace #{config.namespace}"
+    s0.push "(**"
+    s0.push " * Entitas Generated Components for #{config.namespace}"
+    s0.push " *"
+    s0.push " * do not edit this file"
+    s0.push " *)"
+    s0.push "[<AutoOpen>]"
+    s0.push "module ComponentExtensions ="
+    s0.push ""
+    s0.push "    open Entitas"
+    s0.push "    open System"
+    s0.push "    open System.Collections.Generic"
+    s0.push ""

     s1.push "namespace #{config.namespace}"
     s1.push "(**"
@@ -80,9 +95,6 @@
     s1.push "    open System"
     s1.push "    open System.Collections.Generic"
     s1.push ""
-    s1.push "    let isNull x = match x with null -> true | _ -> false"
-    s1.push "    let notNull x = match x with null -> false | _ -> true"
-    s1.push ""

     s2.push "namespace #{config.namespace}"
     s2.push "(**"
@@ -111,9 +123,6 @@
     s3.push "    open System"
     s3.push "    open System.Collections.Generic"
     s3.push ""
-    s3.push "    let isNull x = match x with null -> true | _ -> false"
-    s3.push "    let notNull x = match x with null -> false | _ -> true"
-    s3.push ""

     ###
@@ -121,17 +130,17 @@
     ###
     for Name, properties of config.components
       name = Name[0].toLowerCase()+Name[1...]
-      sb.push ""
-      sb.push "    [<AllowNullLiteral>]"
-      sb.push "    type #{Name}Component() ="
-      sb.push "        inherit Component()"
+      s0.push ""
+      s0.push "    [<AllowNullLiteral>]"
+      s0.push "    type #{Name}Component() ="
+      s0.push "        inherit Component()"
       if properties is false
-        sb.push "        member val active = false with get, set"
+        s0.push "        member val active = false with get, set"
       else
         for p in properties
           name = p.split(':')[0]
           value = getDefault(p.split(':')[1])
-          sb.push "        member val #{name} = #{value} with get, set"
+          s0.push "        member val #{name} = #{value} with get, set"

     ###
      * Systems Type Definitions
@@ -173,20 +182,29 @@
     ###
      * Components List
     ###
-    sb.push ""
-    sb.push "    type Component with "
+    s0.push "    type Component with "
+    s1.push "    type Entity with"
+    s1.push ""
+    s2.push "    type Matcher with"
+    s3.push "    type World with"
+    s3.push ""
+
     kc = 0
+    s0.push ""
     for Name, properties of config.components
       name = Name[0].toLowerCase()+Name[1...]
-      sb.push "        static member #{Name} with get() = #{++kc}"
-    sb.push "        static member TotalComponents  with get() = #{++kc}"
-    sb.push ""
+      s0.push "        static member #{Name} with get() = #{++kc}"
+    s0.push "        static member TotalComponents  with get() = #{++kc}"
+    s0.push ""

+
     for Name, properties of config.components
       name = Name[0].toLowerCase()+Name[1...];
+
+      s1.push "        (** Entity: #{Name} methods*)"
+
       switch properties
         when false
-          s1.push "    type Entity with"
           s1.push ""
           s1.push "        static member #{name}Component= new #{Name}Component()"
           s1.push ""
@@ -203,13 +221,13 @@
           s1.push "            this.is#{Name} <- value"
           s1.push "            this"
           s1.push ""
-          s2.push "    type Matcher with "
           s2.push "        static member #{Name}"
-          s2.push "            with get() = Matcher.AllOf(Component.#{Name}) "
+          s2.push "            with get() = "
+          s2.push "                Matcher.AllOf(Component.#{Name}) "
           s2.push ""

         else
-          s1.push "    type Entity with"
+          s1.push ""
           s1.push "        member this.#{name}"
           s1.push "            with get() = this.GetComponent(Component.#{Name}):?>#{Name}Component"
           s1.push ""
@@ -249,16 +267,18 @@
           s1.push "            this.RemoveComponent(Component.#{Name}) |> ignore"
           s1.push "            this._#{name}ComponentPool.Push(c)"
           s1.push ""
-          s2.push "    type Matcher with "
           s2.push "        static member #{Name}"
-          s2.push "            with get() = Matcher.AllOf(Component.#{Name}) "
+          s2.push "            with get() = "
+          s2.push "                Matcher.AllOf(Component.#{Name}) "
           s2.push ""

     ###
      * Pooled Entities
     ###
-    s3.push "    type World with"
     for Name, pooled of config.entities
+
+      s3.push "        (** World: #{Name} methods*)"
+
       if pooled
         name = Name[0].toLowerCase()+Name[1...];
         properties = config.components[Name]
@@ -312,6 +332,7 @@
     #
     # ./<Namespace>.fs - Component declarations
     # ./Systems/*.fs - System Classes
+    # ./Extensions/GlobalExtensions.fs
     # ./Extensions/EntityExtensions.fs
     # ./Extensions/MatcherExtensions.fs
     # ./Extensions/WorldExtensions.fs
@@ -319,10 +340,13 @@
     mkdirp.sync path.dirname(path.join(process.cwd(), config.output.fsharp))
     mkdirp.sync path.join(process.cwd(), config.output.fsharp, "Systems")
     mkdirp.sync path.join(process.cwd(), config.output.fsharp, "Extensions")
+
+    fs.writeFileSync(path.join(process.cwd(), config.output.fsharp,
+      "Extensions/GlobalExtensions.fs"), sb.join('\n'))

     # Components
     fs.writeFileSync(path.join(process.cwd(), config.output.fsharp,
-      "Extensions/ComponentExtensions.fs"), sb.join('\n'))
+      "Extensions/ComponentExtensions.fs"), s0.join('\n'))

     # Extensions
     for Name, ex of ext


Use @subscribe instead of @on

Use @subscribe instead of @on

Using both @subscribe and @on is confusing

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -17,8 +17,8 @@
     super
     @renderMarkdown()
     @subscribe atom.syntax, 'grammar-added grammar-updated', _.debounce((=> @renderMarkdown()), 250)
-    @on 'core:move-up', => @scrollUp()
-    @on 'core:move-down', => @scrollDown()
+    @subscribe this, 'core:move-up', => @scrollUp()
+    @subscribe this, 'core:move-down', => @scrollDown()
     @subscribe @buffer, 'saved reloaded', =>
       @renderMarkdown()
       pane = @getPane()


Fix zooming in/out when previewing markdown

--- lib/markdown-preview-view.coffee
+++ lib/markdown-preview-view.coffee
@@ -103,23 +103,19 @@
     @disposables.add atom.grammars.onDidUpdateGrammar _.debounce((=> @renderMarkdown()), 250)

     atom.commands.add @element,
-      'core:move-up': =>
-        @scrollUp()
-      'core:move-down': =>
-        @scrollDown()
       'core:save-as': (event) =>
         event.stopPropagation()
         @saveAs()
       'core:copy': (event) =>
         event.stopPropagation() if @copyToClipboard()
       'markdown-preview:zoom-in': =>
-        zoomLevel = parseFloat(@css('zoom')) or 1
-        @css('zoom', zoomLevel + .1)
+        zoomLevel = parseFloat(getComputedStyle(@element).zoom)
+        @element.style.zoom = zoomLevel + 0.1
       'markdown-preview:zoom-out': =>
-        zoomLevel = parseFloat(@css('zoom')) or 1
-        @css('zoom', zoomLevel - .1)
+        zoomLevel = parseFloat(getComputedStyle(@element).zoom)
+        @element.style.zoom = zoomLevel - 0.1
       'markdown-preview:reset-zoom': =>
-        @css('zoom', 1)
+        @element.style.zoom = 1

     changeHandler = =>
       @renderMarkdown()


New debugging statements

New debugging statements

--- lib/receivers/tcpReceiver.coffee
+++ lib/receivers/tcpReceiver.coffee
@@ -30,6 +30,8 @@
     metadata: metadata ? {}

 parseTask = (task, callback) ->
+  console.log "parseTask Invoked"
+  console.log task
   unless task?
     return composeErrorReply 'Internal Error', 'Bad Request', new Error('Bad Request'), {}
   if typeof task is 'object'
@@ -77,6 +79,7 @@
         return
       else
         console.log "About to parse task"
+        console.log task
         parseTask task, (parseError, parsedTask) ->
           console.log "Task parsing complete"
           if parseError?


Moved functions to one line

Moved functions to one line

--- core/app/backbone/views/facts/auto_complete_fact_relations_view.coffee
+++ core/app/backbone/views/facts/auto_complete_fact_relations_view.coffee
@@ -143,11 +143,9 @@
     else
       'Disagreeing'

-  clearForm: ->
-    @$('.content').val('')
-
-  showErrorMessage: ->
-    @$('.js-comment-error-message').show()
+  clearForm: -> @$('.content').val('')
+
+  showErrorMessage: -> @$('.js-comment-error-message').show()

   submit: ->
     content = @$('.content').val()


Fix atom.getVersion.

Fix atom.getVersion.

--- src/atom-application.coffee
+++ src/atom-application.coffee
@@ -158,7 +158,7 @@
     ipc.on 'new-window', =>
       @open()

-    ipc.on 'get-version', (event) ->
+    ipc.on 'get-version', (event) =>
       event.result = @version

   sendCommand: (command) ->


throw out rduplex that has never been used

--- library/membrane/securing.coffee
+++ library/membrane/securing.coffee
@@ -36,78 +36,9 @@
 util = require "util"
 url = require "url"

-{Duplex} = require "./duplex"
 {Barebones} = require "./skeleton"
 {RedisClient} = require "./redis"
 tools = require "../nucleus/tools"
-
-# This is an extension of the `Duplex` abstract base service that
-# implements a dialogue sort of protection for the duplex channel.
-# It means that a client who made the initial GET request must be
-# ensured to be the same client that tried to connect over duplex
-# channel. Also, each request has a TTL for the duplex connection.
-# The implementation depends on `RedisClient` and running Redis!
-module.exports.MarkingDuplex = class MarkingDuplex extends Duplex
-
-    # This is a marker that indicates to some internal subsystems
-    # that this class has to be considered abstract and therefore
-    # can not be treated as a complete class implementation. This
-    # mainly is used to exclude or account for abstract classes.
-    # Once inherited from, the inheritee is not abstract anymore.
-    @abstract yes
-
-    # These declarations below are implantations of the abstracted
-    # components by the means of the dynamic recomposition system.
-    # Please take a look at the `Composition` class implementation
-    # for all sorts of information on the composition system itself.
-    # Each of these will be dynamicall integrated in class hierarchy.
-    @implanting RedisClient
-
-    # A hook that will be called once the Connect middleware writes
-    # off the headers. Please refer to this prototype signature for
-    # information on the parameters it accepts. Beware, this hook
-    # is asynchronously wired in, so consult with `async` package.
-    # Please be sure invoke the `next` arg to proceed, if relevant.
-    # This implementation marks legitimate requests ok for duplex.
-    headers: (request, response, resource, domain, next) ->
-        assert mark = "Demilitarize %s request"
-        assert statusCode = try response.statusCode
-        return next() unless (statusCode or 0) is 200
-        sha1 = -> require("crypto").createHash "sha1"
-        key = (x) -> "securing:marking:token:#{x}"
-        gen = (x) -> sha1().update(x).digest "hex"
-        internal = (e) -> "internal Redis error: #{e}"
-        noUuid = "the request has no UUID attached"
-        assert _.isString(u = request.uuid), noUuid
-        @redis.incr gen(key(u)), (error, value) =>
-            assert.ifError error, internal error
-            @redis.expire gen(key(u)), 60, (error) ->
-                assert.ifError error, internal error
-                logger.debug mark.toString(), u.bold
-                assert value >= 1; return next()
-
-    # A usable hook that gets asynchronously invoked once a new
-    # channel (socket) gets past the prescreening hook and is rated
-    # to be good to go through the screening process. This is good
-    # place to implementation various schemes for authorization. If
-    # you wish to decline, just don't call `next` and close socket.
-    # This implementation checks whether socket connection is legit.
-    screening: (context, socket, binder, next) ->
-        assert acc = "Verified %s request".green
-        assert rej = "Militarized %s request".red
-        sha1 = -> require("crypto").createHash "sha1"
-        key = (x) -> "securing:marking:token:#{x}"
-        gen = (x) -> sha1().update(x).digest "hex"
-        internal = (e) -> "internal Redis error: #{e}"
-        noUuid = "the request has no UUID attached"
-        assert uuid = binder.uuid.request, noUuid
-        @redis.decr gen(key(uuid)), (error, value) =>
-            assert.ifError error, internal error
-            message = if value < 0 then rej else acc
-            logger.debug message.toString(), uuid.bold
-            return socket.disconnect() if value < 0
-            return next() if value and value >= 1
-            @redis.del gen(key(uuid)), -> next()

 # This is an abstract base class API stub service. Its purpose is
 # providing the boilerplate for ensuring that the connection is


Remove the LinearColorMapperTransform class

--- bokehjs/src/coffee/common/models.coffee
+++ bokehjs/src/coffee/common/models.coffee
@@ -63,7 +63,6 @@
   LinearColorMapper:        require '../models/mappers/linear_color_mapper'
   LinearMapper:             require '../models/mappers/linear_mapper'
   LogMapper:                require '../models/mappers/log_mapper'
-  LinearColorMapperTransform:        require '../models/mappers/linear_color_mapper_transform'

   Transform:                require '../models/transforms/transform'
   Jitter:                   require '../models/transforms/jitter'


Suffix data name with \'Plugin\' to remove data api conflicts

--- lib/generators/js_percolator/templates/plugin.coffee
+++ lib/generators/js_percolator/templates/plugin.coffee
@@ -38,8 +38,8 @@
   $.fn.<%= file_name.camelize(:lower) %> = ( option ) ->
     this.each ->
       $this = $(@)
-      data = $this.data '<%= file_name.camelize(:lower) %>'
-      if !data then $this.data '<%= file_name.camelize(:lower) %>', (data = new <%= file_name.camelize %> @)
+      data = $this.data '<%= file_name.camelize(:lower) %>Plugin'
+      if !data then $this.data '<%= file_name.camelize(:lower) %>Plugin', (data = new <%= file_name.camelize %> @)
       if typeof option is 'string' then data[option].call $this

   $.fn.<%= file_name.camelize(:lower) %>.Constructor = <%= file_name.camelize %>


Ensure profile names cannot have spaces and add some other restrictions to address FMC-455

--- fabric/fmc-webui/src/main/webapp/app/controllers/profiles_page.coffee
+++ fabric/fmc-webui/src/main/webapp/app/controllers/profiles_page.coffee
@@ -151,9 +151,13 @@
         control: @name
         controller: @
         validator: (text) =>
+          regex = /^[a-zA-Z0-9_-]*$/
           if !text || text == ""
             ok: false,
             msg: "You must specify a profile name"
+          else if !regex.test(text)
+            ok: false
+            msg: "Name can only contain letters, numbers, \u201c-\u201d, and \u201c_\u201d"
           else if @compare(text)
             ok: false,
             msg: "Profile names must be unique"


Start Roxy in Earnest

Start Roxy in Earnest

--- src/coffee/game/roxy.coffee
+++ src/coffee/game/roxy.coffee
@@ -4,43 +4,39 @@
     {leads} = enums

     storylet 'roxyStart',
-      'roxy, old-school detective'
-      '''
-      roxy is in his office when you find him. It's never a good sign if roxy is in his
-      office. It means he's too out of work to be busy and too despondent to be drinking. Judging
-      by his state, times are even tougher than you thought.
-
-      "Oh god, it's you," he mutters as you enter. "And to what do I owe the pleasure of the great
-      Disillusionist's presence?"
-
-      You explain about Horace Brown.
-
-      "What? Is that all? You know what I'm going to say. Same thing you would if I came to you,
-      Dis. 'Screw off!' What kind of detective would I be if I didn't respect my clients'
-      confidentiality? People come here for all sorts of private stuff. I'd be out of work in no
-      time if I started spilling their beans."
-
-      You suppress the urge to argue that, point of fact, it doesn't look like anyone has been
-      coming here for _any_ sort of stuff lately. Instead, you appeal to the point that Mr. Brown
-      is missing, possibly dead, and this could help lead to his rescue.
-
-      "That just makes it worse. If he's dead, he can't tell anyone whether he'd give a damn about
-      my secrecy, can he? Get lost, pal. These lips are sealed."
+      'Roxy Malone, big-time gangster'
+      '''
+      There are three big gangs in the city. North of the river belongs to Mac Turleigh's boys.
+      The financial district is under the watchful eye of Ambrose Beard. And everything else?
+      Everything else belongs to Roxy Malone's gang.
+
+      Roxy appeared during the war, already leading a small gang of street toughs. While the old
+      gangs had their eyes turned to war profiteering, she was qietly eating away at their
+      territory back home. While the old gangs were trying to understand how to get mages to work
+      for them, she was already an accomplished sorceress in her own right. Roxy was a native of
+      the new world. And one by one, the old gangs fell before her or joined her outright. Smart
+      money says she won't rest until she owns the whole city.
+
+      So why is she concerning herself with Mrs. Brown? What does she have to gain from driving a
+      grieving woman from her home? There's more going on here than meets the eye.
+
+      This won't be the first time you two have crossed paths, but you don't know where she's
+      operating out of these days. You'll need to get a bead on Roxy before you can confront her.
       '''
       choices: [
-        choice 'briberoxy',
-          'Try to bribe him'
-          'roxy looks like he could use the money, and principles won\'t pay the rent.'
-          active:
-            bluebacks: reqs.gte 6
-        choice 'dontBriberoxy',
-          'Get lost'
-          'Looks like this is a dead end.'
+        choice 'findRoxy',
+          'Begin searching for her'
+          'You\'re going to need to do some investigation just to find a place to start looking.'
+          active:
+            rumor: reqs.gte 6
+        choice 'dontFindRoxy',
+          'No good'
+          'Roxy is still giving you the slip.'
           next: false
       ]

-    storylet 'briberoxy',
-      'Try to bribe him'
+    storylet 'findRoxy',
+      'Begin searching for her'
       '''
       For a long moment, roxy hungrily eyes the bills in your hand. But just when you think he's
       going to take your offer—


Add sugar methods to quickly show a type of message

--- src/coffee/messenger.coffee
+++ src/coffee/messenger.coffee
@@ -695,6 +695,32 @@

         @run(m_opts)

+    error: (m_opts={}) ->
+      if typeof m_opts is 'string'
+        m_opts = {message: m_opts}
+
+      m_opts.type = 'error'
+
+      @post m_opts
+
+    info: (m_opts={}) ->
+      if typeof m_opts is 'string'
+        m_opts = {message: m_opts}
+
+      m_opts.type = 'info'
+
+      @post m_opts
+
+    success: (m_opts={}) ->
+      if typeof m_opts is 'string'
+        m_opts = {message: m_opts}
+
+      m_opts.type = 'success'
+
+      @post m_opts
+
+
+
 $.fn.messenger = (func={}, args...) ->
     $el = this


Allow to update products with sku only. Refs #33.

--- src/coffee/validator.coffee
+++ src/coffee/validator.coffee
@@ -49,7 +49,9 @@
   validateOffline: (csvContent) ->
     @header.validate()
     @checkDelimiters()
-    @buildProducts csvContent
+
+    variantHeader = CONS.HEADER_VARIANT_ID if @header.toIndex(CONS.HEADER_VARIANT_ID)
+    @buildProducts csvContent, variantHeader

   checkDelimiters: ->
     allDelimiter = {
@@ -98,7 +100,7 @@

   # TODO: Allow to define a column that defines the variant relationship.
   # If the value is the same, they belong to the same product
-  buildProducts: (content, variantColumn = CONS.HEADER_VARIANT_ID) ->
+  buildProducts: (content, variantColumn) ->
     _.each content, (row, index) =>
       rowIndex = index + 2 # Excel et all start counting at 1 and we already popped the header
       if @isProduct row, variantColumn
@@ -148,14 +150,14 @@
       variantId = row[@header.toIndex(CONS.HEADER_VARIANT_ID)]
       parseInt(variantId) > 1
     else
-      @_hasVariantCriteria row, variantColumn
+      false

   isProduct: (row, variantColumn) ->
     hasProductTypeColumn = not _s.isBlank(row[@header.toIndex(CONS.HEADER_PRODUCT_TYPE)])
     if variantColumn is CONS.HEADER_VARIANT_ID
       hasProductTypeColumn and row[@header.toIndex(CONS.HEADER_VARIANT_ID)] is '1'
     else
-      hasProductTypeColumn and not @_hasVariantCriteria row, variantColumn
+      hasProductTypeColumn

   _hasVariantCriteria: (row, variantColumn) ->
     critertia = row[@header.toIndex(variantColumn)]


Override SubjectViewer.rescale to allow for browser page zoom

--- overrides.coffee
+++ overrides.coffee
@@ -1,4 +1,25 @@
+MAX_PAGE_WIDTH = 600
+
 require './readymade/overrides.coffee'
+SubjectViewer = require 'zooniverse-readymade/lib/subject-viewer'
+
+SubjectViewer::rescale = ()->
+  width = Math.min MAX_PAGE_WIDTH, @markingSurface.el.parentNode.offsetWidth
+  scale = width / @maxWidth
+  @markingSurface.maxWidth = @maxWidth
+  @markingSurface.maxHeight = @maxHeight
+  @markingSurface.svg.attr
+    width: scale * @maxWidth
+    height: scale * @maxHeight
+
+SubjectViewer::crop = (rectangle, margin = 25, limit = 1.5)->
+  w = rectangle.width + margin * 2
+  h = rectangle.height + margin * 2
+  scale = @markingSurface.el.parentNode.offsetWidth / w
+  scale = Math.min scale, limit
+  @markingSurface.svg.attr 'width', scale * w
+  @markingSurface.svg.attr 'height', scale * h
+  @markingSurface.rescale rectangle.left - margin, rectangle.top - margin, w, h

 currentProject = require 'zooniverse-readymade/current-project'
 classify_page = currentProject.classifyPages[0]
@@ -12,7 +33,5 @@
   @rescale() if @scaleX is 0

 classify_page.on classify_page.LOAD_SUBJECT, (e, subject)->
-  ms.maxWidth = subjectViewer.maxWidth
-  ms.maxHeight = subjectViewer.maxHeight
   ms.rescale 0, 0, subjectViewer.maxWidth, subjectViewer.maxHeight


Complete Kyocera 5602 pattern calculator

--- src/pattern/default/kyocera/5602.coffee
+++ src/pattern/default/kyocera/5602.coffee
@@ -5,31 +5,74 @@
   settings = pattern.settings
   pattern.name ?= 'KYOCERA_' + element.name

-
   padParams =
     pitch: 0.4
     count: Object.keys(element.pins).length
+    order: 'rows'
+    pad:
+      type: 'smd'
+      shape: 'rectangle'
+      height: 0.23
+      mask: 0.01
+      layer: ['topCopper', 'topMask', 'topPaste']

   switch housing.type

     when 'plug'
-      padParams.pad =
-        type: 'smd'
-        shape: 'rectangle'
-        width: 0.7
-        height: 0.23
-      padParams.distance = 3.31
+      padParams.pad.width = 0.7
+      padParams.distance = 3.56
+      bodyWidth = 2.06
+      bodyLength = padParams.pitch * padParams.count/2 + 1.1

-      pattern.setLayer 'top'
+      dual pattern, padParams
+      if housing.boss
+        bossPad =
+          type: 'mount'
+          name: 'MH'
+          drill: 0.65
+          x: 0.6
+          y: (padParams.pitch * padParams.count/2 + 0.5) / 2
+          layer: ['topCopper', 'topMask', 'intCopper', 'bottomCopper', 'bottomMask']
+        pattern.addPad bossPad
+        bossPad.x = -bossPad.x
+        bossPad.y = -bossPad.y
+        pattern.addPad bossPad
+
+    when 'receptacle'
+      padParams.pad.width = 1
+      padParams.distance = 4.5
+      padParams.mirror = true
+      bodyWidth = 4
+      bodyLength = padParams.pitch * padParams.count/2 + 2.1
+
       dual pattern, padParams

-    when 'receptacle'
-      padParams.pad =
-        type: 'smd'
-        shape: 'rectangle'
-        width: 1
-        height: 0.23
-      padParams.distance = 4
+  pattern.setLayer 'topSilkscreen'
+  lineWidth = settings.lineWidth.silkscreen
+  pattern.setLineWidth lineWidth
+  # Boundary
+  boundWidth = 4
+  boundLength = padParams.pitch * padParams.count/2 + 2.1
+  padHeight = padParams.pad.height
+  x = boundWidth/2
+  y1 = boundLength/2
+  y2 = padParams.pitch*(padParams.count/4 - 0.5) + padHeight/2 + lineWidth/2 + settings.clearance.padToSilk
+  pattern.addLine { x1: -x, y1: y1, x2: x, y2: y1 }
+  pattern.addLine { x1: x, y1: y1, x2: x, y2: y2 }
+  pattern.addLine { x1: -x, y1: y1, x2: -x, y2: y2 }
+  pattern.addLine { x1: -x, y1: -y1, x2: x, y2: -y1 }
+  pattern.addLine { x1: x, y1: -y1, x2: x, y2: -y2 }
+  pattern.addLine { x1: -x, y1: -y1, x2: -x, y2: -y2 }
+  # RefDes
+  fontSize = settings.fontSize.refDes
+  pattern.addAttribute 'refDes',
+    x: 0
+    y: -boundLength/2 - fontSize/2 - 2*lineWidth
+    halign: 'center'
+    valign: 'center'

-      pattern.setLayer 'top'
-      dual pattern, padParams
+  # Assembly
+  pattern.setLayer 'topAssembly'
+  pattern.setLineWidth settings.lineWidth.assembly
+  # Body
+  pattern.addRectangle { x: -bodyWidth/2, y: -bodyLength/2, width: bodyWidth, height: bodyLength }


Change the sidebar to use Flexbox for container

--- internal_packages/account-sidebar/lib/components/account-sidebar.cjsx
+++ internal_packages/account-sidebar/lib/components/account-sidebar.cjsx
@@ -1,6 +1,6 @@
 _ = require 'underscore'
 React = require 'react'
-{OutlineView, ScrollRegion} = require 'nylas-component-kit'
+{OutlineView, ScrollRegion, Flexbox} = require 'nylas-component-kit'
 AccountSwitcher = require './account-switcher'
 SidebarStore = require '../sidebar-store'

@@ -39,15 +39,15 @@
   render: =>
     {accounts, focusedAccounts, userSections, standardSection} = @state

-

+    <Flexbox direction="column" style={order: 0, flexShrink: 1, flex: 1}>
       <AccountSwitcher accounts={accounts} focusedAccounts={focusedAccounts} />
-      <ScrollRegion className="account-sidebar" >
+      <ScrollRegion className="account-sidebar" style={order: 2}>

           <OutlineView {...standardSection} />
           {@_renderUserSections(userSections)}

       </ScrollRegion>
-

+    </Flexbox>

 module.exports = AccountSidebar


Use finish instead of stop

Use finish instead of stop

It clears up transition attributes instead of just stopping the animation.

--- resources/assets/coffee/_classes/nav2.coffee
+++ resources/assets/coffee/_classes/nav2.coffee
@@ -53,11 +53,11 @@
   autoMobileNav: (e, {target, tree}) =>
     if target == 'mobile-menu'
       @clickMenu.show('mobile-nav')
-      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).stop().slideDown()
+      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).finish().slideDown()

     if tree.indexOf('mobile-menu') == -1
       Blackout.hide()
-      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).stop().slideUp()
+      Timeout.set 0, => $(@clickMenu.menu('mobile-menu')).finish().slideUp()
     else
       Blackout.show()


fixed cursor movement issues

fixed cursor movement issues

--- src/public/javascripts/lib/utils.coffee
+++ src/public/javascripts/lib/utils.coffee
@@ -109,10 +109,7 @@
         cursor = utils.get_cursor this
         woot_character = woot.generate_insert(
           cursor, k, woot_state.participant_name, woot_state.sequence_number, woot_state.string)
-        woot.integrate_insert woot_state.string, woot_character
-        utils.add_applied_op woot_state.applied_ops, constants.INSERT_OPERATION, woot_character
-        woot_state.sequence_number += 1
-        element.val woot.value(woot_state.string)
+        utils.execute_operation constants.DELETE_OPERATION, woot_character, woot_state, element
         utils.set_cursor this, cursor + 1

         utils.send_op(woot_state.events_ref, constants.INSERT_OPERATION, woot_character)
@@ -123,29 +120,23 @@
     utils = this
     element.keydown (event) ->
       if event.keyCode == 8
+        selection_delete = utils.check_and_delete_selection this, woot_state
+        if selection_delete
+          return
         # This is the case for backspace
-        selection = utils.get_selection_range(this)
-        if selection.text.length
-          # We need to delete all of the selected text.
-          delete_characters = []
-          for i in [selection.start..selection.end - 1]
-            delete_characters.push woot.generate_delete i, woot_state.string
-          for character in delete_characters
-            utils.execute_operation constants.DELETE_OPERATION, character, woot_state, element
-          utils.set_cursor this, selection.start
-          utils.send_bulk_op woot_state.events_ref, constants.DELETE_OPERATION, delete_characters
-        else
-          cursor = utils.get_cursor this
-          woot_character = woot.generate_delete cursor - 1, woot_state.string
-          if woot_character
-            # We have a visible character to delete
-            utils.execute_operation constants.DELETE_OPERATION, woot_character, woot_state, element
-            utils.set_cursor this, cursor - 1
-
-            utils.send_op woot_state.events_ref, constants.DELETE_OPERATION, woot_character
+        cursor = utils.get_cursor this
+        woot_character = woot.generate_delete cursor - 1, woot_state.string
+        if woot_character
+          # We have a visible character to delete
+          utils.execute_operation constants.DELETE_OPERATION, woot_character, woot_state, element
+          utils.set_cursor this, cursor - 1
+
+          utils.send_op woot_state.events_ref, constants.DELETE_OPERATION, woot_character

         event.stopPropagation()
         return false
+      else
+        console.log event.keyCode

   bind_paste: (element, woot_state) ->
     utils = this
@@ -175,6 +166,20 @@
       event.stopPropagation()
       return false

+  check_and_delete_selection: (element, woot_state) ->
+    selection = utils.get_selection_range element
+    if selection.text.length
+      # We need to delete all of the selected text.
+      delete_characters = []
+      for i in [selection.start..selection.end - 1]
+        delete_characters.push woot.generate_delete i, woot_state.string
+      for character in delete_characters
+        utils.execute_operation constants.DELETE_OPERATION, character, woot_state, element
+      utils.set_cursor element, selection.start
+      utils.send_bulk_op woot_state.events_ref, constants.DELETE_OPERATION, delete_characters
+      return true
+    return false
+
   execute_operation: (operation, woot_character, woot_state, element) ->
     if operation == constants.DELETE_OPERATION
       woot.integrate_delete woot_state.string, woot_character


Move command-matching regex to the class level

--- services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee
@@ -251,22 +251,23 @@
 					callback error
 			return $.ajax options

+		blacklistedCommandRegex: ///
+			\\                    # initial backslash
+			(label                # any of these commands
+			|[a-z]{0,2}ref
+			|usepackage
+			|begin
+			|end
+			|[a-z]{0,2}cite
+			|input
+			|include
+			|includegraphics)
+			( \[ [^\]]* \] )?     # optional [...] args
+				\{ [^}]*  \}        # the {...} args
+		///g
+
 		blankOutBlacklistedCommands: (line) ->
-			commandRegex = ///
-				\\                    # initial backslash
-				(label                # any of these commands
-				|[a-z]{0,2}ref
-				|usepackage
-				|begin
-				|end
-				|[a-z]{0,2}cite
-				|input
-				|include
-				|includegraphics)
-				( \[ [^\]]* \] )?     # optional [...] args
-				  \{ [^}]*  \}        # the {...} args
-			///g
-			line.replace commandRegex, (command) ->
+			line.replace @blacklistedCommandRegex, (command) ->
 				command.replace(
 					/{.*}/, (args) ->
 						'{' + args.slice(1, args.length-1).split('').map((_char)-> '.').join('') + '}'


Machine: make sure provided JMachine is valid

For tests we are providing fake jMachine data which breaks TestRunner

--- client/app/lib/providers/machine.coffee
+++ client/app/lib/providers/machine.coffee
@@ -65,7 +65,7 @@
         @queryString = null
         computeController.reset yes

-    @jMachine.on 'update', =>
+    @jMachine.on? 'update', =>

       { reactor } = kd.singletons
       actions     = require 'app/flux/environment/actiontypes'


Improve upload - check type and size before upload - error handling - make thumbs first, then do upload

--- client/app/models/photoprocessor.coffee
+++ client/app/models/photoprocessor.coffee
@@ -2,17 +2,22 @@

 # width and height of generated thumbs
 MAX_WIDTH = MAX_HEIGHT = 100
+TYPEREGEX = ///image/.*///

 # read the file from photo.file using a FileReader
 # create photo.img : an Image
-# next : callback when the file have been read

 readFile = (photo, next) ->
+    if photo.file.size > 6*1024*1024
+        return next('too big')
+
+    if not photo.file.type.match TYPEREGEX
+        return next('not an image')
+
     reader = new FileReader()
     photo.img = new Image()
     reader.readAsDataURL photo.file
     reader.onloadend = =>
-        photo.file_du = reader.result
         photo.img.src = reader.result
         photo.img.onload = ->
             next()
@@ -34,13 +39,10 @@
     canvas.height = MAX_HEIGHT
     ctx = canvas.getContext '2d'
     ctx.drawImage photo.img, 0, 0, newWidth, newHeight
-    photo.thumb_du = canvas.toDataURL 'image/jpeg'
+    photo.thumb_du = canvas.toDataURL photo.file.type

     # let the view update itself
-    photo.trigger 'change'
-
-    # free memory
-    delete photo.img
+    photo.set 'thumbsrc', photo.thumb_du

     next()

@@ -58,7 +60,6 @@
 # create a FormData object with photo.file and photo.thumb
 # save the model with these files
 upload = (photo, next) ->
-
     formdata = new FormData()
     formdata.append 'raw', photo.file
     formdata.append 'thumb', photo.thumb, "thumb_#{photo.file.name}"
@@ -67,40 +68,59 @@

     Backbone.sync 'create', photo,
       contentType: false # Prevent $.ajax from being smart
-      success: -> next()
+      success: ->
+            next()
+      error: ->
+            photo.set 'thumbsrc', 'img/error.gif'
+            next() # clear tmps anyway
       data: formdata

+# async waterfall of all the above
+makeThumbWorker = (photo , done) ->
+    async.waterfall [
+        (cb) -> readFile         photo, cb
+        (cb) -> makeThumbDataURI photo, cb
+        (cb) ->
+            delete photo.img
+            cb()
+    ], (err) ->
+        if err
+            photo.set
+                thumbsrc:'img/error.gif'
+                title: photo.get('title') + ' is ' + err
+        done(err)

-# delete all object attached to the photo
-# the photo is now backed by the server
-clearTemp = (photo, next) ->
-    delete photo.file
-    delete photo.img
-    delete photo.file_du
-    delete photo.thumb
-    delete photo.thumb_du
-    next()
+uploadWorker = (photo, done) ->
+    async.waterfall [
+        (cb) -> makeThumbBlob    photo, cb
+        (cb) -> upload           photo, cb
+        (cb) ->
+            # the photo is now backed by the server
+            # delete all object attached to the photo
+            delete photo.file
+            delete photo.thumb
+            delete photo.thumb_du
+            cb()
+    ], (err) ->
+        if err
+            photo.set
+                thumbsrc:'img/error.gif'
+                title: photo.get('title') + ' is ' + err
+        done(err)

-# async waterfall of all the above
-operation = (task , callback) ->
-    {photo, file} = task
-    photo.file = file
-    async.waterfall [
-        (cb) -> readFile photo         , cb
-        (cb) -> makeThumbDataURI photo , cb
-        (cb) -> makeThumbBlob photo    , cb
-        (cb) -> upload photo           , cb
-        (cb) -> clearTemp Photo        , cb
-    ], callback
+class PhotoProcessor

-# number of pictures being processed at any time
-concurrency = 3
+    # create thumbs 3 by 3
+    thumbsQueue: async.queue makeThumbWorker, 3

-queue = async.queue operation, concurrency
+    # upload 2 by 2
+    uploadQueue: async.queue uploadWorker, 2

-module.exports.process = (file, photo) ->
-    queue.push
-        file: file
-        photo: photo
-    , ->+    process: (photo) ->
+        @thumbsQueue.push photo, (err) =>
+            return console.log err if err
+            @uploadQueue.push photo, (err) =>
+                return console.log err if err
+
+module.exports = new PhotoProcessor()


Added createShortcutsView method to IDEView.

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -47,6 +47,9 @@
       editor    : editor

     @createPane_ editor, paneOptions, file
+
+  createShortcutsView: ->
+    @createPane_ new IDE.ShortcutsView, { name: 'Shortcuts' }

   createTerminal: (vm) ->
     terminalPane = new IDE.TerminalPane { vm }


Add label attribute to return either title or id

--- client/lanes/screens/Definitions.coffee
+++ client/lanes/screens/Definitions.coffee
@@ -55,6 +55,10 @@
         extension_path:
             deps: ['extension', 'model'], fn: ->
                 _.classify(@extension) + ".Screens." + @view
+
+        label:
+            deps: ['id', 'title'], fn: ->
+                @title || _.field2title(@id)

     getScreen: ->
         Lanes.u.getPath(@extension_path)


fix parenthesis on path_dict

fix parenthesis on path_dict

--- src/render/render_svg.coffee
+++ src/render/render_svg.coffee
@@ -12,7 +12,7 @@

   path_tags = (paths) ->
     return [] unless paths
-    path_dict = {stroke: world.get 'stroke', fill: world.get 'fill'}
+    path_dict = {stroke: world.get('stroke'), fill: world.get('fill')}
     paths.map (path) ->
       path_dict['d'] = path
       SVG.path path_dict


[pagerduty] by default, only ack the incidents assigned to you

--- src/scripts/pagerduty.coffee
+++ src/scripts/pagerduty.coffee
@@ -121,6 +121,9 @@

   robot.respond /(pager|major)( me)? ack(nowledge)?$/i, (msg) ->
     pagerDutyIncidents msg, 'triggered,acknwowledged', (incidents) ->
+      email  = msg.message.user.pagerdutyEmail || msg.message.user.email_address
+      incidents = incidentsForEmail(incidents, email)
+
       incidentNumbers = (incident.incident_number for incident in incidents)
       if incidentNumbers.length < 1
         msg.send "Nothing to acknowledge"
@@ -406,6 +409,10 @@
     else
       '(???)'

+  incidentsForEmail = (incidents, user_email) ->
+    incidents.filter (incident) ->
+      getUserForIncident(incident) == user_email
+
   generateIncidentString = (incident, hookType) ->
     console.log "hookType is " + hookType
     assigned_user   = getUserForIncident(incident)


CSS measuring plain text fixed

CSS measuring plain text fixed

Bug with missed support for new lines characters and wraping by character fixed.

--- impl/css/level0/text.coffee
+++ impl/css/level0/text.coffee
@@ -83,17 +83,31 @@
 		x = width = 0
 		height = lineHeight = pixelSize * item._lineHeight
 		maxWidth = if data.wrap then item._width else Infinity
-		for char in text
-			charWidth = getTextSizeWidth fontDef, char
-			charWidth += letterSpacing
-			if wordSpacing isnt 0 and char is ' '
-				charWidth += wordSpacing
-			if x + charWidth > maxWidth
+		wordWidth = charWidth = 0
+		textLength = text.length
+		char = ''
+
+		for i in [0..textLength] by 1
+			if i < textLength
+				char = text[i]
+				charWidth = getTextSizeWidth fontDef, char
+				charWidth += letterSpacing
+
+			if i is textLength or char is ' '
+				if x + wordWidth > maxWidth
+					height += lineHeight
+					x = 0
+				x += wordWidth
+				if x > width
+					width = x
+				if char is ' '
+					x += charWidth + wordSpacing
+				wordWidth = 0
+			else if char is '\n'
 				height += lineHeight
 				x = 0
-			x += charWidth
-			if x > width
-				width = x
+			else
+				wordWidth += charWidth

 		item.contentWidth = width
 		item.contentHeight = height


do not require static react to work (so can build docker image) -- this is kind of dangerous, but whatever

--- src/smc-webapp/render.coffee
+++ src/smc-webapp/render.coffee
@@ -20,13 +20,18 @@
 global['$'] = global['window'].$ = $ = ->
 $.get = ->

-static_react_pages = [
-    [require('./billing.cjsx').render_static_pricing_page(), 'policies/_static_pricing_page.html'],
-    #[require('./r_help.cjsx').render_static_about(), '_static_about.html'],
-    [require('./r_misc.cjsx').render_static_footer(), '_static_footer.html'],
-    #[require('./r_misc.cjsx').render_static_sage_preview(), '_static_sage_preview.html'],
-    [require('./r_help.cjsx').render_static_third_party_software(), '_static_third_party_software.html'],
-]
+try
+    static_react_pages = [
+        [require('./billing.cjsx').render_static_pricing_page(), 'policies/_static_pricing_page.html'],
+        #[require('./r_help.cjsx').render_static_about(), '_static_about.html'],
+        [require('./r_misc.cjsx').render_static_footer(), '_static_footer.html'],
+        #[require('./r_misc.cjsx').render_static_sage_preview(), '_static_sage_preview.html'],
+        [require('./r_help.cjsx').render_static_third_party_software(), '_static_third_party_software.html'],
+    ]
+catch err
+    # this fails right now in the standalone docker image, and doesn't really matter for that anyways...
+    console.warn("Skipping static react pages", err);
+    static_react_pages = []

 # Code for static server-side rendering of the subscription options.
 # note, that we use renderToStaticMarkup, not renderToString


Minor optimization to refining.coffee

Minor optimization to refining.coffee

Use one less datatables API call to achieve the same results.

--- src/coffee/refining.coffee
+++ src/coffee/refining.coffee
@@ -16,8 +16,8 @@
     ).index()

     if oreRow? # Found
-      oldData = refiningTable.cell(oreRow, 1).data()
-      refiningTable.cell(oreRow, 1).data(oldData + quantity)
+      oreCell = refiningTable.cell(oreRow, 1)
+      oreCell.data(oreCell.data() + quantity)
     else # Not found
       refiningTable.row.add([itemName, quantity])


Stray console.log

Stray console.log

--- src/commands/status.coffee
+++ src/commands/status.coffee
@@ -50,7 +50,6 @@
     ovaed = formatOvaed ovaed
     archived = formatArchived archived
     expires = formatExpires expires
-    console.log rearmsLeft
     rearms = if statusName is 'MISSING' then '' else formatRearms rearmsLeft
     columns vm.name, status, ovaed, archived, expires, rearms


Fix xml compiler.

Fix xml compiler.

--- src/compilers/index.coffee
+++ src/compilers/index.coffee
@@ -20,4 +20,4 @@
   scss:       enumerable: true, get: -> require './sass'
   styl:       enumerable: true, get: -> require './stylus'
   htm:        enumerable: true, get: -> require './html'
-  xml:        enumerable: true, get: -> require './xml'
+  xml:        enumerable: true, get: -> require './html'


expire temporary packs and roll over to a new pack each day

--- services/track-changes/app/coffee/PackManager.coffee
+++ services/track-changes/app/coffee/PackManager.coffee
@@ -3,6 +3,8 @@
 {db, ObjectId, BSON} = require "./mongojs"
 logger = require "logger-sharelatex"
 LockManager = require "./LockManager"
+
+DAYS = 24 * 3600 * 1000 # one day in milliseconds

 module.exports = PackManager =
 	# The following functions implement methods like a mongo find, but
@@ -476,7 +478,7 @@

 	flushCompressedUpdates:	(project_id, doc_id, lastUpdate, newUpdates, temporary, callback = (error) ->) ->
 		return callback() if newUpdates.length == 0
-		if lastUpdate?
+		if lastUpdate? and not (temporary and ((Date.now() - lastUpdate.meta?.start_ts) > 1 * DAYS))
 			PackManager.appendUpdatesToExistingPack project_id, doc_id, lastUpdate, newUpdates, temporary, callback
 		else
 			PackManager.insertUpdatesIntoNewPack project_id, doc_id, newUpdates, temporary, callback
@@ -497,6 +499,8 @@
 				end_ts: last.meta.end_ts
 			v: first.v
 			v_end: last.v
+		if temporary
+			newPack.expiresAt = new Date(Date.now() + 7 * DAYS)
 		logger.log {project_id, doc_id, newUpdates}, "inserting updates into new pack"
 		db.docHistory.insert newPack, callback

@@ -519,6 +523,8 @@
 			$set:
 				"meta.end_ts": last.meta.end_ts
 				"v_end": last.v
+		if lastUpdate.expiresAt and temporary
+			update.$set.expiresAt = new Date(Date.now() + 7 * DAYS)
 		logger.log {project_id, doc_id, lastUpdate, newUpdates}, "appending updates to existing pack"
 		db.docHistory.findAndModify {query, update}, callback


use path.extname instead of regExp

use path.extname instead of regExp

--- src/src-lines-counter.coffee
+++ src/src-lines-counter.coffee
@@ -1,4 +1,5 @@
 fs = require 'fs'
+p = require 'path'

 sourceLinesCounter = (path) ->

@@ -35,8 +36,9 @@
         if stats.isDirectory()
             walk fullPath, resultArray
         else
-            ext = fullPath.match /.*(\..*)$/
-            type = ext[1] if ext isnt null and ext[1] isnt null
+            ext = p.extname fullPath
+            type = ext if ext isnt null
+
             if not extConfig[type]
                 continue

@@ -49,9 +51,9 @@
         'can not read :' + path + e

     # check type
-    ext = path.match /.*(\..*)$/
-    type = ext[1] if ext isnt null and ext[1] isnt null
-    type = '.js' if not extConfig[type]
+    ext = p.extname path
+    type = ext if ext isnt null
+    type = 'js' if not extConfig[type]

     lines = content.split '\n'
     counter = 0
@@ -95,27 +97,27 @@
     counter

 extConfig =
-    '.js':
+    'js':
         wordsReg: /\w/
         inlineCommentReg: /^\s*\/\//
         blockCommentStartReg: /\s*(\S*)\s*(\/\*)\s*/
         blockCommentEndReg: /(\*\/)\s*(\w)*\s*$/
-    '.java':
+    'java':
         wordsReg: /\w/
         inlineCommentReg: /^\s*\/\//
         blockCommentStartReg: /\s*(\S*)\s*(\/\*)\s*/
         blockCommentEndReg: /(\*\/)\s*(\w)*\s*$/
-    '.css':
+    'css':
         wordsReg: /\w/
         inlineCommentReg: /^\s*\/\//
         blockCommentStartReg: /\s*(\S*)\s*(\/\*)\s*/
         blockCommentEndReg: /(\*\/)\s*(\w)*\s*$/
-    '.coffee':
+    'coffee':
         wordsReg: /\w/
         inlineCommentReg: /^\s*\/\//
         blockCommentStartReg: /\s*(\S*)\s*(\/\*)\s*/
         blockCommentEndReg: /(\*\/)\s*(\w)*\s*$/
-    '.html':
+    'html':
         wordsReg: /\w/
         inlineCommentReg: /^\s*\/\//
         blockCommentStartReg: /\s*(\S*)\s*(<!--)\s*/


string_extensions

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -60,9 +60,15 @@
 ###*
 Return the class or constant named in this string.

+

+
+"Constant".constantize()
+# => Constant
+# notice this isn't a string. Useful for calling methods on class named Constant
+

+
 @name constantize
 @methodOf String#
-
 @returns {Object} The class or constant named in this string.
 ###
 String::constantize = ->


Fix beatmap selector hiding incorrect overlay

--- resources/assets/coffee/react/beatmap-discussions/beatmap-list.coffee
+++ resources/assets/coffee/react/beatmap-discussions/beatmap-list.coffee
@@ -17,11 +17,8 @@

   componentDidMount: =>
-    $(document).on 'click.beatmapList', @hideSelector
-    @syncBlackout()
-
-
-  componentDidUpdate: =>
+    $(document).on 'click.beatmapList', @onDocumentClick
+    $(document).on 'turbolinks:before-cache.beatmapList', @hideSelector
     @syncBlackout()

@@ -63,18 +60,23 @@
         count: count

-  hideSelector: (e) =>
+  hideSelector: =>
+    return unless @state.showingSelector
+
+    @setSelector false
+
+
+  onDocumentClick: (e) =>
     return if e.button != 0
-    return unless @state.showingSelector
     return if $(e.target).closest('.js-beatmap-list-selector').length

-    @setSelector false
+    @hideSelector()

   setSelector: (state) =>
     return if @state.showingSelector == state

-    @setState showingSelector: state
+    @setState showingSelector: state, @syncBlackout

   selectBeatmap: (e) =>


Expanded keyboard controls

Expanded keyboard controls

Vertical look now included, by default @controls =
Firecracker.Controls.SimpleKeyboardControls( camera ) will create first
person fly controls. You can lock vertical height by giving an extra
parameter, for example: SimpleKeyboardControls( camera, 10 ) would lock
y at 10

--- src/core/Firecracker.coffee
+++ src/core/Firecracker.coffee
@@ -464,48 +464,9 @@
     #                                                         #
     ###########################################################

-    ##  Requires PointerLockControls.js to be included in project  ##
-    standardControls: (camera, scene) =>
-        controls = new THREE.PointerLockControls( camera )
-        controls.enabled = true
-        scene.add( controls.getObject() )
-
-        havePointerLock = 'pointerLockElement' in document or 'mozPointerLockElement' in document or 'webkitPointerLockElement' in document
-        if ( havePointerLock )
-            element = document.body
-
-            pointerlockchange = ( event ) =>
-                if (document.pointerLockElement is element or document.mozPointerLockElement is element or document.webkitPointerLockElement is element )
-                    controls.enabled = true
-                else
-                    controls.enabled = false
-
-            requestPointerLock = () =>
-                element.requestPointerLock = element.requestPointerLock or element.mozRequestPointerLock or element.webkitRequestPointerLock
-                element.requestPointerLock()
-
-            pointerlockerror = ( event ) =>
-                alert("Pointer Lock Error")
-
-            listenForPointerLock = () =>
-
-                document.addEventListener( 'pointerlockchange', pointerlockchange, false )
-                document.addEventListener( 'mozpointerlockchange', pointerlockchange, false )
-                document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false )
-
-                document.addEventListener( 'pointerlockerror', pointerlockerror, false )
-                document.addEventListener( 'mozpointerlockerror', pointerlockerror, false )
-                document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false )
-
-                document.addEventListener( 'click', requestPointerLock, false )
-
-            listenForPointerLock()
-        else
-            alert("Pointer Lock Error")
-
-        return controls
-
-    SimpleKeyboardControls: ( camera ) =>
+    SimpleKeyboardControls: ( camera, y_height=null ) =>
+
+        camera.rotation.order = "YXZ"

         controls = {

@@ -538,14 +499,14 @@
                     @turn_right = false

             MouseMove: ( event ) =>
-                PI_2 = (Math.PI)*(Math.PI)
+                PI_2 = Math.PI / 2

                 @movementX = event.movementX or event.mozMovementX or event.webkitMovementX or 0
                 @movementY = event.movementY or event.mozMovementY or event.webkitMovementY or 0

                 camera.rotation.y -= @movementX * 0.002
-                # camera.rotation.x += @movementY * 0.002
-                # camera.rotation.x = Math.max( - PI_2, Math.min( PI_2, camera.rotation.x ) )
+                camera.rotation.x -= @movementY * 0.002
+                camera.rotation.x = Math.max( - PI_2, Math.min( PI_2, camera.rotation.x ) )

             update: () =>
                 if @move_forward
@@ -557,9 +518,12 @@
                 if @move_right
                     camera.translateX(4)
                 if @turn_left
-                    camera.rotateY(Math.PI/100)
+                    camera.rotation.y += 10 * 0.002
                 if @turn_right
-                    camera.rotateY(-Math.PI/100)
+                    camera.rotation.y -= 10 * 0.002
+
+                if y_height?
+                    camera.position.y = y_height
         }

         canvas = $("canvas")[0]


Adding init method callback on startup

Adding init method callback on startup

--- lib/Opifex.coffee
+++ lib/Opifex.coffee
@@ -41,6 +41,7 @@
 			self.queue = Queue
 			self.queue.bind exchange, key
 			self.queue.subscribe self
+			self['init']?.apply(self,[])
 	self.send = (msg,route,recipient) -># route & recipient are optional, default to destination exchange and key respectively
 		route ?= dest
 		recipient ?= path


use async parallel to speed up test

use async parallel to speed up test

--- server/test/integration/api_indicator.coffee
+++ server/test/integration/api_indicator.coffee
@@ -134,7 +134,7 @@
 )

 test('GET /api/indicators?withData=true only returns indicators with indicator data associated', (done) ->
-  async.series([helpers.createIndicator, helpers.createIndicator], (err, indicators) ->
+  async.parallel([helpers.createIndicator, helpers.createIndicator], (err, indicators) ->
     indicatorWithData = indicators[0]

     helpers.createIndicatorData(


Calculator Fixes: [#3738002]

Calculator Fixes: [#3738002]

Multiple operator button presses will replace the previous operator in
the calculation

--- javascripts/lib/math.coffee
+++ javascripts/lib/math.coffee
@@ -434,7 +434,6 @@

   class_mixer(_ImplicitMultiplication)

-
   class _OverrideIfOperatorOrAppend
     initialize: (@expression)->
     with: (operator)->


Update index.coffee

Update index.coffee

Fixed links being too wide.

--- jeffreel/views/index.coffee
+++ jeffreel/views/index.coffee
@@ -10,6 +10,7 @@
 			p {color: #E0A666; margin: 0px}
 			a {color: #D18026; text-decoration: none}
 			h1 {font-size: 48px; text-align: center; color: #E0A666}
+			#button {width: 600px; margin: 0 auto}
 			#broadcast {background-color: #34414C; width: 600px; margin: 0 auto 10px auto; padding: 0 0 18px 4px}
 			#title {font-size: 22px; margin-bottom: 0px}
 			#date {display: inline-box; float: left}
@@ -22,7 +23,7 @@
 		h1 "Mixlr Archives"

 		for broadcast in @broadcasts
-			a href: broadcast.streams.http.url, ->
+			div id: 'button', -> a href: broadcast.streams.http.url, ->
 				div id: 'broadcast', ->
 					div id: 'title', -> broadcast.title
 					div id: 'date', -> p "Started: " + broadcast.started_at


replace tab with space

replace tab with space

--- shared/start.coffee
+++ shared/start.coffee
@@ -15,7 +15,7 @@
 Handlebars.registerHelper 'signupClass', ->
   if Accounts.oauth && Accounts.oauth.serviceNames().length > 0
     "collapse"
-
+
 Handlebars.registerHelper 'otherLoginServices', ->
   Accounts.oauth &&
   Accounts.oauth.serviceNames().length > 0


LOL HOSTNAME IS TRUE

LOL HOSTNAME IS TRUE

--- source/coffee/ntrapy.coffee
+++ source/coffee/ntrapy.coffee
@@ -379,7 +379,7 @@
     opts["title"] =
       #TODO: Figure out why this fires twice: console.log "title"
       """
-      #{obj.name? ? "Details"}
+      #{obj.name ? "Details"}

           #{('

' + backend + '

' for backend in obj.facts.backends).join('')}