[i18n] Dutch translation added

[i18n] Dutch translation added

--- src/i18n/nl.cson
+++ src/i18n/nl.cson
@@ -1,27 +1,27 @@
 {
     # manifest information:
     extName:
-        message: 'IngressIdentity'
+        message: 'IngressIdentiteit'
         description: 'The extension name'
     extDevName:
-        message: 'IngressIdentity - dev'
+        message: 'IngressIdentiteit - dev'
         description: 'The extension name in development builds'
     extDescription:
-        message: 'Adds Ingress identity to G+ profiles'
+        message: 'Voegt Ingress identiteit toe op G+ profielen'
         description: 'The extension description'
     extBrowserAction:
-        message: 'IngressIdentity Options'
+        message: 'IngressIdentiteit Opties'
         description: 'The tooltip for the broser action button that opens the options page'

     # Content script stuff
     content_profileTitle:
-        message: 'Ingress Agent Profile'
+        message: 'Ingress Agent Profiel'
         description: 'The title for the panel on user profiles\' about pages'
     content_anomalies:
-        message: 'Anomalies'
+        message: 'Anomalieën'
         description: 'The plural of anomaly'
     content_agentName:
-        message: 'Agent Name'
+        message: 'Agent Naam'
         description: 'Title to denote the agent name'
     content_level:
         message: 'Level'
@@ -30,7 +30,7 @@
         message: 'L{value}'
         description: 'How the level is shown (L1, L2, ... L16 in English)'
     content_faction:
-        message: 'Faction'
+        message: 'Factie'
         description: 'the word "faction"'
     content_enlightened:
         message: 'Enlightened'
@@ -45,232 +45,232 @@
         message: 'Events'
         description: 'The plural of the singular of events'
     content_errors:
-        message: 'Errors'
+        message: 'Fouten'
         description: 'The plural of error'
     content_sourceFiles:
-        message: 'IngressIdentity Source Files'
+        message: 'IngressIdentiteit Bronbestanden'
         description: 'The header of the source file section in events and communities'

     # Options page
     options_title:
-        message: 'IngressIdentity Options'
+        message: 'IngressIdentiteit Opties'
         description: 'The title of the options page'
     options_help:
         message: 'Help'
         description: 'The word help'
     options_intro:
-        message: 'This options page allows you to configure the manifests to use when identifying people on G+. It also provides some tools to help acquiring data to put in manifest files or source files.'
+        message: 'Deze optiespagina laat je toe in te stellen welke manifesten worden ingeladen om spelers te herkennen op G+.'
         description: 'The options page general introduction'
     options_success:
-        message: 'Success!'
+        message: 'Gelukt!'
         description: 'Message to show when something went right'
     options_warning:
-        message: 'Warning!'
+        message: 'Waarschuwing!'
         description: 'Message to show when something went wrong, but not too wrong to function'
     options_error:
-        message: 'Error!'
+        message: 'Fout!'
         description: 'Message to show when something went horribly wrong'
     options_reload_success:
-        message: 'The data was successfully reloaded.'
+        message: 'De data werd succesvol herladen.'
         description: ''
     options_reload_failed:
-        message: 'Something went wrong when reloading the data.'
+        message: 'Er liep iets mis bij het herladen van de data.'
         description: ''
     options_add_success:
-        message: 'The manifest file was successfully added.'
+        message: 'Het manifest werd succesvol toegevoegd.'
         description: ''
     options_add_duplicate:
-        message: 'The given manifest is already loaded.'
+        message: 'Het manifest was reeds toegevoegd.'
         description: ''
     options_add_failed:
-        message: 'Something went wrong when adding the manifest.'
+        message: 'Er liep iets mis bij het toevoegen van het manifest.'
         description: ''
     options_remove_success:
-        message: 'The manifest file was successfully removed.'
+        message: 'Het manifest werd succesvol verwijderd.'
         description: ''
     options_remove_nonexisting:
-        message: 'The given manifest was not loaded.'
+        message: 'Het manifest was niet ingeladen.'
         description: ''
     options_remove_failed:
-        message: 'Something went wrong when removing the manifest.'
+        message: 'Er liep iets mis bij het herladen van het manifest.'
         description: ''
     options_reorder_success:
-        message: 'The order was successfully changed.'
+        message: 'De volgorde werd succesvol veranderd.'
         description: ''
     options_reorder_failed:
-        message: 'Something went wrong when changing the order.'
+        message: 'Er liep iets mis bij het veranderen van de volgorde.'
         description: ''
     options_rename_success:
-        message: 'The manifest was successfully renamed.'
+        message: 'Het manifest werd succesvol hernoemd.'
         description: ''
     options_rename_failed:
-        message: 'Something went wrong when renaming the manifest.'
+        message: 'Er liep iets mis bij het hernoemen van het manifest.'
         description: ''
     options_data_sources:
-        message: 'Data Sources'
+        message: 'Data Bronnen'
         description: 'Data Sources title'
     options_force_reload:
-        message: 'Force reload'
+        message: 'Herlaad'
         description: 'Text for the button to reload the manifests'
     options_loading_data:
-        message: 'Loading the data…'
+        message: 'De data wordt ingeladen…'
         description: 'Text shown if the data hasn\'t been loaded yet.'
     options_manifest_add_title:
-        message: 'Add a new manifest'
+        message: 'Nieuw manifest toevoegen'
         description: 'Section title'
     options_manifest_key_label:
-        message: 'Manifest Key'
+        message: 'Manifest Sleutel'
         description: 'Label for the input for manifest keys.'
     options_manifest_key_help:
-        message: 'For more information about where to find the manifest key, please take a look at the help page.'
+        message: 'Voor meer informatie over waar je de manifest sleutel kan terugvinden, neem je best een kijkje op de help pagina.'
         description: 'Help text for the input for manifest keys.'
     options_manifest_name_label:
-        message: 'Manifest Name'
+        message: 'Manifest Naam'
         description: 'Label for the input for manifest names.'
     options_manifest_name_help:
-        message: 'Optional. A human-readable name for your manifest.'
+        message: 'Optioneel. Een naam voor je manifest.'
         description: 'Help text for the input for manifest names.'
     options_manifest_add_button:
-        message: ' Add manifest'
+        message: ' Voeg het manifest toe'
         description: 'The content of the button that performs the action.'
     options_manifest_example_intro:
-        message: 'If you want to see an example of a manifest file, add manifest key'
+        message: 'Als je een voorbeeld manifest wil zien, voeg dan het manifest met sleutel'
         description: 'Text above the link to the example manifest.'
     options_manifest_example_outro:
-        message: 'Then take a look at  this post by the Niantic Project or  this post by Klue/ADA.'
+        message: 'toe. Neem dan eens een kijkje naar  deze post van het Niantic Project of  deze post van Klue/ADA.'
         description: 'Text below the link to the example manifest.'
     options_settings_title:
-        message: 'Settings'
+        message: 'Instellingen'
         description: 'Title of the settings section.'
     options_settings_intro:
-        message: 'This section contains some settings for the extension.'
+        message: 'Deze sectie bevat een aantal instellingen van de extensie.'
         description: 'Intro text to the settings section.'
     options_layout_options_title:
-        message: 'Layout Options'
+        message: 'Layout Opties'
         description: 'Title of the layout settings subsection.'
     options_layout_options_intro:
-        message: 'This subsection contains a couple of layout tweaks.'
+        message: 'Deze subsectie bevat een aantal layout tweaks.'
         description: 'Intro to the layout settings subsection.'
     options_settings_enable_anomalies:
-        message: 'Show anomaly badges'
+        message: 'Toon anomalieën.'
         description: 'One of the settings.'
     options_settings_match_popups:
-        message: 'Match profile pop-ups.'
+        message: 'Controleer profiel pop-ups.'
         description: 'One of the settings.'
     options_settings_match_authors:
-        message: 'Match authors of posts and comments, event RSVP, event creator etc.'
+        message: 'Controleer auteurs van berichten en commentaren, event RSVP\'s, de event aanmaker etc.'
         description: 'One of the settings.'
     options_settings_match_mentions:
-        message: 'Match mentions in posts.'
+        message: 'Controleer mentions in posts.'
         description: 'One of the settings.'
     options_settings_match_people:
-        message: 'Match people in the \'In common with you\', \'In his/her circles\' or \'Have him/her in circles\' sections.'
+        message: 'Controleer mensen in de \'Gemeenschappelijk met jou\' secties.'
         description: 'One of the settings.'
     options_settings_extra_event:
-        message: 'Show links to the source spreadsheets linked to an event on the page of that event.'
+        message: 'Toon een link naar het bronbestand dat bij een event hoort op de pagina van dat event.'
         description: 'One of the settings.'
     options_settings_extra_community:
-        message: 'Show links to the source spreadsheets linked to a community on the page of that community.'
+        message: 'Toon een link naar het bronbestand dat bij een community hoort op de pagina van die community.'
         description: 'One of the settings.'
     options_about_title:
-        message: 'About'
+        message: 'Over'
         description: 'Title of the section containing info about the extension.'
     options_about_text:
-        message: 'This extension is written and maintained by Bram Gotink. The code is © 2014 Bram Gotink, licensed under the terms of the MIT License. The source of this project can be found on GitHub.'
+        message: 'Deze extensie werd geschreven en onderhouden door Bram Gotink. De code is © 2014 Bram Gotink, gelicensieerd onder de termen van de MIT License. De broncode van dit project kan je terugvinden op GitHub.'
         description: 'Text of the section containing info about the extension.'
     options_ack_title:
         message: 'Acknowledgements'
         description: 'Title of the acknowledgements section.'
     options_ack_text:
-        message: 'This project uses code by jQuery, Sugar.js, John Resig and Twitter\'s Bootstrap. The logo is derived from the ingress logos created by cr0ybot.'
+        message: 'Dit project gebruikt code van jQuery, Sugar.js, John Resig en Twitters Bootstrap. Het logo is afgeleid van het ingress logo gecreëerd door cr0ybot.'
         description: 'Text of the acknowledgements section.'
     options_bug_title:
         message: 'Bug Report / Feature Request'
         description: 'Title of the bug section.'
     options_bug_text:
-        message: 'If you\'ve found a bug or have an idea for a killer feature, feel free to let us know here if you have a GitHub account or using the feedback feature in the Chrome Web Store found here.'
+        message: 'Als je een fout gevonden hebt, of een idee hebt voor een killer feature, kan je dat gerust hier melden als je een GitHub account hebt, anders via de feedback knop in de Chrome Web Store hier.'
         description: 'Text of the bug section.'
     options_disclaimer_title:
         message: 'Disclaimer'
         description: 'Title of the disclaimer section.'
     options_disclaimer_text:
-        message: 'The extension and its author(s) are in no way affiliated with Ingress, Google or NianticLabs@Google. This extension merely serves as third-party tool to support Ingress players and their communities living on Google+.'
+        message: 'Deze extensie en zijn auteur(s) zijn in geen enkele wijze geaffilieerd met Ingress, Google of NianticLabs@Google. Deze extensie dient er enkel toe Ingress spelers en hun communities op Google+ te ondersteunen.'
         description: 'Text of the disclaimer section.'
     options_empty_1:
-        message: 'No manifests loaded right now, try adding some!'
+        message: 'Er zijn geen manifesten ingeladen. Voeg er gerust een toe!'
         description: 'Part 1 of the text shown when no manifests haven\'t been added yet.'
     options_empty_2:
-        message: 'If you have just reloaded the extension, the manifests will automatically be shown here when the data is ready.'
+        message: 'Als de extensie net ingeladen is, kan het eventjes duren eer alle manifesten zijn ingeladen. De data wordt automatisch hier getoond wanneer alles gedaan is.'
         description: 'Part 2 of the text shown when no manifests haven\'t been added yet.'
     options_empty_3:
-        message: 'If you believe this is in error, try reloading this page or pressing "Force reload".'
+        message: 'Als je denkt dat je dit bericht niet zou mogen zien, kan je de pagina herladen of op "Herlaad" duwen.'
         description: 'Part 3 of the text shown when no manifests haven\'t been added yet.'
     options_manifest_info:
-        message: 'Faction: {faction}, {count} players, version {version}'
+        message: 'Factie: {faction}, {count} spelers, versie {version}'
         description: 'Information shown about the manifests.'
     options_rename:
-        message: 'Rename'
+        message: 'Hernoem'
         description: 'Tooltip for the rename button.'
     options_remove:
-        message: 'Remove'
+        message: 'Verwijder'
         description: 'Tooltip for the remove button.'
     options_enabled:
-        message: 'Enabled'
+        message: 'Aangeschakeld'
         description: 'The text on the button for an option that has been enabled by the user.'
     options_disabled:
-        message: 'Disabled'
+        message: 'Uitgeschakeld'
         description: 'The text on the button for an option that has been disabled by the user.'

     # Export page
     export_title:
-        message: 'IngressIdentity Data Exporter'
+        message: 'IngressIdentiteit Data Exporteur'
         description: 'Title of the export page'
     export_intro:
-        message: 'This page tries to fetch Ingress agent data from a Google+ community. You can modify it before exporting it to a Google Drive Spreadsheet.'
+        message: 'Deze pagina probeert Ingress agent informatie op te halen vanuit een Google+ community. Je kan de data nog wijzigen vooraleer ze naar een Google Drive spreadsheet te exporteren.'
         description: 'The intro for the export page.'
     export_options_title:
-        message: 'Options'
+        message: 'Opties'
         description: 'The word "Options".'
     export_options_intro:
-        message: 'The following buttons allow you to enable some features for the exporter:'
+        message: 'De volgende knoppen laten je toe een aantal features van de exporteur aan/uit te schakelen:'
         description: 'Intro text for the options section of the export page.'
     export_enabled:
-        message: 'Enabled'
+        message: 'Aangeschakeld'
         description: 'The text on the button for an option that has been enabled by the user.'
     export_disabled:
-        message: 'Disabled'
+        message: 'Uitgeschakeld'
         description: 'The text on the button for an option that has been disabled by the user.'
     export_option_extract_name:
-        message: 'Try to extract agent name from the Google+ name if unknown.'
+        message: 'Probeer agent namen uit de Google+ naam af te leiden indien de agent naam nog niet gekend is.'
         description: 'One of the options.'
     export_option_remove_existing:
-        message: 'Filter out existing entries for this community.'
+        message: 'Toon enkel nieuwe agenten in deze community.'
         description: 'One of the options.'
     export_option_add_header:
-        message: 'Add a header and dummy row to the exported data. Note that the table below always shows a header.'
+        message: 'Voeg een hoofding en dummy rij toe aan de geëxporteerde data. Merk op dat de tabel hieronder altijd een hoofding toont.'
         description: 'One of the options.'
     export_result_title:
-        message: 'Result'
+        message: 'Resultaat'
         description: 'The title for the result section.'
     export_copy_button:
-        message: 'Copy'
+        message: 'Kopieer'
         description: 'The text for the copy button.'
     export_copy_text:
-        message: 'Press this button to copy the data to the clipboard, then paste it in a Google Drive Spreadsheet.'
+        message: 'Duw op de knop om de data te kopiëren naar je plakbord. Plak ze dan in een Google Drive Spreadsheet.'
         description: 'An explanatory text next to the copy button.'
     export_modal_pe_title:
-        message: 'Parsing and exporting data…'
+        message: 'Ontleden en exporteren van de data…'
         description: 'Title for the modal used when parsing and exporting.'
     export_modal_e_title:
-        message: 'Exporting data…'
+        message: 'Exporteren van de data…'
         description: 'Title for the modal used when only exporting.'
     export_modal_p_subtitle:
-        message: 'Parsing:'
+        message: 'Ontleden:'
         description: 'Subtitle for parsing'
     export_modal_e_subtitle:
-        message: 'Exporting:'
+        message: 'Exporteren:'
         description: 'Subtitle for exporting'
     export_progress:
-        message: '% complete'
+        message: '% klaar'
         description: 'The "" part will contain a number.'
 }


