updated client docs

updated client docs

--- lib/datatables.client.coffee
+++ lib/datatables.client.coffee
@@ -4,8 +4,7 @@
 # ## Initialization

 # ##### rendered()
-#   When the component is first rendered datatables is initialized
-#   The `this` context for all of the component methods is `templateInstance.__component__`
+# When the component is first rendered datatables is initialized `templateInstance.__component__` is the this context
 Template.dataTable.rendered = ->
   templateInstance = @
   instantiatedComponent = templateInstance.__component__
@@ -13,15 +12,13 @@
   instantiatedComponent.initialize()

 # ##### destroyed()
-#   Currently nothing is done when the component is destroyed
-#   This may change
+# Currently nothing is done when the component is destroyed.
 Template.dataTable.destroyed = ->
   @log "destroyed"

 # ##### initialize()
-#   Set the initial table properties from the component declaration
-#   Initialize the jQuery DataTables object
-#   Initialize other third parties if they exist ( plugins, select2, etc. )
+# Set the initial table properties from the component declaration, initialize the jQuery DataTables object, and initialize
+# other third parties if they exist ( plugins, select2, etc. )
 Template.dataTable.initialize = ->
   @prepareQuery()
   @prepareCollection()
@@ -33,32 +30,33 @@
   @preparePagination()
   @log "initialized", @

+# ### Collection Counts
+# Datatables maintains counts of both the base query and filtered query reactively.
+# These counts are published by the datatables publication
+
+DataTableSubscriptionCount = new Meteor.Collection "datatable_subscription_count"
+
 # ## Component Parameters

 # #### `options` Object ( optional )
-#   Any additional options you would like merged with the defaults `_.defaults options, defaultOptions`
-#   The datatables options object, see datatables docs for more info
-#   The default options are listed below.
-#   The default options can changed by setting `Template.dataTable.defaultOptions.yourDumbProperty`
-
+# `options` are additional options you would like merged with the defaults `_.defaults options, defaultOptions`.
+# For more information on available dataTable options see the [DataTables Docs](https://datatables.net/usage/).
+# The default options are listed below and can be changed by setting `Template.dataTable.defaultOptions.yourDumbProperty`
 # ##### [DataTables Options Full Reference](https://datatables.net/ref)
-#   All options passed through the options parameter will be merged with the default options.
-#   e.g. `_.defaults options, defaultOptions`
 Template.dataTable.defaultOptions =
   bJQueryUI: false
   bAutoWidth: true
   bDeferRender: true
   sPaginationType: "full_numbers"
   # ##### Bootstrap 3 Markup
-  #   You can change this by setting `Template.dataTable.defaultOptions.sDom` property.
-  #   For some example Less / CSS styles check out [luma-ui's dataTable styles](https://github.com/LumaPictures/luma-ui/blob/master/components/dataTables/dataTables.import.less)
+  # You can change this by setting `Template.dataTable.defaultOptions.sDom` property.
+  # For some example Less / CSS styles check out [luma-ui's dataTable styles](https://github.com/LumaPictures/luma-ui/blob/master/components/dataTables/dataTables.import.less)
   sDom: "<\"datatable-header\"fl><\"datatable-scroll\"rt><\"datatable-footer\"ip>"
   oLanguage:
     sSearch: "_INPUT_"
     sLengthMenu: "Show : _MENU_"
     # ##### Loading Message
-    #   Set `oLanguage.sProcessing` to whatever you want, event html
-    #   Haven't tried a Meteor template yet, could be fun!
+    # Set `oLanguage.sProcessing` to whatever you want, event html. I haven't tried a Meteor template yet, could be fun!
     sProcessing: "Loading"
     oPaginate:
       sFirst: "First"
@@ -81,24 +79,25 @@
   return @getData().options or @presetOptions() or false

 # ##### prepareOptions()
-#   Prepares the datatable options object by merging the options passed in with the defaults
-#   If the componet was declared with a collection and a query it is setup as a reactive datatable
-#   `options.sAjaxSource` is currently useless, but is passed into `fnServerData` by datatables
-#   binds the datatables `fnServerData` server callback to this component instance
+# Prepares the datatable options object by merging the options passed in with the defaults.
 Template.dataTable.prepareOptions = ->
   options = @getOptions() or {}
   options.aaData = @getRows() or []
   options.aoColumns = @getColumns() or []
+  # If the componet was declared with a collection and a query it is setup as a reactive datatable.
   if @getCollection() and @getQuery()
     options.bServerSide = true
     options.bProcessing = true
+    # `options.sAjaxSource` is currently useless, but is passed into `fnServerData` by datatables.
     options.sAjaxSource = "useful?"
+    # This binds the datatables `fnServerData` server callback to this component instance.
+    # `_.debounce` is used to prevent unneccesary subcription calls while typing a search
     options.fnServerData = _.debounce( @fnServerData.bind( @ ), 300 )
   @setOptions _.defaults( options, @defaultOptions )

 # #### `selector` String ( required )
-#   The table selector for the dataTable instance you are creating.
-#   Needs to be unique in the page scope or you will get datatable mulit-render error
+# The table selector for the dataTable instance you are creating, must be unique in the page scope or you will get
+# datatable mulit-render error.
 Template.dataTable.setSelector = ( selector ) ->
   Match.test selector, String
   @setData 'selector', selector
@@ -114,8 +113,7 @@
     @setSelector "datatable-#{ @getGuid() }"

 # #### `rows` Array of Object ( optional )
-#   this is a static datasource
-#   can be used to display static data, or reactive client side data
+# Can be used to display static data, or reactive client side data.

 # ##### setRows()
 Template.dataTable.setRows = ( rows ) ->
@@ -134,7 +132,7 @@
   else return @getData().rows or false

 # ##### getRowIndex()
-#   gets the datatable index of a row by mongo id
+# Gets the datatable index of a row by mongo id.
 Template.dataTable.getRowIndex = ( _id ) ->
   index = false
   counter = 0
@@ -147,9 +145,8 @@
   return index

 # #### `columns` Array of Objects ( required )
-#   The column definitions you are passing to the datatable component
-#   This is where to map object properties to columns and their headers
-#   This is where you define custom templates for rendering data in cells
+# The column definitions you are passing to the datatable component. This is where to map object properties to columns
+# and their headers. You can also define custom templates for rendering data in cells using the `mData` property.

 # ##### setColumns()
 Template.dataTable.setColumns = ( columns ) ->
@@ -158,20 +155,20 @@
   @log "columns:set", columns

 # ##### prepareColumns()
-#   adds _id as a hidden column by default
-#   sets a default cell render function for every column
 Template.dataTable.prepareColumns = ->
   columns = @getColumns() or []
+  # Adds _id as a hidden column by default.
   columns.push
     sTitle: "id"
     mData: "_id"
     bVisible: false
     bSearchable: false
+  # Sets a default cell render function for every column.
   @setDefaultCellValue column for column in columns
   @setColumns columns

 # ##### setDefaultCellValue()
-#   defaults all cells to "" if undefined
+# The default cell render function defaults all cells to "" if undefined.
 Template.dataTable.setDefaultCellValue = ( column ) ->
   Match.test column.mData, String
   Match.test column.sTitle, String
@@ -184,29 +181,29 @@
   return @getData().columns or false

 # #### `table_template` String ( optional )
-#   The name of table layout template that you want to render.
-#   default is `default_table_template` found [here](lib/datatables.html)
-#   You can set your default template by assigning the template name to `Template.datatable.defaultTemplate`
+# The name of table layout template that you want to render.
+# Default is `default_table_template` found [here](lib/datatables.html).
+# You can set your default template by assigning the template name to `Template.datatable.defaultTemplate`.

 # ##### Default Table Template
-#   The default table template is defined in datatables.html
+# The default table template is defined in datatables.html.
 Template.dataTable.defaultTemplate = 'default_table_template'

 # ##### chooseTemplate Helper
-#   Return the template specified in the component parameters
-#   Set table template to default if no template name is passed in
-#   If the template is defined return it
-#   Otherwise return the default template
+# Return the template specified in the component parameters
 Template.dataTable.chooseTemplate = ( table_template = null ) ->
+  # Set table template to default if no template name is passed in
   table_template ?= Template.dataTable.defaultTemplate
+  # If the template is defined return it
   if Template[ table_template ]
     return Template[ table_template ]
+  # Otherwise return the default template
   else return Template[ @defaultTemplate ]

 # #### `collection` Meteor Collection ( required )
-#   This is the collection that house the documents your datatable is displaying
-#   This collection must be defined on both the client and the server
+# This is the collection that houses the documents your datatable is displaying
+# and must be defined on both the client and the server.
 Template.dataTable.setCollection = ( collection ) ->
   Match.test collection, Object
   @setData 'collection', collection
@@ -224,8 +221,8 @@
   return

 # #### `subscription` String ( required )
-#   The name of the subscription your datatables is paging, sorting, and filtering
-#   This must be a datatable compatible publication ( for more info see Publishing below )
+# The name of the subscription your datatable is paging, sorting, and filtering.
+# This must be a datatable compatible publication ( for more info see Server )
 Template.dataTable.setSubscription = ( subscription ) ->
   Match.test subscription, Object
   @setData 'subscription', subscription
@@ -241,8 +238,7 @@
   @log 'subscription:options:set', options

 # ##### setSubscriptionHandle()
-#   subscribes to the dataset for the current table state
-#   stores the handle for later access
+# Subscribes to the dataset for the current table state and stores the handle for later access.
 Template.dataTable.setSubscriptionHandle = ->
   if @getSubscriptionHandle()
     @getSubscriptionHandle().stop()
@@ -251,8 +247,8 @@
   @log 'subscription:handle:set', handle

 # ##### setSubscriptionAutorun()
-#   creates a reactive computation that runs when the subscription is `ready()`
-#   sets up local cursor ( identical to server except no skip )
+# Creates a reactive computation that runs when the subscription is `ready()`
+# and sets up local cursor ( identical to server except no skip ).
 Template.dataTable.setSubscriptionAutorun = ( fnCallback ) ->
   Match.test fnCallback, Object
   if @getSubscriptionAutorun()
@@ -265,19 +261,19 @@
         limit: @getTableState().iDisplayLength
         sort: @getTableState().sort
       @setCursor @getCollection().find @getTableState().query, cursorOptions
-      #   fetches data from collection and passes it to datatables
-      #     does this by calling the `fnCallback()` passed to `fnServerData()`
-      #     sends an unaltered copy of `sEcho` ( draw count ) to datatables
-      #     gets total docs, before filtering i.e. the total number of records in the database
-      #     gets total records, after filtering i.e. the total number of records after filtering has been applied
+      # Here data is fetched from the collection and passed dataTables by calling the `fnCallback()`
+      # passed to `fnServerData()`.
       aaData = @getCursor().fetch()
       @log 'fnServerData:aaData', aaData
-      #     both of these counts come from the `DataTableSubscriptionCount` collection on the client
-      #     `DataTableSubscriptionCount` is populated reactively by the DataTables publication for this table
-      #     The selector used to retrieve the counts is the subscription name and + '_filtered'
       fnCallback
+        # Sends an unaltered copy of `sEcho` ( draw count ) to datatables.
         sEcho: @getTableState().sEcho
+        # Gets total docs, before filtering i.e. the total number of records in the server collection.
+        # Both of these counts come from the `DataTableSubscriptionCount` collection on the client.
+        # `DataTableSubscriptionCount` is populated reactively by the DataTables publication for this table.
+        # The selector used to retrieve the counts is the subscription name and + '_filtered'.
         iTotalRecords: @getTotalCount()
+        # Gets total records, after filtering i.e. the total number of records after filtering has been applied
         iTotalDisplayRecords: @getFilteredCount()
         aaData: aaData
   @setData 'autorun', autorun
@@ -300,9 +296,9 @@
   return @getData().autorun or false

 # #### `query` MongoDB Selector ( optional )
-#   The initial filter for your datatable
-#   You should attempt to narrow your selection as much as possbile to improve performance
-#   The default query is `{}`
+# The initial filter for your datatable.
+# The default query is `{}`
+# You should attempt to narrow your selection as much as possbile to improve performance.

 # ##### setQuery()
 Template.dataTable.setQuery = ( query ) ->
@@ -320,15 +316,15 @@
   return @getData().query or false

 # #### `debug` String ( optional )
