Broken comments

Broken comments

--- server/sandboxes.coffee
+++ server/sandboxes.coffee
@@ -61,9 +61,9 @@
 # * (object) `options` - An options object which may contain the following
 #
 # values:
-* (string) `cookie` - Cookie string.
-* (string) `cookieDomain` - The domain the cookie applies to.
-* (string) `url` - The canonical URL of the HTML document.
+#     * (string) `cookie` - Cookie string.
+#     * (string) `cookieDomain` - The domain the cookie applies to.
+#     * (string) `url` - The canonical URL of the HTML document.

   # *Create a DOM from an HTML document.*
   createDocument: (html, options = {}) ->


TMS-2618 Fix link for updated stacks notification

https://koding.atlassian.net/browse/TMS-2618

--- client/app/lib/components/sidebarstacksection/index.coffee
+++ client/app/lib/components/sidebarstacksection/index.coffee
@@ -75,7 +75,7 @@

     { router } = kd.singletons

-    router.handleRoute '/Stacks'
+    router.handleRoute '/Stacks/My-Stacks'

   getStackUnreadCount: ->


MachineGeneralSettingsView: Use thin arrow.

--- client/app/lib/providers/machinegeneralsettingsview.coffee
+++ client/app/lib/providers/machinegeneralsettingsview.coffee
@@ -44,7 +44,7 @@
     kd.singletons.computeController.getKloud()

       .info { machineId, currentState }
-      .then (response) =>
+      .then (response) ->

         if   response.State is Running
         then statusToggle.setOn  no
@@ -53,7 +53,7 @@
         statusToggle.show()
         statusLoader.hide()

-      .catch (err) =>
+      .catch (err) ->

         kd.warn 'Failed to fetch info for machine settings:', err
         statusLoader.hide()


Add (back in) showing of progress bar at 100% when connecting.

--- client/components/connectionStatus/connectionStatus.coffee
+++ client/components/connectionStatus/connectionStatus.coffee
@@ -18,6 +18,8 @@
     if @interval then Meteor.clearInterval @interval

     switch status.status
+      when 'connecting'
+        Session.setTemp 'offlinePc', '100%'
       when 'connected'
         Session.setTemp 'offlinePc', '0%'
         delete Session.keys['connectRetrySeconds']


_pendingState is usually null, and using @state here is safe enough

--- core/app/backbone/views/popovers/react_signin_popover.coffee
+++ core/app/backbone/views/popovers/react_signin_popover.coffee
@@ -12,7 +12,7 @@
     @setState opened: true

   _onSignedInChange: ->
-    if FactlinkApp.signedIn() && @_pendingState.opened
+    if FactlinkApp.signedIn() && @state.opened
       @props.onSubmit?()
       @setState opened: false

@@ -22,7 +22,7 @@
     if FactlinkApp.signedIn()
       @props.onSubmit?()
     else
-      @setState opened: !@_pendingState.opened
+      @setState opened: !@state.opened

   render: ->
     if @state.opened && !FactlinkApp.signedIn()


minor comment typo

minor comment typo

--- Skuler.coffee
+++ Skuler.coffee
@@ -285,7 +285,7 @@
 				@setSL color.saturation, color.lightness - slOffset
 				true

-			when 89 # z
+			when 89 # y
 				if event.ctrlKey
 					@drawing.redo()
 					@updateViewCurrColor()
@@ -315,4 +315,4 @@

 # init
 window.skuler = new Skuler
-$("#skuler").css "visibility", "visible" # reveal after init+$("#skuler").css "visibility", "visible" # reveal after init


payment: fix a type error that crept in during refactoring

We got rid of a KDModalViewWithForms, but there was still a remaining reference to @modalTabs

--- client/landing/app/MainApp/payment/paymentformmodal.coffee
+++ client/landing/app/MainApp/payment/paymentformmodal.coffee
@@ -26,7 +26,7 @@
         @emit 'PaymentMethodSelected', paymentMethodId
       else
         @useExistingView.hide()
-        @modalTabs.show()
+        @paymentForm.show()

     @addSubView @useExistingView


temporary: don't use git cache for ec2 server


Add provisional section model

Add provisional section model


Verify network events, not output

Verify network events, not output


* [bug] duplication callback return when error occur


Update exercise div for tests

Update exercise div for tests


Sort states alphabetically.

Sort states alphabetically.

--- src/data/states.coffee
+++ src/data/states.coffee
@@ -1,43 +1,43 @@
 module.exports =
   data:
+    ak: 'Alaska'
     al: 'Alabama'
-    ak: 'Alaska'
+    ar: 'Arkansas'
     az: 'Arizona'
-    ar: 'Arkansas'
     ca: 'California'
     co: 'Colorado'
     ct: 'Connecticut'
+    dc: 'District of Columbia'
     de: 'Delaware'
-    dc: 'District of Columbia'
     fl: 'Florida'
     ga: 'Georgia'
     hi: 'Hawaii'
+    ia: 'Iowa'
     id: 'Idaho'
     il: 'Illinois'
     in: 'Indiana'
-    ia: 'Iowa'
     ks: 'Kansas'
     ky: 'Kentucky'
     la: 'Louisiana'
+    ma: 'Massachusetts'
+    md: 'Maryland'
     me: 'Maine'
+    mi: 'Michigan'
+    mn: 'Minnesota'
+    mo: 'Missouri'
+    ms: 'Mississippi'
     mt: 'Montana'
+    nc: 'North Carolina'
+    nd: 'North Dakota'
     ne: 'Nebraska'
-    nv: 'Nevada'
     nh: 'New Hampshire'
     nj: 'New Jersey'
     nm: 'New Mexico'
+    nv: 'Nevada'
     ny: 'New York'
-    nc: 'North Carolina'
-    nd: 'North Dakota'
     oh: 'Ohio'
     ok: 'Oklahoma'
     or: 'Oregon'
-    md: 'Maryland'
-    ma: 'Massachusetts'
-    mi: 'Michigan'
-    mn: 'Minnesota'
-    ms: 'Mississippi'
-    mo: 'Missouri'
     pa: 'Pennsylvania'
     ri: 'Rhode Island'
     sc: 'South Carolina'
@@ -45,11 +45,11 @@
     tn: 'Tennessee'
     tx: 'Texas'
     ut: 'Utah'
+    va: 'Virginia'
     vt: 'Vermont'
-    va: 'Virginia'
     wa: 'Washington'
+    wi: 'Wisconsin'
     wv: 'West Virginia'
-    wi: 'Wisconsin'
     wy: 'Wyoming'
     aa: 'U.S. Armed Forces – Americas'
     ae: 'U.S. Armed Forces – Europe'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/array_extensions.coffee
+++ test/array_extensions.coffee
@@ -124,8 +124,9 @@
   equals [-345, 38, 8347].maximum(), 8347

 test "#maximum with function", ->
-  equals 3, [3, 4, 5].maximum (n) ->
+  equals [3, 4, 5].maximum((n) ->
     n % 4
+  ), 3

 test "#minima", ->
   minima = [-52, 0, 78].minima()


Correctly handle LGPL/GPL and COPYING as a name