Fixed when params is null

Fixed when params is null

--- src/index.coffee
+++ src/index.coffee
@@ -31,7 +31,7 @@

   find: (table, params, after = ->) ->
     @getInstance table, (db, collection) ->
-      params._id = ensureObjectId params._id if params._id?
+      params._id = ensureObjectId params._id if params?._id?

       collection.find(params).toArray (error, results) ->
         console.log 'Error with fetching documents: ' + error if error


fix missing fat issue

fix missing fat issue

--- src/index.coffee
+++ src/index.coffee
@@ -186,7 +186,7 @@
   p = @cart.set id, quantity, locked
   if @promise != p
     @promise = p
-    @promise.then(->
+    @promise.then(=>
       riot.update()
       m.trigger Events.UpdateItems, @data.get 'order.items'
     ).catch (err)->


test vm.$data

test vm.$data

--- src/index.coffee
+++ src/index.coffee
@@ -1,9 +1,12 @@
 Vue = require 'vue'

-app = new Vue
-  data:
-    title: 'color-vue!'
-    message: 'Hello, Vue.js!!'
+data =
+  title: 'color-vue!'
+  message: 'Hello, Vue.js!!'
+app = new Vue { data }
+
+# Vue constructor data = instance $data
+console.log app.$data is data

 count = 3
 tick = ->


fix condition

fix condition

--- src/index.coffee
+++ src/index.coffee
@@ -84,7 +84,7 @@
   makeScopeName: (name) ->
     name = @resolveMacros(name)
     if @autoAppendScopeName
-      if not @hasGrammarScopeName.test(name)
+      unless @hasGrammarScopeName.test(name)
         return "#{name}.#{@grammarScopeName}"

     name
@@ -153,7 +153,7 @@
                 c[ck] = { name: @makeScopeName(cv) }

         when "p", "patterns"
-          if not v instanceof Array
+          unless v instanceof Array
             v = [ v ]
           P.patterns = (@makePattern(p) for p in v)


Enable reuse of GM instance

Enable reuse of GM instance

--- src/index.coffee
+++ src/index.coffee
@@ -68,12 +68,12 @@
   @returnExif = opts.returnExif or config.returnExif

   @meta = {}
-  @gm = null
+  @gm = gm @src

   @

 Image.prototype.getMeta = (cb) ->
-  @gm = gm(@src).identify (err, val) =>
+  @gm.identify (err, val) =>
     return cb err if err
     @meta =
       format: val.format.toLowerCase()
@@ -116,7 +116,7 @@
     ".#{version.format}"
   ].join('')

-  img = gm(@src)
+  img = @gm
     .resize(version.maxWidth, version.maxHeight)
     .quality(version.quality or @config.resizeQuality)


Exposing memorystore

Exposing memorystore

--- src/index.coffee
+++ src/index.coffee
@@ -18,3 +18,4 @@

   SingletonWindow : require("./singleton-window")

+  MemoryStore : require("./store").MemoryStore


reversed

reversed

--- src/kvstore.iced
+++ src/kvstore.iced
@@ -89,9 +89,6 @@

 exports.Flat = class Flat extends Base

-  constructor : () ->
-    @flat_lock = new Lock
-
   make_kvstore_key : ({type,key}) ->
     type or= key[-2...]
     "kv:" + super { type, key }
@@ -99,27 +96,8 @@
   make_lookup_name : ({type,name}) ->
     "lo:" + super { type, name }

-  _get_reverse_lookup : ({name}, cb) ->
-    await @_get { key : ("r" + name) }, defer err, rlookup
-    if err? and (err instanceof E.NotFoundError)
-      err = null
-      rlookup = []
-    cb err, rlookup
-
-  _put_reverse_lookup : ({name, list}, cb) ->
-    await @_put { key : ("r" + name), value : list }, defer err
-    cb err
-
   _link : ({key, name}, cb) ->
-    await @flat_lock.acquire defer()
-    esc = make_esc cb, "Flat::_link"
-    await @_get_reverse_lookup { name }, defer err, list
-    unless err?
-      list.push name
-      await @_put_reverse_lookup { name, list }, defer err
-    unless err?
-      await @_put { key : name, value : key }, defer err
-    @flat_lock.release()
+    await @_put { key : name, value : key }, defer err
     cb err

   _unlink : ({name}, cb) ->


Apply sortable plugin on not polymorphic associations after form insert.

--- app/assets/javascripts/activeadmin_polymorphic.js.coffee
+++ app/assets/javascripts/activeadmin_polymorphic.js.coffee
@@ -150,6 +150,7 @@
       target.prepend form

       container.trigger "polymorphic_has_many_form:inserted", [form]
+      $(target).find('.has_many_container').trigger('has_many_add:after')
     error: (xhr, status, error) ->
       stop_loading_indicator(target, spinner)

@@ -169,6 +170,7 @@
     $(target).find('fieldset').replaceWith(fieldset)
     container = $(target).closest '.polymorphic_has_many_container'
     container.trigger "polymorphic_has_many_form:inserted", [ target ]
+    $(target).find('.has_many_container').trigger('has_many_add:after')

 window.remoteSubmit = (target, callback) ->
   action = $(target).attr('action')


Fixing label for cart popover to make it write name of product variant without page load

--- app/assets/javascripts/darkswarm/services/cart.js.coffee
+++ app/assets/javascripts/darkswarm/services/cart.js.coffee
@@ -64,6 +64,7 @@
       @create_line_item(variant) unless exists

     create_line_item: (variant)->
+      variant.extended_name = @extendedVariantName(variant)
       variant.line_item =
         variant: variant
         quantity: null


thought I got all the places with the settings for chosen

--- app/assets/javascripts/hanuman/hanuman-surveys.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-surveys.js.coffee
@@ -53,12 +53,15 @@
   # END TYPEAHEAD

-  # CHOSEN
+  # CHOSEN SINGLE SELECT
   $(".chosen-select").chosen
+    allow_single_deselect: true
     no_results_text: "No results matched"
     size: "100%"
+    single_backstroke_delete: false
+    search_contains: true

-  # chosen multiselect
+  # CHOSEN MULTISELECT
   $(".chosen-multiselect").chosen
     allow_single_deselect: true
     no_results_text: "No results matched"


Addedd Status.manuallyLoading attribute, set to `true` to trigger loading animation

--- app/assets/javascripts/sprangular/services/status.coffee
+++ app/assets/javascripts/sprangular/services/status.coffee
@@ -7,11 +7,12 @@
     requestedPath: null
     httpLoading: false
     routeChanging: false
+    manuallyLoading: false
     cachedProducts: []
     meta: {}

     isLoading: ->
-      @httpLoading || @routeChanging
+      @manuallyLoading || @httpLoading || @routeChanging

     cacheProduct: (product) ->
       status.cachedProducts.push(product)


fix #195: run cells after adding them

fix #195: run cells after adding them

--- app/notebook.coffee
+++ app/notebook.coffee
@@ -147,12 +147,13 @@
     value: Editor.get_value cell.editor

 import_notebook = (notebook, cell, imported, options) ->
-  for imported_cell in imported.cells
+  cells = _.map imported.cells, (imported_cell) ->
     if imported_cell.type is 'input'
       cell = add_input_cell notebook, after: cell
       set_cell_value cell, imported_cell.value
-      if options.run
-        run cell
+      cell
+  if options.run
+    _.each cells, run
   notebook

 update_view = (notebook) ->


simplify method parameter lists in choropleth

--- app/scripts/directives/choropleth.coffee
+++ app/scripts/directives/choropleth.coffee
@@ -1,5 +1,6 @@
 angular.module('ldEditor').directive 'choropleth', ($timeout, ngProgress, mapMediatorService) ->

+  # TODO: once we have initialized everything in the form this can go
   defaults =
     projection: 'mercator'
     quantizeSteps: 9
@@ -13,23 +14,25 @@
   #    #############################################

   renderVisualization = (scope) ->
+    path = deducePathProjection(scope.projection)
+    renderMap(scope, path)
+
+
     map = scope.map
     data = scope.data
-    path = deducePathProjection(scope.projection)
     valueProperty = scope.valueProperty
     mappingPropertyOnMap = scope.mappingPropertyOnMap
     mappingPropertyOnData = scope.mappingPropertyOnData
-    quantizeSteps = scope.quantizeSteps || defaults.quantizeSteps
-
-    renderMap(scope, map, path)
-
-    bounds =  path.bounds(map)
+
+
+    bounds =  path.bounds(scope.map)
     resizeMap(scope.svg, bounds)

     if data
-      allMappingPropertiesOnMap = deduceAllAvailableMappingOnMap(map, mappingPropertyOnMap)
-      valFn = deduceValueFunction(data, valueProperty, quantizeSteps, allMappingPropertiesOnMap, mappingPropertyOnData, scope.mapId)
-      renderData(scope, data, mappingPropertyOnData, valueProperty, mappingPropertyOnMap, valFn)
+      quantizeSteps = scope.quantizeSteps || defaults.quantizeSteps
+      allMappingPropertiesOnMap = deduceAllAvailableMappingOnMap(scope.map, scope.mappingPropertyOnMap)
+      valFn = deduceValueFunction(scope, quantizeSteps, allMappingPropertiesOnMap)
+      renderData(scope, valFn)
       renderLegend(scope, valFn)