-#   A handy option for granular debug logs
-#   `true` logs all messages from datatables
-#   Set debug to any string to only log messages that contain that string
-#   Some useful debug settings are :
-#     * `rendered` logs the instantiated component on render
-#     * `destroyed` logs when the component is detroyed
-#     * `initialized` logs the inital state of the datatable after data is acquired
-#     * `options` logs the datatables options for that instantiated component
-#     * `fnServerData` logs each request to the server by the component
+# A handy option for granular debug logs.
+# `true` logs all messages from datatables.
+# Set debug to any string to only log messages that contain that string
+# ##### examples
+#   + `rendered` logs the instantiated component on render
+#   + `destroyed` logs when the component is detroyed
+#   + `initialized` logs the inital state of the datatable after data is acquired
+#   + `options` logs the datatables options for that instantiated component
+#   + `fnServerData` logs each request to the server by the component

 # ##### isDebug()
 Template.dataTable.isDebug = ->
@@ -343,77 +339,70 @@
 # ## Querying MongoDB

 # ##### mapTableState()
-#   Take the `aoData` parameter of `fnServerData` and map it into a more usable object
-#   Convert aoData to key -> value pairs
+# Take the `aoData` parameter of `fnServerData` and map it into a more usable object.
 Template.dataTable.mapTableState = ( aoData ) ->
   aoData = @arrayToDictionary aoData, 'name'
   @log 'mapTableState:aoData', aoData

   # ##### tableState
-  #   `aoData.sEcho` is a request counter, incremented on every server call
-  #   `aoData.columns` contains the property meta data for each column
-  #   `aoData.iColumns` is the number of columns being displayed
-  #   `aoData.iSortingCols` is the number of columns being sorted
-  #   `aoData.iDisplayLength` is the number of rows being displayed
-  #   `aoData.iDisplayStart` is the number of rows to skip for pagination
-  #   `aoData.sSearch` is the datatables search input value
   tableState =
+    #   + `aoData.sEcho` is a request counter, incremented on every server call
     sEcho: aoData.sEcho.value or 1
     bRegex: aoData.bRegex.value or false
+    #   + `aoData.columns` contains the property meta data for each column
     columns: []
+    #   + `aoData.iColumns` is the number of columns being displayed
     iColumns: aoData.iColumns.value or 0
+    #   + `aoData.iSortingCols` is the number of columns being sorted
     iSortingCols: aoData.iSortingCols.value or 0
     sColumns: aoData.sColumns.value or ""
+    #   + `aoData.iDisplayLength` is the number of rows being displayed
     iDisplayLength: aoData.iDisplayLength.value or 10
+    #   + `aoData.iDisplayStart` is the number of rows to skip for pagination
     iDisplayStart: aoData.iDisplayStart.value or 0
+    #   + `aoData.sSearch` is the datatables search input value
     sSearch: aoData.sSearch.value or ""

   # ##### getDataProp()
-  #   function scope helper for getting `aoData` properties
+  # Function scope helper for getting `aoData` properties.
   getDataProp = ( key, index ) ->
     key = "#{ key }_#{ index }"
     return aoData[ key ].value

   # ##### mapColumns()
-  #   iterator for setting up columns
-  #   create an element for each column
-  #   `mDataProp` is the field name
-  #   `bRegex` is a boolean for if the field has a search input
-  #   `bSearchable` is a boolean used to determine which fields are searchable
-  #   `bSortable` is a boolean used to determine which fields are sortable
-  #   `sSearch` contains the column search string if column filters are setup
+  # iterator for setting up columns
   mapColumns = ( index ) ->
     tableState.columns[ getDataProp 'mDataProp', index ] =
+      #   + `mDataProp` is the field name
       mDataProp: getDataProp 'mDataProp', index
+      #   + `bRegex` is a boolean for if the field has a search input
       bRegex: getDataProp 'bRegex', index
+      #   + `bSearchable` is a boolean used to determine which fields are searchable
       bSearchable: getDataProp 'bSearchable', index
+      #   + `bSortable` is a boolean used to determine which fields are sortable
       bSortable: getDataProp 'bSortable', index
+      #   + `sSearch` contains the column search string if column filters are setup
       sSearch: getDataProp 'sSearch', index
   mapColumns index for index in [ 0..( tableState.iColumns - 1 ) ]

   # ##### mapQuery()
-  #   If there is a global filter the filter query is merged with the tables default query
-  #   `mapQuery()` is actually just an iterator
-  #   The filter query is initialized as an `$or` of all the searchable columns against the search regex
-  #   Letter case is ignored for all search querys.
-  #   The `$or`'s run in parrallel in MongoDB, improving search performance
-  #   You MUST still index all searchable fields
-  #   If the base query is for all records in the collection, the filter query is the only query run
-  #   If the base query is already filter the collection, the filter query is run as an `$and` against it
-  #   e.g. all documents must pass both the table query and search query
   if tableState.sSearch isnt ""
+    # The filter query is initialized as an `$or` of all the searchable columns against the search regex.
     searchQuery = $or: []
     mapQuery = ( key, property ) ->
       unless property.bSearchable is false
         obj = {}
         obj[ key ] =
           $regex: tableState.sSearch
+          # Letter case is ignored for all search querys.
           $options: 'i'
         searchQuery.$or.push obj
     for key, property of tableState.columns
       mapQuery key, property
+    # If the base query is for all records in the collection, the filter query is the only query run.
     if @getQuery is {}
       tableState.query = searchQuery