--- src/legal-eagle.coffee
+++ src/legal-eagle.coffee
@@ -49,6 +49,7 @@
       license = license.type ? 'UNKNOWN'
     license = 'BSD' if license.match /^[\s(]*BSD-.*/
     license = 'LGPL' if license.match /^[\s(]*LGPL(-.+)*/
+    license = 'GPL' if license.match /^[\s(]*GPL(-.+)*/
     license = 'MIT' if license.match(/^[\s(]*MIT\W/)
     license = 'Apache' if license.match /^[\s(]*Apache.*/
     license = 'WTF' if license is 'WTFPL'
@@ -111,6 +112,14 @@
     licenseText = readIfExists(join(path, licenseFileName))

   unless licenseText?
+    licenseFileName = 'COPYING'
+    licenseText = readIfExists(join(path, licenseFileName))
+
+  unless licenseText?
+    licenseFileName = 'COPYING.md'
+    licenseText = readIfExists(join(path, licenseFileName))
+
+  unless licenseText?
     licenseFileName = 'MIT-LICENSE.txt'
     if licenseText = readIfExists(join(path, licenseFileName))
       license = 'MIT'
@@ -134,11 +143,16 @@
       'Unlicense'
     else if licenseText.indexOf('The ISC License') > -1
       'ISC'
+    else if licenseText.indexOf('GNU LESSER GENERAL PUBLIC LICENSE') > -1
+      'LGPL'
+    else if licenseText.indexOf('GNU GENERAL PUBLIC LICENSE') > -1
+      'GPL'
+    else if licenseText.indexOf('The ISC License') > -1
+      'ISC'
     else if licenseText.toLocaleLowerCase().indexOf('public domain')  > -1
       'Public Domain'

-  if license?
-    {license, source: licenseFileName, sourceText: licenseText}
+  {license ? 'UNKNOWN', source: licenseFileName, sourceText: licenseText}

 readIfExists = (path) ->
   readFileSync(path, 'utf8') if existsSync(path)
@@ -209,7 +223,7 @@
     else
       false

-PermissiveLicenses = ['MIT', 'BSD', 'Apache', 'WTF', 'LGPL', 'ISC', 'Artistic-2.0', 'Unlicense', 'CC-BY', 'Public Domain']
+PermissiveLicenses = ['MIT', 'BSD', 'Apache', 'WTF', 'LGPL', 'LGPL-2.0', 'LGPL-3.0', 'ISC', 'Artistic-2.0', 'Unlicense', 'CC-BY', 'Public Domain']

 omitPermissiveLicenses = (licenseSummary) ->
   for name, {license} of licenseSummary


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/level_state.coffee
+++ src/level_state.coffee
@@ -1,5 +1,6 @@
 LevelState = (I={}) ->
   Object.reverseMerge I,
+    duration: 1
     level: 'level1'

   self = GameState(I)


Add safety

Add safety

--- src/lib/Helpers.coffee
+++ src/lib/Helpers.coffee
@@ -429,6 +429,7 @@
                 outs[name] = component.outPorts[name]

             outs = outs[outPorts[0]] if outPorts.length is 1 # for simplicity
+            groups = [] unless groups
             whenDoneGroups = groups.slice 0
             whenDone = (err) ->
               if err


added stable version for options.coffee

added stable version for options.coffee

--- src/lib/options.coffee
+++ src/lib/options.coffee
@@ -4,7 +4,8 @@
 # For all details and documentation:
 # <http://neocotic.com/template>

-#### Private constants
+# Private constants
+# -----------------

 # Default locale to use if no matching locales were found in `locales`.
 DEFAULT_LOCALE   = 'en'
@@ -15,12 +16,14 @@
 # Regular expression used to validate keyboard shortcut inputs.
 R_VALID_SHORTCUT = /[A-Z0-9]/

-#### Private variables
+# Private variables
+# -----------------

 # Easily accessible reference to the extension controller.
 {ext} = chrome.extension.getBackgroundPage()

-#### Load functions
+# Load functions
+# --------------

 # Update the options page with the values from the current settings.
 load = ->
@@ -102,7 +105,7 @@
         $('#moveUp_btn').removeAttr 'disabled'
       # Disable the *Down* control since the selected option is at the bottom
       # of the list.
-      if  opt.is ':last-child'
+      if opt.is ':last-child'
         $('#moveDown_btn').attr 'disabled', 'disabled'
       else
         $('#moveDown_btn').removeAttr 'disabled'
@@ -383,7 +386,7 @@
   images       = $ '#feature_image'
   for image in ext.images
     $('<option/>',
-      text:  image.name,
+      text:  image.name
       value: image.id
     ).appendTo(images).data 'file', image.file
     if image.separate
@@ -406,7 +409,7 @@
     $('#notifications').removeAttr 'checked'
   notificationDuration = utils.get 'notificationDuration'
   timeInSecs = 0
-  timeInSecs = notificationDuration / 1000 if notificationDuration > timeInSecs
+  timeInSecs = notificationDuration * .001 if notificationDuration > timeInSecs
   $('#notificationDuration').val timeInSecs

 # Update the toolbar behaviour section of the options page with the current
@@ -462,7 +465,8 @@
   $('#yourlsUrl').val utils.get 'yourlsUrl'
   $('#yourlsUsername').val utils.get 'yourlsUsername'

-#### Save functions
+# Save functions
+# --------------

 # Update the settings with the values from the options page.
 save = ->
@@ -481,7 +485,7 @@
   features = []
   # Update the settings for each feature based on their corresponding options.
   $('#features option').each ->
-    features.push options.deriveFeature $ this
+    features.push deriveFeature $ this
   # Ensure the data for all features reflects the updated settings.
   ext.saveFeatures features
   ext.updateFeatures()
@@ -524,7 +528,73 @@
   utils.set 'yourlsUrl',       $('#yourlsUrl').val().trim()
   utils.set 'yourlsUsername',  $('#yourlsUsername').val().trim()

-#### Validation functions
+# Update the specified `option` element that represents a feature with the
+# values taken from the available input fields.
+updateFeature = (opt) ->
+  if opt.length
+    opt.data 'content',  $('#feature_template').val()
+    opt.data 'enabled',  String $('#feature_enabled').is ':checked'
+    opt.data 'image',    $('#feature_image option:selected').val()
+    opt.data 'shortcut', $('#feature_shortcut').val().trim().toUpperCase()
+    opt.text $('#feature_title').val().trim()
+    opt.val  $('#feature_name').val().trim()
+    return opt
+
+# Update the existing feature with information extracted from the imported
+# feature provided.
+# `feature` should not be altered in any way and the properties of `existing`
+# should only be changed if the replacement values are valid.
+# Protected properties will only be updated if `existing` is not read-only and
+# the replacement value is valid.
+updateImportedFeature = (feature, existing) ->
+  # Ensure read-only features are protected.
+  unless existing.readOnly
+    existing.content = feature.content
+    # Only allow valid titles.
+    if feature.title.length > 0 and feature.title.length <= 32
+      existing.title = feature.title
+  existing.enabled = feature.enabled
+  # Only only existing images.
+  for image in ext.images when image.id is feature.image
+    existing.image = feature.image
+    break
+  # Only allow valid keyboard shortcuts.
+  existing.shortcut = feature.shortcut if isShortcutValid feature.shortcut
+  return existing
+
+# Update the selection of features in the toolbar behaviour section to reflect
+# those available in the features section.
+updateToolbarFeatures = ->
+  features                = []
+  toolbarFeatures         = $ '#toolbarFeatureName'
+  toolbarFeatureName      = utils.get 'toolbarFeatureName'
+  lastSelectedFeature     = toolbarFeatures.find 'option:selected'
+  lastSelectedFeatureName = ''
+  if lastSelectedFeature.length
+    lastSelectedFeatureName = lastSelectedFeature.val()
+  toolbarFeatures.find('option').remove()
+  $('#features option').each ->
+    $this = $ this
+    feature =
+      name:     $this.val()
+      selected: no
+      title:    $this.text()
+    if lastSelectedFeatureName
+      feature.selected = yes if feature.name is lastSelectedFeatureName
+    else if feature.name is toolbarFeatureName
+      feature.selected = yes
+    features.push feature
+  features.sort (a, b) ->
+    return a.title > b.title
+  for feature in features
+    option = $ '<option/>',
+      text:  "#{feature.title} (#{feature.name})"
+      value: feature.name
+    option.attr 'selected', 'selected' if feature.selected
+    toolbarFeatures.append option
+
+# Validation functions
+# --------------------

 # Indicate whether or not the specified `name` is available for use as a
 # feature.
@@ -595,7 +665,7 @@
     $this = $ this
     if validateFeature $this, no, usedShortcuts
       shortcut = $this.data('shortcut').trim()
-      # Only stores shortcut if used and feature is enabled
+      # Only stores shortcut if used and feature is enabled.
       if $this.data('enabled') is 'true' and shortcut
         usedShortcuts.push shortcut
     else
@@ -619,7 +689,8 @@
   'string'  is typeof feature.shortcut and
   'string'  is typeof feature.title

-#### Miscellaneous functions
+# Miscellaneous functions
+# -----------------------

 # Create a feature from the information from the imported `feature` provided.
 # `feature` is not altered in any way and the new feature is only created if
@@ -697,208 +768,61 @@
   icon = ico for ico in icons when not icon or ico.size < icon.size
   return icon

-#### TODO: START: Convert to CoffeeScript
-
-/**
- *
Reads the imported data created by {@link options.createImport} and
- * extracts all valid imported features.

- *
Where the feature will overwrite an existing feature, only fields
- * with valid values will be accepted with exception to protected fields
- * (i.e. on read-only features).

- *
Where the feature is new default values will replace any fields with
- * invalid values assigned to them.

- * @param {Object} importData The data parsed from the imported string.
- * @param {Object[]} importData.templates The features to be extracted and
- * validated.
- * @param {String} importData.version The version of this extension used to
- * export the data currently being imported.
- * @returns {Object} An object containing the list of features extracted
- * from the imported data, if any.
- * @since 0.2.0.0
- * @private
- */
-readImport: function (importData) {
-    var data = {
-            features: []
-        },
-        existing = {},
-        feature = {},
-        names = [];
-    for (var i = 0; i < importData.templates.length; i++) {
-        existing = {};
-        feature = importData.templates[i];
-        if (options.validateImportedFeature(feature)) {
-            if (options.isNameAvailable(feature.name, names)) {
-                // Attempts to create and add new feature
-                feature = options.addImportedFeature(feature);
-                if (feature) {
-                    data.features.push(feature);
-                    names.push(feature.name);
-                }
-            } else {
-                // Attempts to update previously imported feature
-                for (var j = 0; j < data.features.length; j++) {
-                    if (data.features[j].name === feature.name) {
-                        existing = options.updateImportedFeature(feature,
-                                data.features[j]);
-                        data.features[j] = existing;
-                        break;
-                    }
-                }
-                if (!existing.name) {
-                    // Attempts to derive existing feature from options
-                    existing = options.deriveFeature(
-                            $('#features option[value="' + feature.name +
-                            '"]'));
-                    // Attempts to update derived feature
-                    if (existing) {
-                        existing = options.updateImportedFeature(feature,
-                                existing);
-                        data.features.push(existing);
-                        names.push(existing.name);
-                    }
-                }
-            }
-        }
-    }
-    return data;
-},
-
-/**
- *
Toggles the acccessibility of the toolbar feature details.

- * @since 0.3.0.0
- * @private
- */
-toggleToolbarFeature: function () {
-    if ($('#toolbarPopup').is(':checked')) {
-        $('#toolbarFeatureName_txt, ' +
-                '#toolbarFeatureDetails_txt').addClass('disabled');
-        $('#toolbarFeatureName, #toolbarFeatureDetails').attr('disabled',
-                'disabled');
-    } else {
-        $('#toolbarFeatureName_txt, ' +
-                '#toolbarFeatureDetails_txt').removeClass('disabled');
-        $('#toolbarFeatureName, ' +
-                '#toolbarFeatureDetails').removeAttr('disabled');
-    }
-},
-
-/**
- *
Updates the specified <option> element that represents a
- * feature with values taken from the available fields.

- * @param {jQuery} opt The jQuery wrapped <option> to be
- * updated.
- * @since 0.1.0.3
- * @private
- */
-updateFeature: function (opt) {
-    if (opt.length) {
-        opt.data('content', $('#feature_template').val());
-        opt.data('enabled', String($('#feature_enabled').is(':checked')));
-        opt.data('image', $('#feature_image option:selected').val());
-        opt.data('shortcut',
-                $('#feature_shortcut').val().trim().toUpperCase());
-        opt.text($('#feature_title').val().trim());
-        opt.val($('#feature_name').val().trim());
-        return opt;
-    }
-},
-
-/**
- *
Updates the existing feature with information extracted from the
- * imported feature provided.

- *
The feature provided is not altered in anyw way and the fields of the
- * existing feature are only changed if the new values are valid.

- *
Protected fields are only updated if the existing feature is not
- * read-only and the new value is valid.

- * @param {Object} feature The imported feature on which the existing
- * feature should be modified to reflect.
- * @param {Object} existing The existing feature which should be modified
- * so that its fields reflect that of the import feature.
- * @returns {Object} The existing feature with the modified fields.
- * @since 0.2.0.0
- * @private
- */
-updateImportedFeature: function (feature, existing) {
-    var bg = chrome.extension.getBackgroundPage();
-    // Ensures read-only templates are protected
-    if (!existing.readOnly) {
-        existing.content = feature.content;
-        // Only updates valid titles
-        if (feature.title.length > 0 && feature.title.length <= 32) {
-            existing.title = feature.title;
-        }
-    }
-    existing.enabled = feature.enabled;
-    // Only updates image if identifier exists
-    for (var i = 0; i < bg.ext.images.length; i++) {
-        if (bg.ext.images[i].id === feature.image) {
-            existing.image = feature.image;
-            break;
-        }
-    }
-    // Only updates valid shortcuts
-    if (options.isShortcutValid(feature.shortcut)) {
-        existing.shortcut = feature.shortcut;
-    }
-    return existing;
-},
-
-/**
- *
Updates the selection of features in the toolbar behaviour section
- * to reflect that available in the templates section.

- * @since 0.3.0.0
- * @private
- */
-updateToolbarFeatures: function () {
-    var features = [],
-        toolbarFeatures = $('#toolbarFeatureName'),
-        toolbarFeatureName = utils.get('toolbarFeatureName'),
-        lastSelectedFeature = toolbarFeatures.find('option:selected'),
-        lastSelectedFeatureName = '';
-    if (lastSelectedFeature.length) {
-        lastSelectedFeatureName = lastSelectedFeature.val();
-    }
-    toolbarFeatures.find('option').remove();
-    $('#features option').each(function () {
-        var $this = $(this),
-            feature = {
-                name: $this.val(),
-                selected: false,
-                title: $this.text()
-            };
-        if (lastSelectedFeatureName) {
-            if (feature.name === lastSelectedFeatureName) {
-                feature.selected = true;
-            }
-        } else if (feature.name === toolbarFeatureName) {
-            feature.selected = true;
-        }
-        features.push(feature);
-    });
-    features.sort(function (a, b) {
-        return a.title > b.title;
-    });
-    var option;
-    for (var i = 0; i < features.length; i++) {
-        option = $('<option/>', {
-            text: features[i].title + ' (' + features[i].name + ')',
-            value: features[i].name
-        });
-        if (features[i].selected) {
-            option.attr('selected', 'selected');
-        }
-        toolbarFeatures.append(option);
-    }
-},
-
-#### TODO: END: Convert to CoffeeScript
-
-#### Options page setup
+# Read the imported data created by `createImport` and extract all of the
+# imported features that appear to be valid.
+# When overwriting an existing feature, only the properties with valid values
+# will be transferred with the exception of protected properties (i.e. on
+# read-only features).
+# When creating a new feature, any invalid properties will be replaced with
+# their default values.
+readImport = (importData) ->
+  data     = features: []
+  names    = []
+  for feature in importData.templates
+    existing = {}
+    if validateImportedFeature feature
+      if isNameAvailable feature.name, names
+        # Attempt to create and add the new feature.
+        feature = addImportedFeature feature
+        if feature
+          data.features.push feature
+          names.push feature.name
+      else
+        # Attempt to update the previously imported feature.
+        for imported, i in data.features
+          if imported.name is feature.name
+            existing         = updateImportedFeature feature, imported
+            data.features[i] = existing
+            break
+        unless existing.name
+          # Attempt to derive the existing feature from the available options.
+          existing = deriveFeature $ "#features
+            option[value='#{feature.name}']"
+          # Attempt to update the derived feature.
+          if existing
+            existing = updateImportedFeature feature, existing
+            data.features.push existing
+            names.push existing.name
+  return data
+
+# Toggle the acccessibility of the toolbar feature details.
+toggleToolbarFeature = ->
+  fields = $ '#toolbarFeatureName, #toolbarFeatureDetails'
+  spans  = $ '#toolbarFeatureName_txt, #toolbarFeatureDetails_txt'
+  if $('#toolbarPopup').is ':checked'
+    spans.addClass 'disabled'
+    fields.attr 'disabled', 'disabled'
+  else
+    spans.removeClass 'disabled'
+    fields.removeAttr 'disabled'
+
+# Options page setup
+# ------------------

 options = window.options =

-  #### Public functions
+  # Public functions
+  # ----------------

   # Initialize the options page.
   # This will involve inserting and configuring the UI elements as well as


Docco on iterators and mutators.

Docco on iterators and mutators.

--- src/lib/strata_.coffee
+++ src/lib/strata_.coffee
@@ -1713,25 +1713,86 @@
 # ### Iterator
 #
 # The application developer uses an iterator to move across the leaf pages of
-# the b-tree in ascending collation order. The cursor is always visiting a
-# single leaf page.
+# the b-tree in ascending collation order, one leaf page a time. The leaf pages
+# themselves are visited one at a time, not the records. The iterator can
+# randomly access any record a the currently visited leaf page.
+#
+# The application developer obtains an iterator by calling `Strata.cursor` and
+# providing a key. The key is used to find the page that would contain the key.
+# Alternatively, the application developer can obtain an iterator that begins at
+# the left most leaf page by by calling `Strata.first`.
+#
+# By locking the pages left to right hand over hand, then there is now way for
+# the tree to mutate such that would defeat our iteration. Leaf pages that we've
+# visited may by edited by another descent after we've visited them, however.
+#
+# `strata.iterator(key, _)`
+#
+# `strata.mutator(_)`

 #
 class Iterator
-  constructor: (@_io, @_page, @_index, @_key, @_found, @_exclusive) ->
-    @_exclusive or= false
-    @_offset = 0
-
-  found: (_) -> @_found
-
-  unlock: -> @_io.unlock @_page
-
-  end: -> @_index + @_offset is @_page.count
-
-  get: (_) ->
-    if @_index + @_offset < @_page.count
-      @_io.stash(@_page, @_index + @_offset++, _).record
-
+  # #### Ambiguous Range Start
+  #
+  # As the result of a descent, we will know the index of the record that
+  # corresponds to the key used to find the leaf page. This is either the index
+  # of the key's record, or if there no record that could derive the key in the
+  # b-tree, the location where the key's record would be inserted.
+  #
+  # There are good reasons to search for key that does not exist in the tree. We
+  # may be interested in searching for time series data that occured between
+  # noon and midnight. It doesn't matter to us if there were no events that
+  # occured exactly at the millisecond that defines noon.
+  #
+  # We are interested in starting our iteration from where that record would be.
+  # That is the insert location of the millisecond that defines noon. We need a
+  # way to find an ***insert location*** for a record, in order to implement a
+  # ranged search.
+  #
+  # When we ask for the insert location of a key, if the key is greater than the
+  # largest record in the leaf page, then the index is the end of the leaf page.
+  # If this is the case, the real insert location might be in the right sibling
+  # leaf page, or in a page beyond the right sibling leaf page. We won't know
+  # for certain if the non-existing record would be in the current leaf page
+  # without testing to see if it is less than the first record of the right
+  # sibling page.
+  #
+  # However, we do know that the first leaf page is the correct leaf page for
+  # the search key, because we used a b-tree descent to find the page. That is
+  # unambiguous. When we search for the search key in the leaf page, even if it
+  # does not exist, and is greater than the last record, we still know for
+  # certain that the key does not belong to any of the right siblings.
+  #
+  # The challenge here is an interface challenge. How do we convey the range to
+  # the application developer? We cannot expose the binary search, because the
+  # results of a binary search for a missing key are unambiguous only for the
+  # first leaf page, and only if the key is same search key that brought us to
+  # the first page.
+  #
+  # We expose an `index` property that is the index of the least record that is
+  # equal to or greater than the search key in the current page. When we move to
+  # a right sibling leaf page, the index property becomes zero.
+
+  # Additional public properties of the `Iterator` are the `key` used to create
+  # the iterator, `found` which indicates whether or not the record actually
+  # exists in the b-tree, `count` which is the number of leaf pages visited by
+  # this iterator, and `exclusive` indicating whehter we are an iterator or
+  # a mutator. This properties are read-only, so make sure you only read them.
+
+  #
+  constructor: (@_io, @_page, @index, @key, @found, @exclusive) ->
+    @exclusive or= false
+    @count = 1
+
+  # Get a the record at the given `index` from the current leaf page.
+  get: (index, _) ->
+    if not index < @_page.count
+      throw new Error "index out of bounds"
+
+    @_io.stash(@_page, index, _).record
+
+  # Go to the next leaf page. Returns true if there is a next, false if there
+  # the current leaf page is the last leaf page.
   next: (_) ->
     if @_page.right is -1
       false
@@ -1741,69 +1802,122 @@
       @_page = next
       true

+  # Unlock all leaf pages held by the iterator.
+  unlock: -> @_io.unlock @_page
+
+# ### Mutator
+
+# A mutator is an iterator that can also edit leaf pages. It can delete records
+# from the currently visit leaf page. It can insert records into the current
+# leaf page, if the record belongs in the current leaf page. Like an `Iterator`
+# it  moves bacross the leaf pages of an the b-tree in ascending collation
+# order. It has random access to the records in the page using an index into the
+# array of records.
+#
+# #### Ambiguous Insert Locations
+#
+# Like the ambiguous range starts above, insert locations for new records can be
+# ambiguous if the binary search indicates that a record should be inserted at
+# the end of the leaf page, after the current last record in the leaf page. If
+# the insert location is after the last record, it could be the case that the
+# record really belongs to a right sibling leaf page page.
+#
+# As in the case of ambiguous range starts, an insert location is unambiguous if
+# the key is the search key used to locate the first page of the mutator. The
+# key is determined to belong inside the leaf page by virtue of a desent of the
+# b-tree. That is unambiguous.
+#
+# Using the example of time series data again, the application developer may
+# have an hour of events she wishes to insert into a day of events, within a
+# b-tree of years of events. Because the events took place within an hour, they
+# would all be inserted into leaf pages that are close to each other, perhaps
+# right next to each other, or maybe they all fall within a single leaf page.
+#
+# Rather than inserting an event at a time, desecnding the tree for each event,
+# the application developer can create a mutator uses the earliest event as a
+# key. This will give her the correct page for that first event. She can then
+# insert the remainder of the events in their ascending order, while moving
+# forward with the mutator in ascending order. If an event does not belong in
+# the current leaf page, she can move the mutator forward to the next leaf page
+# and see if it belongs there.
+#
+# The problem is that, after inserting that first record, if we determine that
+# the  insert location for a subsequent record is after the last record on the
+# current page, then it could be the case that it really belongs in in some
+# right sibling page. If the first key of the next sibling page is less than or
+# equal to the insert key, then we are on the wrong page. Unless we inspect the
+# next page, the insert location is ambiguous.
+#
+# To obtain an unambiguous insert location without descending the b-tree to
+# create a new mutator, we add a `Mutator.peek` method.
+#
+# This gives the mutator permission to peek at the right sibling leaf page of
+# the current leaf page, to determine if a record whose insert location is after
+# the last record of the current page.
+#
+# If you do not grant the mutator permission to peek, then you cannot use the
+# mutator to insert records into subsequent pages. You have to grant permission
+# to peek for each page.
+#
+# Why would you want to deny permission to peek when you're inserting a set of
+# records? Maybe you're not certain that the records are close togther.
+#
+# Let's say our application developer wants to insert a set of events, but only
+# a few of those events occured at or about the same time. They are for the most
+# part in completely different months of the year.
+#
+# She might take the opportunity to see if the next event belongs on the current
+# leaf page, but if it is actually a month away, the insert location will
+# definately be after the last record in the page. She might not want to load
+# the next page to resolve the ambiguity, when it is not likely to be resovled
+# that the currnet leaf page is the correct leaf page.  Instead, she creates a
+# new mutator to resolve the ambiguity, even though there is a slight chance
+# she'll come back to the same page.
+#
+# If she is only inserting a single record, there's no ambiguity, because she'll
+# use the key of the record to create the mutator. There is no need to enable
+# peek for a single insert, but there is no real cost either.
+#
+# TODO The lock must held because deleting the record definately can change
+# during editing. The balancer can swoop in and prune the dead first records and
+# thereby change the key. It could not delete the page nor merge the page, but
+# it can prune dead first records.
+#
+# The lock is held becase the first record of the next sibling might otherwise
+# change, it might be deleted. When the first key is deleted, the range of the
+# the keys that valid for the current page increases. We have a race condition
+# where we might reject an insertion into the current record because it is
+# less than the first key of the next, but the first key of the next page has
+# been deleted, and the current record is less than the range of the new first
+# key, it is within the extended range.
+
+#
 class Mutator extends Iterator
-  constructor: (io, page, index, key, found) ->
-    super io, page, index, key, found, true
-
-  # To insert does not indicate that the record cannot be inserted into the
-  # b-tree. Only that it cannot be inserted, with certainty, into the current
-  # page.
-  #
-  # If the binary search determines that the record would be inserted at the end
-  # of the page, after the last current record, it may be the case that
-  # the record really belongs in a subsequent page.
-  #
-  # There are two ways to determine if this is the correct page in this case.
-  #
-  # If the key of the record to insert is the key we used to find this page when
-  # we descended the tree, than this is the correct page regardless of where the
-  # record would be inserted. If it were not the correct page, we would have
-  # landed elsehwere.
-  #
-  # If we've moved from the first page, or the key is not the key we used to
-  # find the first page than we must look at the key of the first record of the
-  # next sibling page. If that key is greater than or key of the record we're
-  # inserting, than the record to insert, then the record does indeed belong in
-  # the current page.
-  #
-  # Thus, if you're attempting to insert a series of records, and you get a
-  # false result, you can continue by either descending the tree again to obtain
-  # a new cursor, or by calling `peek` to inspect the first key of the next
-  # sibling page. After calling `peek`, if insert returns false, then this is
-  # not the correct leaf page for the record.
-  #
-  # When you call `peek`, a lock is acquired on the next page, and its first
-  # record is read. The lock is held until the mutator moves to the next leaf
-  # page using `next`, or the cursor is unlocked using `unlock`.
-  #
-  # TODO The lock must held because deleting the record definately can change
-  # during editing. The balancer can swoop in and prune the dead first records
-  # and thereby change the key. It could not delete the page nor merge the page,
-  # but it can prune dead first records.
-  #
-  # The lock is held becase the first record of the next sibling might otherwise
-  # change, it might be deleted. When the first key is deleted, the range of the
-  # the keys that valid for the current page increases. We have a race condition
-  # where we might reject an insertion into the current record because it is
-  # less than the first key of the next, but the first key of the next page has
-  # been deleted, and the current record is less than the range of the new first
-  # key, it is within the extended range.
-  #
-  # TK Move. If we are locking the pages left to right hand over hand, inserting
-  # a each record from the set into its correct page as we visit the page, then
-  # there is now way for the tree to mutate such that we would miss the place
-  # were a record ought to be inserted.
+  peek: ->
+    @_peek = @_count
   insert: (cassette, _) ->
-    # We need to know if this is, without doubt, the leaf page for the record
-    # according to the descent of the b-tree. Otherwise, if we perform a find
-    # and determine that the record would go at the very end, it might actually
-    # belong to a left sibling, so we have to check.
-    ambiguous = not (@_key? and @_io.comparator(@_key, cassette.key) is 0)
-
-    if not ambiguous and not @_offset
-      index = @_index
+    # If we are at the first page and the key is equal to the key that created
+    # the mutator, than this is the correct leaf page for the record.
+    unambiguous = @count is 1 and @key and @_io.comparator(@key, key) is 0
+
+    if unambiguous
+      index = @index
     else
+      index = @_io.find page, key, page.deleted, _
       die { ambiguous, cassette }
+
+    # duplicate keys: Duplicate keys again. Now it occurs to me that duplicates
+    # are actually not difficult for the application developer to implement
+    # given a mutator. The application developer can move forward through a
+    # series and append a record that has one greater than the maximum record.
+    # Not a problem to worry about ambiguity in this case. Ah, we need to peek
+    # though, because we need to get that number.
+    #
+    # In fact, given a key plus a maximum series value, you will always land
+    # after the last one the series, or else a record that is less than the key,
+    # which means that the series is zero. Deleted keys present a problem, so we
+    # need to expose a leaf page key to the user, which, in case of a delete, is
+    # definately the greatest in the series.

     # Since we need to fsync anyway, we open the file and and close the file
     # when we append a JSON object to it.  Because no file handles are kept
@@ -1814,6 +1928,9 @@
     fs.close fd, _

     @_page.positions.splice index, 0, position
+
+  unlock: (_) ->
+    super()

 class Cassette
   constructor: (@record, @key) ->


Make broken symlink text red

Make broken symlink text red

--- src/link-lister.coffee
+++ src/link-lister.coffee
@@ -30,7 +30,7 @@
       try
         realpath = fs.realpathSync(link)
       catch error
-        realpath = '???'
+        realpath = '???'.red
       "#{path.basename(link).yellow} -> #{realpath}"

   run: ->


Rename modal "factory()" to "create()"

Rename modal "factory()" to "create()"

--- src/modal/index.coffee
+++ src/modal/index.coffee
@@ -16,6 +16,6 @@

 module.exports =

-  factory: (init) ->
+  create: (init) ->
     defaults init, {context: new Modal}
     (new ModalView init).context


Add loading indicator

Add loading indicator

--- src/scenes/Game.coffee
+++ src/scenes/Game.coffee
@@ -29,6 +29,8 @@
     xResponse: 0
     z: 0
   )
+
+  Crafty.e('BigText, LoadingText').bigText('Loading')

   # Load default sprites
   # This is a dirty fix to prevent
@@ -83,7 +85,9 @@
         )
       console.log 'End of content!'

-  wait.then -> executeScript(startScript, options)
+  wait.then ->
+    Crafty('LoadingText').destroy()
+    executeScript(startScript, options)

   Crafty.bind 'GameOver', ->
     window.ga('send', 'event', 'Game', 'End', "Checkpoint #{script.currentCheckpoint}")


Remove clearHistory parameter to ::load()

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -108,7 +108,7 @@
     @digestWhenLastPersisted = params?.digestWhenLastPersisted ? false

     @setPath(params.filePath) if params?.filePath
-    @load(true) if params?.load
+    @load() if params?.load

   # Called by {Serializable} mixin during deserialization.
   deserializeParams: (params) ->
@@ -1322,16 +1322,16 @@
     @updateCachedDiskContentsSync()
     @finishLoading()

-  load: (clearHistory=false) ->
-    @updateCachedDiskContents().then => @finishLoading(clearHistory)
-
-  finishLoading: (clearHistory) ->
+  load: ->
+    @updateCachedDiskContents().then => @finishLoading()
+
+  finishLoading: ->
     if @isAlive()
       @loaded = true
       if @digestWhenLastPersisted is @file?.getDigestSync()
         @emitModifiedStatusChanged(true)
       else
-        @reload(clearHistory)
+        @reload(true)
     this

   destroy: ->


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -1,8 +1,7 @@
 ###*
 The Text Screen class is a GameState that provides convenience methods for drawing text to screen.

-@see TextScreen
-@name TitleScreen
+@name TextScreen
 @constructor
 ###
 TextScreen = (I={}) ->


do not send user-agent in browser

do not send user-agent in browser

--- src/travis/http.coffee
+++ src/travis/http.coffee
@@ -75,7 +75,7 @@
         req       = new XMLHttpRequest()
         req.open(options.method, options.url, true)
         for name, value of options.headers
-          req.setRequestHeader(name, value)
+          req.setRequestHeader(name, value) if name.toLowerCase() != 'user-agent'
         req.onreadystatechange = ->
           if req.readyState == 4
             headers = {}


[client][framework][utils] changed equals to semicolon

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -707,7 +707,7 @@
     octal   = '' + @_permissionMap()[user] + @_permissionMap()[group] + @_permissionMap()[other]

   # private member for tracking z-indexes
-  zIndexContexts  = {}
+  zIndexContexts:{}

   # Get next highest Z-index
   getNextHighestZIndex:(context)->


KodingKite: fix logTransportFailures

KodingKite: fix logTransportFailures

--- client/Main/kite/kodingkite.coffee
+++ client/Main/kite/kodingkite.coffee
@@ -28,14 +28,15 @@
     return {reason, code, wasClean, timestamp, type}

   logTransportFailures:->
-    if @transport.ws?
-      @transport.ws.addEventListener 'close', (event)->
-        params = extractInfoFromWsEvent event
-        ErrorLog.create 'ws closed', params
+    return  unless @transport?.ws?

-      @transport.ws.addEventListener 'error', (event)->
-        params = extractInfoFromWsEvent event
-        ErrorLog.create 'ws error', params
+    @transport.ws.addEventListener 'close', (event)->
+      params = extractInfoFromWsEvent event
+      ErrorLog.create 'ws closed', params
+
+    @transport.ws.addEventListener 'error', (event)->
+      params = extractInfoFromWsEvent event
+      ErrorLog.create 'ws error', params

   getTransport: -> @transport


KodingRouter: use currentGroupHasStack helper

--- client/app/lib/kodingrouter.coffee
+++ client/app/lib/kodingrouter.coffee
@@ -89,7 +89,7 @@
     currentGroup       = groupsController.getCurrentGroup()

     # For koding default route still is /IDE always, for others if there is
-    # stack template defined for the active group, it goes again to /IDE but if
+    # stack template defined for the active group, it goes again to /IDE but if
     # there is no stack template defined for the current group it goes /Activity
     # directly.
     #
@@ -100,9 +100,9 @@
       if not currentGroup or currentGroup.slug is 'koding'
         return '/IDE'

-      if not currentGroup?.stackTemplates?.length > 0
-      then return '/Activity'
-      else return '/IDE'
+      if groupsController.currentGroupHasStack()
+      then return '/IDE'
+      else return '/Activity'

     else
       return '/Home'


client: home: removed hometabview ref

client: home: removed hometabview ref

--- client/home/lib/homeappview.coffee
+++ client/home/lib/homeappview.coffee
@@ -1,7 +1,6 @@
 kd                = require 'kd'
 globals           = require 'globals'
 HomeAppAvatarView = require './commons/homeappavatarview'
-# HomeTabView       = require './commons/hometabview'
 HomeTabHandle     = require './commons/hometabhandle'

 module.exports = class HomeAppView extends kd.ModalView


Only log warning about leap if leap would be used

--- jax-core/lib/assets/javascripts/jax/input/leap.js.coffee
+++ jax-core/lib/assets/javascripts/jax/input/leap.js.coffee
@@ -48,8 +48,12 @@

   startLooping: ->
     return if @looping
-    # TODO don't enable gestures if they are not being used
-    Leap.loop {enableGestures: true}, (data) => @loop data
+    if typeof(Leap) is 'undefined'
+      console.log 'Your controller is listening for Leap Motion actions, but Leap could not be found.'
+      console.log '  Did you include leap.js?'
+    else
+      # TODO don't enable gestures if they are not being used
+      Leap.loop {enableGestures: true}, (data) => @loop data
     @looping = true

   stopListening: ->
@@ -57,11 +61,6 @@
     @_reset()

   register: (controller) ->
-    if typeof(Leap) is 'undefined'
-      console.log 'Your controller is listening for Leap Motion actions, but Leap could not be found.'
-      console.log '  Did you include leap.js?'
-      return
-
     @registerLeapFrame           controller
     @registerFrameRotated        controller
     @registerFrameScaled         controller


Suppress require errors in console

Suppress require errors in console

--- src/coffee/common.coffee
+++ src/coffee/common.coffee
@@ -2,6 +2,7 @@
 #credentials = require("../karmadb.coffee")
 #query.connectionParameters = "postgres://karma_app_db:!@jjS234ffJ@localhost:5432/Karma"
 #pg = require("pg")
+###
 express = require("/node_modules/express")
 expressLogger = require("/node_modules/morgan")
 alert "1"
@@ -14,3 +15,4 @@
 alert "Yipeeeeee 1"

+###


Set fallback to make tipsy work.

Set fallback to make tipsy work.

--- client/Framework/core/KDView.coffee
+++ client/Framework/core/KDView.coffee
@@ -739,6 +739,7 @@
     o.engine    or= "tipsy" # we still can use twipsy
     o.gravity   or= placementMap[o.placement]
     o.fade      or= o.animate
+    o.fallback  or= o.title

     @listenTo
       KDEventTypes        : "viewAppended"


admins: add lee to admin list

admins: add lee to admin list

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -674,7 +674,7 @@

   dummyAdmins = [ "sinan", "devrim", "gokmen", "chris", "fatihacet", "arslan",
                   "sent-hil", "kiwigeraint", "cihangirsavas", "leventyalcin",
-                  "samet" ]
+                  "samet", "leeolayvar" ]

   userIsExempt: (callback)->
     # console.log @isExempt, this


removed tainted accounts related methods

--- workers/social/lib/social/models/account.coffee
+++ workers/social/lib/social/models/account.coffee
@@ -930,16 +930,6 @@

     JUser.one {username: @profile.nickname}, callback

-  @taintedAccounts = {}
-  @taint =(id)->
-    @taintedAccounts[id] = yes
-
-  @untaint =(id)->
-    delete @taintedAccounts[id]
-
-  @isTainted =(id)->
-    isTainted = @taintedAccounts[id]
-    isTainted

   sendNotification: (event, contents) ->
     @createSocialApiId (err, socialApiId) =>


Added more snippets and updated names

Added more snippets and updated names

--- snippets/template.cson
+++ snippets/template.cson
@@ -1,20 +1,21 @@
 '.text.html.ss':
-  # scope
-  'control':
+  # scope 2.4
+  'Control 2.4':
     'prefix': 'control '
     'body': """
     <% control ${1} %>
     \t${2}
     <% end_control %>
     """
-  'loop':
+  # scope 3.0 +
+  'Loop 3.0 +':
     'prefix': 'loop '
     'body': """
     <% loop ${1} %>
     \t${2}
     <% end_loop %>
     """
-  'with':
+  'With 3.0 +':
     'prefix': 'with '
     'body': """
     <% with ${1} %>
@@ -29,14 +30,14 @@
     \t${2}
     <% end_if %>
     """
-  'ifnot':
+  'if not':
     'prefix': 'ifnot '
     'body': """
     <% if not ${1} %>
     \t${2}
     <% end_if %>
     """
-  'ifelse':
+  'if else':
     'prefix': 'ifelse '
     'body': """
     <% if ${1} %>
@@ -52,37 +53,56 @@
   'include':
     'prefix': 'include '
     'body': '<% include ${1} %>${0}'
-  # Image manipulation
-  'ScaleWidth Image 3.2+':
+  # Image manipulation 3.2
+  'Scale Width Image 3.2+':
     'prefix': 'scalewidth '
     'body': 'ScaleWidth(${1:width})${0}'
-  'ScaleMaxWidth Image 3.2+':
+  'Scale Max Width Image 3.2+':
     'prefix': 'scalemaxwidth '
     'body': 'ScaleMaxWidth(${1:width})${0}'
-  'ScaleHeight Image 3.2+':
+  'Scale Height Image 3.2+':
     'prefix': 'scaleheight '
     'body': 'ScaleHeight(${1:height})${0}'
-  'ScaleMaxHeight Image 3.2+':
+  'Scale Max Height Image 3.2+':
     'prefix': 'scalemaxheight '
     'body': 'ScaleMaxHeight(${1:height})${0}'
   'Fit Image 3.2+':
     'prefix': 'fit '
     'body': 'Fit(${1:width},${1:height})${0}'
-  'FitMax Image 3.2+':
+  'Fit Max Image 3.2+':
     'prefix': 'fitmax '
     'body': 'FitMax(${1:width},${1:height})${0}'
   'Fill Image 3.2+':
     'prefix': 'fill '
     'body': 'Fill(${1:width},${1:height})${0}'
-  'FillMax Image 3.2+':
+  'Fill Max Image 3.2+':
     'prefix': 'fillmax '
     'body': 'FillMax(${1:width},${1:height})${0}'
-  'CropWidth Image 3.2+':
+  'Crop Width Image 3.2+':
     'prefix': 'cropwidth '
     'body': 'CropWidth(${1:width})${0}'
-  'CropHeight Image 3.2+':
+  'Crop Height Image 3.2+':
     'prefix': 'cropheight '
     'body': 'CropHeight(${1:width})${0}'
   'Pad Image 3.2+':
     'prefix': 'pad '
     'body': 'Pad(${1:width},${1:height},${3:hex})${0}'
+  # Image manipulation 3.0 - 3.1
+  'Set Width Image 3.0 - 3.1':
+    'prefix': 'setwidth '
+    'body': 'SetWidth(${1:width})${0}'
+  'Set Height Image 3.0 - 3.1':
+    'prefix': 'setheight '
+    'body': 'SetHeight(${1:height})${0}'
+  'Set Size Image 3.0 - 3.1':
+    'prefix': 'setsize '
+    'body': 'SetSize(${1:width},${1:height})${0}'
+  'Set Ratio Size Image 3.0 - 3.1':
+    'prefix': 'setratiosize '
+    'body': 'SetRatioSize(${1:width},${1:height})${0}'
+  'Cropped Image 3.0 - 3.1':
+    'prefix': 'croppedimage '
+    'body': 'CroppedImage(${1:width},${1:height})${0}'
+  'Padded Image 3.0 - 3.1':
+    'prefix': 'paddedimage '
+    'body': 'PaddedImage(${1:width},${1:height},${3:hex})${0}'


Fix following form

Fix following form

--- assets/javascripts/views/new_following_form.js.coffee
+++ assets/javascripts/views/new_following_form.js.coffee
@@ -30,12 +30,24 @@

   buildEntity: =>
     entity = @$fields.entity.val()
-    entity = if entity.match /^[^.]+$/ && TentStatus.config.tent_host_domain
-      'https://' + entity + TentStatus.config.tent_host_domain
-    else if entity.length && entity.match /^(?!http?s?)/
-      'https://' + entity
-    else
-      entity
+    m = entity.match(/^(https?:\/\/)?([^\/]+)(.*?)$/)
+    return entity unless m
+    parts = {
+      scheme: m[1]
+      domain: m[2]
+      rest: m[4] || ""
+    }
+
+    if TentStatus.config.tent_host_domain
+      if parts.domain.match /^[^.]+$/
+        parts.scheme ?= 'https://'
+        parts.domain += '.' + TentStatus.config.tent_host_domain
+      else if parts.domain.match(new RegExp(TentStatus.config.tent_host_domain)) and !parts.scheme
+        parts.scheme = 'https://'
+
+    parts.scheme ?= 'http://'
+    entity = parts.scheme + parts.domain + parts.rest
+
     entity

   submit: (e) =>


use separate loop for pendingUpdates metric

--- services/document-updater/app/coffee/RealTimeRedisManager.coffee
+++ services/document-updater/app/coffee/RealTimeRedisManager.coffee
@@ -21,6 +21,9 @@
 		multi.exec (error, replys) ->
 			return callback(error) if error?
 			jsonUpdates = replys[0]
+			for jsonUpdate in jsonUpdates
+				# record metric for each update removed from queue
+				metrics.summary "redis.pendingUpdates", jsonUpdate.length, {status: "pop"}
 			updates = []
 			for jsonUpdate in jsonUpdates
 				try
@@ -28,8 +31,6 @@
 				catch e
 					return callback e
 				updates.push update
-				# record metric for updates removed from queue
-				metrics.summary "redis.pendingUpdates", jsonUpdate.length, {status: "pop"}
 			callback error, updates

 	getUpdatesLength: (doc_id, callback)->


test(core): fix config.env test in execute plugin

--- packages/core/test/plugins/execute.coffee
+++ packages/core/test/plugins/execute.coffee
@@ -30,17 +30,19 @@

   describe 'env', ->

-    they 'merge parent metadat with config', ({ssh}) ->
+    they 'merge parent metadata with config', ({ssh}) ->
       nikita
         ssh: ssh
         metadata:
           env: 'NIKITA_PROCESS_ENV_1': '1'
       , ->
-        @execute
+        {env} = await @execute
           command: 'env'
           env: 'NIKITA_PROCESS_ENV_2': '2'
-        , ({config}) ->
-          console.log config.env
+        , ({config}) -> env: config.env
+        env.should.eql
+          NIKITA_PROCESS_ENV_1: '1'
+          NIKITA_PROCESS_ENV_2: '2'

     they 'process.env disabled if some env are provided', ({ssh}) ->
       nikita


rig up clip board

rig up clip board

--- atom/atom.symlink/keymap.cson
+++ atom/atom.symlink/keymap.cson
@@ -30,3 +30,6 @@
 # If you are unfamiliar with CSON, you can read more about it in the
 # Atom Flight Manual:
 # https://atom.io/docs/latest/using-atom-basic-customization#cson
+
+'.platform-darwin atom-text-editor:not([mini])':
+  'cmd-shift-v': 'clipboard-plus:toggle'


Tests: Use createConnectClient utility instead of connectAll

A bit more consise

--- spec/01transport.coffee
+++ spec/01transport.coffee
@@ -14,12 +14,6 @@
   'MQTT': 'mqtt://localhost'
   'AMQP': 'amqp://localhost'

-
-connectAll = (clients, callback) ->
-  connect = (c, cb) ->
-    c.connect cb
-
-  async.map clients, connect, callback

 zip = () ->
   lengthArray = (arr.length for arr in arguments)
@@ -67,7 +61,10 @@
     client.subscribeToQueue queue, ackHandler, cb

   async.map handlers, sub, callback
-
+# End utils
+
+
+# Tests
 transportTests = (type) ->
   address = transports[type]
   broker = null
@@ -92,55 +89,49 @@

   describe 'inqueue==outqueue without binding', ->
     it 'sending should be received on other end', (done) ->
-      sender = transport.getClient address
-      receiver = transport.getClient address
       payload = { foo: 'bar91' }
       sharedQueue = 'myqueue33'
       onReceive = (msg) ->
         chai.expect(msg).to.include.keys 'data'
         chai.expect(msg.data).to.eql payload
         done()
-      connectAll [sender, receiver], (err) ->
-        receiver.createQueue 'inqueue', sharedQueue, (err) ->
+      createConnectClients ['sender', 'receiver'], (clients, err) ->
+        clients.receiver.createQueue 'inqueue', sharedQueue, (err) ->
           chai.expect(err).to.be.a 'null'
-          sender.createQueue 'outqueue', sharedQueue, (err) ->
-            chai.expect(err).to.be.a 'null'
-
-          receiver.subscribeToQueue sharedQueue, onReceive, (err) ->
-            chai.expect(err).to.be.a 'null'
-          sender.sendToQueue sharedQueue, payload, (err) ->
+          clients.sender.createQueue 'outqueue', sharedQueue, (err) ->
+            chai.expect(err).to.be.a 'null'
+
+          clients.receiver.subscribeToQueue sharedQueue, onReceive, (err) ->
+            chai.expect(err).to.be.a 'null'
+          clients.sender.sendToQueue sharedQueue, payload, (err) ->
             chai.expect(err).to.be.a 'null'

   describe 'inqueue==outqueue with binding', ->
     it 'sending should be received on other end', (done) ->
-      sender = transport.getClient address
-      receiver = transport.getClient address
       payload = { foo: 'bar92' }
       sharedQueue = 'myqueue35'
       onReceive = (msg) ->
         chai.expect(msg).to.include.keys 'data'
         chai.expect(msg.data).to.eql payload
         done()
-      connectAll [sender, receiver], (err) ->
-        receiver.createQueue 'inqueue', sharedQueue, (err) ->
+      createConnectClients ['sender', 'receiver'], (clients, err) ->
+        clients.receiver.createQueue 'inqueue', sharedQueue, (err) ->
           chai.expect(err).to.be.a 'null'
-          sender.createQueue 'outqueue', sharedQueue, (err) ->
+          clients.sender.createQueue 'outqueue', sharedQueue, (err) ->
             chai.expect(err).to.be.a 'null'

           broker.addBinding {type:'pubsub', src:sharedQueue, tgt:sharedQueue}, (err) ->
             chai.expect(err).to.be.a 'null'

-            receiver.subscribeToQueue sharedQueue, onReceive, (err) ->
-              chai.expect(err).to.be.a 'null'
-            sender.sendToQueue sharedQueue, payload, (err) ->
+            clients.receiver.subscribeToQueue sharedQueue, onReceive, (err) ->
+              chai.expect(err).to.be.a 'null'
+            clients.sender.sendToQueue sharedQueue, payload, (err) ->
               chai.expect(err).to.be.a 'null'

   describe 'outqueue bound to inqueue', ->
     it 'sending to inqueue, show up on outqueue', (done) ->
-      sender = transport.getClient address
-      receiver = transport.getClient address
       payload = { foo: 'bar99' }
       inQueue = 'inqueue232'
       outQueue = 'outqueue353'
@@ -149,18 +140,18 @@
         chai.expect(msg).to.include.keys 'data'
         chai.expect(msg.data).to.eql payload
         done()
-      connectAll [sender, receiver], (err) ->
-        receiver.createQueue 'inqueue', inQueue, (err) ->
+      createConnectClients ['sender', 'receiver'], (err) ->
+        clients.receiver.createQueue 'inqueue', inQueue, (err) ->
           chai.expect(err).to.be.a 'null'
-          sender.createQueue 'outqueue', outQueue, (err) ->
+          clients.sender.createQueue 'outqueue', outQueue, (err) ->
             chai.expect(err).to.be.a 'null'

           broker.addBinding {type:'pubsub', src:outQueue, tgt:inQueue}, (err) ->
             chai.expect(err).to.be.a 'null'

-            receiver.subscribeToQueue inQueue, onReceive, (err) ->
-              chai.expect(err).to.be.a 'null'
-            sender.sendToQueue outQueue, payload, (err) ->
+            clients.receiver.subscribeToQueue inQueue, onReceive, (err) ->
+              chai.expect(err).to.be.a 'null'
+            clients.sender.sendToQueue outQueue, payload, (err) ->
               chai.expect(err).to.be.a 'null'


mongo import messages more detailed

mongo import messages more detailed

--- data/mongo-imports.coffee
+++ data/mongo-imports.coffee
@@ -49,9 +49,10 @@
           toprocess = row.count
           processed = 0
           scheduledForInsertion = 0
+          inserted = 0

           closeIfFinished = ->
-            console.log "Processed "+processed+" of " +toprocess
+            console.log "Processed "+processed+" of " +toprocess +", "+inserted+" documents inserted"
             if scheduledForInsertion == 0  and  processed == toprocess
               console.log "Closing connections"
               pgclient.end()
@@ -82,6 +83,7 @@
                 scheduledForInsertion++
                 coll.insert row, (err, docs) ->
                   console.log "Inserted aiddata_id: " + row.aiddata_id
+                  inserted++
                   scheduledForInsertion--
                   closeIfFinished()
               processed++


Actually serve avatars from cache

Actually serve avatars from cache

--- lib/atom-io-client.coffee
+++ lib/atom-io-client.coffee
@@ -22,20 +22,23 @@
   avatarPath: (login) ->
     path.join app.getDataPath(), 'Cache/settings-view', "#{login}-#{Date.now()}"

-  cachedAvatar: (login) ->
-    glob @avatarGlob(login), (err, files) ->
+  cachedAvatar: (login, callback) ->
+    glob @avatarGlob(login), (err, files) =>
       files.sort().reverse()
       for imagePath in files
-        if Date.now() - parseInt(imagePath.split('-')[1]) < @expiry
-          # short circuit
-          return imagePath
-    return null
+        filename = path.basename(imagePath)
+        [..., createdOn] = filename.split('-')
+        # TODO don't check expiry if network connection is not avail
+        if Date.now() - parseInt(createdOn) < @expiry
+          callback(null, imagePath)
+          break

   avatar: (login, callback) ->
-    if cached = @cachedAvatar()
-      callback null, cached
-    else
-      @fetchAndCacheAvatar(login, callback)
+    @cachedAvatar login, (err, cached) =>
+      if cached
+        callback null, cached
+      else
+        @fetchAndCacheAvatar(login, callback)

   avatarGlob: (login) ->
     path.join app.getDataPath(), 'Cache/settings-view', "#{login}-*"


Try the Google Maps Geocoder first and fallback on Geonames.org

--- content/scripts/application.coffee
+++ content/scripts/application.coffee
@@ -83,6 +83,12 @@
     lang: "en"
     username: "matias"
     type: "json"
+
+  google_geocoder_errors = [
+    google.maps.GeocoderStatus.OVER_QUERY_LIMIT,
+    google.maps.GeocoderStatus.REQUEST_DENIED,
+    google.maps.GeocoderStatus.INVALID_REQUEST
+  ]

   local_value = store.get(location)

@@ -90,23 +96,26 @@
     success(new google.maps.LatLng(local_value[0], local_value[1]))
   else
     window.setTimeout(->
-      $.getJSON(url, geonames_options, (data) ->
-        if data["status"]?
-          google_geocoder.geocode(
-            address: location
-            , (results, status) ->
-              if status == google.maps.GeocoderStatus.OK
-                latlng = results[0].geometry.location
-                store.set(location, [latlng.lat(), latlng.lng()])
-                success(latlng)
-          )
-        else
-          $("#error").text("").slideUp()
-          if data["geonames"].length > 0
-            lat = data["geonames"][0]["lat"]
-            lng = data["geonames"][0]["lng"]
-            store.set(location, [lat, lng])
-            success(new google.maps.LatLng(lat, lng))
+      window.google_geocoder.geocode(
+        address: location
+        , (results, status) ->
+          if status == google.maps.GeocoderStatus.OK
+            latlng = results[0].geometry.location
+            store.set(location, [latlng.lat(), latlng.lng()])
+            success(latlng)
+          else if status in google_geocoder_errors
+            $.getJSON(url, geonames_options, (data) ->
+              if data["status"]?
+                if data["status"]["value"] in [18, 19, 20]
+                  $("#error").text("Something went wrong.").slideDown()
+              else
+                $("#error").text("").slideUp()
+                if data["geonames"].length > 0
+                  lat = data["geonames"][0]["lat"]
+                  lng = data["geonames"][0]["lng"]
+                  store.set(location, [lat, lng])
+                  success(new google.maps.LatLng(lat, lng))
+            )
       )
     , 200
     )


Fix another test (and enable accidental disable)

--- spec/jumpy-spec.coffee
+++ spec/jumpy-spec.coffee
@@ -73,6 +73,7 @@
     afterEach ->
         atom.commands.dispatch textEditorElement, 'jumpy:clear'

+    describe 'activate', ->
         it 'creates the commands', ->
             expect(hasCommand(workspaceElement, 'jumpy:toggle')).toBeTruthy()
             expect(hasCommand(workspaceElement, 'jumpy:reset')).toBeTruthy()
@@ -305,9 +306,10 @@
             expect(cursorPosition.column).toBe 1
         it "leaves the labels up", ->
             atom.commands.dispatch textEditorElement, 'jumpy:z'
-            relevantLabels = textEditorElement.shadowRoot
-                .querySelectorAll('.label:not(.irrelevant)')
-            expect(relevantLabels.length > 0).toBeTruthy()
+            decorations = textEditor.getOverlayDecorations()
+            relevantDecorations = decorations.filter (d) ->
+                not d.getProperties().item.classList.contains 'irrelevant'
+            expect(relevantDecorations.length > 0).toBeTruthy()

     describe "when toggle is called with a split tab", ->
         it "continues to label consecutively", ->


Removed gray disabled state on framework button. Just disabled tap state.

--- Framework/Views/Controls/Button_Framework.coffee
+++ Framework/Views/Controls/Button_Framework.coffee
@@ -148,50 +148,10 @@

   setEnabled: (enabled) =>
     if enabled
-      @button.updateLayout({
-        height: @startStyle("height")
-        width: @startStyle("width")
-        bottom: @startStyle("bottom")
-        top: @startStyle("top")
-        left: @startStyle("left")
-        right: @startStyle("right")
-        borderRadius: 3
-        borderWidth: 1
-        borderColor: @startStyle("borderColor")
-        backgroundGradient: {
-          type: 'linear'
-          startPoint: { x: 0, y: 0 }
-          endPoint: { x: 0, y: "100%" }
-          colors: @startStyle("gradient")
-          backfillStart: false
-        }
-      })
-      @label.updateLayout({
-        color: @startStyle("labelColor")
-        shadowColor: @startStyle("labelShadowColor")
-      })
-
       @button.addEventListener("singletap", @settings.onClick)
       @button.addEventListener("touchstart", @onTouchStart)
       @button.addEventListener("touchend", @onTouchEnd)
     else
-      @button.updateLayout({
-        labelColor: "#ddd"
-        labelShadowColor: "#999"
-        borderColor: 'CCC'
-        backgroundGradient: {
-          type: 'linear'
-          startPoint: { x: 0, y: 0 }
-          endPoint: { x: 0, y: "100%" }
-          colors: ["#EEE", "#DDD"]
-          backfillStart: false
-        }
-      })
-      @label.updateLayout({
-        color: '#ccc'
-        shadowColor: '#999'
-      })
-
       @button.removeEventListener("singletap", @settings.onClick)
       @button.removeEventListener("touchstart", @onTouchStart)
       @button.removeEventListener("touchend", @onTouchEnd)


Updated Demi to also generate the Duration strings

--- demi/demi.coffee
+++ demi/demi.coffee
@@ -31,6 +31,27 @@
       else
         "#{start.format('MMM Y')} #{@_sep} #{_present}"

+  renderDuration: (from, till) ->
+    start = moment from, moment.ISO_8601
+    end = if till? then moment till, moment.ISO_8601 else @_now
+    duration = moment.duration end.diff start
+    months = Math.round duration.asMonths()
+    years = Math.round duration.asYears()
+
+    if 2 <= months <= 12
+      "#{months} Months"
+    else if years >= 1
+      # Since we want month along with year, get the floor value.
+      years = Math.floor duration.asYears()
+      # Estimate (as correctly as possible!) the months.
+      months = (Math.round duration.asMonths()) - 12 * years
+      year_verbose = if years > 1 then 'Years' else 'Year'
+      if months > 0
+        month_verbose = if months > 1 then 'Months' else 'Month'
+        "#{years} #{year_verbose} #{months} #{month_verbose}"
+      else
+        "#{years} #{year_verbose}"
+
   renderMarkdown: (content) => @marked content

   _init_markdn: ->
@@ -51,7 +72,8 @@
     _dtKey = opts.dateKey or 'dates'
     _mdKey = opts.mdKey or 'description'
     @renderDates = @getMap _dtKey, (node) ->
-      demi.renderDateInterval node.from, node.till
+      interval: demi.renderDateInterval node.from, node.till
+      duration: demi.renderDuration node.from, node.till
     @renderMarkdown = @getMap _mdKey, demi.renderMarkdown

   _mapDeep: (obj, pivot, fn) ->


Tests: Disable Invalid card details suite.

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -64,7 +64,7 @@

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

   [


Added constants and methods

Added constants and methods

--- dev/helpers/app.coffee
+++ dev/helpers/app.coffee
@@ -9,7 +9,10 @@

         @default_path = './public'
         return
+    getModel: (model_name) ->
+        console.log @app.get 'con'
     run: ->
+        that = @
         @app.get('logger').info 'Helper loaded', {
             name: 'App'
         }
@@ -21,4 +24,23 @@
         @app.use body_parser.urlencoded {
             extended: true
         }
+
+        @app.set 'CONSTANTS_TYPE',  {
+            SUCCESS: 0
+            INFO: 1
+            WARNING: 2
+            CRITICAL: 3
+        }
+
+        @app.set 'get_model', (model_name) ->
+            if typeof model_name is 'undefined'
+                console.error 'get_model method must have 1 argument'
+                return
+
+            model_name = model_name.charAt(0).toUpperCase() + model_name.slice(1).toLowerCase();
+            model_name = model_name + 'Model'
+
+            con = that.app.get('database').connection
+
+            con.models[model_name]
         return


GroupsController: setDefaultTemplate: helper to set given JStackTemplate to groups' default

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -30,6 +30,7 @@
     mainController.ready =>
       { slug } = entryPoint  if entryPoint?.type is 'group'
       @changeGroup slug
+

   getCurrentGroup:->
     throw 'FIXME: array should never be passed'  if Array.isArray @currentGroupData.data
@@ -156,3 +157,37 @@
         policy.emit 'MembershipPolicyChangeSaved'
         new KDNotificationView {title:"Membership policy has been updated."}
       showError err
+
+  ###*
+   *  Sets given stack template as current group's default stack template
+   *
+   *  @param  {JStackTemplate}  stackTemplate
+   *  @param  {Func}            callback  [ err ]
+  ###
+  setDefaultTemplate: (stackTemplate, callback = kd.noop) ->
+
+    { computeController }   = kd.singletons
+    { slug } = currentGroup = @getCurrentGroup()
+
+    if slug is 'koding'
+      return callback 'Setting stack template for koding is disabled'
+
+    # Share given stacktemplate with group first
+    stackTemplate.setAccess 'group', (err) ->
+      return callback err  if err
+
+      # Modify group data to use this stackTemplate as default
+      currentGroup.modify stackTemplates: [ stackTemplate._id ], (err) ->
+        return callback err  if err
+
+        new kd.NotificationView
+          title : "Team (#{slug}) stack has been saved!"
+          type  : 'mini'
+
+        # Re-call create default stack flow to make sure it exists
+        computeController.createDefaultStack yes
+
+        # Warn other group members about stack template update
+        currentGroup.sendNotification 'StackTemplateChanged', stackTemplate._id
+
+        callback null


Don't bundle pegjs examples

Don't bundle pegjs examples

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -47,6 +47,7 @@
       path.join('less', 'test')
       path.join('bootstrap', 'docs')
       path.join('bootstrap', 'examples')
+      path.join('pegjs', 'examples')
       # Add .* to avoid matching hunspell_dictionaries.
       path.join('spellchecker', 'vendor', 'hunspell', '.*')
       path.join('xmldom', 'test')


Fixed typo

Fixed typo

--- build/tasks/patch-code.coffee
+++ build/tasks/patch-code.coffee
@@ -43,7 +43,7 @@
               pathFile 'workspace.patch', 'src/workspace.coffee', ->
                 pathFile 'Gruntfile.patch', 'build/Gruntfile.coffee', ->
                   pathFile 'codesign-task.patch', 'build/tasks/codesign-task.coffee', ->
-                    pathFile 'publish-build-task.patch', 'build/tasks/publish-build-task.patch', ->
+                    pathFile 'publish-build-task.patch', 'build/tasks/publish-build-task.coffee', ->
                       if process.platform is 'darwin'
                         pathFile 'atom-Info.patch', 'resources/mac/atom-Info.plist', ->
                           pathFile 'darwin.patch', 'menus/darwin.cson', ->


fix DashOffset issue the right way

fix DashOffset issue the right way

--- bokehjs/src/coffee/common/properties.coffee
+++ bokehjs/src/coffee/common/properties.coffee
@@ -251,7 +251,7 @@

     @cache_select("dash_offset", i)
     if ctx.getLineDashOffset() != @cache.dash_offset
-      ctx.setLineDash([@cache.dash_offset])
+      ctx.setLineDashOffset(@cache.dash_offset)

 class Fill extends ContextProperties


fixed menus

fixed menus

--- menus/sorter.cson
+++ menus/sorter.cson
@@ -2,19 +2,19 @@
 'context-menu':
   'atom-text-editor': [
     {
-      'label': 'Toggle atom-sorterx'
-      'command': 'atom-sorterx:toggle'
+      'label': 'Sort'
+      'command': 'sorter:sort'
     }
   ]
 'menu': [
   {
     'label': 'Packages'
     'submenu': [
-      'label': 'atom-sorterx'
+      'label': 'Sorter'
       'submenu': [
         {
-          'label': 'Toggle'
-          'command': 'atom-sorterx:toggle'
+          'label': 'Sort'
+          'command': 'sorter:sort'
         }
       ]
     ]


making payment work

making payment work

--- lib/conekta-button.coffee
+++ lib/conekta-button.coffee
@@ -15,8 +15,7 @@
             document.querySelector ".c-modal"

         removeModal : ->
-            #do some animations
-            # obj = $(".c-modal")
+            #To-Do: animations
             document.body.removeChild( _helpers.getModal() )
             return

@@ -82,6 +81,7 @@
             wrapHtml.setAttribute("class", "c-modal")
             wrapHtml.innerHTML = _form
             document.body.appendChild wrapHtml
+            # To-Do: Animate
             return

@@ -123,11 +123,10 @@

         _helpers.openModal params

-        cc_number = $("[data-conekta-card-number]")
-        cc_date = $("[data-conekta-card-date]")
-        cc_cvc = $("[data-conekta-card-cvc]")
-
-        ###
+        cc_number = document.querySelector("[data-conekta-card-number]")
+        cc_date = document.querySelector("[data-conekta-card-date]")
+        cc_cvc = document.querySelector("[data-conekta-card-cvc]")
+
         formatted = new Formatter cc_number, {
             'pattern': '{{9999}} {{9999}} {{9999}} {{9999}}',
             'persistent': false
@@ -142,44 +141,54 @@
             'pattern': '{{9999}}',
             'persistent': false
             }
-        ###

         cancelButton = (e) ->
             _helpers.removeModal()

         makePayment = (e) ->
             e.preventDefault()
-
-            date = $("[data-conekta-card-date]").val().split(" / ")
-
-            month = date[0]
-            year = date[1]
-            card =
-                number    : $("[data-conekta-card-number]").val()
-                exp_year  : year
-                exp_month : month
-                cvc       : $("[data-conekta-card-cvc]").val()
-                name      : $("[data-conekta-card-name]").val()
-
-
-            unless Conekta.card.validateNumber(card.number)
+            has_error= false
+            card = {}
+            name = document.querySelector("[data-conekta-card-name]").value
+
+            unless name
                 has_error = true
             else
-                #do something
-                #document.querySelector("[data-conekta-card-number]")
-
-            unless Conekta.card.validateExpirationDate(card.exp_month, card.exp_year)
+                card.name = name
+
+
+            date = document.querySelector("[data-conekta-card-date]").value
+
+            unless date
+                has_error = trueconsole.log "The date is invalid: ", date
+
+            else
+                date = date.split(" / ")
+                month = date[0]
+                year = date[1]
+
+                unless Conekta.card.validateExpirationDate(month, year)
+                    has_error = trueconsole.log "The date is invalid. Month", month, ", Year: ", year
+
+                else
+                    card.exp_year = year
+                    card.exp_month = month
+
+            number = document.querySelector("[data-conekta-card-number]").value
+
+            unless Conekta.card.validateNumber(number)
                 has_error = true
             else
-                #do something
-                #document.querySelector("[data-conekta-card-date]")
-
-
-            unless Conekta.card.validateCVC(card.cvc)
+                card.number = number
+
+
+            cvc = document.querySelector("[data-conekta-card-cvc]").value
+
+            unless Conekta.card.validateCVC(cvc)
                 has_error = true
             else
-                #do something
-                #document.querySelector("[data-conekta-card-cvc]")
+                card.cvc = cvc
+

             unless has_error

@@ -191,16 +200,16 @@

                 success = (charge) ->
                     _helpers.removeModal()
-                    alert "charge"
+                    alert "Cargo Realizado"

                 error = (response) ->
-                    alert "error"
-                    #display error
+                    alert "Error al realizar el cargo"
+                    #To-Do: display error

                 Conekta.charge.create charge, success, error

             else
-                # Animate
+                # To-Do: Animate
                 return


Use consistent escape codes in sample prompt test.

--- test/configfile.coffee
+++ test/configfile.coffee
@@ -25,5 +25,5 @@

   it 'should build the prompt', (done) ->
     impromptu.prompt.build (err, prompt) ->
-      prompt.should.equal '\u001b[42m\u001b[37m user@host \u001b[0m\u001b[44m\u001b[37m ~/path/to/impromptu \u001b[0m'
+      prompt.should.equal '\x1b[42m\x1b[37m user@host \x1b[0m\x1b[44m\x1b[37m ~/path/to/impromptu \x1b[0m'
       done()


add some utility functions

add some utility functions


vary sparkler particle qty with event volume

--- layers/foreground/sparklers/main.coffee
+++ layers/foreground/sparklers/main.coffee
@@ -72,14 +72,14 @@

     @emitterZone = new MirrorPointZone(new THREE.Vector3 0, 0, 0)
     @emitter.addInitializer new SPARKS.Position(@emitterZone)
-    @emitter.addInitializer new SPARKS.Velocity(new SphereZone(0, 0, 0, 50))
+    @emitter.addInitializer new SPARKS.Velocity(new SphereZone(0, 0, 0, 70))
     @emitter.addInitializer new SPARKS.Lifetime .2, 1.5

     @emitter.addAction      new SPARKS.Age
     @emitter.addAction      new SPARKS.RandomDrift @drift, @drift, @drift
     @emitter.addAction      new SPARKS.Move
-    @emitter.addAction      new Resistance 40
-    @emitter.addAction      new Gravity 30
+    @emitter.addAction      new Resistance 60
+    @emitter.addAction      new Gravity 35

     @emitter.addCallback "created", @onParticleCreated
     @emitter.addCallback "dead",    @onParticleDead
@@ -104,7 +104,7 @@
     @emitter.start()

   setupValues: ->
-    @drift = THREE.Math.randFloat 0, 500
+    @drift = 200#THREE.Math.randFloat 0, 500

     @baseColor = new THREE.Color().setHSV(
       THREE.Math.randFloat 0, 1
@@ -145,13 +145,19 @@
       @geom.vertices[p.target].copy p.position
       @vertexAttributes.energy.value[p.target] = p.energy

-  onBeat: ->
-    @counter.particles = @particlesPerBeat
-    @counter.used = no
+  onMusicEvent: (event) ->
+    if event.segment
+      if @counter.used
+        @counter.particles = @particlesPerSegment * event.segment.volume
+        @counter.used = no

-  onSegment: ->
-    if @counter.used
-      @counter.particles = @particlesPerSegment
+    else if event.bar
+      # console.log @velocity
+      @counter.particles = @particlesPerBeat * event.bar.volume
+      @counter.used = no
+
+    else if event.beat
+      @counter.particles = @particlesPerBeat * event.beat.volume
       @counter.used = no

   alive: ->


giving the sprint graph the correct height

--- app/coffee/directives/graphs.coffee
+++ app/coffee/directives/graphs.coffee
@@ -82,6 +82,7 @@
     redrawChart = (dataToDraw) ->
         width = element.width()
         element.height(width/6)
+        element.height(190)

         days = _.map(dataToDraw, (x) -> moment(x.day))


Fixing team icons opacity

Fixing team icons opacity

--- app/coffee/modules/team/main.coffee
+++ app/coffee/modules/team/main.coffee
@@ -90,10 +90,10 @@
         max = _.max(stat)
         min = _.min(stat)
         singleStat = _.map stat, (value, key) ->
+            if value == min
+                return [key, 0.1]
             if value == max
                 return [key, 1]
-            else if value == min
-                return [key, 0.1]
             return [key, (value * 0.5) / max]
         singleStat = _.object(singleStat)
         return singleStat
@@ -150,19 +150,19 @@
 TeamMemberStatsDirective = () ->
     template = """

-
-

-

-
-

-

-
-

-

-
-

-

-
+
+

+

+
+

+

+
+

+

+
+

+

+


Moved fallback logic into CollectionView

--- coffee/views/collection_view.coffee
+++ coffee/views/collection_view.coffee
@@ -16,7 +16,16 @@

     # The list element the item views are actually appended to.
     # If empty, $el is used.
-    $listElement: null
+    # Set the selector property in the derived class to use a specific element.
+
+    listSelector: null
+    $list: null
+
+    # Selector for a fallback element which is shown if the collection is empty.
+    # Set the selector property in the derived class to use a specific element.
+
+    fallbackSelector: null
+    $fallback: null

     # Selector which identifies child elements belonging to collection
     # All children are seen as belonging to collection if not present
@@ -25,20 +34,33 @@
     # Track a list of the visible views
     visibleItems: null

+
+    # Returns an instance of the view class
+    # This method has to be overridden by a derived class.
+    # This is not simply a property with a constructor so that
+    # several item view constructors are possible depending
+    # on the item model type.
+
+    getView: ->
+      throw new Error 'CollectionView#getView must be overridden'
+
+
     initialize: (options = {}) ->
       super
-      #console.debug 'CollectionView#initialize', @, options
-
+      console.debug 'CollectionView#initialize', @, @collection, options
+
+      # Default options
       _(options).defaults
-        render: true
-        renderItems: true
-        filterer: null
-
+        render: true      # Render the view immediately per default
+        renderItems: true # Render all items immediately per default
+        filterer: null    # No filter function
+
+      # Initialize lists for views and visible items
       @viewsByCid = {}
-
       @visibleItems = []

-      @addModelListeners()
+      # Start observing the model
+      @addCollectionListeners()

       # Set the filter function
       @filter options.filterer if options.filterer
@@ -46,14 +68,13 @@
       # Render template once
       @render() if options.render

-      # Render all items
-      #console.debug '\tRender all items initially'
+      # Render all items initially
       @renderAllItems() if options.renderItems

-    # Binding of model listeners
-
-    addModelListeners: ->
+    # Binding of collection listeners
+
+    addCollectionListeners: ->
       @modelBind 'loadStart', @showLoadingIndicator
       @modelBind 'load',      @hideLoadingIndicator
       @modelBind 'add',       @itemAdded
@@ -81,7 +102,7 @@
       #console.debug 'CollectionView#itemAdded', @, item.cid, item
       @renderAndInsertItem item, options.at

-    # When an item is removed, remove the view from DOM and caches
+    # When an item is removed, remove the corresponding view from DOM and caches

     itemRemoved: (item) =>
       #console.debug 'CollectionView#itemRemoved', @, item.cid, item
@@ -94,7 +115,55 @@
       @renderAllItems()

+    # Main render method (should be called only once)
+
+    render: ->
+      super
+      #console.debug 'CollectionView#render', @, @collection
+
+      # Set the $list property
+      @$list = if @listSelector then @$(@listSelector) else @$el
+
+      @initFallback()
+
+
+    #
+    # Fallback message when the collection is empty
+    #
+
+    # Initialize the fallback
+
+    initFallback: ->
+      return unless @fallbackSelector
+
+      # Set the $fallback property
+      @$fallback = @$(@fallbackSelector)
+
+      # The collection has to be a deferred in order that
+      # the fallback can be shown properly
+      f = 'function'
+      isDeferred = typeof @collection.done is f and typeof @collection.state is f
+      return unless isDeferred
+
+      # Listen for visible items changes
+      @bind 'visibilityChange', @showHideFallback
+
+      # Register a done handler on the collection
+      # TODO: How to dispose this reference?
+      @collection.done @showHideFallback
+
+    # Show or hide the fallback when the visible items change
+
+    showHideFallback: =>
+      console.debug 'CollectionView#showHideFallback', @, @visibleItems, @collection
+      # Show fallback message if no item is visible and
+      # the collection Deferred has been resolved
+      empty = @visibleItems.length is 0 and @collection.state() is 'resolved'
+      @$fallback.css 'display', if empty then 'block' else 'none'
+
+
     # Render and insert all items
+    # Accepts the options `shuffle` (Boolean) and `limit` (Number)

     renderAllItems: (options = {}) =>

@@ -102,10 +171,12 @@
       #console.debug 'CollectionView#renderAllItems', items.length

       # Shuffle
-      items = MovieExplorer.utils.shuffle @collection.models if options.shuffle
+      if options.shuffle
+        items = MovieExplorer.utils.shuffle @collection.models

       # Apply limit
-      items = items.slice(0, options.limit) if options.limit
+      if options.limit
+        items = items.slice(0, options.limit)

       # Reset visible items
       @visibleItems = []
@@ -139,10 +210,11 @@
           #console.debug '\trender and insert new view for', item.cid
           @renderAndInsertItem item, index

-      # If no view was created, trigger `visibilityChange` manually
+      # If no view was created, trigger `visibilityChange` event manually
       unless items.length
         #console.debug 'CollectionView#renderAllItems', 'visibleItems', @visibleItems.length
         @trigger 'visibilityChange', @visibleItems
+

     # Applies a filter to the collection. Expects an interator function as parameter.
     # Hides all items for which the iterator returns false.
@@ -178,13 +250,6 @@
       @trigger 'visibilityChange', @visibleItems

-    # Returns an instance of the view class
-    # This has to be overridden by the class which inherits from CollectionView
-
-    getView: ->
-      throw new Error 'CollectionView#getView must be overridden'
-
-
     # Render the view for an item

     renderAndInsertItem: (item, index) ->
@@ -227,17 +292,18 @@
       included = if @filterer then @filterer(item, position) else true
       #console.debug '\tincluded?', included

-      # Get the view's top element
-      $viewEl = $(view.el)
+      # Get the view’s top element
+      $viewEl = view.$el

       if included
         # Make view transparent if animation is enabled
         $viewEl.css 'opacity', 0 if animationDuration
       else
-        # Hide the view if it's filtered
+        # Hide the view if it’s filtered
         $viewEl.css 'display', 'none'

-      $list = @$listElement || @$el
+      # Get the lsit and its children which are originate from item views
+      $list = @$list
       children = $list.children(@itemSelector)

       if position is 0
@@ -297,6 +363,7 @@

     # Update visibleItems list and trigger a `visibilityChanged` event
     # if an item changed its visibility
+
     updateVisibleItems: (item, includedInFilter, triggerEvent = true) ->
       visibilityChanged = false

@@ -322,16 +389,18 @@

       visibilityChanged

+
     # Remove the whole list from DOM
+
     dispose: =>
-      #console.debug 'CollectionView#dispose', @, 'disposed?', @disposed
+      #console.debug 'CollectionView#dispose', @, 'disposed?', @disposed
       return if @disposed

       # Dispose all item views
       view.dispose() for own cid, view of @viewsByCid

       # Remove jQuery object, item view cache and reference to collection
-      properties = '$listElement viewsByCid visibleItems'.split(' ')
+      properties = '$list viewsByCid visibleItems'.split(' ')
       delete @[prop] for prop in properties

       # Self-disposal


fix a deprecation

fix a deprecation

--- lib/editor/overlay.coffee
+++ lib/editor/overlay.coffee
@@ -4,7 +4,7 @@
     view.style.position = 'absolute'
     view.style.zIndex = 100
     dom.appendChild view
-    if scroll then @scrollContinuous ed, view
+    if scroll then @scrollContinuous atom.views.getView(ed), view

   remove: (view) ->
     view.parentElement?.removeChild(view)


Minor fixes (#72)

Minor fixes (#72)

* Add links to navigation

* Minor CSS fixes

* Fix card layout

* CSS

* Fix failing MathJax Hub object

--- common/lib/xmodule/xmodule/js/src/html/display.coffee
+++ common/lib/xmodule/xmodule/js/src/html/display.coffee
@@ -4,7 +4,7 @@
     @el = $(@element)
     JavascriptLoader.executeModuleScripts(@el)
     Collapsible.setCollapsibles(@el)
-    if MathJax?
+    if MathJax? and MathJax.Hub?
 	    MathJax.Hub.Queue ["Typeset", MathJax.Hub, @el[0]]
     if setupFullScreenModal?
       setupFullScreenModal()


JTag: We need to allow FollowHappened and UnfollowHappened events in instance level

--- workers/social/lib/social/models/tag.coffee
+++ workers/social/lib/social/models/tag.coffee
@@ -47,6 +47,7 @@
       title         : 'sparse'
       # group         : 'sparse'
     sharedEvents    :
+      static        : ['FollowHappened', 'UnfollowHappened']
       instance      : [
         { name: 'updateInstance' }
       ]


:abc: Converted fuzzy-provider.coffee from JSDoc to TomDoc

--- lib/fuzzy-provider.coffee
+++ lib/fuzzy-provider.coffee
@@ -17,15 +17,11 @@
     @currentBuffer.on "saved", @onSaved
     @currentBuffer.on "changed", @onChanged

-  # ! Public API
-
-  ###
-   * Gets called when the document has been changed. Returns an array with
-   * suggestions. If `exclusive` is set to true and this method returns suggestions,
-   * the suggestions will be the only ones that are displayed.
-   * @return {Array}
-   * @public
-  ###
+  # Public:  Gets called when the document has been changed. Returns an array
+  # with suggestions. If `exclusive` is set to true and this method returns
+  # suggestions, the suggestions will be the only ones that are displayed.
+  #
+  # Returns an {Array} of Suggestion instances
   buildSuggestions: ->
     selection = @editor.getSelection()
     prefix = @prefixOfSelection selection
@@ -41,42 +37,34 @@
     # Now we're ready - display the suggestions
     return suggestions

-  ###
-   * Gets called when a suggestion has been confirmed by the user. Return true
-   * to replace the word with the suggestion. Return false if you want to handle
-   * the behavior yourself.
-   * @param  {Suggestion} suggestion
-   * @return {Boolean}
-   * @public
-  ###
+  # Public: Gets called when a suggestion has been confirmed by the user. Return
+  # true to replace the word with the suggestion. Return false if you want to
+  # handle the behavior yourself.
+  #
+  # item - The confirmed {Suggestion}
+  #
+  # Returns a {Boolean} that specifies whether autocomplete+ should replace
+  # the word with the suggestion.
   confirm: (item) ->
     return true

-  # ! Private Methods
-
-  ###
-   * Gets called when the user saves the document. Rebuilds the word list
-   * @private
-  ###
+  # Private: Gets called when the user saves the document. Rebuilds the word
+  # list.
   onSaved: =>
     @buildWordList()

-  ###
-   * Gets called when the buffer's text has been changed. Checks if the user
-   * has potentially finished a word and adds the new word to the word list.
-   * @param  {Event} e
-   * @private
-  ###
+  # Private: Gets called when the buffer's text has been changed. Checks if the
+  # user has potentially finished a word and adds the new word to the word list.
+  #
+  # e - The change {Event}
   onChanged: (e) =>
     if e.newText in ["\n", " "]
       newLine = e.newText is "\n"
       @addLastWordToList newLine

-  ###
-   * Adds the last typed word to the wordList
-   * @param {Boolean} newLine
-   * @private
-  ###
+  # Private: Adds the last typed word to the wordList
+  #
+  # newLine - {Boolean} Has a new line been typed?
   addLastWordToList: (newLine) ->
     lastWord = @lastTypedWord newLine
     return unless lastWord
@@ -84,13 +72,12 @@
     if @wordList.indexOf(lastWord) < 0
       @wordList.push lastWord

-  ###
-   * Finds the last typed word. If newLine is set to true, it looks
-   * for the last word in the previous line.
-   * @param {Boolean} newLine
-   * @return {String}
-   * @private
-  ###
+  # Private: Finds the last typed word. If newLine is set to true, it looks
+  # for the last word in the previous line.
+  #
+  # newLine - {Boolean} Has a new line been typed?
+  #
+  # Returns {String} the last typed word
   lastTypedWord: (newLine) ->
     selectionRange = @editor.getSelection().getBufferRange()
     {row} = selectionRange.start
@@ -113,10 +100,7 @@

     return lastWord

-  ###
-   * Generates the word list from the editor buffer(s)
-   * @private
-  ###
+  # Private: Generates the word list from the editor buffer(s)
   buildWordList: ->
     # Abuse the Hash as a Set
     wordList = []
@@ -142,12 +126,11 @@

     p.stop()

-  ###
-   * Finds possible matches for the given string / prefix
-   * @param  {String} prefix
-   * @return {Array}
-   * @private
-  ###
+  # Private: Finds possible matches for the given string / prefix
+  #
+  # prefix - {String} The prefix
+  #
+  # Returns an {Array} of Suggestion instances
   findSuggestionsForWord: (prefix) ->
     p = new Perf "Finding matches for '#{prefix}'", {@debug}
     p.start()
@@ -162,11 +145,9 @@
     p.stop()
     return results

-  ###
-   * Finds autocompletions in the current syntax scope (e.g. css values)
-   * @return {Array}
-   * @private
-  ###
+  # Private: Finds autocompletions in the current syntax scope (e.g. css values)
+  #
+  # Returns an {Array} of strings
   getCompletionsForCursorScope: ->
     cursorScope = @editor.scopesForBufferPosition @editor.getCursorBufferPosition()
     completions = atom.syntax.propertiesForScope cursorScope, "editor.completions"


Unblocked server when converting to KMZ. Added more logging.

--- src/EntityUtils.coffee
+++ src/EntityUtils.coffee
@@ -490,11 +490,11 @@
   downloadInBrowser: (projectId, scenarioId) ->
     projectId ?= Projects.getCurrentId()
     if ScenarioUtils? then scenarioId ?= ScenarioUtils.getCurrentId()
-    Logger.info('Download entities as KMZ', projectId, scenarioId)
     args =
       projectId: projectId
       scenarioId: scenarioId
       createFile: true
+    Logger.info('Download entities as KMZ', args)
     Meteor.call 'entities/to/kmz', args, (err, fileId) =>
       # TODO(aramk) Create and return a buffer instead of a file since it's a temporary asset.
       if err then throw err
@@ -569,10 +569,11 @@
       filename = filePrefix + '.kmz'
       type = 'application/vnd.google-earth.kmz'

+      Logger.info('Generating C3ML entities...')
       c3mlData = Promises.runSync -> EntityUtils.getEntitiesAsJson(args)
       Logger.info('Wrote C3ML entities to', FileLogger.log(c3mlData))
       if c3mlData.c3mls.length == 0
-        throw new Error('No entities to convert')
+        throw new Meteor.Error(500, 'No entities to convert')
       buffer = AssetConversionService.export(c3mlData)

       if args.createFile
@@ -585,5 +586,9 @@
         {filename: filename, type: type, buffer: buffer}

   Meteor.methods
-    'entities/to/json': (args) -> Promises.runSync -> EntityUtils.getEntitiesAsJson(args)
-    'entities/to/kmz': (args) -> EntityUtils.convertToKmz(args)
+    'entities/to/json': (args) ->
+      @unblock()
+      Promises.runSync -> EntityUtils.getEntitiesAsJson(args)
+    'entities/to/kmz': (args) ->
+      @unblock()
+      EntityUtils.convertToKmz(args)


prefer navigator.languages over navigator.language

--- modules/preferences/main/index.coffee
+++ modules/preferences/main/index.coffee
@@ -207,7 +207,7 @@
       modal: modal
     }

-    defaultLocaleId = moment?.locale() or navigator.language
+    defaultLocaleId = moment?.locale() or navigator.languages?[0] or navigator.language
     if not (hx.isString(defaultLocaleId) and lookupLocale(defaultLocaleId))
       defaultLocaleId = 'en'
     @locale defaultLocaleId


Social: fixed indentation

Social: fixed indentation

--- client/Main/CommonViews/messagesList.coffee
+++ client/Main/CommonViews/messagesList.coffee
@@ -58,8 +58,8 @@
       # @hideLazyLoader()

       @emit 'NotificationCountDidChange', notifications.unreadCount
-        callback? null, notifications.notifications
-        @hideLazyLoader()
+      callback null, notifications.notifications
+      @hideLazyLoader()

 class NotificationListItem extends KDListItemView


handle mobile version of modal animation better

--- _source/client/templates/components/modal.coffee
+++ _source/client/templates/components/modal.coffee
@@ -78,6 +78,5 @@
           opacity: 1
         ,
           duration: 250
-
-      # just remove background
-      $(node).remove()
+          complete: (elements) ->
+            $(node).remove()


Oscillator

Oscillator

--- test/oscillator.coffee
+++ test/oscillator.coffee
@@ -20,8 +20,8 @@

   equals o(0), 0
   equals o(30), 0
-  equals o(15) # => 0
-  o(7.5) #=> 10
+  equals o(15), 0
+  equalso(7.5) #=> 10
   o(22.5) #=> -10

 # Clear out the module


Source Formatting

Source Formatting

--- lib/linter-liferay.coffee
+++ lib/linter-liferay.coffee
@@ -15,7 +15,7 @@

 	regexFlags: 'i'

-	constructor: () ->
+	constructor: ->
 		@subscriptions = new CompositeDisposable

 		@subscriptions.add atom.config.observe 'linter-liferay.lintJS', => @args = @formatArgs()
@@ -63,13 +63,11 @@
 		regex = XRegExp @regex, @regexFlags

 		XRegExp.forEach output, regex, (match, i) =>
-			msg =
+			messages.push
 				filePath: @editor.getPath()
 				range: @computeRange match
 				type: 'warning'
 				text: match.message
-
-			messages.push msg
 		, this

 		messages
@@ -86,8 +84,8 @@

 		# Ranges are 0-based
 		[
-			[rowStart - 1, colStart - 1],
-			[rowEnd - 1, colEnd - 1]
+			[--rowStart, --colStart],
+			[--rowEnd, --colEnd]
 		]

 	destroy: ->


Oscillator

Oscillator


Refactor: No need to call annotate() on each bill change, only on init

--- app/scripts/controllers/bill.coffee
+++ app/scripts/controllers/bill.coffee
@@ -9,7 +9,6 @@
       billCode = makeBillCode()
       $location.path("bills/#{billCode}")
       getBillText makeBillUrl(billCode)
-      annotate()

     annotate = ->
       setTimeout (->


IDEView: Disable split view with handlers feature for now

--- client/ide/lib/views/tabview/ideview.coffee
+++ client/ide/lib/views/tabview/ideview.coffee
@@ -180,6 +180,8 @@

   ensureSplitHandlers: ->

+    return # Disable this feature for now, see #106093732
+
     @verticalSplitHandle?.destroy()

     @tabView.addSubView @verticalSplitHandle = createSplitHandle 'vertical'