@@ -46,9 +49,9 @@
     mapPaths.exit().remove()

-  renderMap = (scope, map, path) ->
+  renderMap = (scope, path) ->
     mapPaths = scope.mapGroup.selectAll('path')
-      .data(map.features)
+      .data(scope.map.features)
     mapPaths.enter().append('path')
       .attr('d', path)

@@ -77,23 +80,23 @@
   #      Data Visualization
   #    #############################################

-  renderData = (scope, data, mappingPropertyOnData, valueProperty, mappingPropertyOnMap, valFn) ->
-    return if typeof data != 'object'
+  renderData = (scope, valFn) ->
+    return if typeof scope.data != 'object'
     valueById = d3.map()
     mapInstance = mapMediatorService.getUIModel(scope.mapId)
     mapInstance.regionsWithMissingDataPoints = [] # reset
     mapInstance.usedDataValues = [] # reset
     usedDataPoints = []
-    data.forEach (d) ->
-      dataPropertyId = d[mappingPropertyOnData] || +d[defaults.mappingPropertyOnData]
+    scope.data.forEach (d) ->
+      dataPropertyId = d[scope.mappingPropertyOnData] || +d[defaults.mappingPropertyOnData]
       if mapInstance.getValueType() == 'categorical'
-        val = d[valueProperty] || d[defaults.valueProperty]
+        val = d[scope.valueProperty] || d[defaults.valueProperty]
       else
-        val = +d[valueProperty] || +d[defaults.valueProperty]
+        val = +d[scope.valueProperty] || +d[defaults.valueProperty]
       valueById.set(dataPropertyId, val)
     scope.mapGroup.selectAll('path')
       .attr('class', (d) ->
-        mapPropertyId = d.properties[mappingPropertyOnMap] || +d.properties[defaults.mappingPropertyOnMap]
+        mapPropertyId = d.properties[scope.mappingPropertyOnMap] || +d.properties[defaults.mappingPropertyOnMap]
         val = valueById.get(mapPropertyId)
         if val
           mapInstance.usedDataValues.push(val) if mapInstance.usedDataValues.indexOf(val) == -1
@@ -124,8 +127,8 @@

   # either quantize or ordinal depending on type of data
-  deduceValueFunction = (data, valueProperty, quantizeSteps, allMappingPropertiesOnMap, mappingPropertyOnData, mapId) ->
-    mapInstance = mapMediatorService.getUIModel(mapId)
+  deduceValueFunction = (scope, quantizeSteps, allMappingPropertiesOnMap) ->
+    mapInstance = mapMediatorService.getUIModel(scope.mapId)
     if mapInstance.getValueType() == 'categorical'
       categoryValues = mapInstance.getCategoryValues()
       valFn = d3.scale.ordinal()
@@ -134,8 +137,8 @@
           "q#{i}-#{categoryValues.length}"
         )
     else