+    # If the base query is already filter the collection, the filter query is run as an `$and` against it.
     else
       tableState.query =
         $and: [
@@ -423,11 +412,10 @@
   else tableState.query = @getQuery()

   # ##### mapSortOrder()
-  #   Another column iterator
-  #   Only runs if columns are being sorted
-  #   Sets sort direction for each sorted field, allowing multi column sort
+  # Only runs if columns are being sorted.
   if tableState.iSortingCols > 0
     tableState.sort = {}
+    # Sets sort direction for each sorted field, allowing multi column sort.
     mapSortOrder = ( sortIndex ) ->
       sortIndex = sortIndex - 1
       propertyIndex = getDataProp 'iSortCol', sortIndex
@@ -450,32 +438,22 @@
   return @getData().tableState or false

 # ##### fnServerData()
-#   The callback for every dataTables user / reactivity event
-#   `sSource` is the currently useless `sAjaxProp` from the options
-#   `aoData` is an array of objects provided by datatables reflecting its current state
-#   `fnCallback` is the function that will be called when the server returns a result
-#   `oSettings` is the datatables settings object
+# The callback for every dataTables user / reactivity event
+# ###### Parameters
+#   + `sSource` is the currently useless `sAjaxProp` from the options
+#   + `aoData` is an array of objects provided by datatables reflecting its current state
+#   + `fnCallback` is the function that will be called when the server returns a result
+#   + `oSettings` is the datatables settings object
 Template.dataTable.fnServerData = ( sSource, aoData, fnCallback, oSettings ) ->
+  # `setTableState()` parses aoData and creates a usable table state object.
   @setTableState aoData
+  # `setSubscriptionOptions()` turns the table state into a MongoDB query options object.
   @setSubscriptionOptions()
+  # `setSubscriptionHandle()` subscribes the the dataset for the current table state.
   @setSubscriptionHandle()
+  # `setSubscriptionAutorun()` creates a Deps.autrun computation. The autorun computation will call datatables fnCallback
+  # when the current table state subscription is ready.
   @setSubscriptionAutorun fnCallback
-  # ##### Wat?
-  #   `setTableState()` parses aoData and creates a usable table state object
-  #   `setSubscriptionOptions()` turns the table state into a MongoDB query options object
-  #   `setSubscriptionHandle()` subscribes the the dataset for the current table state
-  #     * reactive subscription
-  #     * base query and filter query
-  #     * with sort order
-  #     * limited to the table display limit
-  #   `setSubscriptionAutorun()` creates a Deps.autrun computation
-  #     sets the datatable when the subscription is ready
-
-# ### Collection Counts
-#   datatables maintains counts of both the base query and filtered query reactively
-#   these counts are published by the datatables publication
-
-DataTableSubscriptionCount = new Meteor.Collection "datatable_subscription_count"

 # ##### prepareCountCollection()
 Template.dataTable.prepareCountCollection = ->


Replace iamport notice_url

Replace iamport notice_url

--- public/assets/coffee/order-id-payment.coffee
+++ public/assets/coffee/order-id-payment.coffee
@@ -24,7 +24,7 @@
       buyer_name:   name
       buyer_tel:    $info.data('phone')
       buyer_addr:   $info.data('address1')
-      notice_url:   'https://test-share.theopencloset.net/webhooks/import'
+      notice_url:   'https://test-share.theopencloset.net/webhooks/iamport'
     , (res) ->
       console.log res
       if res.success


update comments

update comments


Switch to non-fuzzy suggestions.

Switch to non-fuzzy suggestions.

Suggests only snippets whose trigger starts with the current prefix.

--- lib/snippets-provider.coffee
+++ lib/snippets-provider.coffee
@@ -77,16 +77,13 @@
   findSuggestionsForWord: (prefix) ->
     return [] unless @snippets?

-    snippetsByPrefixes = {}
-    prefixes = _.values(@snippets).map (snippet) ->
-      snippetsByPrefixes[snippet.prefix] = snippet
-      return snippet.prefix
+    # Only accept snippets that start with prefix
+    matchesPrefix = (snippet) ->
+      snippet.prefix.lastIndexOf(prefix, 0) isnt -1

-    # Merge the scope specific words into the default word list
-    words = fuzzaldrin.filter prefixes, prefix
-
-    results = for word in words
-      snippet = snippetsByPrefixes[word]
-      new Suggestion this, word: word, prefix: prefix, label: snippet.label
+    results = for snippet in @snippets when matchesPrefix(snippet)
+      word = snippet.prefix
+      label = snippet.label
+      new Suggestion this, word: word, prefix: prefix, label: label

     return results


Updated the code to pass the working test

The previous test was flawed, causing this code to false-pass.

--- lib/streams/pistachio.coffee
+++ lib/streams/pistachio.coffee
@@ -37,9 +37,9 @@
   # We wait for flush to be called to implicitly know the end of the incoming,
   # and then apply the replace.
   _flush: ->
-    @_data.replace pistachio_matcher, (pist) -> pist.replace /\@/g, 'this.'
-    # Now that we have replaced what we expect, push out our data
-    @push @_data
+    # Push the output of our pistachio replace
+    @push @_data.replace pistachio_matcher, (pist) ->
+      pist.replace /\@/g, 'this.'
     # And then null to end the stream
     @push null


elektron: return default storage if it doesn't exist

--- elektron/src/storage.coffee
+++ elektron/src/storage.coffee
@@ -34,4 +34,4 @@
     catch e
       console.log e

-    return storage
+    return storage or {}


just assign to compute at random

just assign to compute at random

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -352,12 +352,18 @@
                             return 0
                     dbg("scored host info = #{misc.to_json(([info.host,info.score] for info in v))}")
                     # finally choose one of the hosts with the highest score at random.
+                    ###
                     best_score = v[0].score
                     i = 0
                     while i < v.length and v[i].score == best_score
                         i += 1
-                    w = v.slice(0,i)
-                    opts.cb(undefined, misc.random_choice(w).host)
+                    v = v.slice(0,i)
+                    ###
+
+                    # I'm disabling this, since in practice random is probably
+                    # better than the very naive approach above, given that we sometimes
+                    # have 100+ projects created at once, and they all end up in the same place!
+                    opts.cb(undefined, misc.random_choice(v).host)

     remove_from_cache: (opts) =>
         opts = defaults opts,


Prefer preloaded URL over the currently preloading URL

--- src/trix/models/attachment.coffee
+++ src/trix/models/attachment.coffee
@@ -127,7 +127,7 @@
   # Previewable

   getPreviewURL: ->
-    @preloadingURL or @previewURL
+    @previewURL or @preloadingURL

   setPreviewURL: (url) ->
     unless url is @getPreviewURL()


Unify vendor search placeholder with one used in report.

Missing from 88217497cf4b71545060545a1aa1bfff2f3cc94b

--- kirppu/static_src/js/checkout/vendorfindmode.coffee
+++ kirppu/static_src/js/checkout/vendorfindmode.coffee
@@ -21,7 +21,7 @@

   glyph: -> "user"
   title: -> gettext("Vendor Search")
-  inputPlaceholder: -> gettext("Search vendor")
+  inputPlaceholder: -> gettext("Search terms separated by space")

   actions: -> [
     ["", @onSearchVendor]


Add fetch interval for cursor posts

Add fetch interval for cursor posts

--- lib/assets/javascripts/views/unread_count.js.coffee
+++ lib/assets/javascripts/views/unread_count.js.coffee
@@ -3,6 +3,7 @@

   initialize: ->
     @interval = new TentStatus.FetchInterval fetch_callback: @fetchCount
+    @cursor_interval = new TentStatus.FetchInterval fetch_callback: @fetchCursor

     # find or fetch existing cursor post
     TentStatus.Models.CursorPost.find(
@@ -15,6 +16,21 @@
       failure: @fetchFailure
       complete: =>
         @trigger('fetch:complete')
+    )
+
+  fetchCursor: =>
+    return unless cursor_post = TentStatus.Models.CursorPost.find(cid: @post_cid)
+    cursor_post.fetch(
+      params: {
+        since: cursor_post.get('received_at') + ' ' + cursor_post.get('version.id')
+      }
+
+      success: =>
+        @cursor_interval.reset()
+        @reset()
+
+      failure: =>
+        @cursor_interval.increaseDelay()
     )

   hide: =>


Fix for issue #75

Fix for issue #75

--- lib/calatrava/templates/kernel/plugins/alert.coffee
+++ lib/calatrava/templates/kernel/plugins/alert.coffee
@@ -3,9 +3,9 @@
     message: message

 calatrava.confirm = (message, onOkExecute) ->
-  okCallbackHandle = calatrava.bridge.plugins.rememberCallback () ->
+  okCallbackHandle = calatrava.bridge.plugins.rememberCallback (result) ->
     calatrava.bridge.plugins.deleteCallback(okCallbackHandle)
-    onOkExecute()
+    onOkExecute(result)

   calatrava.bridge.plugins.call 'alert', 'displayConfirm',
     message: message


add callbacks to instance superclass

add callbacks to instance superclass

--- code/instance.coffee
+++ code/instance.coffee
@@ -11,30 +11,30 @@

   # Create a server ready to run. Somewhere on the internet.
   # Servers are created in the "stopped" state.
-  @create: (config) ->
+  @create: (config, callback) ->
     @config = new cf.Config config

   # Destroy a previously created server.
-  @destroy: (instance) ->
+  @destroy: (instance, callback) ->

   # List instances in the cloud
-  @list: ->
+  @list: (callback) ->

   # Start the server. Immediately go into the 'starting' state,
   # and if everything goes okay, then sometime later it will be
   # in the 'running' state.
-  start: ->
+  start: (callback) ->

   # Stop the server. Immediately go into the 'stopping' state,
   # and if everything goes okay, then sometime later it will be
   # in the 'stopped' state.
-  stop: ->
+  stop: (callback) ->

   # Return the state of the server.  Will be one of:
   # 'stopped', 'running', 'starting', 'stopping', and probably
   # others.
-  state: ->
+  state: (callback) ->

   # Run a shell command as root on the server. Return stdout.
   # Throw stderr as an error on nonzero exit.
-  sh: -> (command) ->
+  sh: -> (command, callback) ->


Fix bug in Agent Spawner

Fix bug in Agent Spawner

--- assets/agent_spawner.coffee
+++ assets/agent_spawner.coffee
@@ -6,8 +6,8 @@
   $.ajax("/assets/data/agents.json").done($.proxy((data_agents) ->
       for data_agent in data_agents
         agent = new Agent
-        customer.fromAgentData(data_agent)
-        @potential_agents.push(data_agent)
+        agent.fromAgentData(data_agent)
+        @potential_agents.push(agent)
     , this)
   )
   hireAgent: ()->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -20,9 +20,9 @@
   obj.update()

   obj2 = GameObject
-    x: 50
-    y: 50
-    width: 32
+    x: 100
+    y: 250
+    width: 16
     height: 32

   obj2.include(ActiveBounds)


Working canvas render

Working canvas render

--- bokehjs/src/coffee/models/annotations/label.coffee
+++ bokehjs/src/coffee/models/annotations/label.coffee
@@ -19,6 +19,9 @@
       @listenTo(@model, 'data_update', @plot_view.request_render)

   render: () ->
+
+    @set_data(@mget('source'))
+
     if @mget('render_mode') == 'css'
       @_css_text()
     else
@@ -27,14 +30,21 @@
   _canvas_text: () ->
     ctx = @plot_view.canvas_view.ctx

-    ctx.save()
-    ctx.translate(@mget('x')+@mget('x_offset'), @mget('y')+@mget('y_offset'))
-    ctx.rotate(@mget('angle'))
+    x_offset = @mget('x_offset')
+    y_offset = @mget('y_offset')

-    @visuals.text.set_value(ctx)
-    # ctx.fillText(@mget('text'), 0, 0)
-    ctx.fillText(@mget('text'), 300, 300)
-    ctx.restore()
+    [@sx, @sy] = @map_to_screen(_.map(@x, (x) -> x + x_offset),
+                                _.map(@y, (y) -> y + y_offset))
+
+    debugger;
+    for i in [0...@x.length]
+      ctx.save()
+      ctx.translate(@sx[i], @sy[i])
+      ctx.rotate(@mget('angle'))
+
+      @visuals.text.set_value(ctx)
+      ctx.fillText(@text[i], 0, 0)
+      ctx.restore()

   _css_text: () ->
     console.log('css not implemented')
@@ -48,12 +58,19 @@

   props: ->
     return _.extend {}, super(), {
-      render_mode:  [ p.RenderMode,   'canvas'  ]
-      x_units:      [ p.SpatialUnits, 'data'    ]
-      y_units:      [ p.SpatialUnits, 'data'    ]
-      angle:        [ p.AngleSpec,    0         ]
-      x_offset:     [ p.Number,       0         ]
-      y_offset:     [ p.Number,       0         ]
+      x:            [ p.NumberSpec                      ]
+      x_units:      [ p.SpatialUnits, 'data'            ]
+      y:            [ p.NumberSpec                      ]
+      y_units:      [ p.SpatialUnits, 'data'            ]
+      text:         [ p.StringSpec,   { field :"text" } ]
+      angle:        [ p.AngleSpec,    0                 ]
+      x_offset:     [ p.Number,       0                 ]
+      y_offset:     [ p.Number,       0                 ]
+      level:        [ p.RenderLevel, 'overlay'          ]
+      source:       [ p.Instance                        ]
+      x_range_name: [ p.String,      'default'          ]
+      y_range_name: [ p.String,      'default'          ]
+      render_mode:  [ p.RenderMode,  'canvas'           ]
     }

   defaults: ->


report timeout errors to sentry

report timeout errors to sentry

--- services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
@@ -7,6 +7,8 @@

 		class PDFRenderer
 			JOB_QUEUE_INTERVAL: 25
+			PAGE_LOAD_TIMEOUT: 30*1000
+			PAGE_RENDER_TIMEOUT: 30*1000

 			constructor: (@url, @options) ->
 				PDFJS.disableFontFace = true  # avoids repaints, uses worker more
@@ -131,13 +133,14 @@

 				timedOut = false
 				timer = $timeout () =>
+					Raven.captureMessage?('pdfng page load timed out after ' + @PAGE_LOAD_TIMEOUT + 'ms')
 					# console.log 'page load timed out', pagenum
 					timedOut = true
 					@spinner.stop(element.canvas)
 					# @jobs = @jobs - 1
 					# @triggerRenderQueue(0)
 					this.errorCallback?('timeout')
-				, 30*1000
+				, @PAGE_LOAD_TIMEOUT

 				@pageLoad[pagenum] = @getPage(pagenum)

@@ -221,11 +224,12 @@

 				timedOut = false

-				timer = $timeout () ->
+				timer = $timeout () =>
+					Raven.captureMessage?('pdfng page render timed out after ' + @PAGE_RENDER_TIMEOUT + 'ms')
 					# console.log 'page render timed out', pagenum
 					timedOut = true
 					result.cancel()
-				, 30*1000
+				, @PAGE_RENDER_TIMEOUT

 				result.then () ->
 					# console.log 'page rendered', pagenum


add test

add test

--- tests/scripts/unit/normalize.coffee
+++ tests/scripts/unit/normalize.coffee
@@ -153,6 +153,11 @@
       expected: 'Span'
     )

+    lineTest.run('remove redundant elements 3',
+      initial:  'Span'
+      expected: 'Span'
+    )
+
     lineTest.run('wrap text node',
       initial:  'Hey'
       expected: 'Hey'


check for the debug flag in the correct place in hat-trick.js.coffee


add action to context menu

add action to context menu


heroku testing

heroku testing

--- system/core/Loader.coffee
+++ system/core/Loader.coffee
@@ -326,6 +326,9 @@

     if $return is true then return DB #($params, $active_record)

+    @controller.db = DB
+    return
+
     # Load the DB class
     defineProperty @controller, 'db'
       enumerable  : true


Fixing the missing dependancy for defaultPerms

--- lib/util/handlers/collection-static-method.coffee
+++ lib/util/handlers/collection-static-method.coffee
@@ -1,5 +1,6 @@
 sendError = require '../sendError'
 sendResult = require '../sendResult'
+defaultPerms = require "../defaultPerms"

 module.exports = (route) ->
   [Model] = route.meta.models
@@ -21,4 +22,4 @@
       return sendError res, err if err?
       sendResult res, data

-  return out+  return out


handleSearch not in use anymore

handleSearch not in use anymore

--- lms/static/coffee/src/discussion/discussion.coffee
+++ lms/static/coffee/src/discussion/discussion.coffee
@@ -31,13 +31,6 @@
     $local = Discussion.generateLocal($discussionNonContent)

     id = $discussion.attr("_id")
-
-    handleSearch = (text, isSearchWithinBoard) ->
-      if text.length
-        if $local(".discussion-search-within-board").is(":checked")
-          window.location = window.location.pathname + '?text=' + encodeURI(text)
-        else
-          window.location = Discussion.urlFor('search') + '?text=' + encodeURI(text)

     handleSubmitNewPost = (elem) ->
       title = $local(".new-post-title").val()


fix dir.dist is undefined in templates/gulpfile

--- templates/GulpFile.coffee
+++ templates/GulpFile.coffee
@@ -3,10 +3,10 @@
 gulp = require("gulp")
 $ = require("gulp-load-plugins")(lazy: false) #plugins

-# if port is in use and  Error: listen EADDRINUSE is thrown run
+# if port is in use and Error: listen EADDRINUSE is thrown run
 # $ netstat -a -t -p
-# get pid of proccess using portand kill like so
-# kill -9 PID
+# get pid of proccess using port and kill it like so
+# $kill -9 PID

 gulp.task "connect", $.connect.server(
   host: server.hostname
@@ -29,10 +29,10 @@
     .pipe $.coffeelint.reporter()
     .pipe $.coffee(bare: true).on("error", $.util.log)
     .pipe $.concat("main.js")
-    .pipe gulp.dest(dir.dist + dir.scripts)
+    .pipe gulp.dest(dir.build + dir.scripts)
     .pipe $.rename(suffix: ".min")
     .pipe $.uglify()
-    .pipe gulp.dest(dir.dist + dir.scripts)
+    .pipe gulp.dest(dir.build + dir.scripts)
     .pipe $.connect.reload()
     .pipe $.notify(message: "Scripts task complete")

@@ -42,20 +42,20 @@
       use: ["nib"]
       import: ["nib"]
     )
-    .pipe gulp.dest(dir.dist + dir.styles)
+    .pipe gulp.dest(dir.build + dir.styles)
     .pipe $.connect.reload()
     .pipe $.notify(message: "Styles task complete")

 gulp.task "images", ->
   gulp.src(dir.source + dir.images + "**/*")
-    .pipe $.newer(dir.dist + dir.images)
+    .pipe $.newer(dir.build + dir.images)
     .pipe $.imagemin(
       optimizationLevel: 3
       progressive: true
       interlaced: true
     )
     .pipe $.connect.reload()
-    .pipe gulp.dest(dir.dist + dir.images)
+    .pipe gulp.dest(dir.build + dir.images)
     .pipe $.notify(message: "Images task complete")

 gulp.task "clean", ->


Cleanup script

Cleanup script

--- hubot/scripts/docker.coffee
+++ hubot/scripts/docker.coffee
@@ -1,5 +1,5 @@
 # Description:
-#   Monitors docker-compose events, publish them using pubsub.
+#   Execute docker-compose actions
 #
 # Commands:
 #   hubot docker ps - shows application's docker containers
@@ -8,7 +8,6 @@

 module.exports = (robot) ->
   robot.respond /docker (.*)/i, (msg) ->
-    @exec = require('child_process').exec
     command = msg.match[1]
     switch command
       when 'ps'


Move Marker.handleDeprecatedParams to deprecated section

--- src/marker.coffee
+++ src/marker.coffee
@@ -41,27 +41,6 @@
       outputParams.properties = properties if size(properties) > 0
     outputParams

-  @handleDeprecatedParams: (params) ->
-    if params.isReversed?
-      Grim.deprecate("The option `isReversed` is deprecated, use `reversed` instead")
-      params.reversed = params.isReversed
-      delete params.isReversed
-
-    if params.hasTail?
-      Grim.deprecate("The option `hasTail` is deprecated, use `tailed` instead")
-      params.tailed = params.hasTail
-      delete params.hasTail
-
-    if params.persist?
-      Grim.deprecate("The option `persist` is deprecated, use `persistent` instead")
-      params.persistent = params.persist
-      delete params.persist
-
-    if params.invalidation
-      Grim.deprecate("The option `invalidation` is deprecated, use `invalidate` instead")
-      params.invalidate = params.invalidation
-      delete params.invalidation
-
   @serializeSnapshot: (snapshot) ->
     return unless snapshot?
     serializedSnapshot = {}
@@ -532,3 +511,24 @@
   Marker::setAttributes = (args...) ->
     Grim.deprecate("Use Marker::setProperties instead.")
     @setProperties(args...)
+
+  Marker.handleDeprecatedParams = (params) ->
+    if params.isReversed?
+      Grim.deprecate("The option `isReversed` is deprecated, use `reversed` instead")
+      params.reversed = params.isReversed
+      delete params.isReversed
+
+    if params.hasTail?
+      Grim.deprecate("The option `hasTail` is deprecated, use `tailed` instead")
+      params.tailed = params.hasTail
+      delete params.hasTail
+
+    if params.persist?
+      Grim.deprecate("The option `persist` is deprecated, use `persistent` instead")
+      params.persistent = params.persist
+      delete params.persist
+
+    if params.invalidation
+      Grim.deprecate("The option `invalidation` is deprecated, use `invalidate` instead")
+      params.invalidate = params.invalidation
+      delete params.invalidation


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -6,6 +6,7 @@
     y: 50
     width: 32
     height: 32
-    activeBounds: Re
+
+  obj.inclu

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/active_bounds.coffee
+++ test/active_bounds.coffee
@@ -5,7 +5,9 @@
 App.width = 640
 App.height = 480

-test "should not be destroyed when inside "
+test "should not be destroyed when inside activeBounds", ->
+  obj = GameObject
+    x

 test "should remove objects outside of activeBounds", 2, ->
   obj = GameObject


add font loading

add font loading

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -83,62 +83,78 @@

 rgb_to_hex = (rgb) -> '#'+rgb.map(rescale_to_hex).join('')

-remove_element = (element) ->
+removeElement = (element) ->
     if element? and (parent = element.parentElement)?
         parent.removeChild element

 loadScript = (script_src, callback) ->
-    run_callback = true
+    runCallback = true

     script = document.createElement 'script'

     script.onload = ->
-            remove_element script
-            if run_callback
-                run_callback = false
+            removeElement script
+            if runCallback
+                runCallback = false
                 callback()
     script.onerror = ->
-            remove_element script
-            if run_callback
-                run_callback = false
+            removeElement script
+            if runCallback
+                runCallback = false
                 callback "error loading script #{script.src}"

     script.type = 'text/javascript'
+    script.charset = 'utf-8'
     script.async = true
     script.src = script_src

     document.head.appendChild script

-_loading_threejs_callbacks = []
-_orbitcontrols_setup = false
-
-math3d.threejs_src = "//cdnjs.cloudflare.com/ajax/libs/three.js/r68/three.min.js"
-
-math3d.load_threejs = (callback) ->
-    if THREE? and _orbitcontrols_setup
+_loadingThreejsCallbacks = []
+_orbitControlsSetup = false
+
+math3d.threejsSource = "//cdnjs.cloudflare.com/ajax/libs/three.js/r68/three.min.js"
+math3d.fontSources = []
+
+math3d.loadThreejs = (callback) ->
+    if THREE? and _orbitControlsSetup and not math3d.fontsSources.length
         return callback()

-    _loading_threejs_callbacks.push callback
-    if _loading_threejs_callbacks.length > 1
+    _loadingThreejsCallbacks.push callback
+    if _loadingThreejsCallbacks.length > 1
         return

-    run_callbacks = (error) ->
-        while callback = _loading_threejs_callbacks.shift()
+    runCallbacks = (error) ->
+        while callback = _loadingThreejsCallbacks.shift()
             callback error

-    setup_orbitcontrols = ->
-        _orbitcontrols_setup = true
-        OrbitControls.prototype = Object.create THREE.EventDispatcher.prototype
-        run_callbacks()
-
-    if THREE?
-        return setup_orbitcontrols()
-
-    loadScript math3d.threejs_src, (error) ->
-        if (error)
-            run_callbacks error
+    setupOrbitControls = (callback) ->
+        if not _orbitControlsSetup
+            OrbitControls.prototype = Object.create THREE.EventDispatcher.prototype
+            _orbitControlsSetup = true
+        callback()
+
+    setupFonts = (callback) ->
+        if math3d.fontSources.length
+            loadScript math3d.fontSources.shift(), (error) ->
+                if error
+                    runCallbacks error
+                else
+                    setupFonts callback
         else
-            setup_orbitcontrols()
+            callback()
+
+    setupThreejs = (callback) ->
+        if THREE?
+            callback()
+        else
+            loadScript math3d.threejsSource, (error) ->
+                if error
+                    runCallbacks error
+                else
+                    callback()
+
+    setupThreejs (-> setupFonts (-> setupOrbitControls runCallbacks))

 _scene_using_renderer = undefined
 _renderer = {}
@@ -198,7 +214,7 @@
             thickness       : .4        # zero thickness disables the frame
             labels          : true      # whether or not to enable labels on the axes

-        math3d.load_threejs (error) =>
+        math3d.loadThreejs (error) =>
             if error
                 return @opts.callback? error

@@ -242,7 +258,7 @@

     attach_to_dom: (parent_element) ->
         if @element?
-            remove_element @element
+            removeElement @element
         else
             @element = document.createElement 'span'
             @element.className = 'math-3d-viewer'
@@ -268,7 +284,7 @@
         @renderer_type = 'dynamic'

         # remove the current renderer (if it exists)
-        remove_element @element.lastChild
+        removeElement @element.lastChild

         # place renderer in correct place in the DOM
         @element.appendChild @renderer.domElement
@@ -304,7 +320,7 @@
             @last_canvas_target = @controls.target

         # remove the current renderer (if it exists)
-        remove_element @element.lastChild
+        removeElement @element.lastChild

         # place renderer in correct place in the DOM
         @element.appendChild @static_image
@@ -415,6 +431,7 @@
         opts = defaults opts,
             text        : required
             loc         : [0,0,0]
+            fontface    : undefined # defaults to Text3d's default font
             rotation    : undefined # by default will always face the camera
             size        : 1         # should really be specified
             material    : required
@@ -434,6 +451,7 @@
             text        : required
             loc         : [0, 0, 0]
             rotation    : [0, 0, 0]
+            fontface    : "helvetiker"
             size        : 1         # should really be specified
             depth       : 1         # ditto
             material    : required
@@ -442,7 +460,7 @@
         geometry = new THREE.TextGeometry opts.text,
             size        : opts.size
             height      : opts.depth
-            font        : "helvetiker"
+            font        : opts.fontface

         material =  new THREE.MeshBasicMaterial
             opacity     : opts.material.opacity


only hide if it exists.

only hide if it exists.

--- lib/assets/javascripts/aha/color_picker.js.coffee
+++ lib/assets/javascripts/aha/color_picker.js.coffee
@@ -48,7 +48,7 @@
         color: {hex: @hexColor, converted: @color}
       )

-    @picker.hide()
+    @picker.hide() if @picker

   createColorPicker: () ->
     picker = $(@template).appendTo("body")


test(windows): fix path related issue

test(windows): fix path related issue

--- test/helpers/tests.coffee
+++ test/helpers/tests.coffee
@@ -37,6 +37,7 @@
 	run:
 		spec: (spec) ->
 			specFile = "#{TESTS_PATH}#{spec}-spec#{FILE_EXT}"
-			moduleHelp.cache.delete specFile # for jasmine watch
+			specFile = path.normalize specFile # for windows
+			moduleHelp.cache.delete specFile   # for jasmine watch
 			require specFile


Wrap up project tests

Wrap up project tests

--- test/nodes/project.coffee
+++ test/nodes/project.coffee
@@ -111,3 +111,20 @@
         expect(resp).to.be.an('object')
         done()

+  describe 'changeColor', ->
+    beforeEach ->
+      nock(@config.apiBase)
+        .put("#{@config.apiPath}#{@project.nodePath}/color", color: 'red')
+        .reply(200, require('../fixtures/project/changeColor'))
+
+    it 'does not error', (done) ->
+      @project.changeColor 'red', (err, resp) ->
+        expect(err).to.be(null)
+        done()
+
+    it 'returns a response object', (done) ->
+      @project.changeColor 'red', (err, resp) ->
+        expect(resp).to.be.an('object')
+        expect(resp.error).to.be('success')
+        done()
+


correction bug canvas

correction bug canvas

--- examples/canvas/draw.coffee
+++ examples/canvas/draw.coffee
@@ -131,7 +131,7 @@
 	changeRadiusSelection = (scale) ->
 		bool = false
 		for i in [0..allPoint.length - 1]
-			if allPoint[i].selected == true
+			if allPoint[i] and allPoint[i].selected == true
 				@bool = true
 				break
 		style.point.radiusSelected *= if scale > 1 then 1.1 else 0.9 if @bool


Add a function to invert SVG image elements


Add new gulp task "assets"

Add new gulp task "assets"

--- Gulpfile.coffee
+++ Gulpfile.coffee
@@ -124,6 +124,10 @@
         .pipe $.imagemin(envRequireConfig("imagemin.coffee"))
         .pipe g.dest("#{gulpOption.buildDir}/renderer/images/")

+g.task "assets", ->
+    g.src "#{gulpOption.sourceDir}/assets/**"
+        .pipe g.dest("#{gulpOption.buildDir}/assets/")
+
 #
 # package.json copy Task
 #
@@ -190,6 +194,11 @@
     ], ->
         g.start ["images"]

+    $.watch [
+        "#{rendererSrcRoot}/assets/**"
+    ], ->
+        g.start ["assets"]
+

 g.task "packaging", (cb) ->
     pack = require "electron-packager"
@@ -267,7 +276,7 @@
 #
 # Define default
 #
-g.task "build", ["webpack", "styles", "jade", "images", "fonts", "copy-browser-files", "package-json"]
+g.task "build", ["webpack", "styles", "jade", "images", "fonts", "copy-browser-files", "assets", "package-json"]
 g.task "publish", ["production"]
 g.task "dev", ["build", "watch"]
 g.task "default", ["self-watch", "electron-dev"]


also don't overflow atom

also don't overflow atom

--- lib/TermView.coffee
+++ lib/TermView.coffee
@@ -162,7 +162,6 @@

     @resize cols, rows
     @term.resize cols, rows
-    atom.views.getView(atom.workspace).style.overflow = 'visible'

   getDimensions: ->
     fakeRow = $("

").css visibility: 'hidden'


feat(color): fix color visisted state

feat(color): fix color visisted state

--- internal_packages/message-list/lib/email-frame.cjsx
+++ internal_packages/message-list/lib/email-frame.cjsx
@@ -65,11 +65,11 @@
   }

   a:hover {
-    color: #0f7982;
+    color: #1f7498;
   }

   a:visited {
-    color: #11A1A2;
+    color: #1f7498;
   }
   a img {
     border-bottom: 0;


Refactor test code:

Refactor test code:

- Improve test description of `it`

- Return `Promise` in `it` callback for readability
See Mocha.js Promise support (https://mochajs.org/#asynchronous-code)

--- tests/spec/invalid.coffee
+++ tests/spec/invalid.coffee
@@ -91,22 +91,18 @@
       ).done (-> done()), done

 describe "Print Java error message", ->
-  it "when `validate()` called", (done) ->
+  it "The message should be printed when `validate()` called", ->
     vnu.validate("<html/>", ("ss": "64"))
       .catch (err) ->
         expect(err).to.be.an.instanceof(SyntaxError)
         expect(err.message).to.match(
-          /^Unexpected token I\nInvalid thread stack size: -Xss64\n/
+          /^Unexpected token [\s\S]+Invalid thread stack size: -Xss64/
         )
-        done()
-      .done (-> ;), done

-  it "when `validateFiles()` called", (done) ->
+  it "The message should be printed when `validateFiles()` called", ->
     vnu.validateFiles(["./tests/data/invalid.html"], ("ss": "32"))
       .catch (err) ->
         expect(err).to.be.an.instanceof(SyntaxError)
         expect(err.message).to.match(
-          /^Unexpected token I\nInvalid thread stack size: -Xss32\n/
+          /^Unexpected token [\s\S]+Invalid thread stack size: -Xss32/
         )
-        done()
-      .done (-> ;), done


Lib: prevent worker race conditions; track active.

--- lib/content._coffee
+++ lib/content._coffee
@@ -29,6 +29,8 @@

 # TEMP: Simple implementation for just two data stores: Redis and flat files.
 USE_REDIS = config.DATA_STORE is 'redis'
+
+WORKER_TIMEOUT_MS = 1000 * 60 * 5   # Five mins

 # TODO: We should also support auth'ed Redis, which is an async connection.
 redisClient = redis.createClient() if USE_REDIS
@@ -68,7 +70,9 @@
         else
             throw err

-enqueueForConversion = (content) ->
+enqueueForConversion = (content, _) ->
+    return if content._isActive
+
     # TODO: We should properly use a queue and workers for conversion!
     # For now, converting directly on our web servers (but still async'ly).
     Worker.process content, (err) ->
@@ -76,16 +80,15 @@
         # so the only thing we do here is fail-fast.
         throw err if err

-# Enqueues the given content for conversion if we're processing old content and
-# this content hasn't been processed yet. Returns the content always.
-#
-# TODO: This is open to race conditions of multiple enqueues and thus workers.
-# We should ideally synchronize access, but we should also be robust to worker
-# failures or timeouts in that case, e.g. with a queue visibility timeout.
-#
-enqueueIfNeeded = (content) ->
-    if config.PROCESS_OLD_CONTENT and not content.ready
-        enqueueForConversion content
+    content._markActive _
+
+# TEMP: Enqueues the given content for conversion if we're processing old
+# content and this one hasn't been processed yet -- including not failed yet.
+# Returns the content always.
+enqueueIfNeeded = (_, content) ->
+    if config.PROCESS_OLD_CONTENT and not (content.ready or content.failed)
+        enqueueForConversion content, _
+
     content

@@ -164,8 +167,17 @@
         clone[prop] = val for prop, val of raw
         clone

+    # Private properties
+    get _isActive: ->
+        # Return true if this content is actively being worked on by a worker,
+        # but be robust to worker timeout:
+        age = Date.now() - Date.parse @_data.activeAt
+        @_data.active and age < WORKER_TIMEOUT_MS
+
     ## Instance methods:

+    # NOTE: This is for public consumption, *not* for saving to our db!
+    # See the `_stringifyData` method for saving to our db.
     toJSON: ->
         # The native JSON.stringify doesn't pick up property getters on our
         # *prototype*, which is where they're defined.
@@ -174,11 +186,21 @@
         obj[prop] = val for prop, val of @
         obj

-    # TODO:
-    # - markProgress
-    # - others?
+    # Mark that this content is actively being processed (internally) now.
+    _markActive: (_) ->
+        @_data.active = true
+        @_data.activeAt = new Date().toISOString()
+        # let `finishedAt` remain where it was previously, for debugging
+        @_data.ready = false
+        @_data.failed = false
+        @_data.progress = 0
+        delete @_data.dzi
+        @_save _

     markReady: (dzi, _) ->
+        @_data.active = false
+        # let `activeAt` remain where it was, for debugging
+        @_data.finishedAt = new Date().toISOString()
         @_data.ready = true
         @_data.failed = false
         @_data.progress = 1
@@ -186,11 +208,18 @@
         @_save _

     markFailed: (_) ->
+        @_data.active = false
+        # let `activeAt` remain where it was, for debugging
+        @_data.finishedAt = new Date().toISOString()
         @_data.ready = false
         @_data.failed = true
-        # let `progress` remain where it was
-        delete @_data.dzi
+        # let `progress` remain where it was, for debugging
+        # let `dzi` remain where it was, for debugging
         @_save _
+
+    # TODO:
+    # - markProgress
+    # - others?

     _save: (_) ->
         # TODO: Can we have saves be precise and isolated?
@@ -213,7 +242,7 @@
                 readFile (getFilePathForId id), _

         if json
-            enqueueIfNeeded new Content JSON.parse json
+            enqueueIfNeeded _, new Content JSON.parse json
         else
             null

@@ -227,7 +256,7 @@
             # note that our URL files are symlinks to the ID files, and
             # node's FS.readFile() follows symlinks natively. sweet!
             if json = readFile (getFilePathForURL url), _
-                enqueueIfNeeded new Content JSON.parse json
+                enqueueIfNeeded _, new Content JSON.parse json
             else
                 null

@@ -256,6 +285,6 @@
             FS.symlink urlToIdPath, urlPath, _

         # Either way now:
-        enqueueForConversion content
+        enqueueForConversion content, _

         content


Add index to events

Add index to events

--- lib/assets/javascripts/nested_form_fields.js.coffee
+++ lib/assets/javascripts/nested_form_fields.js.coffee
@@ -5,15 +5,16 @@
   $('body').on 'click', '.add_nested_fields_link', ->
     $link = $(this)
     object_class = $link.data('object-class')
-    $.event.trigger("fields_adding.nested_form_fields",{object_class: object_class});
     association_path = $link.data('association-path')
+    added_index = $(".nested_#{association_path}").length
+    $.event.trigger("fields_adding.nested_form_fields",{object_class: object_class, added_index: added_index, association_path: association_path});
     $template = $("##{association_path}_template")

     template_html = $template.html()

     # insert association indexes
     index_placeholder = "__#{association_path}_index__"
-    template_html = template_html.replace(new RegExp(index_placeholder,"g"), $(".nested_#{association_path}").length)
+    template_html = template_html.replace(new RegExp(index_placeholder,"g"), added_index)

     # replace child template div tags with script tags to avoid form submission of templates
     $parsed_template = $(template_html)
@@ -23,20 +24,21 @@
       $child.replaceWith($("<script id='#{$child.attr('id')}' type='text/html' />").html($child.html()))

     $template.before( $parsed_template )
-    $.event.trigger("fields_added.nested_form_fields",{object_class: object_class});
+    $.event.trigger("fields_added.nested_form_fields",{object_class: object_class, added_index: added_index, association_path: association_path});
     false

   $('body').off("click", '.remove_nested_fields_link')
   $('body').on 'click', '.remove_nested_fields_link', ->
     $link = $(this)
     object_class = $link.data('object-class')
-    $.event.trigger("fields_removing.nested_form_fields",{object_class: object_class});
     delete_association_field_name = $link.data('delete-association-field-name')
+    removed_index = parseInt(delete_association_field_name.match('(\\d+\\]\\[_destroy])')[0][0])
+    $.event.trigger("fields_removing.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index });
     $nested_fields_container = $link.parents(".nested_fields").first()
     $nested_fields_container.before "<input type='hidden' name='#{delete_association_field_name}' value='1' />"
     $nested_fields_container.hide()
     $nested_fields_container.find('input[required]:hidden').removeAttr('required')
-    $.event.trigger("fields_removed.nested_form_fields",{object_class: object_class});
+    $.event.trigger("fields_removed.nested_form_fields",{object_class: object_class, delete_association_field_name: delete_association_field_name, removed_index: removed_index});
     false

 $(document).on "page:change", ->


TeamUtils: expose getPlanData helper

TeamUtils: expose getPlanData helper

--- workers/social/lib/social/models/computeproviders/teamutils.coffee
+++ workers/social/lib/social/models/computeproviders/teamutils.coffee
@@ -4,12 +4,18 @@
 konstraints = require 'konstraints'
 { clone }   = require 'underscore'

+# returns plan data, if plan not found it fallbacks to default
+getPlanData = (plan) ->
+
+  plan = 'default'  if plan not in Object.keys TEAMPLANS
+  return clone TEAMPLANS[plan]
+
+
 # Takes plan name as reference and generates valid konstraint
 # rules based on the TEAMPLANS data ~ GG
 generateConstraints = (plan) ->

-  plan  = 'default'  if plan not in Object.keys TEAMPLANS
-  plan  = clone TEAMPLANS[plan]
+  plan  = getPlanData plan

   # First rule be an object.
   rules = [ { $typeof : 'object' } ]
@@ -89,4 +95,4 @@
   return rules

-module.exports = { generateConstraints }
+module.exports = { generateConstraints, getPlanData }


Update fixture

Update fixture

--- test/src/fixtures/fixtures.coffee
+++ test/src/fixtures/fixtures.coffee
@@ -224,7 +224,9 @@
   "content attachment": do ->
     content = """

ruby-build 20150413 is out, with definitions for 2.2.2, 2.1.6, and 2.0.0-p645 to address recent security issues: https://t.co/YEwV6NtRD8
— Sam Stephenson (@sstephenson) April 13, 2015
"""
     href = "https://twitter.com/sstephenson/status/587715996783218688"
-    attachment = new Trix.Attachment {content, href}
+    contentType = "embed/twitter"
+
+    attachment = new Trix.Attachment {content, contentType, href}
     text = Trix.Text.textForAttachmentWithAttributes(attachment)

     figure = Trix.makeElement
@@ -238,6 +240,7 @@

     data =
       trixAttachment: JSON.stringify(attachment)
+      trixContentType: contentType
       trixId: attachment.id

     figure.dataset[key] = value for key, value of data


fix logger unmount

fix logger unmount

--- SingularityUI/app/controllers/LogViewer.cjsx
+++ SingularityUI/app/controllers/LogViewer.cjsx
@@ -38,9 +38,7 @@
     # create log view
     @view = new LogView @store

-    @setView @view
-
-    @view.render()
+    @setView @view  # does nothing
     app.showView @view

 module.exports = LogViewer


Track User

Track User

--- app/routes/authenticated.coffee
+++ app/routes/authenticated.coffee
@@ -15,6 +15,7 @@
   moment: service()
   session: service()
   realtime: service()
+  mixpanel: service()
   socketIOService: service 'socket-io'

   beforeModel: (transition)->
@@ -32,6 +33,15 @@
         name: user.get "username"
         email: user.get "email"
       window.Intercom 'trackEvent', 'logged-in'
+    try
+      mixpanel = @get "mixpanel"
+      mixpanel.identify user.get "id"
+      mixpanel.peopleSet
+        id: user.get "id"
+        username: user.get "username"
+        email: user.get "email"
+
+

     @get('notify').setDefaultAutoClear ENV.notifications.autoClear


remove console.lot

remove console.lot

--- app/scripts/mbdatepicker.coffee
+++ app/scripts/mbdatepicker.coffee
@@ -74,7 +74,7 @@
   restrict: 'E',
   transclude: true,
   link: (scope, element, attrs) ->
-    console.log scope.calendarHeader
+
 # Vars
     selectors = document.querySelector('#dateSelectors')
     today = moment()


quick fix

quick fix

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -181,7 +181,7 @@
             {"width":"15px";"max-height":aglh*2.6,
             "z-index": 20,"position":"relative",
             "top":offset-12-$(".ace_scrollbar").scrollTop()+"px",
-            "left":"32px","display": "block"})
+            "left":aglw-15+"px","display": "block"})
         @poffset = $(".ace_scrollbar").scrollTop()
         return