-      maxValue = deduceMaxValue(data, valueProperty, allMappingPropertiesOnMap, mappingPropertyOnData)
-      minValue = deduceMinValue(data, valueProperty, allMappingPropertiesOnMap, mappingPropertyOnData)
+      maxValue = deduceMaxValue(scope.data, scope.valueProperty, allMappingPropertiesOnMap, scope.mappingPropertyOnData)
+      minValue = deduceMinValue(scope.data, scope.valueProperty, allMappingPropertiesOnMap, scope.mappingPropertyOnData)
       valFn = d3.scale.quantize()
         .domain([minValue, maxValue])
         .range(d3.range(quantizeSteps).map (i) ->


fix bug in angular update

fix bug in angular update

angular 1.2 does not allow '_' in object key names

--- app/scripts/models/choropleth_map.coffee
+++ app/scripts/models/choropleth_map.coffee
@@ -26,7 +26,7 @@
     sanitizedData = @_getSnippetModel().data('data').map (dataEntry) ->
       sanitizedEntry = {}
       for key, value of dataEntry
-        sanitizedKey = livingmapsWords.camelCase(key).replace('%', 'Percent')
+        sanitizedKey = livingmapsWords.camelCase(key).replace('%', 'Percent').replace('_', '')
         sanitizedValue = value
         sanitizedEntry[sanitizedKey] = sanitizedValue
         keyMapping[key] = sanitizedKey


no self-notifications

no self-notifications

--- app/scripts/services/notification.coffee
+++ app/scripts/services/notification.coffee
@@ -1,4 +1,4 @@
-angular.module("kvetchApp").service "Notification", ($firebase, $window, $rootScope) ->
+angular.module("kvetchApp").service "Notification", ($firebase, $window, $rootScope, Author) ->
   return N =
     init: ({rootId}) ->
       if not $window.Notification?
@@ -14,6 +14,10 @@
           $rootScope.$evalAsync ->
             message = messages.$getRecord(key)
             return unless message
+
+            # don't notify if you posted the message yourself
+            if message.author && message.author.uid && message.author.uid == Author.get().uid
+              return

             if event is 'child_added' and not document.hasFocus()
               parent = message


Updated chatListItem file to add new elements and event bindings.

--- client/activity/lib/components/chatlistitem/index.coffee
+++ client/activity/lib/components/chatlistitem/index.coffee
@@ -6,21 +6,75 @@
 MessageBody          = require 'activity/components/common/messagebody'
 ProfileText          = require 'app/components/profile/profiletext'
 ProfileLinkContainer = require 'app/components/profile/profilelinkcontainer'
+ButtonWithMenu       = require 'app/components/buttonwithmenu'
+ActivityLikeLink     = require 'activity/components/activitylikelink'
+keycode              = require 'keycode'

 module.exports = class ChatListItem extends React.Component
+  constructor: (props) ->
+
+    @state = { hover: no, showMenuForMouseAction: no, editMode: no }

   getItemProps: ->
     key       : @props.message.get 'id'
-    className : kd.utils.curry 'ChatItem', @props.className
+    className : if @state.hover then kd.utils.curry 'ChatItem mouse-enter', @props.className else kd.utils.curry 'ChatItem', @props.className
+    onMouseEnter: =>
+      @setState hover: yes
+
+    onMouseLeave: =>
+      @setState hover: no
+      @setState showMenuForMouseAction: no
+
+  getMenuItems: ->
+    return [
+      {title: 'Edit Post',   key: 'editpost',  onClick: @editPost}
+      {title: 'Delete Post', key: 'deletepost', onClick: @deletePost}
+      {title: 'Mark User as Troll', key: 'markuser', onClick: @markUser}
+      {title: 'Block User', key: 'blockuser', onClick: @blockUser}
+      {title: 'impersonate User', key: 'impersonate', onClick: @impersonate}
+    ]
+
+  editPost: =>
+    @setState editMode: yes
+
+  deletePost: ->
+    console.log "delete post clicked"
+
+  markUser: ->
+    console.log "mark user clicked"
+
+  blockUser: ->
+    console.log "block user clicked"
+
+  impersonate: ->
+    console.log "impersonate clicked"
+
+  updateMessage: =>
+    console.log "update message"
+
+  cancelEdit: =>
+    @setState editMode: no
+
+  handleEditMessageKeyDown: (event) ->
+
+    code = event.which or event.keyCode
+    key  = keycode code
+
+    @setState editMode: no if key is 'esc'
+    @setState editMode: no if key is 'enter'
+

   render: ->
     { message } = @props
+    editFormClass       = if @state.editMode then 'ChatItem-updateMessageForm' else 'ChatItem-updateMessageForm hidden'
+    mediaContentClass   = if @state.editMode then 'MediaObject-content hidden' else 'MediaObject-content'
+    contentWrapperClass = if @state.editMode then 'ChatItem-contentWrapper MediaObject editing' else 'ChatItem-contentWrapper MediaObject'

-

+

           {makeAvatar message.get 'account'}

-

+

               {makeProfileLink message.get 'account'}
@@ -28,19 +82,24 @@

               {makeTimeAgo message.get 'createdAt'}

+            <ActivityLikeLink />

             <MessageBody source={message.get 'body'} />

+

+          <textarea onKeyDown = { @bound 'handleEditMessageKeyDown' } defaultValue={ message.get 'body' }></textarea>
+          <button className="solid green done-button" type="button" onClick={@bound 'updateMessage'} >DONE</button>
+          <button className="cancel-editing" type="button" onClick={@bound 'cancelEdit'} >CANCEL</button>
+

+        <ButtonWithMenu items={@getMenuItems()} showMenuForMouseAction={@state.showMenuForMouseAction}/>

 makeTimeAgo = (createdAt) ->
-
-    <TimeAgo from={createdAt} />
-
+  <TimeAgo from={createdAt} />

 makeProfileLink = (imAccount) ->
   <ProfileLinkContainer origin={imAccount.toJS()}>


Define Analytics.page method

Define Analytics.page method

--- client/landing/site.landing/coffee/core/analytics.coffee
+++ client/landing/site.landing/coffee/core/analytics.coffee
@@ -10,3 +10,15 @@

     data = { action, properties }
     request 'track', 'post', data
+
+
+  @page: (name, category) ->
+
+    properties =
+      url      : window.location.toString()
+      path     : window.location.pathname
+      title    : window.document.title
+      referrer : window.document.referrer
+
+    data = { name, category, properties }
+    request 'page', 'post', data


initialize placeholder properly

initialize placeholder properly

Former-commit-id: 3b8fc6175a2034617c28834cc46e5522d958e6bc [formerly e95fc8c5e0edc6771cada3a584648ce00d988edc] [formerly 32366dbcea563c763c6638e725e99c91127a1632 [formerly 5a37b12501b1dd5dd8b57e308580a90333fb197a]] [formerly 7a5b354993de6be9e7fc5ea91769b4297f423159 [formerly f59dcdcc39b5662c326836eaca5cda551c3186b2] [formerly 2efacdb1e4b0641e27df9f34a3740248f26184cb [formerly 2efacdb1e4b0641e27df9f34a3740248f26184cb [formerly d8e62695c6ce9a2959347acaa78db37805154d31]]]]
Former-commit-id: e7c7f44295744bff20d119286744ee125a09c721 [formerly 5e68f70f2a068805ca35411886d9fc1a032a6931] [formerly 4055f5f8204ec42c4394252699ad1dae78e63530 [formerly a81d66a76ebc9207fc112e6383975734ed97c773]]
Former-commit-id: a666b759f4aef7d6eb00ae914d5b05ed5d8c42e9 [formerly fbc7fa916ece48dd06b610c895f9d04a49f8a058]
Former-commit-id: 67211babf450568c5ae18bb650065906ad6cf774
Former-commit-id: 75d5c40e7356a8ba1ec5d8ada5c9ddbd87ec0366

--- @client/assets/javascripts/vendor_extensions/jquery_extensions.js.coffee
+++ @client/assets/javascripts/vendor_extensions/jquery_extensions.js.coffee
@@ -37,6 +37,7 @@

       if @options.placeholder
         @options.placeholder.style['visibility'] = 'hidden'
+        @options.placeholder.style['display'] = 'none'

       @resize()
       @update()


remove duplicate integration test from imgLoaded

--- test/integration/assert/imgLoaded_test.coffee
+++ test/integration/assert/imgLoaded_test.coffee
@@ -5,11 +5,6 @@
   before ->
     @browser = getBrowser()
     @browser.navigateTo '/'
-
-  it 'throws an error when passed bad arguments', ->
-    msg = 'imgLoaded(selector) - requires selector string'
-    err = assert.throws => @browser.assert.imgLoaded()
-    assert.include msg, err.message

   it 'throws an error when the image was not found', ->
     msg = 'imgLoaded "img.not-in-the-page": element not found'


Removed all condition

Removed all condition

--- pimatic-smartmeter.coffee
+++ pimatic-smartmeter.coffee
@@ -53,17 +53,14 @@
         @actualusage = Number data.currentUsage
         @emit "actualusage", Number @actualusage

-        if @activetariff != Number data.currentTariff
-          @activetariff = Number data.currentTariff
-          @emit "activetariff", Number @activetariff
+        @activetariff = Number data.currentTariff
+        @emit "activetariff", Number @activetariff

-        if @tariff1totalusage != Number data.tariffOneTotalUsage
-          @tariff1totalusage = Number data.tariffOneTotalUsage
-          @emit "tariff1totalusage", Number @tariff1totalusage
+        @tariff1totalusage = Number data.tariffOneTotalUsage
+        @emit "tariff1totalusage", Number @tariff1totalusage

-        if @tariff2totalusage != Number data.tariffTwoTotalUsage
-          @tariff2totalusage = Number data.tariffTwoTotalUsage
-          @emit "@tariff2totalusage", Number @tariff2totalusage
+        @tariff2totalusage = Number data.tariffTwoTotalUsage
+        @emit "@tariff2totalusage", Number @tariff2totalusage

     getActualusage: -> Promise.resolve @actualusage


Fix specs for Editor View

Fix specs for Editor View

--- spec/editor-view-spec.coffee
+++ spec/editor-view-spec.coffee
@@ -10,8 +10,6 @@
   [buffer, editorView, editor, cachedLineHeight, cachedCharWidth] = []

   beforeEach ->
-    atom.packages.activatePackage('language-text', sync: true)
-    atom.packages.activatePackage('language-javascript', sync: true)
     editor = atom.project.openSync('sample.js')
     buffer = editor.buffer
     editorView = new EditorView(editor)
@@ -25,6 +23,12 @@
       @height(getLineHeight() * heightInLines)
       @width(getCharWidth() * widthInChars) if widthInChars
       $('#jasmine-content').append(this)
+
+    waitsForPromise ->
+      atom.packages.activatePackage('language-text', sync: true)
+
+    waitsForPromise ->
+      atom.packages.activatePackage('language-javascript', sync: true)

   getLineHeight = ->
     return cachedLineHeight if cachedLineHeight?


client: fix missing require statement

client: fix missing require statement

--- client/activity/lib/views/privatemessage/privatemessagepane.coffee
+++ client/activity/lib/views/privatemessage/privatemessagepane.coffee
@@ -4,7 +4,7 @@
 KDFormViewWithFields = kd.FormViewWithFields
 KDView = kd.View
 ActivityAutoCompleteUserItemView = require '../activityautocompleteuseritemview'
-ParticipantSearchController = './participantsearchcontroller'
+ParticipantSearchController = require './participantsearchcontroller'
 MessagePane = require '../messagepane'
 PrivateMessageForm = require './privatemessageform'
 PrivateMessageListItemView = require './privatemessagelistitemview'


added credit for subset algo

added credit for subset algo

--- algorithms.coffee
+++ algorithms.coffee
@@ -13,8 +13,7 @@
     return results

-# from https://github.com/blazs/subsets
-# TODO:40 add credit
+# @Thanks to https://github.com/blazs/subsets for the inspiration!
 get_subsets = (set, k) ->
     subsets = []
     n = set.length


Fixed key flags not being marked when editing an existing object. Closes #24.

--- app/assets/javascripts/objects.js.coffee
+++ app/assets/javascripts/objects.js.coffee
@@ -2,7 +2,7 @@
   newObj = {}
   f = (obj, prefix) ->
     for k, v of obj
-      if typeof(v) is 'object'
+      if Object.prototype.toString.call(v) == '[object Object]'
         f(v, "#{prefix}#{k}.")
       else
         newObj["#{prefix}#{k}"] = v


I18n hotfix.

I18n hotfix.

I forgot about having added this straight into the production branch.

Now that I've rebased, production branch won't mix with this and I'm better off killing the production branch and making a new clean one.

So in the interest of keeping the history readable it's a new commit.

--- common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
@@ -325,7 +325,7 @@
         @submit_button.hide()
         @queueing()
         @grader_status = @$(@grader_status_sel)
-        @grader_status.html("" + gettext "Your response has been submitted. Please check back later for your grade." + "")
+        @grader_status.html("" + gettext("Your response has been submitted. Please check back later for your grade.") + "")
       else if @child_type == "selfassessment"
         @setup_score_selection()
     else if @child_state == 'post_assessment'


JS: fix function parameter names (although unused)

--- core/htdocs_source/app/index/route.coffee
+++ core/htdocs_source/app/index/route.coffee
@@ -1,4 +1,4 @@
 import Route from '@ember/routing/route'

 export default Route.extend
-    redirect: (req) -> @transitionTo "openxpki", "welcome"
+    redirect: (model, transition) -> @transitionTo "openxpki", "welcome"


Re-add fallback port, as env port is not always set

--- server/config.coffee
+++ server/config.coffee
@@ -3,7 +3,7 @@
     adminSegment: 'admin'
     apiSegment: 'api'
     salt: 'BUCKETS4LIFE!!1'
-    port: process.env.PORT
+    port: process.env.PORT || 3000
     env: process.env.NODE_ENV or 'development'
     templatePath: "#{__dirname}/../user/templates/"
   db: "mongodb://localhost/buckets_#{process.env.NODE_ENV or 'development'}"


Remove some config usages from text editor spec

--- spec/text-editor-spec.coffee
+++ spec/text-editor-spec.coffee
@@ -36,22 +36,6 @@
       expect(editor2.getSelections()[1].isReversed()).toBeTruthy()
       expect(editor2.isFoldedAtBufferRow(4)).toBeTruthy()
       editor2.destroy()
-
-    it "preserves the invisibles setting", ->
-      atom.config.set('editor.showInvisibles', true)
-      previousLineText = editor.lineTextForScreenRow(0)
-      editor2 = TextEditor.deserialize(editor.serialize(), atom)
-      expect(editor2.lineTextForScreenRow(0)).toBe(previousLineText)
-
-    it "updates invisibles if the settings have changed between serialization and deserialization", ->
-      atom.config.set('editor.showInvisibles', true)
-      previousLineText = editor.lineTextForScreenRow(0)
-      state = editor.serialize()
-      atom.config.set('editor.invisibles', eol: '?')
-      editor2 = TextEditor.deserialize(state, atom)
-
-      expect(editor2.lineTextForScreenRow(0)).not.toBe(previousLineText)
-      expect(editor2.lineTextForScreenRow(0).endsWith('?')).toBe(true)

   describe "when the editor is constructed with the largeFileMode option set to true", ->
     it "loads the editor but doesn't tokenize", ->
@@ -93,66 +77,6 @@
       expect(editor2.getSelectedBufferRanges()).not.toEqual editor.getSelectedBufferRanges()
       editor2.unfoldBufferRow(4)
       expect(editor2.isFoldedAtBufferRow(4)).not.toBe editor.isFoldedAtBufferRow(4)
-
-  describe "config defaults", ->
-    it "uses the `editor.tabLength`, `editor.softWrap`, and `editor.softTabs`, and `core.fileEncoding` config values", ->
-      editor1 = null
-      editor2 = null
-      atom.config.set('editor.tabLength', 4)
-      atom.config.set('editor.softWrap', true)
-      atom.config.set('editor.softTabs', false)
-      atom.config.set('core.fileEncoding', 'utf16le')
-
-      waitsForPromise ->
-        atom.workspace.open('dir/a').then (o) -> editor1 = o
-
-      runs ->
-        expect(editor1.getTabLength()).toBe 4
-        expect(editor1.isSoftWrapped()).toBe true
-        expect(editor1.getSoftTabs()).toBe false
-        expect(editor1.getEncoding()).toBe 'utf16le'
-
-        atom.config.set('editor.tabLength', 8)
-        atom.config.set('editor.softWrap', false)
-        atom.config.set('editor.softTabs', true)
-        atom.config.set('core.fileEncoding', 'macroman')
-
-      waitsForPromise ->
-        atom.workspace.open('dir/b').then (o) -> editor2 = o
-
-      runs ->
-        expect(editor2.getTabLength()).toBe 8
-        expect(editor2.isSoftWrapped()).toBe false
-        expect(editor2.getSoftTabs()).toBe true
-        expect(editor2.getEncoding()).toBe 'macroman'
-
-        atom.config.set('editor.tabLength', -1)
-        expect(editor2.getTabLength()).toBe 1
-        atom.config.set('editor.tabLength', 2)
-        expect(editor2.getTabLength()).toBe 2
-        atom.config.set('editor.tabLength', 17)
-        expect(editor2.getTabLength()).toBe 17
-        atom.config.set('editor.tabLength', 128)
-        expect(editor2.getTabLength()).toBe 128
-
-    it "uses scoped `core.fileEncoding` values", ->
-      editor1 = null
-      editor2 = null
-
-      atom.config.set('core.fileEncoding', 'utf16le')
-      atom.config.set('core.fileEncoding', 'macroman', scopeSelector: '.js')
-
-      waitsForPromise ->
-        atom.workspace.open('dir/a').then (o) -> editor1 = o
-
-      runs ->
-        expect(editor1.getEncoding()).toBe 'utf16le'
-
-      waitsForPromise ->
-        atom.workspace.open('sample-with-comments.js').then (o) -> editor2 = o
-
-      runs ->
-        expect(editor2.getEncoding()).toBe 'macroman'

   describe "title", ->
     describe ".getTitle()", ->
@@ -5998,6 +5922,14 @@
       atom.config.set('editor.showInvisibles', true)
       expect(editor.lineTextForScreenRow(0).indexOf(atom.config.get('editor.invisibles.eol'))).toBe(-1)

+  describe "::setInvisibles(invisibles)", ->
+    it "updates the editor's invisible character substitutions", ->
+      previousLineText = editor.lineTextForScreenRow(0)
+      editor.setInvisibles(eol: '?')
+      expect(editor.lineTextForScreenRow(0)).not.toBe(previousLineText)
+      expect(editor.lineTextForScreenRow(0).endsWith('?')).toBe(true)
+      expect(editor.getInvisibles()).toEqual(eol: '?')
+
   describe "indent guides", ->
     it "shows indent guides when `editor.showIndentGuide` is set to true and the editor is not mini", ->
       editor.setText("  foo")


Fix next-task selection rule

Fix next-task selection rule

--- app/classifier/tasks/generic-editor.cjsx
+++ app/classifier/tasks/generic-editor.cjsx
@@ -125,7 +125,7 @@
         <button type="button" className="workflow-choice-add-button" title="Add choice" onClick={@addChoice.bind this, choicesKey}>+</button>
       </div>

-      {unless @props.task.type isnt 'single' or @props.isSubtask
+      {unless @props.task.type is 'single' or @props.isSubtask
         <div>
           Next task{' '}
           <NextTaskSelector workflow={@props.workflow} name="next" value={@props.task.next ? ''} onChange={@handleInputChange} />


Focus preview list after expanding/collapsing all

--- src/packages/command-panel/lib/command-panel-view.coffee
+++ src/packages/command-panel/lib/command-panel-view.coffee
@@ -98,9 +98,11 @@

   onExpandAll: (event) =>
     @previewList.expandAllPaths()
+    @previewList.focus()

   onCollapseAll: (event) =>
-      @previewList.collapseAllPaths()
+    @previewList.collapseAllPaths()
+    @previewList.focus()

   attach: (text='', options={}) ->
     @errorMessages.hide()


Better error message.

Better error message.

--- server/manage.coffee
+++ server/manage.coffee
@@ -315,7 +315,7 @@
         publication.process null, initCallback, textCallback, pageImageCallback
         Publications.update publication._id, $set: numberOfPages: publication.numberOfPages
       catch error
-        console.error error.stack or error.toString?() or error
+        console.error "Error processing PDF:", error.stack or error.toString?() or error

     console.log "Done"


Fix a bug, now we can delete attachment files from user stories

--- app/coffee/controllers/user-story.coffee
+++ app/coffee/controllers/user-story.coffee
@@ -170,7 +170,7 @@

     removeAttachment: (attachment) ->
         promise = @confirm.confirm(@i18next.t('common.are-you-sure'))
-        promise.then () ->
+        promise.then () =>
             @scope.attachments = _.without(@scope.attachments, attachment)
             attachment.remove()


Fix bux 923: Wysiwyg smart enter handling

--- app/coffee/modules/common/wisiwyg.coffee
+++ app/coffee/modules/common/wisiwyg.coffee
@@ -68,6 +68,29 @@
         markdownSettings =
             nameSpace: 'markdown'
             onShiftEnter: {keepDefault:false, openWith:'\n\n'}
+            onEnter:
+                keepDefault: false
+                replaceWith: (data) ->
+                    lastLine = data.textarea.value[0..(data.caretPosition - 1)].split("\n").pop()
+
+                    match = lastLine.match /^(\s*- ).*/
+                    return "\n#{match[1]}" if match
+
+                    match = lastLine.match /^(\s*\* ).*/
+                    return "\n#{match[1]}" if match
+
+                    match = lastLine.match /^(\s*1\. ).*/
+                    return "\n#{match[1]}" if match
+
+                    return "\n"
+
+                afterInsert: (data) ->
+                    # Calculate the scroll position
+                    totalLines = data.textarea.value.split("\n").length
+                    line = data.textarea.value[0..(data.caretPosition - 1)].split("\n").length
+                    scrollRelation = line / totalLines
+                    $el.scrollTop((scrollRelation * $el[0].scrollHeight) - ($el.height() / 2))
+
             markupSet: [
                 {
                     name: $i18next.t('wiki-editor.heading-1')


Replace soundex-code node module with node-soundex which also works in browser

--- app/static/script/directive/chart.coffee
+++ app/static/script/directive/chart.coffee
@@ -156,7 +156,7 @@
         if (words = str.split(' ')).length > 1
           colors = R.map fuzzyColor, R.filter(R.I, words)
           return R.reduce tinycolor.mix, R.head(colors), R.tail(colors)
-        sndx = soundexPhonetics(str or ' ')
+        sndx = Soundex(str or ' ')
         # hue = (sndx[0].charCodeAt() % 64)  * (360 / 64) # modulo is for unicode chars. Or 360?
         # hue = (((sndx[0].charCodeAt() - (65)) + 26) % 26) * (360 / 26)
         hue = (((sndx[0].charCodeAt())) % 26) * (360 / 26)


Define time duration in a more readable format

--- server/models.coffee
+++ server/models.coffee
@@ -5,7 +5,7 @@
 Key = require '../lib/bitcoin/key'
 { PUBKEY_LEN, PUBKEY_C_LEN } = require '../lib/bitcoin'

-TX_EXPIRY = 172800 # Two days
+TX_EXPIRY = '2 days'

 to_buff = (val) -> if val instanceof Buffer then val else new Buffer val, 'base64'
 to_base64 = (val) -> val?.toString 'base64'


updated watch tasks

updated watch tasks

--- app/templates/grunt/contrib-watch.coffee
+++ app/templates/grunt/contrib-watch.coffee
@@ -43,7 +43,7 @@
     <% } %>
     <% } %><% if (precompilerJS) { %>js:
       files: [
-        'assets/javascript/**/*.js'
+        'assets/js/**/*.js'
       ]
       tasks: [
         'jshint'
@@ -52,6 +52,12 @@
       options:
         livereload: true
         spawn: false
+    <% } %><% if (!precompilerJade) { %>html:
+      files: [
+        'assets/partial/**/*.html'
+      ]
+      options:
+        livereload: true
     <% } %><% if (precompilerLess) { %>less:
       files: [
         '<%%= less.dev.options.paths[0] %>/**/*.less'


fix issue with selects auto filling

fix issue with selects auto filling

--- _source/client/templates/components/forms/select.coffee
+++ _source/client/templates/components/forms/select.coffee
@@ -17,7 +17,7 @@

     self = @

-    if self.data().preFill
+    if self.data().preFill and self.data().preFill > 0
       self.value.set self.data().preFill

     # if Apollos.isMobile()


Add several memes, fix typo.

Add several memes, fix typo.

--- src/memes.coffee
+++ src/memes.coffee
@@ -29,13 +29,18 @@
   "badchoice": "Milk Was a Bad Choice (anchorman)",
   "biw": "BABY Insanity Wolf",
   "blb": "Bad Luck Brian",
+  "boat": "I should buy a boat cat",
   "both": "Why Not Both?",
   "bs": "This is Bull, Shark",
   "buzz": "X, X Everywhere",
+  "captain": "I'm the captain now",
   "cb": "Confession Bear",
+  "cbg": "Comic Book Guy",
+  "ch": "Captain Hindsight",
   "chosen": "You Were the Chosen One!",
   "crazypills": "I Feel Like I'm Taking Crazy Pills",
   "doge": "Doge",
+  "dwight": "Schrute Facts",
   "elf": "You Sit on a Throne of Lies",
   "ermg": "Ermahgerd",
   "fa": "Forever Alone",
@@ -47,24 +52,28 @@
   "hipster": "Hipster Barista",
   "icanhas": "I can Has Cheezburger",
   "interesting": "The Most Interesting Man in the World",
+  "ive": "Jony Ive Redesigns Things",
   "iw": "Insanity Wolf",
   "jetpack": "Jetpack Guy / Nothing to Do Here",
+  "joker": "It's Simple, Kill the Batman",
   "jw": "Probably Not a Good Idea",
   "keanu": "Conspiracy Keanu",
   "kermit": "But That's None of My Business",
   "live": "Do It Live!",
   "ll": "Laughing Lizard",
+  "mmm": "Minor Mistake Marvin",
   "mordor": "One Does Not Simply Walk into Mordor",
   "morpheus": "Matrix Morpheus",
   "officespace": "That Would be Great",
   "oag": "Overly Attached Girlfriend",
   "oprah": "Oprah You Get a Car",
   "philosoraptor": "Philosoraptor",
+  "red": "Oh, Is that what we're going to do today?",
   "regret": "I Immediately Regret This Decision (anchorman)",
   "remembers": "Pepperidge Farm Remembers",
   "sadfrog": "Sad Frog / Feels Bad Man",
   "sarcasticbear": "Sarcastic Bear",
-  "sb": "Scumbag Brian",
+  "sb": "Scumbag Brain",
   "sf": "Sealed Fate",
   "ski": "Super Cool Ski Instructor (You're gonna have a bad time)",
   "sohappy": "I Would Be So Happy",


endless fancy select

endless fancy select

--- app/assets/javascripts/ab_admin/core/ui_utils.js.coffee
+++ app/assets/javascripts/ab_admin/core/ui_utils.js.coffee
@@ -123,11 +123,11 @@

             log cond

-          data = {q: term, class: $el.data('class'), token: true}
+          data = {q: term, class: $el.data('class'), token: true, page: page}
           _.extend(data, cond)

         results: (data, page) ->
-          results: data
+          results: data, more: data.length > 0

     $el.select2(options)


container refactoring

container refactoring

--- app/assets/javascripts/luca/containers/container.coffee
+++ app/assets/javascripts/luca/containers/container.coffee
@@ -11,13 +11,16 @@

 container.replaces                "Luca.Container"

-container.defines
+container.publicConfiguration
+  components:[]
+
+container.privateConfiguration
+  emptyContainerElements: false
   className: 'luca-ui-container'
   componentTag: 'div'
   componentClass: 'luca-ui-panel'
   isContainer: true
   rendered: false
-  components: []

   # @componentEvents provides declarative syntax for responding to events on
   # the components in this container.  the format of the syntax is very similar
@@ -55,6 +58,11 @@

     Luca.View::initialize.apply @, arguments

+  remove: ()->
+    Luca.View::remove.apply(@, arguments)
+    @eachComponent (component)->
+      component.remove?()
+
   # Rendering Pipeline
   #
   # A container has nested components.  these components
@@ -262,6 +270,9 @@
           # try in the window context.  this is almost always certainly a bug
           # so look into wtf is going on and which components are problematic
           containerElement = @$( component.container ).eq(0) if containerElement.length is 0
+
+        if @emptyContainerElements is true
+          containerElement.empty()

         containerElement.append( component.el )

@@ -347,7 +358,10 @@

   allChildren: ()->
     children = @components
-    grandchildren = _( @subContainers() ).invoke('allChildren')
+
+    grandchildren = _( @subContainers() ).map (component)->
+      component?.allChildren?()
+
     _([children,grandchildren]).chain().compact().flatten().value()

   findComponentForEventBinding: (nameRoleOrGetter, deep=true)->
@@ -355,11 +369,11 @@

   findComponentByGetter: (getter, deep=false)->
     _( @allChildren() ).detect (component)->
-      component.getter is getter
+      component?.getter is getter

   findComponentByRole: (role,deep=false)->
     _( @allChildren() ).detect (component)->
-      component.role is role or component.type is role or component.ctype is role
+      component?.role is role or component?.type is role or component?.ctype is role

   findComponentByType: (desired,deep=false)->
     _( @allChildren() ).detect (component)->
@@ -433,6 +447,8 @@
     _.flatten( components )

+container.register()
+
 # This is the method by which a container injects the rendered child views
 # into the DOM.  It will get passed the container object, and the component
 # that is being rendered.
@@ -472,17 +488,16 @@
   container = @

   childrenWithGetter = _( @allChildren() ).select (component)->
-    component.getter?
+    component?.getter?

   _( childrenWithGetter ).each (component)->
-    container[ component.getter ] ||= ()->
-      component
+    container[ component.getter ] ||= ()-> component

 createMethodsToGetComponentsByRole = ()->
   container = @

   childrenWithRole = _( @allChildren() ).select (component)->
-    component.role?
+    component?.role?

   _( childrenWithRole ).each (component)->
     getter = _.str.camelize( "get_" + component.role )


Fix result order being taken from wrong array

--- resources/assets/coffee/osu_common.coffee
+++ resources/assets/coffee/osu_common.coffee
@@ -251,7 +251,7 @@
   # TODO: add support for multiple badges and/or move server side?
   # note: the display priority is as defined, from left to right
   userGroupBadge: (user) ->
-    _.intersection(_.concat(user.default_group, user.groups), ['bot', 'dev', 'gmt', 'nat', 'bng', 'bng_limited', 'support', 'alumni'])[0]
+    _.intersection(['bot', 'dev', 'gmt', 'nat', 'bng', 'bng_limited', 'support', 'alumni'], _.concat(user.default_group, user.groups))[0]

   navigate: (url, keepScroll, {action = 'advance'} = {}) ->
     osu.keepScrollOnLoad() if keepScroll


Update versions.coffee

Update versions.coffee


fix safari-11 webcam capture depracation issue

fixes COMMS-567

test plan:
- test in Safari 10 and 11
- enable user avatars
- go to /profile/settings and click the edit button on the user avatar
- select the "Take a Picture" tab
- note that webcam picture capture works

Change-Id: I54a117a9629e11225944288cfaffa90c119187cf
Reviewed-on: https://gerrit.instructure.com/134362
Tested-by: Jenkins
Reviewed-by: Venk Natarajan <f9eab59b292314637a3c917fea4742741e36a19b@instructure.com>
QA-Review: KC Naegle <9fbecd016338f9910821a8a5d5d0ae4fc9abfaa5@instructure.com>
Product-Review: Felix Milea-Ciobanu <9384aa226ac9d3237ed0bc52e18ffcb251ee0bf7@instructure.com>

--- app/coffeescripts/views/profiles/TakePictureView.coffee
+++ app/coffeescripts/views/profiles/TakePictureView.coffee
@@ -61,7 +61,8 @@

     displayMedia: (@stream) =>
       @$video.removeClass('pending')
-      @$video.attr('src', window.URL.createObjectURL(@stream))
+      try @$video.get(0).srcObject = @stream
+      catch err then @$video.attr('src', window.URL.createObjectURL(@stream))
       @$video.on('onloadedmetadata loadedmetadata', _.once(@onMediaMetadata).bind(this))

     onMediaMetadata: (e) ->


Add Ajax call in intervention input computation

--- app/assets/javascripts/backend/interventions.js.coffee
+++ app/assets/javascripts/backend/interventions.js.coffee
@@ -183,6 +183,14 @@
             options.success.call(this, data, status, request) if options.success?
             console.groupEnd()

+            if options['display_cost']
+              targettedElement = options['targetted_element']
+              parentBlock = $(targettedElement).closest('.nested-product-parameter')
+              quantity = $(parentBlock).find('input[data-intervention-field="quantity-value"]').val()
+              unitName = $(parentBlock).find('select[data-intervention-field="quantity-handler"]').val()
+              E.interventionForm.displayCost(targettedElement, quantity, unitName)
+
+
     hideKujakuFilters: (hideFilters) ->
       if hideFilters
         $('.feathers input[name*="nature"], .feathers input[name*="state"]').closest('.feather').hide()
@@ -278,12 +286,12 @@
   #  selector:initialized
   $(document).on 'selector:change', '*[data-intervention-updater]', (event) ->
     $(this).each ->
-      E.interventions.refresh $(this)
-
-      parentBlock = $(event.target).closest('.nested-product-parameter')
-      quantity = $(parentBlock).find('input[data-intervention-field="quantity-value"]').val()
-      unitName = $(parentBlock).find('select[data-intervention-field="quantity-handler"]').val()
-      E.interventionForm.displayCost(event.target, quantity, unitName)
+      options = {}
+      options['display_cost'] = true
+      options['targetted_element'] = $(event.target)
+
+      E.interventions.refresh $(this), options
+

   $(document).on 'keyup', 'input[data-selector]', (e) ->
     $(this).each ->
@@ -291,19 +299,19 @@

   $(document).on 'keyup change', 'input[data-intervention-updater]:not([data-selector])', (e) ->
     $(this).each ->
-      E.interventions.refresh $(this)
-
-      quantity = $(event.target).val()
-      unitName = $(event.target).parent().find('select[data-intervention-field="quantity-handler"]').val()
-      E.interventionForm.displayCost(event.target, quantity, unitName)
+      options = {}
+      options['display_cost'] = true
+      options['targetted_element'] = $(event.target)
+
+      E.interventions.refresh $(this), options

   $(document).on 'keyup change', 'select[data-intervention-updater]', ->
     $(this).each ->
-      E.interventions.refresh $(this)
-
-      quantity = $(event.target).parent().find('input[data-intervention-field="quantity-value"]').val()
-      unitName = $(event.target).val()
-      E.interventionForm.displayCost(event.target, quantity, unitName)
+      options = {}
+      options['display_cost'] = true
+      options['targetted_element'] = $(event.target)
+
+      E.interventions.refresh $(this), options

   $(document).on "keyup change dp.change", ".nested-fields.working-period:first-child input.intervention-started-at", (e) ->
     $(this).each ->


API service - add ability to add custom headers to be sent with all API requests.

--- app/assets/javascripts/monster_mithril/core/api.coffee
+++ app/assets/javascripts/monster_mithril/core/api.coffee
@@ -33,6 +33,7 @@

 class ApiBase
   @preload = (typeof _isomorphic != 'undefined')
+  @custom_headers = {}

   get: request_wrap('GET')
   post: request_wrap('POST')
@@ -44,7 +45,9 @@
     iso_pathless = (iso_path == undefined)

     config = (xhr)=>
-      xhr.setRequestHeader 'X-CSRF-Token',  $dom.get("meta[name='csrf-token']")[0].content
+      for k, v of @custom_headers
+        xhr.setRequestHeader k, v
+      xhr

     ev_success = (data)->
       $broadcast iso_path, data unless iso_pathless


Call JS to transform tabs to dropdown if it is mobile

--- app/assets/javascripts/neighborly/neighborly.js.coffee
+++ app/assets/javascripts/neighborly/neighborly.js.coffee
@@ -68,6 +68,9 @@
     $('input, select, textarea').blur ->
       $('meta[name=viewport]').attr('content', 'width=device-width,initial-scale=1,maximum-scale=10')

+    if jQuery.browser.mobile
+      Neighborly.TransformTabsToDropdown.init()
+
   Loading:
     show: ->
       $('#loading').addClass('show')


Tests.

Tests.

--- apps/partner/test/client/overview.coffee
+++ apps/partner/test/client/overview.coffee
@@ -56,7 +56,9 @@
         @profile = new Profile fabricate 'partner_profile'
         @partner = new Partner @profile.get 'owner'
         @template = sinon.stub()
+        @artistsGridTemplate = sinon.stub()
         mod.__set__ 'template', @template
+        mod.__set__ 'artistsGridTemplate', @artistsGridTemplate
         @view = new PartnerOverviewView
           profile: @profile
           partner: @partner
@@ -67,17 +69,17 @@
     afterEach ->
       Backbone.sync.restore()

-    describe '#fetchAndOrganizeShows', ->
+    describe '#renderArtistsGrid', ->

-      it 'fetches all the partner artists and renders them', ->
+      it 'fetches all the partner artists and renders them in grid', ->
         @partnerArtists.fetchUntilEnd = (options) =>
           @partnerArtists.add @pas
           options.success?()
-        @view.renderArtists = sinon.stub()
         @view.initializeArtists()
-        @view.renderArtists.calledOnce.should.be.ok
-        @view.renderArtists.args[0][0].should.have.lengthOf 2
-        @view.renderArtists.args[0][0][0].label.should.equal 'represented artists'
-        @view.renderArtists.args[0][0][0].list.should.have.lengthOf 7
-        @view.renderArtists.args[0][0][1].label.should.equal 'works available by'
-        @view.renderArtists.args[0][0][1].list.should.have.lengthOf 4
+        @artistsGridTemplate.calledOnce.should.be.ok
+        @artistsGridTemplate.args[0][0].partner.get('name').should.equal @partner.get('name')
+        @artistsGridTemplate.args[0][0].groups.should.have.lengthOf 2
+        @artistsGridTemplate.args[0][0].groups[0].label.should.equal 'represented artists'
+        @artistsGridTemplate.args[0][0].groups[0].list.should.have.lengthOf 7
+        @artistsGridTemplate.args[0][0].groups[1].label.should.equal 'works available by'
+        @artistsGridTemplate.args[0][0].groups[1].list.should.have.lengthOf 4


Revert "Revert "Fix multiple ^{username} mentions""

This reverts commit e8cbb262c4820f3f1ba6c0d0f280d1be7d993cdb.

--- assets/javascripts/helpers/url.js.coffee
+++ assets/javascripts/helpers/url.js.coffee
@@ -72,25 +72,26 @@
         _entities[entity] = true

     if TentStatus.config.tent_host_domain
-      m = text.match(
-        new RegExp("[\\^]([a-z0-9]+(?:\.#{TentStatus.config.tent_host_domain})?)(?=[\\W]|\\$)")
-      )
-      if entity = m?[1]
-        original_text = entity
-        _length = original_text.length
-        _indices = TentStatus.Helpers.substringIndices(text, original_text)
-        if entity.match(new RegExp(TentStatus.config.tent_host_domain))
-          entity = "https://#{entity}"
-        else
-          entity = "https://#{entity}.#{TentStatus.config.tent_host_domain}"
+      _regex = new RegExp("[\\^]([a-z0-9]{2,}(?:\.#{TentStatus.config.tent_host_domain})?)(?=[\\W]|$)")
+      _offset = 0
+      while m = _regex.exec(text.substr(_offset, text.length))
+        if entity = RegExp.$1
+          _offset += text.substr(_offset, text.length).indexOf(entity)
+          original_text = entity
+          _length = original_text.length
+          _indices = TentStatus.Helpers.substringIndices(text, original_text)
+          if entity.match(new RegExp(TentStatus.config.tent_host_domain))
+            entity = "https://#{entity}"
+          else
+            entity = "https://#{entity}.#{TentStatus.config.tent_host_domain}"

-        _mentions.push {
-          entity: entity
-          text: original_text
-          url: entity
-          indices: _indices
-        }
-        _entities[entity] = true
+          _mentions.push {
+            entity: entity
+            text: original_text
+            url: entity
+            indices: _indices
+          }
+          _entities[entity] = true

     _mentions


Display 0 instead of blank field if reposCount is null

--- assets/scripts/app/models/account.coffee
+++ assets/scripts/app/models/account.coffee
@@ -5,8 +5,13 @@
   login:       Ember.attr('string')
   name:        Ember.attr('string')
   type:        Ember.attr('string')
-  reposCount:  Ember.attr(Number)
+  _reposCount:  Ember.attr(Number, key: 'repos_count')

   urlGithub: (->
     "http://github.com/#{@get('login')}"
   ).property()
+
+  # TODO: maybe it would be good to add a "default" value for Ember.attr
+  reposCount: (->
+    @get('_reposCount') || 0
+  ).property('_reposCount')


Expose experiment APIs

Expose experiment APIs

--- atom/browser/lib/chrome-extension.coffee
+++ atom/browser/lib/chrome-extension.coffee
@@ -32,6 +32,7 @@
       startPage: page
       name: manifest.name
       srcDirectory: srcDirectory
+      exposeExperimentalAPIs: true
     extensionInfoMap[manifest.name]

 # The loaded extensions cache and its persistent path.


Add overlayer class to ReactEditorView

Add overlayer class to ReactEditorView

Closed #2442

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -5,7 +5,9 @@

 module.exports =
 class ReactEditorView extends View
-  @content: -> @div class: 'editor react-wrapper'
+  # The `overlayer` class is included for backwards compatibility with
+  # context menus. It should be removed in v1.0.0
+  @content: -> @div class: 'editor react-wrapper overlayer'

   focusOnAttach: false


tweaking compileWithDeclarations

tweaking compileWithDeclarations

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -209,14 +209,10 @@
   # declarations of all inner variables pushed up to the top.
   compileWithDeclarations: (o) ->
     code = @compileNode(o)
-    code = """
-      #{@tab}var #{ o.scope.compiledAssignments().replace /\n/g, '$&' + @tab };
-      #{code}
-    """ if o.scope.hasAssignments this
-    code = """
-      #{@tab}var #{o.scope.compiledDeclarations()};
-      #{code}
-    """ if not o.globals and o.scope.hasDeclarations this
+    if o.scope.hasAssignments this
+      code = "#{@tab}var #{ o.scope.compiledAssignments().replace /\n/g, '$&' + @tab };\n#{code}"
+    if not o.globals and o.scope.hasDeclarations this
+      code = "#{@tab}var #{o.scope.compiledDeclarations()};\n#{code}"
     code

   # Compiles a single expression within the expressions body. If we need to


Ignore Parens entirely.

Ignore Parens entirely.

They wreak havoc with the lisp AST.

Milestone: Now passing the basic tests for conditionals included with
CoffeeScript!

--- src/nodes.coffee
+++ src/nodes.coffee
@@ -1530,10 +1530,7 @@
     if expr instanceof Value and expr.isAtomic()
       expr.front = @front
       return expr.compile o
-    code = expr.compile o, LEVEL_PAREN
-    bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
-      (expr instanceof For and expr.returns))
-    if bare then code else "(#{code})"
+    expr.compile o, LEVEL_PAREN

 #### For


Move convert() to private (no change on code)

--- src/numbo.coffee
+++ src/numbo.coffee
@@ -47,35 +47,36 @@
 	zhTW: zhTW
 	zhCN: zhCN

+langNames = ['enUS', 'zhTW', 'zhCN']
+convert = (input, options...) ->
+	# `input` can be string or number
+	# `options` can be language ('zhTW') and style ('number', 'amount' or 'cheque')
+	if options.length is 0 # not provide any option
+		langs.enUS(input)
+	else
+		zeroCent = false #todo: temp setup
+		parsedOptions = parseOptions(langNames, options)
+		otherOptions = parsedOptions.otherOptions
+
+		# Skip and return null if any error occurs
+		if parsedOptions.error is true then null
+		# Currently, otherOptions is not allowed yet.
+		else if otherOptions.length > 0 and otherOptions.toString() isnt 'zeroCent'
+			#todo: the condition about 'zeroCent' should be done in parseOption()
+			console.log 'Error: Invalid option. Possibly more than one template is selected. Or, some option(s) are parsed into [otherOptions], but it is not allowed in current version yet.'
+			null
+		else
+			# execute convert() according to options provided
+			if otherOptions.toString() is 'zeroCent'
+				#todo: should be done in parseOption()
+				zeroCent = true
+			langs[parsedOptions.plugin](input, parsedOptions.template, zeroCent) #todo: temp setup for zeroCent
+
+
 class Numbo

-	langNames = ['enUS', 'zhTW', 'zhCN']
-
-	convert: (input, options...) ->
-		# `input` can be string or number
-		# `options` can be language ('zhTW') and style ('number', 'amount' or 'cheque')
-		if options.length is 0 # not provide any option
-			langs.enUS(input)
-		else
-			zeroCent = false #todo: temp setup
-			parsedOptions = parseOptions(langNames, options)
-			otherOptions = parsedOptions.otherOptions
-
-			# Skip and return null if any error occurs
-			if parsedOptions.error is true then null
-			# Currently, otherOptions is not allowed yet.
-			else if otherOptions.length > 0 and otherOptions.toString() isnt 'zeroCent'
-				#todo: the condition about 'zeroCent' should be done in parseOption()
-				console.log 'Error: Invalid option. Possibly more than one template is selected. Or, some option(s) are parsed into [otherOptions], but it is not allowed in current version yet.'
-				null
-			else
-				# execute convert() according to options provided
-				if otherOptions.toString() is 'zeroCent'
-					#todo: should be done in parseOption()
-					zeroCent = true
-				langs[parsedOptions.plugin](input, parsedOptions.template, zeroCent) #todo: temp setup for zeroCent
-
 # Public methods
+Numbo::convert = convert
 Numbo::tools = utils
 Numbo::enUS = enUS
 Numbo::zhTW = zhTW


Change scope to head. Remove first() function.

Agreed - no need to use first() when most of the time the title and meta description will be in the 'head' section. I still think it is necessary to strip white space for sites that have newlines inside the single title tag though.

--- src/scripts/http-info.coffee
+++ src/scripts/http-info.coffee
@@ -45,8 +45,8 @@
     done = (errors, window) ->
       unless errors
         $ = window.$
-        title = $('title').first().text().replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim()
-        description = $('meta[name=description]').first().attr('content').replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim() || ''
+        title = $('head title').text().replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim()
+        description = $('head meta[name=description]').attr('content').replace(/(\r\n|\n|\r)/gm,'').replace(/\s{2,}/g,' ').trim() || ''

         if title and description and not process.env.HUBOT_HTTP_INFO_IGNORE_DESC
           msg.send "#{title}\n#{description}"


another command fix

another command fix

--- src/scripts/lastfm_np.coffee
+++ src/scripts/lastfm_np.coffee
@@ -32,5 +32,5 @@
 module.exports = (robot) ->
   robot.respond /what(')?s (.*) playing/i, (msg) ->
     getSong(msg)
-  robot.respond /what(')?s am I playing/i, (msg) ->
+  robot.respond /what am I playing/i, (msg) ->
     getSong(msg, msg.message.user.name)


Update src/scripts/translate.coffee

Update src/scripts/translate.coffee

simply changed '&&' to 'and' to make it more coffee-fied

--- src/scripts/translate.coffee
+++ src/scripts/translate.coffee
@@ -97,10 +97,10 @@
       .header('User-Agent', 'Mozilla/5.0')
       .get() (err, res, body) ->
         data   = body
-        if data.length > 4 && data[0] == '['
+        if data.length > 4 and data[0] == '['
           parsed = eval(data)
           language =languages[parsed[2]]
-          parsed = parsed[0] && parsed[0][0] && parsed[0][0][0]
+          parsed = parsed[0] and parsed[0][0] and parsed[0][0][0]
           if parsed
             if msg.match[2] is undefined
               msg.send "#{term} is #{language} for #{parsed}"


Remove unused code.

Remove unused code.

--- src/scripts/wikipedia.coffee
+++ src/scripts/wikipedia.coffee
@@ -40,6 +40,7 @@

 wikiMe = (msg, query, cb) ->
   articleURL = makeArticleURL(makeTitleFromQuery(query))
+
   msg.http(articleURL)
     .header('User-Agent', 'Hubot Wikipedia Script')
     .get() (err, res, body) ->
@@ -51,8 +52,7 @@
       if /does not have an article/.test body
         return cb "Wikipedia has no idea what you're talking about."

-      [rawHTML, articleTitle] = extractHTMLAndTitleFromBody(body)
-      paragraphs = parseHTML(rawHTML, "p")
+      paragraphs = parseHTML(body, "p")

       bodyText = findBestParagraph(paragraphs) or "Have a look for yourself:"
       cb bodyText, articleURL
@@ -66,9 +66,6 @@
     return (childrenOfType(child, nodeType) for child in root.children)

   []
-
-extractHTMLAndTitleFromBody = (body) ->
-  [body, parseHTML(body, "h1#firstHeading").data]

 findBestParagraph = (paragraphs) ->
   return null if paragraphs.length is 0


Do not show 'choose hero' in game menu for game dev, web dev levels

--- app/views/play/menu/GameMenuModal.coffee
+++ app/views/play/menu/GameMenuModal.coffee
@@ -48,7 +48,7 @@
     context

   showsChooseHero: ->
-    return false if @level?.isType('course', 'course-ladder')
+    return false if @level?.isType('course', 'course-ladder', 'game-dev', 'web-dev')
     return false if @level?.get('assessment') is 'open-ended'
     return false if @options.levelID in ['zero-sum', 'ace-of-coders', 'the-battle-of-sky-span']
     return true


fixed crash when trying to draw 0 pieces in box

--- platform/src/gamEvolve.coffee
+++ platform/src/gamEvolve.coffee
@@ -220,6 +220,9 @@
       # Evaluate values of the "from" clauses
       # TODO: in the case of models, get reference to model rather than evaluate the data here
       bindingValues = GE.compileParameter(bindingExpression, constants, oldBindings).get()
+      # If bindingValues is empty, drop out
+      if bindingValues.length == 0 then return []
+
       index = 0
       for bindingIndex in [0..bindingValues.length - 1]
         # Avoid polluting old object, and avoid creating new properties


Fix hipchat

Fix hipchat

--- plugins/hipchat/index.coffee
+++ plugins/hipchat/index.coffee
@@ -5,7 +5,7 @@
   API_BASE_URL = "https://api.hipchat.com/"

   getApiUrl = (config, path) ->
-    if config.host?
+    if config.host? && config.host != ""
       host = if /\/$/.test(config.host) then config.host else config.host + "/"
     else
       host = API_BASE_URL


hub/raw server index: small adjustemnts, next step would be to use some template rendering to avoid this html madness

--- src/smc-hub/share/raw.coffee
+++ src/smc-hub/share/raw.coffee
@@ -46,21 +46,30 @@
         # CREATE INDEX IF NOT EXISTS project_id_2char ON public_paths (substring(project_id::text from 0 for 2));
         links = ("#{l}..." for l in HEXCHARS)
         links = links.join('
')
-        res.send(links)
+        out = """
+
Public Projects

+        #{links}
+        """
+        res.send(out)

     router.get /^\/[0-9a-z]$/, (req, res) ->
         # matches one uuid char
         c1 = req.path[1]
         links = ("#{c1}#{l}..." for l in HEXCHARS)
         links = links.join('
')
-        res.send(links)
+        out = """
+
Public Projects

+        UP

+        #{links}
+        """
+        res.send(out)

     router.get /^\/[0-9a-z]{2}$/, (req, res) ->
         # matches two uuid char
         c2 = req.path[1..2]
         console.log(c2)
         opts.database._query
-            query : 'SELECT project_id FROM public_paths'
+            query : 'SELECT DISTINCT(project_id) FROM public_paths'
             where :
                 "substring(project_id::text from 1 for 2) = $::TEXT" : c2
             order_by : 'project_id'
@@ -68,8 +77,11 @@
                 if err
                     res.send(JSON.stringify(err))
                 else
-                    out = "Found #{result.rowCount}
"
-                    out += "UP
"
+                    out = """
+
Public Projects

+                    UP

+                    Found #{result.rowCount}

+                    """
                     for row in result.rows
                         pid = row.project_id
                         out += "#{pid}
"


More logging.

More logging.

--- src/adapters/campfire.coffee
+++ src/adapters/campfire.coffee
@@ -32,7 +32,8 @@
     withAuthor = (callback) -> (id, created, room, user, body) ->
       if user is null
         user = bot.Me
-        author = self.userForId(user.id)
+        console.log user.id
+        author = self.userForId(user.id, user)
         author.room = room
         callback id, created, room, user, body, author
       else
@@ -58,7 +59,8 @@

     bot.on "TimestampMessage", withAuthor (id, created, room, user , author) ->
       console.log "Timestamp"
-      console.log author
+      console.log author.id
+      console.log author.room

     bot.Me (err, data) ->
       bot.info = data.user


Clear project listing search on submit

Clear project listing search on submit

Also remove some inappropriate usage of redux.getActions

--- src/smc-webapp/projects.cjsx
+++ src/smc-webapp/projects.cjsx
@@ -830,7 +830,7 @@
     render_deleted_button: ->
         style = if @props.deleted then 'warning' else "default"
         if @props.show_deleted_button
-            <Button onClick={=>redux.getActions('projects').setState(deleted: not @props.deleted)} bsStyle={style}>
+            <Button onClick={=>@actions('projects').setState(deleted: not @props.deleted)} bsStyle={style}>
                 <Icon name={if @props.deleted then 'check-square-o' else 'square-o'} fixedWidth /> Deleted
             </Button>
         else
@@ -839,7 +839,7 @@
     render_hidden_button: ->
         style = if @props.hidden then 'warning' else "default"
         if @props.show_hidden_button
-            <Button onClick = {=>redux.getActions('projects').setState(hidden: not @props.hidden)} bsStyle={style}>
+            <Button onClick = {=>@actions('projects').setState(hidden: not @props.hidden)} bsStyle={style}>
                 <Icon name={if @props.hidden then 'check-square-o' else 'square-o'} fixedWidth /> Hidden
             </Button>

@@ -860,7 +860,7 @@
         open_first_project : undefined

     clear_and_focus_input: ->
-        redux.getActions('projects').setState(search: '')
+        @actions('projects').setState(search: '')
         @refs.projects_search.clear_and_focus_search_input()

     delete_search_button: ->
@@ -876,7 +876,7 @@
             type         = 'search'
             value        = {@props.search}
             placeholder  = 'Search for projects...'
-            on_change    = {(value)=>redux.getActions('projects').setState(search: value)}
+            on_change    = {(value)=>@actions('projects').setState(search: value)}
             on_submit    = {@props.open_first_project}
             button_after = {@delete_search_button()}
         />
@@ -1122,7 +1122,7 @@
         user_map : undefined

     show_all_projects: ->
-        redux.getActions('projects').setState(show_all : not @props.show_all)
+        @actions('projects').setState(show_all : not @props.show_all)

     render_show_all: ->
         if @props.projects.length > MAX_DEFAULT_PROJECTS
@@ -1340,6 +1340,7 @@
     open_first_project: ->
         project = @visible_projects()[0]
         if project?
+            @actions('projects').setState(search : '')
             @actions('projects').open_project(project_id: project.project_id, switch_to: true)
     ###
     # Consolidate the next two functions.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -131,6 +131,9 @@
   catch e
     this.toString()

+###*
+
+###
 String::startsWith = (str) ->
   @lastIndexOf(str, 0) is 0


More string extensions

More string extensions

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -1,3 +1,30 @@
+###*
+Returns true if this string only contains whitespace characters.
+
+@name blank
+@methodOf String#
+
+@returns Whether or not this string is blank.
+@type Boolean
+###
+String::blank = ->
+  /^\s*$/.test(this)
+
+###*
+@name camelize
+@methodOf String#
+###
+String::camelize = ->
+  this.trim().replace /(\-|_|\s)+(.)?/g, (match, separator, chr) ->
+    if chr then chr.toUpperCase() else ''
+
+###*
+@name capitalize
+@methodOf String#
+###
+String::capitalize = ->
+  this.charAt(0).toUpperCase() + this.substring(1).toLowerCase()
+
 ###*
 Return the class or constant named in this string.

@@ -13,6 +40,13 @@
     that
   else
     undefined
+
+###*
+@name humanize
+@methodOf String#
+###
+String::humanize = ->
+  this.replace(/_id$/, "").replace(/_/g, " ").capitalize()

 ###*
 Parse this string as though it is JSON and return the object it represents. If it
@@ -32,14 +66,11 @@
     return this

 ###*
-Returns true if this string only contains whitespace characters.
+@name titleize
+@methodOf String#
+###
+String::titleize = ->
+    this.split(/[- ]/).map (word) ->
+      word.capitalize()
+    .join(' ')

-@name blank
-@methodOf String#
-
-@returns Whether or not this string is blank.
-@type Boolean
-###
-String::blank = ->
-  /^\s*$/.test(this)
-


Allow INSTALL_PATH env variable for now

Allow INSTALL_PATH env variable for now

--- src/ievm.coffee
+++ src/ievm.coffee
@@ -49,12 +49,11 @@
     Win8: 0

   # The ievms home (`INSTALL_PATH` in ievms parlance).
-  @ievmsHome: path.join process.env.HOME, '.ievms'
+  @ievmsHome: process.env.INSTALL_PATH ? path.join process.env.HOME, '.ievms'

   # The command used to install virtual machines via ievms.
-  @ievmsCmd: 'cd ~/Code/ievms && cat ievms.sh | bash'
-  # @ievmsCmd:
-  #   'curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | bash'
+  @ievmsCmd: process.env.IEVMS_CMD ?
+    'curl -s https://raw.github.com/xdissent/ievms/master/ievms.sh | bash'

   # The host IP as seen by the VM.
   @hostIp = '10.0.2.2'


spaces around the +

spaces around the +

--- src/app/language-mode.coffee
+++ src/app/language-mode.coffee
@@ -84,7 +84,7 @@
         indent = @minIndentLevelForRowRange(start, end)
         indentString = @editSession.buildIndentString(indent)
         for row in [start..end]
-          buffer.change([[row, 0], [row, indentString.length]], indentString+commentStartString)
+          buffer.change([[row, 0], [row, indentString.length]], indentString + commentStartString)

   # Folds all the foldable lines in the buffer.
   foldAll: ->


Removed deprecated api directory

Removed deprecated api directory

--- api/plunks.coffee
+++ api/plunks.coffee
@@ -1,57 +0,0 @@
-mime = require("mime")
-_ = require("underscore")._
-
-module.exports = (store) ->
-  plunks = require("../lib/plunks")(store)
-
-  load: (req, id, cb) ->
-    # Fetch the plunk (async)
-    plunks.fetch id, (err, plunk) ->
-      return cb(err) if err
-
-      # Although not necessarily the 'appropriate' place to put this check for
-      # a token passed in the query string or in the Authorization header.
-      if req.query.token and req.query.token == plunk.token
-        req.authorized = true
-      else if auth = req.header("Authorization")
-        [token] = auth.match(/^token (\S+)$/i)
-
-        if token and token == plunk.token
-          req.authorized = true
-
-      cb(null, plunk)
-
-  index: (req, res, next) ->
-    plunks.list (err, plunks) ->
-      return next({number: 500, message: err}) if err
-
-      _.each plunks, (plunk) ->
-        delete plunk.token
-
-      return res.json(plunks)
-
-  create: (req, res, next) ->
-    plunks.create req.body, (err, plunk) ->
-      return next(err) if err
-      return res.json(plunk, 201)
-
-
-  show: (req, res, next) ->
-    delete req.plunk.token unless req.authorized
-
-    return res.json(req.plunk)
-
-  update: (req, res, next) ->
-    return next({number: 404, message: "Not authorized"}) unless req.authorized
-
-    plunks.update req.plunk, req.body, (err, plunk) ->
-      return next(err) if err
-      return res.json(plunk, 200)
-
-  destroy: (req, res, next) ->
-    return next({number: 404, message: "Not found"}) unless req.authorized
-
-    store.destroy req.plunk.id, (err) ->
-      return next({number: 500, message: err}) if err
-      return res.send(204)
-


clean up crypto.coffee

clean up crypto.coffee

--- api/services/crypto.coffee
+++ api/services/crypto.coffee
@@ -1,5 +1,11 @@

 # encrypts and decrypts data
+
+# References:
+# http://stackoverflow.com/questions/29807108/derive-key-and-iv-from-string-for-aes-encryption-in-cryptojs-and-php
+# http://stackoverflow.com/questions/23188593/cryptojs-check-if-aes-passphrase-is-correct
+# http://stackoverflow.com/questions/27179685/how-to-encrypt-a-message-at-client-side-using-crypto-js-library-and-decrypt-it-a
+
 CryptoJS = require('crypto-js')
 cryptoConfig = require('../config/crypto')
 iterations = 500
@@ -27,10 +33,7 @@
   .toString()

   # salt will be hex 32 in length
-  result = salt.toString() + encrypted
-  console.log "salt: #{salt.toString()}"
-  console.log "encrypted: #{encrypted}"
-  result
+  return salt.toString() + encrypted

 module.exports.decrypt = (text) ->


improve shortened version of array toString

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -1359,7 +1359,7 @@
       else if isFunction(obj)
         "function"
       else if isArray(obj)
-        return "{..}" if recursionDepth > 5
+        return "[..]" if recursionDepth > 5
         "[" + _.map(_.toString, obj).toString() + "]"
       else if obj?.toString? and obj.toString!=Object.prototype.toString
         obj.toString()


Add more specs for RootView::openAsync

Add more specs for RootView::openAsync

--- spec/root-view-spec.coffee
+++ spec/root-view-spec.coffee
@@ -1,4 +1,5 @@
 {$, $$, fs, RootView, View} = require 'atom'
+Q = require 'q'
 path = require 'path'
 Pane = require '../src/pane'

@@ -280,26 +281,103 @@
           expect(activePane.focus).not.toHaveBeenCalled()

   describe ".openAsync(filePath)", ->
+    beforeEach ->
+      spyOn(Pane.prototype, 'focus')
+
+    describe "when there is no active pane", ->
+      beforeEach ->
+        rootView.getActivePane().remove()
+        expect(rootView.getActivePane()).toBeUndefined()
+
+      describe "when called with no path", ->
+        it "creates a empty edit session as an item on a new pane, and focuses the pane", ->
+          promise = rootView.openAsync().then (editSession) ->
+            expect(rootView.getActivePane().activeItem).toBe editSession
+            expect(editSession.getPath()).toBeUndefined()
+            expect(rootView.getActivePane().focus).toHaveBeenCalled()
+
+          waitsForPromise ->
+            promise
+
+        it "can create multiple empty edit sessions as items on a pane", ->
+          editSession1 = null
+          editSession2 = null
+
+          waitsForPromise ->
+            rootView.openAsync()
+              .then (o) ->
+                console.log "first"
+                editSession1 = o
+                rootView.openAsync()
+              .then (o) ->
+                console.log "second"
+                editSession2 = o
+
+          runs ->
+            expect(rootView.getActivePane().getItems().length).toBe 2
+            expect(editSession1).not.toBe editSession2
+
+      describe "when called with a path", ->
+        it "creates an edit session for the given path as an item on a new pane, and focuses the pane", ->
+          editSession = null
+          waitsForPromise ->
+            rootView.openAsync('b').then (o) -> editSession = o
+
+          runs ->
+            expect(rootView.getActivePane().activeItem).toBe editSession
+            expect(editSession.getPath()).toBe require.resolve('./fixtures/dir/b')
+            expect(rootView.getActivePane().focus).toHaveBeenCalled()
+
     describe "when there is an active pane", ->
       [activePane] = []
+
       beforeEach ->
         activePane = rootView.getActivePane()
-        spyOn(activePane, 'focus')
+
+      describe "when called with no path", ->
+        it "opens an edit session with an empty buffer as an item on the active pane and focuses it", ->
+          editSession = null
+
+          waitsForPromise ->
+            rootView.openAsync().then (o) -> editSession = o
+
+          runs ->
+            expect(activePane.getItems().length).toBe 2
+            expect(activePane.activeItem).toBe editSession
+            expect(editSession.getPath()).toBeUndefined()
+            expect(activePane.focus).toHaveBeenCalled()

       describe "when called with a path", ->
-        describe "when the active pane does not have an edit session item for the path being opened", ->
-          it "creates a new edit session for the given path in the active editor and returns a promise", ->
-            openHandler = jasmine.createSpy("Open Handler")
-            promise = rootView.openAsync('b', openHandler)
-            expect(activePane.items.length).toBe 1
-            expect(activePane.focus).not.toHaveBeenCalled()
-
+        describe "when the active pane already has an item for the given path", ->
+          it "shows the existing edit session on the pane", ->
+            previousEditSession = activePane.activeItem
+
+            editSession = null
             waitsForPromise ->
-              promise.then (editSession) ->
-                expect(activePane.activeItem).toBe editSession
+              rootView.openAsync('b').then (o) -> editSession = o

             runs ->
-              expect(activePane.items.length).toBe 2
+              expect(activePane.activeItem).toBe editSession
+              expect(editSession).not.toBe previousEditSession
+
+            waitsForPromise ->
+              rootView.openAsync(previousEditSession.getPath()).then (o) -> editSession = o
+
+            runs ->
+              expect(editSession).toBe previousEditSession
+              expect(activePane.activeItem).toBe editSession
+              expect(activePane.focus).toHaveBeenCalled()
+
+        describe "when the active pane does not have an existing item for the given path", ->
+          it "creates a new edit session for the given path in the active pane", ->
+            editSession = null
+
+            waitsForPromise ->
+              rootView.openAsync('b').then (o) -> editSession = o
+
+            runs ->
+              expect(activePane.activeItem).toBe editSession
+              expect(activePane.getItems().length).toBe 2
               expect(activePane.focus).toHaveBeenCalled()

   describe "window:toggle-invisibles event", ->


Update fs spec for new traverseTree callback format

--- spec/stdlib/fs-spec.coffee
+++ spec/stdlib/fs-spec.coffee
@@ -70,16 +70,19 @@

     it "calls fn for every path in the tree at the given path", ->
       paths = []
-      fs.traverseTree fixturesDir, (path) -> paths.push(path)
+      fs.traverseTree fixturesDir, (path) ->
+        paths.push(fs.join(fixturesDir, path))
+        true
       expect(paths).toEqual fs.listTree(fixturesDir)

     it "does not recurse into a directory if it is pruned", ->
       paths = []
       fs.traverseTree fixturesDir, (path, prune) ->
         if path.match(/\/dir$/)
-          prune()
+          false
         else
           paths.push(path)
+          true

       expect(paths.length).toBeGreaterThan 0
       for path in paths


fix handle Travis-CI error

fix handle Travis-CI error

--- spec/vim-state-spec.coffee
+++ spec/vim-state-spec.coffee
@@ -355,17 +355,17 @@

           expect(_.map(editor.getSelections(), (selection) ->
             selection.getText())
-          ).toEqual(['one','three'])
+          ).toEqual(['one', 'three'])

           keydown('V', shift: true)
           expect(_.map(editor.getSelections(), (selection) ->
             selection.getText())
-          ).toEqual(["line one\n","line three\n"])
+          ).toEqual(["line one\n", "line three\n"])

           keydown('v', ctrl: true)
           expect(_.map(editor.getSelections(), (selection) ->
             selection.getText())
-          ).toEqual(['one','three'])
+          ).toEqual(['one', 'three'])

   describe "marks", ->
     beforeEach ->  editor.setText("text in line 1\ntext in line 2\ntext in line 3")


fix async spec finish q

fix async spec finish q

--- specs/ValidatorSpec.coffee
+++ specs/ValidatorSpec.coffee
@@ -185,6 +185,5 @@
 			# process.exit();
 			expect(validated.length).toEqual(atr.rows)
 			validateRows(instrumentType,instrumentSubType,assetToRows)
-			asyncSpecDone()


Add message to context

Add message to context

--- index.coffee
+++ index.coffee
@@ -197,6 +197,7 @@
     user: user
     game_status: game_status[user.id]
     responses: game_responses[user.id]
+    message: bot_message
     text : _(text).deburr().toLowerCase()
     send: (message)->
       my_last_message[user.id] = message


don't throw strings.

don't throw strings.

--- index.coffee
+++ index.coffee
@@ -43,7 +43,7 @@
 play = (rom, extension) ->
   Promise.resolve()
   .then ->
-    throw 'no rom!' if not rom
+    throw new Error 'no rom!' if not rom
     retro.md5 = sparkmd5.ArrayBuffer.hash rom
     Promise.all([
       System.import settings.extensions[extension]


tiny fix

tiny fix

--- index.coffee
+++ index.coffee
@@ -25,6 +25,7 @@
       alter = share.typed.split('')
       alter[alter.length - share.right] = data
       share.typed = alter.join('')
+      share.right--

 frame.on 'resize', ->


fix heroku deploy with hapi

fix heroku deploy with hapi

--- index.coffee
+++ index.coffee
@@ -7,8 +7,7 @@
 root = require './plugins/root'
 registration = require './plugins/registration'

-console.log('env port is ' + process.env.PORT);
-server = new Hapi.Server 'localhost', process.env.PORT || config.port
+server = new Hapi.Server '0.0.0.0', process.env.PORT || config.port

 module.exports.startServer = (cb) ->


Let voxel-workbench handle opening its GUI

Requires deathcap/voxel-workbench@d85cb990095147f93ac25bb78f0dfe22a641833f

--- index.coffee
+++ index.coffee
@@ -259,14 +259,17 @@

     # TODO: major refactor

-    # 1. block interaction TODO: have blocks handle this
+    # 1. block interaction
     if target.voxel? and !game.buttons.crouch
       clickedBlockID = game.getBlock(target.voxel)
       clickedBlock = registry.getBlockName(clickedBlockID)
-      if clickedBlock == 'workbench'
-        # open workbench
-        workbenchDialog.open()
-        return
+
+      props = registry.getBlockProps(clickedBlock)
+      if props.onInteract?
+        # this block handles its own interaction
+        # TODO: redesign this? cancelable event?
+        preventDefault = props.onInteract()
+        return if preventDefault

     if registry.isBlock(inventoryHotbar.held()?.item)
       # 2. place blocks


corrected podcast title

corrected podcast title

--- index.coffee
+++ index.coffee
@@ -53,7 +53,7 @@
               url: o.stream_url + '?client_id=' + client_id
               size: o.original_content_size
               type: 'audio/mpeg'
-            itunesImage: o.artwork_url.replace('https://','http://')
+            itunesImage: o.artwork_url
             itunesExplicit: no
             itunesAuthor: o.label_name
             itunesSummary: o.description


social: fix new private conversations are not shown bug

--- client/app/lib/activity/sidebar/activitysidebar.coffee
+++ client/app/lib/activity/sidebar/activitysidebar.coffee
@@ -116,7 +116,7 @@

     { channel, channelMessage, unreadCount } = update

-    if isChannelCollaborative channel and channelMessage.payload
+    if isChannelCollaborative(channel) and channelMessage.payload
       if channelMessage.payload['system-message'] in ['start', 'stop']
         @fetchEnvironmentData =>
           @setWorkspaceUnreadCount channel, unreadCount