fixed parser recognizing ends of unrecognized lines as actuall commands

--- web/scripts/playerCodeEditor.coffee
+++ web/scripts/playerCodeEditor.coffee
@@ -209,7 +209,7 @@
                     break

             if result == null
-                result = /^\n/.exec text
+                result = /^\s+/.exec text
                 if result != null
                     currentLine++

@@ -217,8 +217,13 @@
                 result = /^;/.exec text

             if result == null
+                # We do not recognize this line, ignore it.
+                result = /^.*\n/.exec text
+                # alert "Unrecognized"
+
+            if result == null
+                # None of our regexes returned, eat the first character and continue
                 text = text.substring 1
-                # alert "Unrecognized"
             else
                 text = text.substring result[0].length
         return


Removed old grunt.js file references from Gruntfile

This grunt.js file got replaced by Gruntfile.coffee, which should be
watched, but nog be jshinted (coffeescript)

--- js-library/Gruntfile.coffee
+++ js-library/Gruntfile.coffee
@@ -75,10 +75,10 @@
     qunit:
       all: ['test/*.html']
     watch:
-      files: ['src/js/**/*', 'src/css/**/*', 'test/**/*', 'grunt.js', 'libs/**/*.js', 'plugins/**/*.js', 'wrap/*.js']
       tasks: ['coffee', 'jshint', 'qunit', 'concat', 'less', 'copy']
+      files: ['src/js/**/*', 'src/css/**/*', 'test/**/*', 'Gruntfile.coffee', 'libs/**/*.js', 'plugins/**/*.js', 'wrap/*.js']
     jshint:
-      all: ['grunt.js', 'src/js/**/*.js', 'test/**/*.js']
+      all: ['src/js/**/*.js', 'test/**/*.js']
       options:
         # Whether jQuery globals should be predefined.
         jquery: true


Fix typo in Perl 6 grammar name

Fix typo in Perl 6 grammar name

--- lib/grammars.coffee
+++ lib/grammars.coffee
@@ -268,7 +268,7 @@
       command: "perl"
       args: (context) -> [context.filepath]

-  Perl6:
+  "Perl 6":
     "Selection Based":
       command: "perl6"
       args: (context)  -> ['-e', context.getCode()]


On save, also send zoomLevel

On save, also send zoomLevel

--- app/assets/javascripts/oxalis/model/skeletontracing/skeletontracing_statelogger.coffee
+++ app/assets/javascripts/oxalis/model/skeletontracing/skeletontracing_statelogger.coffee
@@ -151,6 +151,7 @@
         comments : @skeletonTracing.getPlainComments()
         activeNode : @skeletonTracing.getActiveNodeId()
         editPosition : @flycam.getPosition()
+        zoomLevel : @flycam.getZoomStep()
       }
       false
     )


changed moment path in libs to work locally

--- services/web/public/coffee/libs.coffee
+++ services/web/public/coffee/libs.coffee
@@ -1,5 +1,5 @@
 define [
-	"moment"
+	"libs/moment-2.7.0"
 	"libs/angular-autocomplete/angular-autocomplete"
 	"libs/ui-bootstrap"
 	"libs/ng-context-menu-0.1.4"


Critical bug fix in ability to edit messages (by nonsuperusers)

--- lib/messages.coffee
+++ lib/messages.coffee
@@ -525,7 +525,7 @@
   message = findMessage message
   return false unless message?
   user? and (
-    canSuper(message, client, user) or
+    canSuper(message.group, client, user) or
     amCoauthor(message, user) or
     (messageRoleCheck(message.group, message, 'edit', user) and
      ((message.published and not message.deleted and not message.private) or


Shorten debounce interval when building "new" token

--- app/assets/javascripts/exo/views/autocomplete_token_field.js.coffee
+++ app/assets/javascripts/exo/views/autocomplete_token_field.js.coffee
@@ -114,7 +114,7 @@
       @resultsPopover.setCollection(resultsCollection)
       @resultsPopover.selectAtIndex(0)
       @resultsPopover.show()
-    , 75)
+    , 25)

     _hideResultsPopover: ->
       @resultsPopover.collection?.reset()


:art: #143 make it explicit than cleaness

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -283,8 +283,10 @@
     @onDidFinishOperation =>
       @collected = false

-    @collect()
-    super
+    unless @collected
+      @collect()
+    else
+      super

   collect: ->
     return if @collected


tests for invalid signatures

tests for invalid signatures

--- zechat/static/test_crypto.coffee
+++ zechat/static/test_crypto.coffee
@@ -54,6 +54,17 @@
       expect(ok).toEqual(true)
       done()

+  it 'should return false for a different signature', (done) ->
+    new zc.Crypto(PRIVATE_KEY).sign 'foo', (signature) ->
+      new zc.Crypto(PUBLIC_KEY).verify 'bar', signature, (ok) ->
+        expect(ok).toEqual(false)
+        done()
+
+  it 'should return false for a malformed signature', (done) ->
+    new zc.Crypto(PUBLIC_KEY).verify 'foo', 'garbage signature', (ok) ->
+      expect(ok).toEqual(false)
+      done()
+
   it 'should encrypt and decrypt', (done) ->
     new zc.Crypto(PUBLIC_KEY).encrypt 'foo', (encrypted) ->
       new zc.Crypto(PRIVATE_KEY).decrypt encrypted, (out) ->


Add callback to request invocation

Add callback to request invocation

This prevents request errors such as "Uncaught Error: getaddrinfo ENOTFOUND"
from going to the console when errors are reported when you aren't online.

--- lib/reporter.coffee
+++ lib/reporter.coffee
@@ -6,12 +6,15 @@
       @request = request

     send: (eventType, data) ->
-      params = timestamp: ((new Date().getTime()) / 1000)
-      params.dimensions = data
+      params =
+        timestamp: new Date().getTime() / 1000
+        dimensions: data

-      @request
+      requestOptions =
         method: 'POST'
         url: "https://collector.githubapp.com/atom/#{eventType}"
         headers:
           'Content-Type' : 'application/vnd.github-octolytics+json'
         body: JSON.stringify(params)
+
+      @request requestOptions, -> # Callback prevents errors from going to the console


strip trailing whitespace

strip trailing whitespace

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -80,7 +80,7 @@
   setupEventHandlers()
   statusBarTileView?.setTitle('WakaTime ready')
   statusBarTileView?.setStatus()
-
+
 settingChangedHandler = (settings) ->
   if settings.showStatusBarIcon
     statusBarTileView?.show()


Revert "don't check SSL cert when downloading wakatime cli, for people behind corporate proxies."

This reverts commit f910df407b5b26de70bd699c5e21209fc83a9395.

--- lib/wakatime.coffee
+++ lib/wakatime.coffee
@@ -155,7 +155,7 @@

 installCLI = (callback) ->
     console.log 'Downloading wakatime cli...'
-    url = { url: 'https://github.com/wakatime/wakatime/archive/master.zip', strictSSL: false }
+    url = 'https://github.com/wakatime/wakatime/archive/master.zip'
     zipFile = __dirname + path.sep + 'wakatime-master.zip'
     downloadFile(url, zipFile, ->
         console.log 'Extracting wakatime-master.zip file...'


Refactor code for stopping broadcast

Refactor code for stopping broadcast

--- lib/broadcast-server.coffee
+++ lib/broadcast-server.coffee
@@ -11,6 +11,7 @@
   editor: null
   server: null
   sockets: []
+  io: null
   ioSocket: null

   start: ->
@@ -58,9 +59,9 @@
   startSocketIOServer: ->
     return unless @server?

-    io = socketIo @server
+    @io = socketIo @server

-    io.on 'connection', (socket) =>
+    @io.on 'connection', (socket) =>
       @ioSocket = socket
       @updateContent()

@@ -69,6 +70,10 @@
       console.error 'Broadcast has not started'
       return

+    @stopSocketIOServer()
+    @stopServer()
+
+  stopServer: ->
     if @sockets.length > 0
       for socket in @sockets
         socket.destroy()
@@ -77,6 +82,11 @@
     @server = null

     console.log 'Broadcast stopped.'
+
+  stopSocketIOServer: ->
+    return unless @io?
+
+    @io = null

   updateContent: ->
     return unless @ioSocket?


Pass along absolute path.

Pass along absolute path.

--- src/module.coffee
+++ src/module.coffee
@@ -126,8 +126,9 @@
           throw new Error "No suitable compiler found for #{@absolutePath}"

         opts =
-          source: source
+          source:        source
           filename:      @normalizedPath
+          absolutePath:  @absolutePath
           sourceMap:     @enableSourceMap
           sourceMapRoot: @sourceMapRoot


Use cleanup plugin for pastes.

Use cleanup plugin for pastes.

--- scripts/configs/aloha.coffee
+++ scripts/configs/aloha.coffee
@@ -48,6 +48,8 @@
         'common/format'
         'common/block'
         'common/list'
+        'common/contenthandler'
+        'common/paste'
         'oer/table'
         'extra/draganddropfiles'
         'oer/overlay'
@@ -64,6 +66,7 @@
         'oer/definition'
         'oer/multipart'
         'oer/copy'
+        'oer/cleanup'
       ]

       # This whole thing is what's needed to:
@@ -130,5 +133,10 @@
     smartContentChange:
       idle: 2000

+    contentHandler:
+      insertHtml: [ 'cleanup' ]
+      initEditable: []
+      getContents: []
+
   # In case some module wants the config object return it.
   return @Aloha


Adjusts transitioning on vertical page

Adjusts transitioning on vertical page

--- apps/articles/client/index.coffee
+++ apps/articles/client/index.coffee
@@ -20,11 +20,16 @@
     unless ctaBarView.previouslyDismissed()
       $('body').append ctaBarView.render().$el
       $('#articles-footer-list').waypoint (direction) ->
-        ctaBarView.transitionIn() if direction is 'down'
+        ctaBarView.transitionIn()
       , { offset: '50%' }
       $('.js-articles-feed-articles').waypoint (direction) ->
         ctaBarView.transitionIn() if direction is 'down'
       , { offset: '50%' }
+      $('.js-articles-feed-articles').waypoint (direction) ->
+        ctaBarView.transitionOut() if direction is 'down'
+        ctaBarView.transitionIn() if direction is 'up'
+        console.log 'hmmm'
+      , { offset: 'bottom-in-view' }

     # Subscribe click
     $('.js-articles-insights-subscribe').click (e)->


fix test

fix test

--- apps/artist/test/templates.coffee
+++ apps/artist/test/templates.coffee
@@ -60,7 +60,7 @@
     it 'displays a link to the auction results', ->
       @artist.get('published_artworks_count').should.be.above 0
       @artist.get('auction_lots_count').should.be.above 0
-      @template.should.include 'artist-auction-results-link'
+      @template.should.include "/artist/#{@artist.id}/auction-results"

   describe 'artist with no auction results', ->
     beforeEach ->


Hiding deletion warning upon selecting "yes" or "no" to accepting deletion.

--- c3/app/assets/javascripts/s4_careYouProvide/g10_breaksInCare.coffee
+++ c3/app/assets/javascripts/s4_careYouProvide/g10_breaksInCare.coffee
@@ -8,11 +8,16 @@
         window.location.href = "/careYouProvide/breaks/" + tr.attr("id")

     $("tbody").on "click", "input[value='Delete']", ->
+        $("tr input[type='button']").attr("disabled", "true")
+        $(".form-steps").hide()
+
         tr = $(this).closest("tr")
         tbody = $(this).closest("tbody")

         $(".breaks-prompt").slideDown ->
             $("input[value='No']").on "click", ->
+                $("tr input[type='button']").removeAttr("disabled")
+                $(".form-steps").slideDown()
                 $(".breaks-prompt").slideUp()

             $("input[value='Yes']").on "click", ->
@@ -21,6 +26,9 @@
                     url: "/careYouProvide/breaksInCare/" + tr.attr("id")

                     success: ->
+                        $("tr input[type='button']").removeAttr("disabled")
+                        $(".form-steps").slideDown()
+                        $(".breaks-prompt").slideUp()
                         element = undefined

                         if tbody.children().length is 1
@@ -33,4 +41,8 @@
                             margin: 0
                         ).wrapInner("<div />").children().slideUp -> tr.remove()

-                    error: -> alert "Failed to delete break - Contact Support"+                    error: ->
+                        $("tr input[type='button']").removeAttr("disabled")
+                        $(".form-steps").slideDown()
+                        $(".breaks-prompt").slideUp()
+                        alert "Failed to delete break - Contact Support"


activity stream: when there are not enough activities, load more to fill up the screen

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -242,8 +242,14 @@
     # we don't need this anymore
     # we need a different approach tho, tBDL - SY

-    # unless @listController.scrollView.hasScrollBars()
-    #   @continueLoadingTeasers()
+    # due to complex nesting of subviews, i used jQuery here. - AK
+    contentPanel     = @getSingleton('contentPanel')
+    scrollViewHeight = @listController.scrollView.$()[0].clientHeight
+    headerHeight     = contentPanel.$('.feeder-header')[0].offsetHeight
+    panelHeight      = contentPanel.$('.activity-content')[0].clientHeight
+
+    if scrollViewHeight + headerHeight < panelHeight
+      @continueLoadingTeasers()

   createContentDisplay:(activity, callback=->)->
     controller = switch activity.bongo_.constructorName


Client: Activity: Added ChannelFlagsStore to keep flags for channels

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -11,6 +11,7 @@
 # the store is falsy. Another ones with `withEmptyList` will return an empty
 # immutable list if data from the store is falsy.

+ChannelFlagsStore              = [['ChannelFlagsStore'], withEmptyMap]
 ChannelsStore                  = [['ChannelsStore'], withEmptyMap]
 MessagesStore                  = [['MessagesStore'], withEmptyMap]
 ChannelThreadsStore            = [['ChannelThreadsStore'], withEmptyMap]
@@ -67,8 +68,11 @@
 channelThreads = [
   ChannelThreadsStore
   MessagesStore
-  (threads, messages) ->
+  ChannelFlagsStore
+  (threads, messages, channelFlags) ->
     threads.map (thread) ->
+      channelId = thread.get 'channelId'
+      thread.set 'flags', channelFlags.get channelId
       thread.update 'messages', (msgs) -> msgs.map (messageId) ->
         message = messages.get messageId
         if message.has('__editedBody')


add missing reference to uservoice since its in vendors

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -17,6 +17,7 @@
 #= require pickadate/picker.date
 #= require pickadate/picker.time
 #= require matchMedia
+#= require uservoice
 #= require_tree .

 $ = jQuery


Fix award tooltip after voting.

Fix award tooltip after voting.

--- app/assets/javascripts/awards_handler.coffee
+++ app/assets/javascripts/awards_handler.coffee
@@ -156,26 +156,46 @@

     $emojiBtn.removeClass("active")

+
+  getAwardTooltip: ($awardBlock) ->
+
+    return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title')
+
+
   removeMeFromUserList: ($emojiBtn, emoji) ->
-    award_block = $emojiBtn
-    authors = award_block
-      .attr("data-original-title")
-      .split(", ")
-    authors.splice(authors.indexOf("me"),1)
-    award_block
-      .closest(".js-emoji-btn")
-      .attr("data-original-title", authors.join(", "))
-    @resetTooltip(award_block)
+
+    awardBlock    = $emojiBtn
+    originalTitle = @getAwardTooltip awardBlock
+
+    authors = originalTitle.split ', '
+    authors.splice authors.indexOf('me'), 1
+
+    newAuthors = authors.join ', '
+
+    awardBlock
+      .closest '.js-emoji-btn'
+      .removeData 'original-title'
+      .removeData 'title'
+      .attr 'data-original-title', newAuthors
+      .attr 'data-title', newAuthors
+
+    @resetTooltip(awardBlock)
+

   addMeToUserList: (emoji) ->
-    award_block = @findEmojiIcon(emoji).parent()
-    origTitle = award_block.attr("data-original-title").trim()
-    users = []
+
+    awardBlock = @findEmojiIcon(emoji).parent()
+    origTitle  = @getAwardTooltip awardBlock
+    users      = []
+
     if origTitle
-      users = origTitle.split(', ')
-    users.push("me")
-    award_block.attr("title", users.join(", "))
-    @resetTooltip(award_block)
+      users = origTitle.trim().split(', ')
+
+    users.push('me')
+    awardBlock.attr('title', users.join(", "))
+
+    @resetTooltip(awardBlock)
+

   resetTooltip: (award) ->
     award.tooltip("destroy")


save current user to window

save current user to window

--- app/assets/javascripts/components/login.cjsx
+++ app/assets/javascripts/components/login.cjsx
@@ -23,7 +23,7 @@
       if result?.data
         @setState
           user: result.data
-          loading: false
+          loading: false, => window.current_user = @state.user
       else
         @setState
           loading: false
@@ -82,7 +82,7 @@
     links = @state.providers.map (link) ->
       icon_id = if link.id == 'zooniverse' then 'dot-circle-o' else link.id

-
+

       { label || "Log In:" }


adding signout

adding signout

--- app/assets/javascripts/components/login.cjsx
+++ app/assets/javascripts/components/login.cjsx
@@ -12,6 +12,19 @@
     user: null
     loading: false
     error: null
+
+  signOut:(e)->
+    e.preventDefault()
+
+    request = $.ajax({url: '/users/sign_out', method: 'delete'})
+
+    request.done =>
+      @setState
+        user:null
+
+    request.error =>
+      @setState
+        error : "Could not log out"

   fetchUser:->
     @setState
@@ -31,7 +44,7 @@
         error: "Having trouble logging you in"

   render:->
-

+

       {@renderError() if @state.error}

       {@renderLoggedIn() if @state.user}
@@ -42,7 +55,7 @@

   renderLoggedIn:->
-
Hello {@state.user.name} Logout

+
Hello {@state.user.name} Logout

   renderLoggedOut:->


Fixed issue with filtering & pressing enter key

--- app/assets/javascripts/gl_dropdown.js.coffee
+++ app/assets/javascripts/gl_dropdown.js.coffee
@@ -311,6 +311,7 @@
         return true

   opened: =>
+    currentIndex = -1
     @addArrowKeyEvent()

     if @options.setIndeterminateIds
@@ -568,9 +569,6 @@
         PREV_INDEX = currentIndex
         $listItems = $(selector, @dropdown)

-        # if @options.filterable
-        #   $input.blur()
-
         if currentKeyCode is 40
           # Move down
           currentIndex += 1 if currentIndex < ($listItems.length - 1)
@@ -583,7 +581,7 @@
         return false

       if currentKeyCode is 13 and currentIndex isnt -1
-        @selectRowAtIndex e, currentIndex
+        @selectRowAtIndex e, $('.is-focused', @dropdown).closest('li').index() - 1

   removeArrayKeyEvent: ->
     $('body').off 'keydown'


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -16,9 +16,9 @@
     $(@node).children(".more-info").text($(@node).attr("data-moreinfo"))
     $(@node).children(".updated-at").text(@get('updatedAtMessage'))

-    width = 260 #width
-    height = 260 #height
-    radius = 130 #radius
+    width = 350 #width
+    height = 350 #height
+    radius = 175 #radius

     color = d3.scale.ordinal()
       #.domain([1,10])


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -62,11 +62,14 @@
     else
       arcs.append("svg:text")
         .attr('fill', "#fff")
-        .attr("text-anchor", "middle").attr("alignment-baseline", "central").text(sum).attr('font-size', radiusi + 'px')
+        .attr("text-anchor", "middle")
+        .attr("alignment-baseline", "central")
+        .attr('font-size', radiusi + 'px')
+        .text(sum)
       arcs.append("svg:text").attr("transform", (d, i) ->
         procent_val = Math.round(data[i].value/sum * 100)
         #d.innerRadius = (radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
-        d.innerRadius = radiusi #(radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
+        d.innerRadius = radiuso#i #(radiuso * (100-procent_val)/100) - radiuso/2 #45  #45=max text size/2
         d.outerRadius = radiuso
         "translate(" + arc.centroid(d) + ")")
         .attr('fill', "#fff")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -309,7 +309,7 @@
             # check whether the default position
             #   overlaps any other labels
             conflicts = []
-            labelLayout.visit( (node, x1, y1, x2, y2) ->
+            #labelLayout.visit( (node, x1, y1, x2, y2) ->
                 #recurse down the tree, adding any overlapping
                 #node is the node in the quadtree,
                 #node.point is the value that we added to the tree
@@ -320,18 +320,18 @@
                 #2. right edge of node is to the left of left edge of label
                 #3. top (minY) edge of node is greater than the bottom of label
                 #4. bottom (maxY) edge of node is less than the top of label
-                if  (x1 > d.r + maxLabelWidth/2) or (x2 < d.l - maxLabelWidth/2) or (y1 > d.b + maxLabelHeight/2) or (y2 < d.t - maxLabelHeight/2 )
-                    return true #don't bother visiting children or checking this node
+            #    if  (x1 > d.r + maxLabelWidth/2) or (x2 < d.l - maxLabelWidth/2) or (y1 > d.b + maxLabelHeight/2) or (y2 < d.t - maxLabelHeight/2 )
+            #        return true #don't bother visiting children or checking this node

-                p = node.point
-                v = false
-                h = false
-                if p #p is defined, i.e., there is a value stored in this node
-                    h =  ( ((p.l > d.l) and (p.l <= d.r)) or ((p.r > d.l) and (p.r <= d.r)) or ((p.l < d.l) and (p.r >=d.r) ) ) #horizontal conflict
-                    v =  ( ((p.t > d.t) and (p.t <= d.b)) or ((p.b > d.t) and (p.b <= d.b)) or ((p.t < d.t) and (p.b >=d.b) ) ) #vertical conflict
-                    if h and v
-                        conflicts.push(p) #add to conflict list
-            )
+            #    p = node.point
+            #    v = false
+            #    h = false
+            #    if p #p is defined, i.e., there is a value stored in this node
+            #        h =  ( ((p.l > d.l) and (p.l <= d.r)) or ((p.r > d.l) and (p.r <= d.r)) or ((p.l < d.l) and (p.r >=d.r) ) ) #horizontal conflict
+            #        v =  ( ((p.t > d.t) and (p.t <= d.b)) or ((p.b > d.t) and (p.b <= d.b)) or ((p.t < d.t) and (p.b >=d.b) ) ) #vertical conflict
+            #        if h and v
+            #            conflicts.push(p) #add to conflict list
+            #)

             if conflicts.length
                 console.log(d, " conflicts with ", conflicts);


Agent pie: edit pointer transition

Agent pie: edit pointer transition


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -200,7 +200,7 @@
         #)
         path.transition().duration(2000).attrTween('d', (d,i) ->
             console.log('d',d)
-            window.oldPieData[myInstanceDataId][i] = d[i]
+            window.oldPieData[myInstanceDataId][i] = d
             opd = window.oldPieData[myInstanceDataId]
             # Interpolate the arcs in data space
             s0 = undefined


newpie experiments

newpie experiments

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -12,6 +12,8 @@
             dataSet = @get("data")
         if !dataSet
             return
+
+        container = $(@node)

         # Remove any previous svg
         container.children("svg").remove();


vm: add error message to state enum

vm: add error message to state enum

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -140,7 +140,7 @@
       meta              : Object
       state             :
         type            : String
-        enum            : ['UNKNOWN', ['RUNNING','STOPPED','FAILED']]
+        enum            : ['unknown state', ['RUNNING','STOPPED','FAILED']]
         default         : 'STOPPED'

   @updateInitScript = secure (client, hostnameAlias, script, callback) ->


Fix nits with the spec.

Fix nits with the spec.

Summary:

Test Plan:

Reviewers:

CC:

Task ID: #

Blame Rev:

--- spec/text-editor-presenter-spec.coffee
+++ spec/text-editor-presenter-spec.coffee
@@ -2300,14 +2300,16 @@

             expect(decorationState[decoration3.id]).toBeUndefined()

-          it "updates when another gutter with higher priority is hidden", ->
-            editor.addGutter({name: 'test-gutter-1', priority: -150, visible: false})
+          it "updates all the gutters, even when a gutter with higher priority is hidden", ->
+            hiddenGutter = {name: 'test-gutter-1', priority: -150, visible: false}
+            editor.addGutter(hiddenGutter)

             # This update will scroll decoration1 out of view, and decoration3 into view.
             expectStateUpdate presenter, -> presenter.setScrollTop(scrollTop + lineHeight * 5)

             decorationState = getContentForGutterWithName(presenter, 'test-gutter')
             expect(decorationState[decoration1.id]).toBeUndefined()
+            expect(decorationState[decoration3.id].top).toBeDefined()

           it "updates when ::scrollTop changes", ->
             # This update will scroll decoration1 out of view, and decoration3 into view.


Add index to Users name

Add index to Users name

--- packages/rocketchat-lib/server/models/Users.coffee
+++ packages/rocketchat-lib/server/models/Users.coffee
@@ -3,6 +3,7 @@
 		@model = Meteor.users

 		@tryEnsureIndex { 'roles': 1 }, { sparse: 1 }
+		@tryEnsureIndex { 'name': 1 }

 	# FIND ONE


Creates ability to specify the type of sub expression in js exp builder

--- javascripts/lib/math/build_expression_from_javascript_object.coffee
+++ javascripts/lib/math/build_expression_from_javascript_object.coffee
@@ -21,8 +21,7 @@
       process: (object_to_convert)->
         return @blank_builder.build() unless object_to_convert
         if object_to_convert instanceof Array
-          exp = @expression_builder.build()
-          @processExpressionParts(exp, object_to_convert)
+          @convertSubExpression(object_to_convert)
         else
           switch typeof(object_to_convert)
             when "number" then @number_builder.build(value: object_to_convert)
@@ -50,27 +49,41 @@
           @number_builder.build(value: parsed[1])

       # privates
-      processExpressionParts: (exp, parts)->
-        throw "Expression parts must be an instance of array" unless parts instanceof Array
+      convertSubExpression: (parts) ->
+        exp = @expression_builder.build()
         for x in parts
           exp = exp.append(@process(x))
         exp

       convertObject: (object)->
         if (it = object['^'])
-          @exponentiation_builder.build(base: @process(it[0]), power: @process(it[1]))
-        else if (it = object['open_expression'])
-          processed_exp = @process(it)
-          if it instanceof Array
-            val = processed_exp.open()
-          else
-            val = @expression_builder.build(expression: [processed_exp], is_open: true)
-          val
+          @convertExponentiation(it)
+        else if ((it = object['open_expression']) != undefined)
+          subexp = @convertImplicitSubexp(it)
+          subexp.open()
         else
           throw "Build Exp not recognized"

+      convertExponentiation: (data)->
+        base = @convertImplicitSubexp(data[0])
+        power = @convertImplicitSubexp(data[1])
+
+        @exponentiation_builder.build(
+          base: base
+          power: power
+        )
+
+      convertImplicitSubexp: (subexp_raw)->
+        if typeof subexp_raw == "number"
+          @expression_builder.build(expression: [@process(subexp_raw)])
+        else if subexp_raw == null
+          @expression_builder.build(expression: [])
+        else
+          @process(subexp_raw)
+
+
+
     class_mixer BuildExpressionFromJavascriptObject
-

     BuildExpressionFromJavascriptObject.buildExpression = ->
       builder = BuildExpressionFromJavascriptObject.build()


Remove more unnecessary allocations

Remove more unnecessary allocations

--- lib/bracket-matcher-view.coffee
+++ lib/bracket-matcher-view.coffee
@@ -8,6 +8,8 @@
 ONE_CHAR_FORWARD_TRAVERSAL = Object.freeze(Point(0, 1))
 ONE_CHAR_BACKWARD_TRAVERSAL = Object.freeze(Point(0, -1))
 TWO_CHARS_BACKWARD_TRAVERSAL = Object.freeze(Point(0, -2))
+MAX_ROWS_TO_SCAN_FORWARD_TRAVERSAL = Object.freeze(Point(MAX_ROWS_TO_SCAN, 0))
+MAX_ROWS_TO_SCAN_BACKWARD_TRAVERSAL = Object.freeze(Point(-MAX_ROWS_TO_SCAN, 0))

 module.exports =
 class BracketMatcherView
@@ -123,7 +125,10 @@
   findMatchingEndPair: (startPairPosition, startPair, endPair) ->
     return if startPair is endPair

-    scanRange = new Range(startPairPosition.traverse([0, 1]), startPairPosition.traverse([MAX_ROWS_TO_SCAN, 0]))
+    scanRange = new Range(
+      startPairPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL),
+      startPairPosition.traverse(MAX_ROWS_TO_SCAN_FORWARD_TRAVERSAL)
+    )
     endPairPosition = null
     unpairedCount = 0
     @editor.scanInBufferRange @matchManager.pairRegexes[startPair], scanRange, (result) =>
@@ -142,7 +147,10 @@
   findMatchingStartPair: (endPairPosition, startPair, endPair) ->
     return if startPair is endPair

-    scanRange = new Range(endPairPosition.traverse([-MAX_ROWS_TO_SCAN, 0]), endPairPosition)
+    scanRange = new Range(
+      endPairPosition.traverse(MAX_ROWS_TO_SCAN_BACKWARD_TRAVERSAL),
+      endPairPosition
+    )
     startPairPosition = null
     unpairedCount = 0
     @editor.backwardsScanInBufferRange @matchManager.pairRegexes[startPair], scanRange, (result) =>
@@ -158,7 +166,7 @@
     startPairPosition

   findAnyStartPair: (cursorPosition) ->
-    scanRange = new Range([0, 0], cursorPosition)
+    scanRange = new Range(Point.ZERO, cursorPosition)
     startPair = _.escapeRegExp(_.keys(@matchManager.pairedCharacters).join(''))
     endPair = _.escapeRegExp(_.keys(@matchManager.pairedCharactersInverse).join(''))
     combinedRegExp = new RegExp("[#{startPair}#{endPair}]", 'g')
@@ -228,11 +236,11 @@
       endPosition = @endMarker.getStartBufferPosition()

       if position.isEqual(startPosition)
-        @editor.setCursorBufferPosition(endPosition.traverse([0, 1]))
+        @editor.setCursorBufferPosition(endPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL))
       else if previousPosition.isEqual(startPosition)
         @editor.setCursorBufferPosition(endPosition)
       else if position.isEqual(endPosition)
-        @editor.setCursorBufferPosition(startPosition.traverse([0, 1]))
+        @editor.setCursorBufferPosition(startPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL))
       else if previousPosition.isEqual(endPosition)
         @editor.setCursorBufferPosition(startPosition)

@@ -273,15 +281,15 @@
         endPosition = endRange.start.traverse(TWO_CHARS_BACKWARD_TRAVERSAL) # Don't select </

     if startPosition? and endPosition?
-      rangeToSelect = new Range(startPosition.traverse([0, 1]), endPosition)
+      rangeToSelect = new Range(startPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL), endPosition)
       @editor.setSelectedBufferRange(rangeToSelect)

   # Insert at the current cursor position a closing tag if there exists an
   # open tag that is not closed afterwards.
   closeTag: ->
     cursorPosition = @editor.getCursorBufferPosition()
-    preFragment = @editor.getTextInBufferRange([[0, 0], cursorPosition])
-    postFragment = @editor.getTextInBufferRange([cursorPosition, [Infinity, Infinity]])
+    preFragment = @editor.getTextInBufferRange([Point.ZERO, cursorPosition])
+    postFragment = @editor.getTextInBufferRange([cursorPosition, Point.INFINITY])

     if tag = @tagFinder.closingTagForFragments(preFragment, postFragment)
       @editor.insertText("</#{tag}>")


Remove load-more button support from main view class

--- assets/javascripts/view.js.coffee
+++ assets/javascripts/view.js.coffee
@@ -60,21 +60,6 @@
             @trigger "partials:#{name}:load"

     @on 'ready', @bindViews
-    @on 'ready', @bindEvents
-
-  loadMore: (key) =>
-    @get(key)?.nextPage()
-
-  bindEvents: =>
-    return unless @container
-    _.each $('.btn.load-more', @container.el), (el) =>
-      viewKey = $(el).attr('data-key')
-
-      $(el).hide() if @get(viewKey)?.onLastPage
-      @get(viewKey)?.on 'fetch:start',   => $(el).hide()
-      @get(viewKey)?.on 'fetch:success', => $(el).show() unless @get(viewKey)?.onLastPage
-
-      $(el).off().on 'click', (=> @loadMore viewKey)

   bindViews: =>
     return unless @container


dat members changed

dat members changed

--- scripts/name-expander.coffee
+++ scripts/name-expander.coffee
@@ -22,12 +22,10 @@
 build_members = ["mvuong", "mstewart", "jromigh", "zeffron", "ilinkletter", "eyap"]

 dat_members = [
-  ivey,
-  josiah,
-  steve,
   kyle,
   fei,
-  CarlQuinn
+  CarlQuinn,
+  afeng
 ]

 chef_repo_mergers = [


Update parallel test suite sets

Update parallel test suite sets

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -21,67 +21,63 @@

 module.exports = [

-  [
-    { name: 'dashboard virtualmachines' }
-  ]
-
-  [
-    { name: 'dashboard stacks' }
-    { name: 'dashboard myteam' }
-    { name: 'dashboard credentials' }
-    { name: 'dashboard utilities' }
-    { name: 'dashboard onboarding' }
-    { name: 'dashboard sidebar' }
-    # { name: 'dashboard teambilling' }
-  ]
+  # [
+  #   { name: 'dashboard myaccount' }
+  #   { name: 'dashboard stacks' }
+  #   { name: 'dashboard virtualmachines' }
+  #   { name: 'dashboard myteam' }
+  #   { name: 'dashboard credentials' }
+  #   { name: 'dashboard utilities' }
+  #   { name: 'dashboard onboarding' }
+  #   { name: 'dashboard sidebar' }
+  #   { name: 'dashboard teambilling' }
+  # ]

   [
     { name: 'register' }
-    { name: 'unittests' }
-    { name: 'dashboard myaccount' }
     { name: 'teams teams' }
+    # { name: 'unittests' }
   ]

-  [
-    { name: 'ide file' }
-    { name: 'ide folder' }
-  ]
+  # [
+  #   { name: 'ide file' }
+  #   { name: 'ide folder' }
+  # ]

-  [
-    # { name: 'ide workspace' }
-    { name: 'ide layout' }
-  ]
+  # [
+  #   # { name: 'ide workspace' }
+  #   { name: 'ide layout' }
+  # ]

-  [
-    { name: 'ide search' }
-    { name: 'ide general' }
-  ]
+  # [
+  #   { name: 'ide search' }
+  #   { name: 'ide general' }
+  # ]

-  [
-    { name: 'ide terminal' }
-  ]
+  # [
+  #   { name: 'ide terminal' }
+  # ]

-  [
-    { name: 'collaboration collaborationsingle'}
-    { name: 'collaboration collaborationpermission', NIGHTWATCH_OPTIONS: '--env host,participant' }
-  ]
+  # [
+  #   { name: 'collaboration collaborationsingle'}
+  #   { name: 'collaboration collaborationpermission', NIGHTWATCH_OPTIONS: '--env host,participant' }
+  # ]

-  [
-    { name: 'collaboration start', NIGHTWATCH_OPTIONS: '--env host,participant' }
-  ]
+  # [
+  #   { name: 'collaboration start', NIGHTWATCH_OPTIONS: '--env host,participant' }
+  # ]

-  [
-    { name: 'collaboration collaborationsession', NIGHTWATCH_OPTIONS: '--env host,participant' }
-  ]
+  # [
+  #   { name: 'collaboration collaborationsession', NIGHTWATCH_OPTIONS: '--env host,participant' }
+  # ]

-  [
-    { name: 'collaboration collaborationfile', NIGHTWATCH_OPTIONS: '--env host,participant' }
-  ]
+  # [
+  #   { name: 'collaboration collaborationfile', NIGHTWATCH_OPTIONS: '--env host,participant' }
+  # ]

-  [
-    { name: 'vmsharing vmsharing', NIGHTWATCH_OPTIONS: '--env host,participant' }
-  ]
-
+  # [
+  #   { name: 'vmsharing vmsharing', NIGHTWATCH_OPTIONS: '--env host,participant' }
+  # ]

   # [
   #   { name: 'environments vm' }


doCheckAndLint

doCheckAndLint

Check and lint sequentially

--- lib/ghc-modi-process.coffee
+++ lib/ghc-modi-process.coffee
@@ -243,5 +243,10 @@
     return Promise.resolve [] if extname(buffer.getUri()) is '.lhs'
     @doCheckOrLintBuffer "lint", buffer, fast

+  doCheckAndLint: (buffer, fast) =>
+    @doCheckBuffer(buffer, fast).then (resCheck) =>
+      @doLintBuffer(buffer, fast).then (resLint) ->
+        return resCheck.concat resLint
+
   getRootDir: (buffer) ->
     @backend?.getRootDir?(buffer) ? Util.getRootDir(buffer)