Refactor FacilityForm service w.r.t. schema changes

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -90,22 +90,35 @@
         # Return a promise since dynamic-forms needs the form template in
         # scope when the controller is invoked
         .$promise.then (facility) ->
-          fields = facility._items[0].fields
-          for f of fields
-            # Use the field name as label if no label was specified
-            if not fields[f].label?
-              fields[f].label = f
-            fields[f].type = 'text' if fields[f].type == 'string'
+          typemap =
+            string: 'text'
+            integer: 'number'
             # FIXME a number field assumes integers, therefore use text
-            fields[f].type = 'text' if fields[f].type == 'float'
-            fields[f].type = 'number' if fields[f].type == 'integer'
-            if fields[f].allowed?
-              fields[f].type = 'select'
-              options = {}
-              options[label] = label: label for label in fields[f].allowed
-              fields[f].options = options
-            fields[f].class = "form-control"
-            fields[f].wrapper = '

'
+            float: 'number'
+            boolean: 'checkbox'
+          mkfield = (type, label, step) ->
+            type: type
+            label: label
+            step: step
+            class: "form-control"
+            wrapper: '

'
+          fields = {}
+          for f, v of facility._items[0].fields
+            if v.type == 'point'
+              fields.longitude = mkfield 'number', 'longitude', 'any'
+              fields.latitude = mkfield 'number', 'latitude', 'any'
+              fields.longitude.model = 'location.coordinates[0]'
+              fields.latitude.model = 'location.coordinates[1]'
+            else
+              # Use the field name as label if no label was specified
+              fields[f] = mkfield typemap[v.type] || v.type, v.label || f
+              if v.type in ['float', 'number']
+                fields[f].step = 'any'
+              if v.allowed?
+                fields[f].type = 'select'
+                options = {}
+                options[label] = label: label for label in v.allowed
+                fields[f].options = options
           fields.submit =
             type: "submit"
             label: "Save"


Fixes and cleans date/time fields

Fixes and cleans date/time fields

--- app/assets/javascripts/form/dates.js.coffee
+++ app/assets/javascripts/form/dates.js.coffee
@@ -1,107 +1,32 @@
 (($) ->

-  # // Initializes date fields
-  # $(document).behave("focusin click keyup change", 'input[data-date]', function (event) {
-  #   var element = $(this), locale, options = {}, name, hidden;
-  #   if (element.prop("datepickerLoaded") !== "Yes!") {
-  #     locale = element.data("date-locale");
-  #     if ($.datepicker.regional[locale] === null || $.datepicker.regional[locale] === undefined) {
-  #       locale = "en";
-  #     }
-  #     $.datepicker.setDefaults( $.datepicker.regional[locale] );
-  #     name = element.attr("name");
-  #     element.removeAttr("name");
-  #     hidden = $("<input type='hidden' name='" + name + "'/>");
-  #     hidden.val(element.data("date-iso"));
-  #     element.before(hidden);
-
-  #     options['dateFormat']  = element.data("date");
-  #     options['altField']    = hidden;
-  #     options['altFormat']   = 'yy-mm-dd';
-  #     options['defaultDate'] = element.val();
-
-  #     // Check for dependents
-  #     if (hidden.data('dependents') !== undefined && hidden.data('dependents') !== null) {
-  #       if (hidden.data('observe') === undefined || hidden.data('observe') === null) {
-  #         hidden.attr('data-observe', '1000');
-  #       }
-  #     }
-  #     element.datepicker(options);
-  #     element.prop("datepickerLoaded", "Yes!");
-  #   }
-  # });
-  if not Modernizr.touch or not Modernizr.inputtypes.date
+  unless Modernizr.touch and Modernizr.inputtypes.date

     # Initializes date fields
-    $(document).on "focusin click keyup change", "input[type=\"date\"]", (event) ->
+    $(document).on "focusin click keyup change", "input[type='date']", (event) ->
       element = $(this)
-      locale = undefined
-      options = {}
-      name = undefined
-      hidden = undefined
       if element.attr("autocomplete") isnt "off"
         locale = element.attr("lang")
-
-        # if ($.datepicker.regional[locale] === null || $.datepicker.regional[locale] === undefined) {
-        # 	locale = "en";
-        # }
-        # $.datepicker.setDefaults( $.datepicker.regional[locale] );
-        # name = element.attr("name");
-        # element.removeAttr("name");
-        # hidden = $("<input type='hidden' name='" + name + "'/>");
-        # hidden.val(element.val());
-        # element.before(hidden);
-
-        # options['dateFormat']  = element.data("format");
-        # options['altField']    = hidden;
-        # options['altFormat']   = 'yy-mm-dd';
-
-        # // Check for dependents
-        # if (hidden.data('dependents') !== undefined && hidden.data('dependents') !== null) {
-        # 	if (hidden.data('observe') === undefined || hidden.data('observe') === null) {
-        # 		hidden.attr('data-observe', '1000');
-        # 	}
-        # }
+        options = {}
         $.extend options, $.datepicker.regional[locale],
           dateFormat: "yy-mm-dd"
-
         element.datepicker options
         element.attr "autocomplete", "off"
       return

-
     # Initializes datetime fields
-    $(document).on "focusin click keyup change", "input[type=\"datetime\"]", (event) ->
+    $(document).on "focusin click keyup change", "input[type='datetime']", (event) ->
       element = $(this)
-      locale = undefined
-      options = {}
-      name = undefined
-      hidden = undefined
       if element.attr("autocomplete") isnt "off"
-        #TODO: change this
-        date = new Date(element.val())
-        dateString = date.getFullYear() + "-" + (date.getMonth()+1) + "-" + date.getDate() + " " +date.getHours() + ":" + date.getMinutes()
-        element.val(dateString)
-
         locale = element.attr("lang")
-        element.datetimepicker # options);
+        element.datetimepicker
           format: "yyyy-mm-dd hh:ii"
           language: locale
           autoclose: true
           minuteStep: 5
           todayBtn: true
-
         element.attr "autocomplete", "off"
       return

-
-  # $.initializeDateSelectors = function() {
-  #   // $('input[type="date"], input[type="datetime"]').trigger('change');
-  #   $('input[type="date"]').trigger('change');
-  # };
-  # $(document).ready($.initializeDateSelectors);
-  # $(document).on("page:load cocoon:after-insert", $.initializeDateSelectors);
-
-
   return
 ) jQuery


Allows to submit attributes popup (seen on first run) with enter key.

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -158,7 +158,8 @@
       this._refreshControls()
       # console.log "controlled"

-      $(this.mapElement).on 'click', '.updateAttributesInPopup', (e) =>
+      @updateAttributes = (e) =>
+        return unless e.which == 1 || e.which == 13
         e.preventDefault()
         featureId = $(e.currentTarget).closest('.leaflet-popup-content').find('*[data-internal-id]').data('internal-id')
         newName = $(e.currentTarget).closest('.popup-content').find('input[type="text"]').val()
@@ -199,6 +200,10 @@
           $(this.element).trigger('mapeditor:feature_update', layer.feature)

         false
+
+      $(this.mapElement).on 'keypress', '.updateAttributesLabelInput', @updateAttributes
+      $(this.mapElement).on 'click', '.updateAttributesInPopup', @updateAttributes
+

       widget.element.trigger "mapeditor:loaded"

@@ -263,7 +268,7 @@
       popup += ""
       popup += "
"
       popup += "
"
-      popup += "<input type='text' value='#{feature.properties.name || this.options.defaultLabel}'/>"
+      popup += "<input type='text' class='updateAttributesLabelInput' value='#{feature.properties.name || this.options.defaultLabel}'/>"

       if this.options.multiLevels?
         popup += "<select>"


Fixed regression on opening the search form

Fixes #139

--- app/assets/javascripts/portus_layout.coffee
+++ app/assets/javascripts/portus_layout.coffee
@@ -1,6 +1,6 @@
 # Jquery to give the final touches to Portus layout

-openSearchForm = ->
+window.openSearchForm = ->
   $('.header-open-search').on 'click', ->
     $(this).fadeOut '300', ->
       $(this).remove()


get validation rules to work...

get validation rules to work...

by moving rules to end of file to get rid of typeError bug

--- app/assets/javascripts/pv_queries.js.coffee
+++ app/assets/javascripts/pv_queries.js.coffee
@@ -2,41 +2,14 @@
 # All this logic will automatically be available in application.js.
 # You can use CoffeeScript in this file: http:#coffeescript.org/
 $ ->
+#$(document).ready ->
   $('#enable_js').hide()
-  #form validation# <<<
-  form_validation = $('#new_pv_query').validate
-    debug: true,
+
+  form_validation = $('#new_pv_query').validate# <<<
+    #debug: true,
     errorClass: 'alert-warning',
     errorPlacement: (error, element) ->
-      element.parent().append(error)
-
-  $('#pv_query_postcode_id').rules(
-    'add',
-      required: true,
-      digits: true,
-      minlength: 4,
-      maxlength: 4
-  )
-  $('.bearing_input').rules(
-    'add',
-      required: true,
-      digits: true,
-      min: 0,
-      max: 360
-  )
-  $('.tilt_input').rules(
-    'add',
-      required: true,
-      digits: true,
-      min: 0,
-      max: 90
-  )
-  $('.area_input').rules(
-    'add',
-      required: true,
-      number: true,
-      min: 0
-  )# >>>
+      element.parent().append(error)# >>>
   #add/remove nested forms without link_to_function (which has been deprecated)# <<<
   add_fields = (event) ->
     #clone fields
@@ -82,7 +55,6 @@
     event.preventDefault()
     remove_fields(event)# >>>
   #animate panel icon# <<<
-  #TODO validate and prevent dud values from activating animation
   $('.bearing_input').change ->
     if form_validation.valid()
       bearing = this.value - 45
@@ -168,12 +140,11 @@
       dni_array = dni_string.split(" ").map(parseFloat)
       for month in [1..12]
         #if you change times here, remember to also change them in panel.dni_received_pa
-        for hour in [5..20] by 0.5
+        for hour in [5..20] by 0.5
           kW = dni_array.shift()
           row = [hour, month, kW]
           data.addRow(row)

-      # specify options
       options =
         width:  "500px"
         height: "450px"
@@ -193,3 +164,31 @@

 # Set callback to run when API is loaded
   google.setOnLoadCallback(drawVisualization)# >>>
+  #rules must be here to work!
+  $('#pv_query_postcode_id').rules(# <<<
+    'add',
+      required: true,
+      digits: true,
+      minlength: 4,
+      maxlength: 4
+  )
+  $('.bearing_input').rules(
+    'add',
+      required: true,
+      digits: true,
+      min: 0,
+      max: 360
+  )
+  $('.tilt_input').rules(
+    'add',
+      required: true,
+      digits: true,
+      min: 0,
+      max: 90
+  )
+  $('.area_input').rules(
+    'add',
+      required: true,
+      number: true,
+      min: 0
+  )# >>>


test some routes

test some routes


Remove unused imports.

Remove unused imports.

--- src/operation-queue.coffee
+++ src/operation-queue.coffee
@@ -1,4 +1,4 @@
-define ['underscore', 'backbone'], (_, Backbone) ->
+define ->

     class OperationQueue
         constructor: ->


remove unnecessary GET

remove unnecessary GET

--- app/components/admin-page/admin-page.coffee
+++ app/components/admin-page/admin-page.coffee
@@ -25,11 +25,10 @@
     $scope.currencies = Currencies()

     $scope.updateGroup = () ->
-      Records.groups.findOrFetchById(groupId).then (group) ->
-        group.save()
-          .then ->
-            Toast.show('You updated '+group.name)
-            $scope.cancel()
+      $scope.group.save()
+        .then ->
+          Toast.show('You updated ' + $scope.group.name)
+          $scope.cancel()

     $scope.viewGroup = (groupId) ->
       $location.path("/groups/#{groupId}")


Make keymap consistent

Make keymap consistent

--- keymap.cson
+++ keymap.cson
@@ -94,4 +94,4 @@

   # clip-history
   'ctrl-y': 'clip-history:paste'
-  'ctrl-Y': 'clip-history:paste-last'
+  'ctrl-shift-y': 'clip-history:paste-last'


Update BsBreadcrumbs.coffee

Update BsBreadcrumbs.coffee

--- app/scripts/components/BsBreadcrumbs.coffee
+++ app/scripts/components/BsBreadcrumbs.coffee
@@ -1,5 +1,5 @@
 ###
-Breadcrumbs compponent.
+Breadcrumbs component.
 ###
 Bootstrap.BsBreadcrumbsItem = Bootstrap.ItemView.extend(
     tagName: ['li']
@@ -62,4 +62,4 @@
             @get('content.lastObject').set 'active', true
 )

-Ember.Handlebars.helper 'bs-breadcrumbs', Bootstrap.BsBreadcrumbs+Ember.Handlebars.helper 'bs-breadcrumbs', Bootstrap.BsBreadcrumbs


Popcorn enemies!

Popcorn enemies!

--- app/scripts/components/entities/Mine.coffee
+++ app/scripts/components/entities/Mine.coffee
@@ -10,7 +10,7 @@
       @attr rotation: @rotation + delta

   mine: (attr = {}) ->
-    @attr _.defaults(attr, h: 25, w: 25, health: 200)
+    @attr _.defaults(attr, h: 25, w: 25, health: 100)
     @origin 'center'

     @enemy()


Removed obsolete code

Removed obsolete code

--- app/scripts/controllers/last-reviews.coffee
+++ app/scripts/controllers/last-reviews.coffee
@@ -25,9 +25,6 @@
     @lastReviewsInLastHour.perHour = @lastReviewsInLastHour.count / @lastReviewsInLastHour.ellapsed

     @loadedReviewsInLastHour = true
-
-  hasSelectedDomain: ->
-    not @domainsSelected.placeholder?

   clearDomainDropdown: ->
     @domainsSelected = {text: 'Filter domain', placeholder: true}


Add ~tell support

Add ~tell support

--- src/scripts/factoid.coffee
+++ src/scripts/factoid.coffee
@@ -13,10 +13,18 @@
   get: (key) ->
     @cache[key] or "No factoid for #{key}"

+  tell: (person, key) ->
+    factoid = this.get key
+    if @cache[key]
+      "#{person}, #{key} is #{factoid}"
+    else
+      factoid

   handleFactoid: (text) ->
     if match = /^~(.+) is (.+)/i.exec text
       this.add match[1], match[2]
+    else if match = /^~tell (.+) about (.+)/i.exec text
+      this.tell match[1], match[2]
     else if match = /^~(.+)/i.exec text
       this.get match[1]


fix current song

fix current song

--- src/scripts/meowziq.coffee
+++ src/scripts/meowziq.coffee
@@ -34,10 +34,10 @@

   watch = (robot) ->
     setTimeout ->
-      request config.url + '/songs'
+      request config.url + '/status'
       .then (r) ->
         json = JSON.parse r.body
-        song = json[0]
+        song = json.song
         return unless song?
         return if playing is song.title
         playing = song.title


fix(regex): Make regex checks case insensitive

--- src/scripts/youtube.coffee
+++ src/scripts/youtube.coffee
@@ -17,11 +17,11 @@

 module.exports = (robot) ->

-  robot.respond /yf add (.*?)$/, (msg) ->
+  robot.respond /yf add (.*?)$/i, (msg) ->
     # do stuff

-  robot.respond /yf remove (.*?)$/, (msg) ->
+  robot.respond /yf remove (.*?)$/i, (msg) ->
     # do stuff

-  robot.respond /yf list$/, (msg) ->
+  robot.respond /yf list$/i, (msg) ->
     # do stuff


:bug: Disallow linting unless got SFTP object

--- lib/hack.coffee
+++ lib/hack.coffee
@@ -24,7 +24,6 @@
       Contents.status = false
       Contents.type = Contents.type || 'remote'
       Contents.port = Contents.port || 22
-      @config = Contents
       return unless Contents.type is 'remote'
       return @showError("Please specify a privateKey") unless Contents.privateKey
       return @showError("Please specify a remoteDir") unless Contents.remoteDir
@@ -35,8 +34,13 @@
         @SSH = new (require('node-ssh'))(Contents)
       catch error
         return @showError(error.toString())
+      @config = Contents
       @SSH.connect().then( =>
-        Contents.status = true
+        @SSH.requestSFTP().then (SFTP) =>
+          Contents.status = true
+          @SFTP = SFTP
+        , (e)=>
+          @showError("Error requesting SFTP Object: " + e.toString())
         @showSuccess("Successfully Connected to Server")
       , (e)=>
         @showError("Error Connecting to Server: " + e.toString())
@@ -44,13 +48,7 @@
   transfer: (LocalPath)->
     return new Promise (Resolve, Reject)=>
       RemotePath = @config.remoteDir + '/' + Path.relative(atom.project.getPaths()[0], LocalPath).replace(Path.sep, '/')
-      if @SFTP is null
-        LePromise = @SSH.requestSFTP()
-      else
-        LePromise = Promise.resolve(@SFTP)
-      LePromise.then (SFTP)=>
-        @SFTP = SFTP
-        @SSH.put(LocalPath, RemotePath, SFTP, true).then(Resolve, Reject)
+      @SSH.put(LocalPath, RemotePath, @SFTP, true).then(Resolve, Reject)
   showError:(Message)->
     Notification = atom.notifications.addError("[Hack] #{Message}", {dismissable: true})
     setTimeout ->


comment things

comment things

--- lib/init.coffee
+++ lib/init.coffee
@@ -12,7 +12,7 @@

 plugin = require './plugin'
-lupa = (require 'lupa').lupa
+#lupa = (require 'lupa').lupa

 fs = require 'fs'
 path_ = require 'path'
@@ -31,17 +31,18 @@
 </div>"

 getFileFor = (name) ->
-    files = plugin.lupa.getState().files
-    found = files.filter(
-        (f) -> f.metadata.filter(
-            (entry) -> entry.name == 'modules' && entry.data.indexOf(name) != -1
-        ).length
-    )
-    console.log('getFileFor ' + name, files)
-    console.log('getFileFor found', found)
-    if found.length
-        return "(" + found[0].path + ")"
     return ''
+    # files = plugin.lupa.getState().files
+    # found = files.filter(
+    #     (f) -> f.metadata.filter(
+    #         (entry) -> entry.name == 'modules' && entry.data.indexOf(name) != -1
+    #     ).length
+    # )
+    # console.log('getFileFor ' + name, files)
+    # console.log('getFileFor found', found)
+    # if found.length
+    #     return "(" + found[0].path + ")"
+    # return ''

 atom.workspace.addLeftPanel(item: el)


change settings name

change settings name

--- lib/init.coffee
+++ lib/init.coffee
@@ -6,11 +6,11 @@
       type: 'string'
       default: 'standard'
       enum: ['standard', 'semi-standard']
-    codeStyleDevDependencies:
+    checkStyleDevDependencies:
       type: 'boolean'
       description: 'Check code style on package.json devDependencies'
       default: false
-    honorStandardSettings:
+    honorStyleSettings:
       type: 'boolean'
       description: 'Honor standard/semistandard settings on pacakge.json'
       defaukt: true


Added a new block every X time steps

Added a new block every X time steps

--- main.coffee
+++ main.coffee
@@ -143,9 +143,12 @@

 		world.add Physics.behavior 'sweep-prune'

+		counter = 0
+
 		world.on 'step', ->
-			y = current_block.state.pos.y
-			if y > CANVAS_HALF_HEIGHT
+			counter += 1
+			if counter > 50*5
+				counter = 0
 				add_block(world)

 			world.render()
@@ -176,14 +179,12 @@

 			if hit
 				tracking = hit
-				console.log 'down:', hit

 		mouse_move = (event) ->
 			if not tracking
 				return
 			[cx,cy] = mouse_2_canvas_coords canvas, event
 			[mousepos_x, mousepos_y] = [cx, cy]
-			console.log 'move:', cx, cy

 		mouse_up = (event) ->
 			[cx,cy] = mouse_2_canvas_coords canvas, event
@@ -192,8 +193,18 @@
 			hit = world.findOne
 				$at: pos

-			if hit
+			if hit and hit != tracking
 				console.log 'up:', hit
+				if hit.regex? and hit.regex == tracking.regex_type
+					# Got a match, increase player score and remove block
+					world.remove(tracking)
+					console.log 'from msg to regex'
+				else if tracking.regex? and tracking.regex == hit.regex_type
+					# Got a match, increase player score and remove block
+					world.remove(hit)
+					console.log 'from regex to hit'
+				else
+					# No match, decrease player score!

 			tracking = false


Remove one unnecessary loop iteration from selection sort

--- main.coffee
+++ main.coffee
@@ -92,7 +92,7 @@
 # @author Bernhard Häussner (https://github.com/bxt)
 ssort = ->
 	min = index-1
-	for j in [index-1 ... colours.length]
+	for j in [index...colours.length]
 		min = j if colours[j].val < colours[min].val

 	swapRects(index-1, min)


give masks full width and height and translate for gpu acceleration in webkit

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -191,7 +191,9 @@
     hMask.className = 'oridomi-mask-h'
     hMask.style.position = 'absolute'
     hMask.style.overflow = 'hidden'
-    hMask.style.height = @panelHeight + 'px'
+    hMask.style.width = '100%'
+    hMask.style.height = '100%'
+    hMask.style[css.transform] = 'translate3d(0, 0, 0)'
     hMask.appendChild contentHolder

     if @shading


detect vendor prefix for box-sizing

detect vendor prefix for box-sizing

--- oridomi.coffee
+++ oridomi.coffee
@@ -47,6 +47,7 @@
   perspective:        'perspective'
   perspectiveOrigin:  'perspectiveOrigin'
   backface:           'backfaceVisibility'
+  boxSizing:          'boxSizing'

 # This function checks for the presence of CSS properties on the test div.
 testProp = (prop) ->


doc tweaks

doc tweaks

--- oridomi.coffee
+++ oridomi.coffee
@@ -1046,7 +1046,7 @@
     nextPanel()

-  # Essentially the inverse of `foldUp`.
+  # The inverse of `foldUp`.
   unfold: (callback) ->
     # If the target isn't folded up, there's no reason to call this method and
     # the callback is immediately invoked.
@@ -1085,13 +1085,13 @@
   # ===================

-  # Completely folds in target.
+  # Convenience proxy to accordion-fold instance to maximum angle.
   collapse: (anchor, options = {}) ->
     options.sticky = false
     @accordion -89, anchor, options

-  # Same as `collapse`, but uses negative angle for slightly different effect.
+  # Same as `collapse`, but uses positive angle for slightly different effect.
   collapseAlt: (anchor, options = {}) ->
     options.sticky = false
     @accordion 89, anchor, options
@@ -1138,6 +1138,7 @@

   # External function to enable `devMode`.
   @devMode = -> devMode = true
+

 # Attach `OriDomi` constructor to `window`.
 root.OriDomi = OriDomi


removed method defaults

removed method defaults

--- oridomi.coffee
+++ oridomi.coffee
@@ -754,20 +754,6 @@
       @stageEl.style.cursor = 'default'

-  # Map of defaults for each method. Some are empty for now.
-  _methodDefaults:
-    accordion:
-      # Sticky keeps the first panel flat on the page.
-      sticky: false
-      # Stairs creates a stairway effect.
-      stairs: false
-      # Twist and fracture are similar effects that result in wild non-origami-like splits.
-      fracture: false
-      twist: false
-    curl:
-      twist: false
-
-
   # Touch / Drag Event Handlers
   # ===========================


removed expects_str

removed expects_str

--- lisp.coffee
+++ lisp.coffee
@@ -38,21 +38,20 @@
     @pos == @code.length

   expects: (pattern, throwing = false) ->
-    valid = @getChar() && (pattern instanceof RegExp and pattern.test @getChar()) || pattern == @getChar()
+    valid = @getChar() &&
+      (pattern instanceof RegExp and pattern.test @getChar()) ||
+      pattern == @code[@pos...@pos + pattern.length]
     if !valid && throwing
       throw "unexpected \"#{@getChar()}\", expects \"#{pattern}\""

     return valid
-
-  expects_str: (str, throwing = true) ->
-    valid = @code[@pos...@pos + str.length] == str

   forwards: (pattern) ->
     @expects pattern, true
     @pos++

   forwards_str: (str) ->
-    @expects_str str
+    @expects str
     @pos += str.length

   atom: ->
@@ -77,7 +76,7 @@
       return new Atom(str)

     #nil
-    if @expects_str 'nil'
+    if @expects 'nil'
       @forwards_str 'nil'
       return new Nil


Activity: privatemessage listitem show/hide initial post logic WIP

--- client/Social/Activity/views/privatemessagelistitem.coffee
+++ client/Social/Activity/views/privatemessagelistitem.coffee
@@ -1,11 +1,29 @@
 class PrivateMessageListItemView extends ActivityListItemView

-  constructor:(options = {},data)->
-    options.cssClass = KD.utils.curry 'privatemessage ', options.cssClass
+  constructor: (options = {}, data) ->
+
+    options.cssClass           = KD.utils.curry 'privatemessage ', options.cssClass
+    options.commentViewClass or= ReplyView

     super options, data

-    @likeView    = new CommentLikeView {}, data
+    @likeView = new CommentLikeView {}, data
+
+    @decorate()
+
+
+  decorate: ->
+
+    {repliesCount} = @getData()
+
+    @showParentPost()  if repliesCount < 3
+
+
+  showParentPost: -> @setClass 'with-parent'
+
+
+  hideParentPost: -> @unsetClass 'with-parent'
+

   pistachio: ->
     """


fixes

fixes

--- controllers/commits.coffee
+++ controllers/commits.coffee
@@ -4,12 +4,13 @@
   (req, res, next) ->
     if req.method is 'POST' and m = req.url.match /^\/teams\/(.+)\/commits$/
       console.log "#{req.method} #{req.url} - POST-COMMIT HOOK"
-      code = m[1]
+      code = decodeURIComponent m[1]
       console.log code
       Team.findOne code: code, (err, team) ->
         return next err if err
         return next 404 unless team
         console.log req.body
+        console.log req.body.package
         try
           package = JSON.parse req.body.package
           for commit in package.commits


Dirty hack but effective to support cmd-d, ctrl-v, I work if singleLine selection

--- lib/visual-blockwise.coffee
+++ lib/visual-blockwise.coffee
@@ -126,7 +126,10 @@
       endColumn -= 1

     ranges = ([[row, startColumn], [row, endColumn]] for row in [start.row..end.row])
-    @editor.setSelectedScreenRanges(ranges, {reversed})
+    # If selection is single line we don't need to add selection to save other mult-selection
+    # This tweeking allow find-and-replace:select-next then ctrl-v, I(or A) flow work.
+    unless selection.isSingleScreenLine()
+      @editor.setSelectedScreenRanges(ranges, {reversed})
     if wasReversed
       @updateProperties {head: @getTop(), tail: @getBottom()}
     else


добавлен _expandConfig

добавлен _expandConfig

--- lib/nbuild/builder.coffee
+++ lib/nbuild/builder.coffee
@@ -359,6 +359,13 @@

+  _expandConfig: (config) ->
+    result = {}
+    for key, val of config
+      continue if key[0] is '@'
+      result[key] = @_parseVars(val)
+    return result
+
   ###*
   * Parse variables
   * @private


do not use global uploadcare object

do not use global uploadcare object

--- app/assets/javascripts/uploadcare/widget/dialog.coffee
+++ app/assets/javascripts/uploadcare/widget/dialog.coffee
@@ -253,7 +253,7 @@
           return

         info = fileInfo.originalImageInfo
-        size = uploadcare.utils.fitSize(
+        size = utils.fitSize(
           @settings.crop[0].preferedSize,
           [info.width, info.height],
           true


Cleaned up the main module again

Cleaned up the main module again

--- lib/main.coffee
+++ lib/main.coffee
@@ -4,9 +4,10 @@
 module.exports =
   activate: ->
     @pkgName = 'levels-language-ruby'
-    @pkgDirPath = atom.packages.resolvePackagePath @pkgName
+    pkgDirPath = atom.packages.resolvePackagePath @pkgName
+    @pkgLanguageDirPath = path.join pkgDirPath, 'language'

-    @configFilePath = CSON.resolve path.join @pkgDirPath, 'language', 'config'
+    @configFilePath = CSON.resolve path.join @pkgLanguageDirPath, 'config'
     @executablePath = @getExecutablePath()

     pkgSubscription = atom.packages.onDidActivatePackage (pkg) =>
@@ -26,7 +27,7 @@
     @activated = false

   getExecutablePath: ->
-    executableDirPath = path.join @pkgDirPath, 'language', 'executables'
+    executableDirPath = path.join @pkgLanguageDirPath, 'executables'
     executable = if process.platform == 'win32' then 'run.exe' else 'run'
     return path.join executableDirPath, process.platform, executable

@@ -64,8 +65,7 @@

   onDidStartUsingLevels: ->
     @setUpConfigManagement()
-    whitelistPath = path.join @pkgDirPath, 'language', 'whitelist'
-    process.env['LRB_WHITELIST_PATH'] = whitelistPath
+    process.env['LRB_WHITELIST_PATH'] = path.join @pkgLanguageDirPath, 'whitelist'

   onDidStopUsingLevels: ->
     @configSubscription.dispose()


lazyload deps

lazyload deps

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,8 +1,4 @@
 meta = require '../package.json'
-
-# Dependencies
-{spawn} = require 'child_process'
-os = require 'os'

 module.exports =
   config:
@@ -73,6 +69,8 @@
   consumeConsolePanel: (@consolePanel) ->

   buildScript: (consolePanel) ->
+    {spawn} = require 'child_process'
+
     editor = atom.workspace.getActiveTextEditor()

     unless editor?


import platform only

import platform only

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,8 +1,8 @@
 meta = require '../package.json'

 # Dependencies
-{spawn} = require 'child_process'
-os = require 'os'
+{ spawn } = require 'child_process'
+{ platform } = require 'os'

 module.exports = InnoSetupCore =
   config:
@@ -140,7 +140,7 @@
         atom.notifications.addError("**language-innosetup**: `ISCC.exe` is not in your PATH [environmental variable](http://superuser.com/a/284351/195953)", dismissable: true)

   which: ->
-    if os.platform() is 'win32'
+    if platform() is 'win32'
       return "where"

     return "which"


simplify, anyway I will decaffeinate(toJS) next #13

--- lib/main.coffee
+++ lib/main.coffee
@@ -3,6 +3,7 @@
 _ = require 'underscore-plus'

 hasCommonJSPackageSemantics = (scopeName, filePath) ->
+  # why use `startsWith` is to make `source.js.jsx` true.
   fs.isDirectorySync(filePath) and (scopeName.startsWith('source.js') or scopeName.startsWith('source.coffee'))

 getBaseName = (file) ->
@@ -46,12 +47,12 @@
   #  - File have same basename
   detectFilePath: (filePath) ->
     # Search existing file from file list.
-    if file = _.detect(@getFiles(filePath), (f) -> fs.isFileSync(f))
+    if file = @getFiles(filePath).find((f) -> fs.isFileSync(f))
       return file

     # Search file have same basename.
     baseName = getBaseName(filePath)
-    _.detect fs.listSync(path.dirname(filePath)), (f) ->
+    fs.listSync(path.dirname(filePath)).find (f) ->
       (getBaseName(f) is baseName) and fs.isFileSync(f)

   getFilePath: (editor, fileName) ->
@@ -60,12 +61,11 @@
     {scopeName} = editor.getGrammar()
     filePath = path.resolve(dirName, fileName)
     if hasCommonJSPackageSemantics scopeName, filePath
-      potentialIndexFiles = ['index.js', 'index.coffee', 'index.jsx']
-      potentialIndexFilePaths = _.map potentialIndexFiles, (indexFile) ->
-        path.join(filePath, indexFile)
-      potentialIndexFilePath = _.find potentialIndexFilePaths, (indexFilePath) ->
-        fs.isFileSync(indexFilePath)
-      return potentialIndexFilePath if potentialIndexFilePath
+      extensions = ['.js', '.coffee', '.jsx']
+      indexFilePath = extensions
+        .map (ext) -> path.join(filePath, "index#{ext}")
+        .find (indexFilePath) -> fs.isFileSync(indexFilePath)
+      return indexFilePath if indexFilePath

     if filePath = @detectFilePath(path.resolve(dirName, fileName))
       return filePath


Conditional request handling in the virtual resource middleware

--- lib/virtual-resource.coffee
+++ lib/virtual-resource.coffee
@@ -1,3 +1,5 @@
+responseCache = require './response-cache.coffee'
+
 resources = {}
 count = 0

@@ -24,12 +26,18 @@
         return next()

       sendContent = ->
-        res.setHeader 'Content-Type', resource.contentType
-        res.setHeader 'Content-Length', resource.content.length
-        res.setHeader 'ETag', resource.modified.getTime() # WARN: This will need to be redone using md5 or similar if there are multiple servers
-        res.setHeader 'Last-Modified', resource.modified.toUTCString()
-        res.setHeader header, value for header, value of resource.headers
-        res.end resource.content
+        # WARN: This will need to be redone using md5 or similar if there are multiple servers
+        etag = resource.modified.getTime()
+        modified = resource.modified
+        responseCache.setCacheHeaders res, etag, modified
+
+        if responseCache.shouldSendResponse req, etag, modified
+          res.setHeader 'Content-Type', resource.contentType
+          res.setHeader 'Content-Length', resource.content.length
+          res.setHeader header, value for header, value of resource.headers
+          res.end resource.content
+        else
+          responseCache.sendNotModified res

       if typeof resource.content == 'function'
         resource.content (content) ->


handle no api.heroku.com netrc entry - fixes #1

--- lib/menu.coffee
+++ lib/menu.coffee
@@ -7,7 +7,7 @@
     machines = netrc()
     herokuAuth = machines['api.heroku.com']

-    if herokuAuth.length == 2
+    if herokuAuth != undefined && herokuAuth.length == 2
       herokuLogin = herokuAuth[0]
       herokuToken = herokuAuth[1]


History now shows a loading bar upon opening

--- app/views/editor/level/versions_view.coffee
+++ app/views/editor/level/versions_view.coffee
@@ -7,6 +7,7 @@
   url: "/db/level/"
   page: "level"
   template: template
+  startsLoading: true

   className: "modal fade"
   closeButton: true


Delegate isLoaded in ArrayProxy on repos list

--- assets/scripts/app/controllers/repos.coffee
+++ assets/scripts/app/controllers/repos.coffee
@@ -23,6 +23,7 @@
       content: Em.ArrayProxy.extend(Em.SortableMixin).create(
         sortProperties: ['sortOrder']
         content: Travis.Repo.find()
+        isLoadedBinding: 'content.isLoaded'
       )
       limit: 30
     @set('content', content)


Refactor PleaseWait to use a true Coffeescript class

--- src/please-wait.coffee
+++ src/please-wait.coffee
@@ -10,129 +10,148 @@
     factory root
   return
 ) this, (exports) ->
-  PleaseWait = ->
-    getTransitionEvent = ->
-      el = document.createElement('fakeelement')
-      transitions =
-        'WebkitAnimation': 'webkitAnimationEnd',
-        'OAnimation': 'oAnimationEnd',
-        'msAnimation': 'MSAnimationEnd',
-        'MozAnimation': 'mozAnimationEnd'
-        'animation' : 'animationend'
+  getTransitionEvent = ->
+    el = document.createElement('fakeelement')
+    transitions =
+      'WebkitAnimation': 'webkitAnimationEnd',
+      'OAnimation': 'oAnimationEnd',
+      'msAnimation': 'MSAnimationEnd',
+      'MozAnimation': 'mozAnimationEnd'
+      'animation' : 'animationend'

-      for key, val of transitions
-        return val if el.style[key]?
+    for key, val of transitions
+      return val if el.style[key]?

-    transitionEvent = getTransitionEvent()
+  transitionEvent = getTransitionEvent()

-    _pleaseWait =
-      defaultOptions:
-        backgroundColor: null
-        logo: null
-        loadingHtml: null
-        template: """
-

-

-

-

-
-

-

-

+  class PleaseWait
+    @_defaultOptions:
+      backgroundColor: null
+      logo: null
+      loadingHtml: null
+      template: """
+

+

+

+

+
+

+

-        """
+

+      """

-      done: ->
-        return unless @_loadingDiv?
-        listener = =>
-          document.body.removeChild(@_loadingDiv)
-          document.body.className += " pg-loaded"
-          if transitionEvent? then @_loadingDiv.removeEventListener(transitionEvent, listener)
-          @_loadingDiv = null
+    constructor: (options) ->
+      defaultOptions = @constructor._defaultOptions
+      @options = {}

+      # Set initial options, merging given options with the defaults
+      for k, v of defaultOptions
+        @options[k] = if options[k]? then options[k] else v
+
+      # Create the loading screen element
+      @_loadingElem = document.createElement("div")
+      # Create an empty array to store the potential list of loading HTML (messages, spinners, etc)
+      # we'll be displaying to the screen
+      @_loadingHtmlToDisplay = []
+      # Add a global class for easy styling
+      @_loadingElem.className = "pg-loading-screen"
+      # Set the background color of the loading screen, if supplied
+      @_loadingElem.style.backgroundColor = @options.backgroundColor if @options.backgroundColor?
+      # Initialize the loading screen's HTML with the defined template. The default can be overwritten via options
+      @_loadingElem.innerHTML = @options.template
+      # Find the element that will contain the loading HTML displayed to the user (typically a spinner/message)
+      # This can be changed via updateLoadingHtml
+      @_loadingHtmlElem = @_loadingElem.getElementsByClassName("pg-loading-html")[0]
+      # Set the initial loading HTML, if supplied
+      @_loadingHtmlElem.innerHTML = @options.loadingHtml if @_loadingHtmlElem?
+      # Set a flag that lets us know if the transitioning between loading HTML elements is finished.
+      # If true, we can transition immediately to a new message/HTML
+      @_readyToShowLoadingHtml = false
+      # Find the element that displays the loading logo and set the src if supplied
+      logoElem = @_loadingElem.getElementsByClassName("pg-loading-logo")[0]
+      logoElem.src = @options.logo if logoElem?
+      # Add the loading screen to the body
+      document.body.appendChild(@_loadingElem)
+      # Add the CSS class that will trigger the initial transitions of the logo/loading HTML
+      @_loadingElem.className += " pg-loading"
+      # Define a listener to look for any new loading HTML that needs to be displayed after the intiial transition finishes
+      listener = =>
+        @_readyToShowLoadingHtml = true
+        if transitionEvent? then @_loadingHtmlElem.removeEventListener(transitionEvent, listener)
+        if @_loadingHtmlToDisplay.length > 0 then @_changeLoadingHtml()
+      # Detect CSS animation support. If not found, we'll call the listener immediately. Otherwise, we'll wait
+      if transitionEvent?
+        @_loadingHtmlElem.addEventListener(transitionEvent, listener)
+      else
+        listener()
+
+    finish: ->
+      return unless @_loadingElem?
+      # Again, define a listener to run once the loading screen has fully transitioned out
+      listener = =>
+        # Remove the loading screen from the body
+        document.body.removeChild(@_loadingElem)
+        # Add a class to the body to signal that the loading screen has finished and the app is ready
+        document.body.className += " pg-loaded"
+        if transitionEvent? then @_loadingElem.removeEventListener(transitionEvent, listener)
+        # Reset the loading screen element since it's no longer attached to the DOM
+        @_loadingElem = null
+
+      # Detect CSS animation support. If not found, we'll call the listener immediately. Otherwise, we'll wait
+      if transitionEvent?
+        # Set a class on the loading screen to trigger a fadeout animation
+        @_loadingElem.className += " pg-loaded"
+        # When the loading screen is finished fading out, we'll remove it from the DOM
+        @_loadingElem.addEventListener(transitionEvent, listener)
+      else
+        listener()
+
+    updateLoadingHtml: (loadingHtml, immediately=false) ->
+      if immediately
+        # Ignore any loading HTML that may be queued up. Show this immediately
+        @_loadingHtmlToDisplay = [loadingHtml]
+        @_readyToShowLoadingHtml = true
+      else
+        # Add to an array of HTML to display to the user
+        @_loadingHtmlToDisplay.push(loadingHtml)
+      # If ready, let's display the new loading HTML
+      if @_readyToShowLoadingHtml then @_changeLoadingHtml()
+
+    _changeLoadingHtml: ->
+      @_readyToShowLoadingHtml = false
+
+      loadingListener = =>
+        # New loading HTML has fully transitioned in. We're now ready to show a new message/HTML
+        @_readyToShowLoadingHtml = true
+        # Remove the CSS class that triggered the fade in animation
+        @_loadingHtmlElem.className = @_loadingHtmlElem.className.replace(" pg-loading ", "")
+        if transitionEvent? then @_loadingHtmlElem.removeEventListener(transitionEvent, loadingListener)
+        # Check if there's still HTML left in the queue to display. If so, let's show it
+        if @_loadingHtmlToDisplay.length > 0 then @_changeLoadingHtml()
+
+      removingListener = =>
+        # Last loading HTML to display has fully transitioned out. Time to transition the new in
+        @_loadingHtmlElem.innerHTML = @_loadingHtmlToDisplay.shift()
+        # Add the CSS class to trigger the fade in animation
+        @_loadingHtmlElem.className = @_loadingHtmlElem.className.replace(" pg-removing ", " pg-loading ")
         if transitionEvent?
-          @_loadingDiv.className  += " pg-loaded"
-          @_loadingDiv.addEventListener(transitionEvent, listener)
+          @_loadingHtmlElem.removeEventListener(transitionEvent, removingListener)
+          @_loadingHtmlElem.addEventListener(transitionEvent, loadingListener)
         else
-          listener()
+          loadingListener()

-      updateLoadingHtml: (loadingHtml, immediately=false) ->
-        if immediately
-          @_loadingHtmlToDisplay = [loadingHtml]
-          @_readyToShowLoadingHtml = true
-        else
-          @_loadingHtmlToDisplay.push(loadingHtml)
+      if transitionEvent?
+        # Add the CSS class that will cause the HTML to fade out
+        @_loadingHtmlElem.className += " pg-removing "
+        @_loadingHtmlElem.addEventListener(transitionEvent, removingListener)
+      else
+        removingListener()

-        if @_readyToShowLoadingHtml then @_changeLoadingHtml()
+  pleaseWait = (options = {}) ->
+    new PleaseWait(options)

-      load: (options = {}) ->
-        options                            = @_createOptions(options)
-        @_loadingDiv                       ||= document.createElement("header")
-        @_loadingHtmlToDisplay             = []
-        @_loadingDiv.className             = "pg-loading-screen"
-        @_loadingDiv.style.backgroundColor = options.backgroundColor if options.backgroundColor?
-        @_loadingDiv.innerHTML             = options.template
-        @_loadingHtml                      = @_loadingDiv.getElementsByClassName("pg-loading-html")[0]
-        @_loadingHtml.innerHTML            = options.loadingHtml if @_loadingHtml?
-        @_readyToShowLoadingHtml           = false
-        logo                               = @_loadingDiv.getElementsByClassName("pg-loading-logo")[0]
-        logo.src                           = options.logo if logo?
-
-        document.body.appendChild(@_loadingDiv)
-        @_loadingDiv.className += " pg-loading"
-
-        listener = =>
-          @_readyToShowLoadingHtml = true
-          if transitionEvent? then @_loadingHtml.removeEventListener(transitionEvent, listener)
-          if @_loadingHtmlToDisplay.length > 0 then @_changeLoadingHtml()
-
-        if transitionEvent?
-          @_loadingHtml.addEventListener(transitionEvent, listener)
-        else
-          listener()
-
-        return @_loadingDiv
-
-      _createOptions: (options = {}) ->
-        defaultOptions = @defaultOptions
-        newOptions     = {}
-
-        # Set options
-        for k, v of defaultOptions
-          newOptions[k] = if options[k]? then options[k] else v
-
-        newOptions
-
-      _changeLoadingHtml: ->
-        @_readyToShowLoadingHtml = false
-
-        loadingListener  = =>
-          @_readyToShowLoadingHtml = true
-          @_loadingHtml.className = @_loadingHtml.className.replace(" pg-loading ", "")
-          if transitionEvent? then @_loadingHtml.removeEventListener(transitionEvent, loadingListener)
-          if @_loadingHtmlToDisplay.length > 0 then @_changeLoadingHtml()
-
-        removingListener = =>
-          @_loadingHtml.innerHTML = @_loadingHtmlToDisplay.shift()
-          @_loadingHtml.className = @_loadingHtml.className.replace(" pg-removing ", " pg-loading ")
-          if transitionEvent?
-            @_loadingHtml.removeEventListener(transitionEvent, removingListener)
-            @_loadingHtml.addEventListener(transitionEvent, loadingListener)
-          else
-            loadingListener()
-
-        if transitionEvent?
-          @_loadingHtml.className += " pg-removing "
-          @_loadingHtml.addEventListener(transitionEvent, removingListener)
-        else
-          removingListener()
-
-    return {
-      start  : (options = {}) -> _pleaseWait.load(options)
-      finish : -> _pleaseWait.done()
-      updateLoadingHtml: (html) -> _pleaseWait.updateLoadingHtml(html)
-    }
-
-  exports.pleaseWait = new PleaseWait()+  exports.pleaseWait = pleaseWait
+  return pleaseWait


Fix tests stats

Fix tests stats

They were not being counted at all

--- lib/assets/javascripts/oojspec.js.coffee
+++ lib/assets/javascripts/oojspec.js.coffee
@@ -7,8 +7,8 @@
     @assertions = buster.assertions
     (logFormatter = buster.create buster.format).quoteStrings = false
     @assertions.format = buster.bind logFormatter, "ascii"
-    @assertions.on 'pass', => @stats.assertions++
-    @assertions.on 'failure', => @stats.failures++
+    @assertions.on 'pass',    => @stats.tests++; @stats.assertions++
+    @assertions.on 'failure', => @stats.tests++; @stats.failures++
     #@runner.on 'context:start', => @stats.contexts++
     @runner.on 'test:timeout', => @stats.timeouts++
     @runner.on 'test:error', => @stats.errors++


:art: #79 determine valid column(tab stop) for indented text.

--- lib/selection-wrapper.coffee
+++ lib/selection-wrapper.coffee
@@ -57,15 +57,24 @@
     @selectRowRange @selection.getBufferRowRange()

   getTailRange: ->
-    {start, end} = @selection.getBufferRange()
-    if (start.row isnt end.row) and (start.column is 0) and (end.column is 0)
+    if @isLinewise()
       [startRow, endRow] = @selection.getBufferRowRange()
       row = if @selection.isReversed() then endRow else startRow
       @selection.editor.bufferRangeForBufferRow(row, includeNewline: true)
     else
-      point = @selection.getTailBufferPosition()
-      columnDelta = if @selection.isReversed() then -1 else +1
-      Range.fromPointWithDelta(point, 0, columnDelta)
+      tail = @selection.getTailBufferPosition()
+      {editor} = @selection
+      tokenizedLine = editor.tokenizedLineForScreenRow(tail.row)
+      if @selection.isReversed()
+        column = tail.column - 1
+        clip = 'backward'
+      else
+        column = tail.column + 1
+        clip = 'forward'
+
+      column = tokenizedLine.clipScreenColumn(column, {clip})
+      head = [tail.row, column]
+      new Range(tail, head)

   preserveCharacterwise: ->
     prop = @detectCharacterwiseProperties()


fix(lint): Indentation causing linter failures

--- internal_packages/onboarding/lib/account-settings-page.cjsx
+++ internal_packages/onboarding/lib/account-settings-page.cjsx
@@ -241,14 +241,12 @@
   _onNetworkError: (err) =>
     errorMessage = err.message
     if errorMessage == "Invite code required"
-      choice = dialog.showMessageBox(
-            remote.getCurrentWindow(),
-            {
-                type: 'info',
-                buttons: ['Okay'],
-                title: 'Confirm',
-                message: 'Due to a large number of sign-ups this week, you’ll need an invitation code to add another account! Visit http://invite.nylas.com/ to grab one, or hold tight!'
-            });
+      choice = dialog.showMessageBox(remote.getCurrentWindow(), {
+        type: 'info',
+        buttons: ['Okay'],
+        title: 'Confirm',
+        message: 'Due to a large number of sign-ups this week, you’ll need an invitation code to add another account! Visit http://invite.nylas.com/ to grab one, or hold tight!'
+      })
       OnboardingActions.moveToPage("token-auth")
     if errorMessage == "Invalid invite code"
       # delay?


make unarchive more responsive by downloading documents in parallel

unarchive is triggered interactively so we should try to make it
reasonably fast

--- services/track-changes/app/coffee/DocArchiveManager.coffee
+++ services/track-changes/app/coffee/DocArchiveManager.coffee
@@ -47,7 +47,7 @@
 				return callback new Error("No docs for project #{project_id}")
 			jobs = _.map docs, (doc) ->
 				(cb)-> DocArchiveManager.unArchiveDocChanges project_id, doc._id, cb
-			async.series jobs, callback
+			async.parallelLimit jobs, 4, callback

 	unArchiveDocChanges: (project_id, doc_id, callback)->
 		MongoManager.getArchivedDocChanges doc_id, (error, count) ->


this should fix oauth state csrf protection

--- main.coffee
+++ main.coffee
@@ -25,6 +25,16 @@
 else
   console.log "config file not found"
   return
+
+# read oauth configuration file
+config.oauth = null
+fs.readFile './oauth-config.json', "utf8", (err, data) ->
+  if err
+    console.log "failed to read OAuth config from file ", err
+    return
+  else
+    config.oauth = JSON.parse(data)
+    config.oauth.states = {}

 # nodejs' http cannot deal with a chain in one file ->
 # quickfix according to http://stackoverflow.com/a/31629223/1518225
@@ -148,17 +158,10 @@
       else res.json {success: false, error: 'incomplete request'}
     else res.json {success: false, error: 'invalid session'}

+
 # read the oauth stuff
 # https://developer.github.com/v3/oauth/
 # https://developer.github.com/v3/#current-version
-config.oauth = {}
-
-fs.readFile './oauth-config.json', "utf8", (err, data) ->
-  if err
-    console.log "failed to read OAuth config from file " + err
-  else
-    config.oauth = JSON.parse(data)
-    config.oauth.states = {}

 newRandomState = (length = 64) ->
     buf = new Buffer length
@@ -166,11 +169,21 @@
     fs.readSync fd, buf, 0, length, null
     return buf.toString 'hex'

+setOAuthState = (req, res) ->
+  #client = req.headers['x-forwarded-for'] || req.connection.remoteAddress
+  client = newRandomState(32)
+  res.cookie 'ctfpad-oauth-state-id', client
+  config.oauth.states[client] = newRandomState()
+  # FIXME: if this log is removed then somehow the states object becomes empty
+  # sometimes o_O
+  console.log "current oauth states", config.oauth.states
+  return config.oauth.states[client]
+
 app.get '/githublogin', (req, res) ->
   unless config.oauth
     res.send 500, "Github Login is disabled"
-  clientaddr = req.headers['x-forwarded-for'] || req.connection.remoteAddress
-  config.oauth.states[clientaddr] = newRandomState()
+    return
+  state = setOAuthState req, res
   url = "https://github.com/login/oauth/authorize?client_id="
   url += escape config.oauth.id
   url += "&redirect_uri="
@@ -178,8 +191,8 @@
   url += "&scope="
   url += escape config.oauth.scope
   url += "&state="
-  url += escape config.oauth.states[clientaddr]
-  console.log "redirecting to #{url}"
+  url += escape state
+  #console.log "redirecting to #{url}"
   res.redirect 302, url

 # get json data from github api access
@@ -201,24 +214,37 @@
         code = eresp.statusCode
       cb {}, {errortype: "request", error: "#{err}", code: code}

+checkOAuthState = (req, res) ->
+  #console.log "current oauth states", config.oauth.states
+  stateIdCookie = 'ctfpad-oauth-state-id'
+  clientStateKey = req.cookies[stateIdCookie]
+  unless req.query.state
+    console.log "checkOAuthState - No state passed for #{clientStateKey} -", req.query
+    res.send 500, "No state passed for #{clientStateKey}"
+    return
+  unless config.oauth.states and config.oauth.states[clientStateKey]
+    console.log "checkOAuthState - No state configured for #{clientStateKey} in ", config.oauth.states
+    res.send 500, "No state exists for #{clientStateKey}"
+    return
+  unless req.query.state == config.oauth.states[clientStateKey]
+    console.log "checkOAuthState - possible CSRF attempt at #{clientStateKey} - ", req.query, config.oauth.states
+    res.send 500, "Github Login failed (state mismatch)"
+    return
+
+  delete config.oauth.states[clientStateKey]
+  res.clearCookie stateIdCookie
+
 app.get '/oauthcb', (req, res) ->
   unless config.oauth
     res.send 500, "Github Login is disabled"
     return
+  unless checkOAuthState req, res
+    return
+  # get the code from github
   unless req.query.code
     res.send 500, "Github Login failed (missing code)"
     return
-  clientaddr = req.headers['x-forwarded-for'] || req.connection.remoteAddress
-  unless req.query.state
-    res.send 500, "No state configured for #{clientaddr}"
-    return
-  if req.query.state != config.oauth.states[clientaddr]
-    console.log "possible CSRF attempt at #{clientaddr} - #{url}"
-    res.end 500, "Github Login failed (state mismatch)"
-    return
-  delete config.oauth.states[clientaddr]
   code = req.query.code
-  # TODO: csrf handling
   #state = req.params.state
   url = 'https://github.com/login/oauth/access_token'
   data = {client_id: config.oauth.id, \


Only remove content after fading

Only remove content after fading

--- core/app/assets/javascripts/base/simple_modal_window.coffee
+++ core/app/assets/javascripts/base/simple_modal_window.coffee
@@ -4,5 +4,5 @@
     $(baseClass).fadeIn 'fast'

   $("#{baseClass}-layer").on 'click', ->
-    $("#{baseClass}-body").html ''
-    $(baseClass).fadeOut 'fast'
+    $(baseClass).fadeOut 'fast', ->
+      $("#{baseClass}-body").html ''


Quick fix for not using NotificationCenter for now (until we implement it properly) -- this is the only notification that doesn't work

--- core/app/backbone/views/wheel/interactive_wheel_view.coffee
+++ core/app/backbone/views/wheel/interactive_wheel_view.coffee
@@ -3,8 +3,8 @@
     if @model.isUserOpinion(opinion_type)
       @model.unsetActiveOpinionType opinion_type,
         error: ->
-          FactlinkApp.NotificationCenter.error "Something went wrong while removing your opinion on the Factlink, please try again."
+          alert "Something went wrong while removing your opinion on the Factlink, please try again."
     else
       @model.setActiveOpinionType opinion_type,
         error: ->
-          FactlinkApp.NotificationCenter.error "Something went wrong while setting your opinion on the Factlink, please try again."
+          alert "Something went wrong while setting your opinion on the Factlink, please try again."


Atom editor / validations not working on Windows

--- lib/sifu.coffee
+++ lib/sifu.coffee
@@ -62,7 +62,7 @@
     lines = javacOutput.split /\r?\n/

     for line in lines
-      match = line.match /.+\[ERROR\][^ ]* (\d+).+<-(.*)/
+      match = line.match /.*\[ERROR\][^ ]* (\d+).+<-(.*)/
       if !!match
         [lineNum, mess] = match[1..2]
         lineNum-- # Fix range-beginning


only add feature as property if ctx.instance exists

--- src/lang/extensions.coffee
+++ src/lang/extensions.coffee
@@ -367,7 +367,7 @@
     construct: (data, ctx) ->
       feature = @binding
       feature = expr.eval feature, ctx for expr in @exprs when feature?
-      (new Model.Property @tag, this).join(ctx.instance) if feature?
+      (new Model.Property @tag, this).join(ctx.instance) if ctx?.instance? and feature?
       return data

   new Extension 'fraction-digits',


no longer need linked-list

no longer need linked-list

--- src/lib/linked-list.coffee
+++ src/lib/linked-list.coffee
@@ -1,58 +0,0 @@
-# Inspired by http://blog.jcoglan.com/2007/07/23/writing-a-linked-list-in-javascript/
-
-class Node
-  constructor: (@data) ->
-    @prev = @next = null
-
-
-class LinkedList
-  @Node: Node
-
-  constructor: ->
-    @length = 0
-    @first = @last = null
-
-  append: (node) ->
-    if @first?
-      node.next = null
-      @last.next = node
-    else
-      @first = node
-    node.prev = @last
-    @last = node
-    @length += 1
-
-  insertAfter: (refNode, newNode) ->
-    newNode.prev = refNode
-    if refNode?
-      newNode.next = refNode.next
-      refNode.next.prev = newNode if refNode.next?
-      refNode.next = newNode
-      @last = newNode if refNode == @last
-    else # Insert after null implies inserting at position 0
-      newNode.next = @first
-      @first.prev = newNode
-      @first = newNode
-    @length += 1
-
-  remove: (node) ->
-    if @length > 1
-      node.prev.next = node.next if node.prev?
-      node.next.prev = node.prev if node.next?
-      @first = node.next if node == @first
-      @last = node.prev if node == @last
-    else
-      @first = @last = null
-    node.prev = node.next = null
-    @length -= 1
-
-  toArray: ->
-    arr = []
-    cur = @first
-    while cur?
-      arr.push(cur)
-      cur = cur.next
-    return arr
-
-
-module.exports = LinkedList


put literally all instantiation logic in the factory method

--- src/mixinfactory.coffee
+++ src/mixinfactory.coffee
@@ -29,13 +29,13 @@
       if hook? && !_.isFunction hook
         throw new TypeError "Expected a function for #{mixinhook_key}"

-  @from_obj: (obj) ->
+  @from_obj: (obj, freeze = true) ->
     unless _.isObject(obj) && !_.isArray(obj)
       throw new TypeError "Expected non-empty object"
     unless _.isString(obj.name) && obj.name
       throw new @ArgumentError "Expected String name in options argument"

-    mixin = new Mixin(obj.name)
+    mixin = new Mixin
     mkeys = Object.keys(_.omit(obj, 'name')).sort()

     if _.isEmpty(mkeys)
@@ -49,9 +49,7 @@
             value
           set: =>
             throw new @MutabilityError "Cannot change #{key} on #{mixin}"
-    Object.freeze mixin
-
-  constructor: (@name) ->
+    (freeze && Object.freeze mixin) || mixin

   toString: ->
     string_keys = _.without(@mixin_keys, 'name')


add this for now

add this for now

--- lib/tree.coffee
+++ lib/tree.coffee
@@ -13,6 +13,7 @@
       view.querySelector(sel).onclick = =>
         onToggle?()
         setTimeout (=> @toggle view), 0
+    view.onToggle = onToggle
     @toggle view unless expand
     view


cleanup and update data-structure / differentiate icon

--- lib/view.coffee
+++ lib/view.coffee
@@ -14,27 +14,27 @@
 isGitRepository = (path) ->
   fs.isDirectorySync _path.join(path, '.git')

+highlightMatches = (context, path, matches, offsetIndex=0) ->
+  lastIndex = 0
+  matchedChars = [] # Build up a set of matched chars to be more semantic
+
+  for matchIndex in matches
+    matchIndex -= offsetIndex
+    continue if matchIndex < 0 # If marking up the basename, omit path matches
+    unmatched = path.substring(lastIndex, matchIndex)
+    if unmatched
+      context.span matchedChars.join(''), class: 'character-match' if matchedChars.length
+      matchedChars = []
+      context.text unmatched
+    matchedChars.push(path[matchIndex])
+    lastIndex = matchIndex + 1
+
+  context.span matchedChars.join(''), class: 'character-match' if matchedChars.length
+  # Remaining characters are plain text
+  context.text path.substring(lastIndex)
+
 class View extends SelectListView
   # Copied from FuzzzyFinder's and modified a little.
-  @highlightMatches: (context, path, matches, offsetIndex=0) ->
-    lastIndex = 0
-    matchedChars = [] # Build up a set of matched chars to be more semantic
-
-    for matchIndex in matches
-      matchIndex -= offsetIndex
-      continue if matchIndex < 0 # If marking up the basename, omit path matches
-      unmatched = path.substring(lastIndex, matchIndex)
-      if unmatched
-        context.span matchedChars.join(''), class: 'character-match' if matchedChars.length
-        matchedChars = []
-        context.text unmatched
-      matchedChars.push(path[matchIndex])
-      lastIndex = matchIndex + 1
-
-    context.span matchedChars.join(''), class: 'character-match' if matchedChars.length
-    # Remaining characters are plain text
-    context.text path.substring(lastIndex)
-
   initialize: ->
     super
     @addClass('project-folder')
@@ -48,15 +48,20 @@
     this

   viewForItem: (item) ->
-    matches  = match(item, @getFilterQuery())
-    basename = _path.basename(item)
+    {itemPath, itemType} = item
+    matches  = match(itemPath, @getFilterQuery())
+    iconName = if itemType is 'group' then 'briefcase' else 'repo'
+    basename = _path.basename(itemPath)
     $$ ->
-      baseOffset = item.length - basename.length
+      baseOffset = itemPath.length - basename.length
       @li class: 'two-lines', =>
-        @div {class: "primary-line file icon icon-repo", 'data-name': basename, 'data-path': item}, =>
-          View.highlightMatches(this, basename, matches, baseOffset)
+        @div {class: "primary-line file icon icon-#{iconName}", 'data-name': basename, 'data-path': itemPath}, =>
+          highlightMatches(this, basename, matches, baseOffset)
         @div {class: 'secondary-line path no-icon'}, =>
-          View.highlightMatches(this, item, matches)
+          highlightMatches(this, itemPath, matches)
+
+  getFilterKey: ->
+    "itemPath"

   getItems: ->
     loadedPaths = atom.project.getPaths()
@@ -69,7 +74,12 @@
           dirs = _.reject(dirs, (path) -> path in loadedPaths)

     dirs.map (dir) ->
-      dir.replace fs.getHomeDirectory(), '~'
+      itemPath = dir.replace(fs.getHomeDirectory(), '~')
+      if itemPath.startsWith("~/github/atom-cursor-history")
+        itemType = 'group'
+      else
+        itemType = 'repo'
+      {itemPath, itemType}

   getNormalDirectories: ->
     dirs = []
@@ -97,28 +107,28 @@

   populateList: ->
     super
-    @removeClass 'add remove'
-    @addClass @action
+    @removeClass('add remove')
+    @addClass(@action)

   # @action should be 'add' or 'remove'
   start: (@action) ->
     @storeFocusedElement()
-    @setItems @getItems()
+    @setItems(@getItems())
     @panel.show()
     @focusFilterEditor()

   confirmAndContinue: ->
     selectedItem = @getSelectedItem()
     return unless selectedItem?
-    this[@action](fs.normalize(selectedItem))
+    this[@action](selectedItem.itemPath)

     selectedItemView = @getSelectedItemView()
     @selectNextItemView()
     selectedItemView.remove()
     @items = (item for item in @items when item isnt selectedItem)

-  confirmed: (item) ->
-    this[@action] fs.normalize(item)
+  confirmed: ({itemPath}) ->
+    this[@action](itemPath)
     @cancel()

   cancelled: ->
@@ -135,29 +145,31 @@
     @action = if @action is 'add' then 'remove' else 'add'
     @setItems @getItems()

-  add: (path) ->
-    atom.project.addPath path
+  add: (itemPath) ->
+    filePath = fs.normalize(itemPath)
+    atom.project.addPath(filePath)

-  remove: (path) ->
+  remove: (itemPath) ->
+    itemPath = fs.normalize(itemPath)
     if settings.get('closeItemsForRemovedProject')
-      dir = _.detect(atom.project.getDirectories(), (d) -> d.getPath() is path)
+      dir = _.detect(atom.project.getDirectories(), (d) -> d.getPath() is itemPath)
       for e in atom.workspace.getTextEditors() when dir.contains(e.getPath())
         e.destroy()

-    atom.project.removePath path
+    atom.project.removePath(itemPath)

   replace: ->
     selected = @getSelectedItem()
-    projectPath = fs.normalize(selected)
-    @add projectPath
-    for p in atom.project.getPaths() when p isnt projectPath
-      @remove p
+    itemPath = fs.normalize(selected.itemPath)
+    @add(itemPath)
+    for p in atom.project.getPaths() when p isnt itemPath
+      @remove(p)
     @cancel()

   openInNewWindow: ->
     selected = @getSelectedItem()
-    projectPath = fs.normalize(selected)
-    atom.open({pathsToOpen: [projectPath], newWindow: true})
+    itemPath = fs.normalize(selected.itemPath)
+    atom.open(pathsToOpen: [itemPath], newWindow: true)
     @cancel()

 module.exports = View


Change event to 'did-terminate-pending-state'

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -574,11 +574,11 @@
     @displayBuffer.getEditorWidthInChars()

   onDidTerminatePendingState: (callback) ->
-    @emitter.on 'did-confirm-pending-state', callback
+    @emitter.on 'did-terminate-pending-state', callback

   terminatePendingState: ->
     @pending = false
-    @emitter.emit 'did-confirm-pending-state', this
+    @emitter.emit 'did-terminate-pending-state', this

   ###
   Section: File Details


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -3,7 +3,7 @@
     font: 'Helvetica'
     fontSize: 24
     fontColor: 'white'
-    y
+    yPosition: A

   self = GameState(I).extend
     centerText: (canvas, options={}) ->


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -5,7 +5,7 @@
     fontColor: 'white'

   self = GameState(I).extend
-    centerText: (canvas, ) ->
+    centerText: (canvas, option) ->
       canvas.font I.font

       canvas.centerText


Add select next menu item

Add select next menu item

--- menus/find-and-replace.cson
+++ menus/find-and-replace.cson
@@ -3,6 +3,7 @@
   'submenu': [
     { 'label': 'Find in Buffer', 'command': 'find-and-replace:show'}
     { 'label': 'Replace in Buffer', 'command': 'find-and-replace:show-replace'}
+    { 'label': 'Select Next', 'command': 'find-and-replace:select-next'}
     { 'type': 'separator' }
     { 'label': 'Find in Project', 'command': 'project-find:show'}
     { 'type': 'separator' }


Refactor types.coffee

Refactor types.coffee

--- src/win32/types.coffee
+++ src/win32/types.coffee
@@ -4,21 +4,26 @@
 module.exports =
 class Types

+  # fundamental types
+  @VOID: ref.types.void
+  @CHAR: ref.types.char
+  @SHORT: ref.types.short
+  @LONG: ref.types.long
+  @UCHAR: ref.types.uchar
+  @USHORT: ref.types.ushort
+  @ULONG: ref.types.ulong
+  @PVOID: ref.refType(@VOID)
+
   # windef.h
-  @VOID: ref.types.void
-  @PVOID: ref.refType(@VOID)
-  @LONG: ref.types.long
-  @ULONG: ref.types.ulong
-  @DWORD: ref.types.ulong
+  @DWORD: @ULONG
   @PDWORD: ref.refType(@DWORD)
-  @HKEY: @PVOID
-  @PHKEY: ref.refType(@HKEY)
-  @BYTE: ref.types.uchar
+  @HKEY: @PVOID # HACK reinterpret_cast
+  @PHKEY: ref.refType(@ULONG) # HACK reinterpret_cast
+  @BYTE: @UCHAR
   @LPBYTE: ref.refType(@BYTE)

   # winnt.h
-  # @WCHAR: require('./ref-wchar')
-  @WCHAR: @BYTE
+  @WCHAR: @USHORT
   @LPCWSTR: ref.refType(@WCHAR)
   @LPWSTR: ref.refType(@WCHAR)
   @ACCESS_MASK: @DWORD


git fetch recursivelly

git fetch recursivelly

--- src/package-fetcher.coffee
+++ src/package-fetcher.coffee
@@ -255,7 +255,7 @@
           error = (err) ->
             fs.rmrf dirPath, ->
             oldError err
-          gitClone = child_process.spawn "git", [ "clone", parsed.format() ], cwd: dirPath, stdio: "inherit"
+          gitClone = child_process.spawn "git", [ "clone", "--recursive", parsed.format() ], cwd: dirPath, stdio: "inherit"
           gitClone.on 'error', (err) -> error "Error executing git clone: #{err}"
           gitClone.on 'exit', (code, signal) =>
             unless code?


Fix activatePackages

Fix activatePackages

--- src/package-manager.coffee
+++ src/package-manager.coffee
@@ -419,7 +419,7 @@
     @config.transact =>
       for pack in packages
         promise = @activatePackage(pack.name)
-        promises.push(promise) unless pack.hasActivationCommands()
+        promises.push(promise) unless pack.activationShouldBeDeferred()
       return
     @observeDisabledPackages()
     @observePackagesWithKeymapsDisabled()


Make route dynamic

Make route dynamic

--- src/routes/index.coffee
+++ src/routes/index.coffee
@@ -1,6 +1,7 @@
 # External Dependencies
 debug = require('debug')('minimum-wage-service')
 express = require 'express'
+request = require 'request'

 # Express Components
 router = express.Router()


Added support to display titles, since they often help to get the hang of the joke.

--- src/scripts/9gag.coffee
+++ src/scripts/9gag.coffee
@@ -22,8 +22,8 @@

 module.exports = (robot)->
   robot.respond /9gag( me)?/i, (message)->
-    send_meme message, false, (text)->
-      message.send text
+    send_meme message, false, (title, src)->
+      message.send title, src

 send_meme = (message, location, response_handler)->
   meme_domain = "http://9gag.com"
@@ -45,13 +45,14 @@
       selectors.unshift("div.badge-animated-container-animated img")

     img_src = get_meme_image( body, selectors )
-
     if img_src.substr(0, 4) != "http"
       img_src = "http:#{img_src}"

-    response_handler img_src
+    img_title = escape_html_characters( get_meme_title( body, [".badge-item-title"] ) )

-get_meme_image = (body, selectors)->
+    response_handler img_title, img_src
+
+select_element = (body, selectors)->
   html_handler  = new HTMLParser.DefaultHandler((()->), ignoreWhitespace: true )
   html_parser   = new HTMLParser.Parser html_handler

@@ -59,4 +60,23 @@
   for selector in selectors
     img_container = Select( html_handler.dom, selector )
     if img_container && img_container[0]
-      return img_container[0].attribs.src
+      return img_container[0]
+
+get_meme_image = ( body, selectors )->
+  select_element(body, selectors).attribs.src
+
+get_meme_title = ( body, selectors )->
+  select_element(body, selectors).children[0].raw
+
+escape_html_characters = (text)->
+  replacements = [
+    [/&/g, '&']
+    [/</g, '<']
+    [/"/g, '"']
+    [/'/g, ''']
+  ]
+
+  for r in replacements
+    text = text.replace r[0], r[1]
+  return text
+


Renamed 9gag GIF env variable from NINEGAG_NO_GIFS to HUBOT_9GAG_NO_GIFS.

--- src/scripts/9gag.coffee
+++ src/scripts/9gag.coffee
@@ -6,7 +6,7 @@
 #   "soupselect": "0.2.0"
 #
 # Configuration:
-#   NINEGAG_NO_GIFS (optional; default is "false")
+#   HUBOT_9GAG_NO_GIFS (optional; default is "false")
 #
 # Commands:
 #   hubot 9gag me - Returns a random meme image
@@ -40,7 +40,7 @@
       location = response.headers['location']
       return send_meme( message, location, response_handler )

-    no_gifs = process.env.NINEGAG_NO_GIFS
+    no_gifs = process.env.HUBOT_9GAG_NO_GIFS
     selectors = ["a img.badge-item-img"]
     if no_gifs != '0' && no_gifs != 't' && no_gifs != 'true'
       selectors.unshift("div.badge-animated-container-animated img")


Simplified 'good boy' command and added root endpoint

--- src/scripts/bark.coffee
+++ src/scripts/bark.coffee
@@ -6,8 +6,7 @@
 # Author:
 #   aaronsky

-https = require 'https'
-
+moment = require 'moment'
 Organization = require('../models/organization').get()

 module.exports = (robot) ->
@@ -20,5 +19,11 @@
   robot.respond /tell me a story/i, (res) ->
     res.send 'woof woof woof'

-  robot.hear /good dog/i, (res) ->
-    res.send ':+1: :dog: :+1:'+  robot.hear /good (dog|boy|pup|puppy|ibizan|ibi)/i, (res) ->
+    res.send ':ok_hand:'
+
+  robot.router.get '/', (req, res) ->
+    res.json {
+      'name': process.env.ORG_NAME + '\'s Ibizan',
+      'time': moment().format()
+    }


fix isup to handle http://domain.com case

--- src/scripts/isup.coffee
+++ src/scripts/isup.coffee
@@ -14,8 +14,8 @@
 #   jmhobbs

 module.exports = (robot) ->
-  robot.respond /is (.*?) (up|down)(\?)?/i, (msg) ->
-    isUp msg, msg.match[1], (domain) ->
+  robot.respond /is (http\:\/\/)?(.*?) (up|down)(\?)?/i, (msg) ->
+    isUp msg, msg.match[2], (domain) ->
       msg.send domain

 isUp = (msg, domain, cb) ->


add support for submit button; improve sortable interaction

--- src/scripts/main.coffee
+++ src/scripts/main.coffee
@@ -214,11 +214,17 @@
       model: responseField
       parentView: @

+    console.log(responseField.is_last_submit() and Formbuilder.options.INCLUDE_BOTTOM_SUBMIT)
+
     #####
     # Calculates where to place this new field.
     #
+    # Is this the last submit button?
+    if responseField.is_last_submit() and Formbuilder.options.INCLUDE_BOTTOM_SUBMIT
+      @$responseFields.parent().append view.render().el
+
     # Are we replacing a temporarily drag placeholder?
-    if options.$replaceEl?
+    else if options.$replaceEl?
       options.$replaceEl.replaceWith(view.render().el)

     # Are we adding to the bottom?
@@ -236,13 +242,15 @@
     # Catch-all: add to bottom
     else
       @$responseFields.append view.render().el
+

   setSortable: ->
     @$responseFields.sortable('destroy') if @$responseFields.hasClass('ui-sortable')
     @$responseFields.sortable
       forcePlaceholderSize: true
+      axis: 'y'
+      containment: @$responseFields.parent()
       placeholder: 'sortable-placeholder'
-      items: '>div:not(.response-field-submit_button:last-child)'
       stop: (e, ui) =>
         if ui.item.data('field-type')
           rf = @collection.create Formbuilder.helpers.defaultFieldAttrs(ui.item.data('field-type')), {$replaceEl: ui.item}
@@ -287,7 +295,8 @@

   createAndShowEditView: (model) ->
     $responseFieldEl = @$el.find(".fb-field-wrapper").filter( -> $(@).data('cid') == model.cid )
-    $responseFieldEl.addClass('editing').siblings('.fb-field-wrapper').removeClass('editing')
+    #Set the editing classes, including fb-field-wrapper outside the list too (ad-hoc for last submit.)
+    $responseFieldEl.addClass('editing').parent().parent().find(".fb-field-wrapper").not($responseFieldEl).removeClass('editing')

     if @editView
       if @editView.model.cid is model.cid
@@ -376,7 +385,7 @@

     SHOW_SAVE_BUTTON: true
     WARN_IF_UNSAVED: true # this is on navigation away
-    INCLUDE_BOTTOM_SUBMIT: true
+    INCLUDE_BOTTOM_SUBMIT: false

     UNLISTED_FIELDS: [
      'submit_button'


Set isBusy and emit change on requests

Set isBusy and emit change on requests

--- src/course/model.coffee
+++ src/course/model.coffee
@@ -81,8 +81,10 @@
   confirm: (studentId) ->
     payload = { id: @id }
     payload.student_identifier = studentId unless _.isEmpty(studentId)
+    @isBusy = true
     api.channel.once "course.#{@id}.receive.confirmation.*", @_onConfirmed
     api.channel.emit("course.#{@id}.send.confirmation", data: payload)
+    @channel.emit('change')

   _onConfirmed:  (response) ->
     {data} = response
@@ -92,24 +94,26 @@
     @errors = data?.errors
     response.stopErrorDisplay = true if @errors
     delete @status unless @hasErrors() # blank status indicates good to go
+    delete @isBusy
     @channel.emit('change')
-

   # Submits a course invite for registration
   register: (inviteCode) ->
-    @errors = []
+    @isBusy = true
     api.channel.once "course.#{@ecosystem_book_uuid}.receive.registration.*", @_onRegistered
     api.channel.emit("course.#{@ecosystem_book_uuid}.send.registration", data: {
       book_uuid: @ecosystem_book_uuid, enrollment_code: inviteCode
     })
+    @channel.emit('change')

   _onRegistered: (response) ->
-    # confirmation has completed
-    _.extend(@, response.data)
-    @errors = response.data.errors
+    {data} = response
+    _.extend(@, data)
+    @errors = data.errors
     # a freshly registered course doesn't contain the is_concept_coach flag
     @is_concept_coach = true
     response.stopErrorDisplay = true if @errors
+    delete @isBusy
     @channel.emit('change')


handle response errors in crystal init

handle response errors in crystal init

--- src/crystal/init.coffee
+++ src/crystal/init.coffee
@@ -158,6 +158,8 @@
 				console.log "Loading popular modules..."

 				response = request 'get', crystal.url('api', 'modules?limit=10')
+				if response.statusCode != 200
+					throw new Error "Unable to load popular modules"
 				modules = JSON.parse response.body.toString()

 				popular_modules = []
@@ -188,6 +190,8 @@
 										result.modules[module_name] = []

 										response = request 'get', crystal.url('api', "modules/#{module_name}")
+										if response.statusCode != 200
+											throw new Error "Unknown module: #{module_name}"
 										module_config = JSON.parse response.body.toString()
 										if !module_config
 											throw new Error "Config not found for module (#{module_name})"


- added modules to crystal init

- added modules to crystal init

--- src/crystal/init.coffee
+++ src/crystal/init.coffee
@@ -8,6 +8,16 @@
 fs     = require 'fs'
 prompt = require 'prompt'
 yaml   = require 'js-yaml'
+
+popular_modules = [
+	'official.readme'
+	'official.license'
+	'official.authors'
+	'official.express'
+	'official.laravel'
+	'official.rails'
+	'official.gorm'
+]

 initProject = (opts, path) ->
 	# validate name
@@ -29,10 +39,12 @@
 	if opts.description
 		config.description = opts.description

-	# add generators to config
-	#default_generators = this.default { _: ['default','generators'] }
-	#if default_generators
-	#	config.generators = default_generators
+	# add modules to config
+	if opts.modules
+		config.modules = {}
+		for i in opts.modules
+			module = popular_modules[parseInt(i)-1]
+			config.modules[module] = 'latest'

 	# convert config obj to yaml doc
 	config = yaml.safeDump config
@@ -44,7 +56,7 @@
 	# create crystal config
 	fs.writeFileSync "#{path}/.crystal/config.yml", config

-	console.log 'Crystal initialization is complete.'
+	console.log 'Crystal initialization is complete.'.green

 init = (opts) ->

@@ -103,10 +115,33 @@
 			if err
 				console.log "\nMaybe next time."
 			else
+				result.id = opts.id || result.id
 				result.name = opts.name || result.name
 				result.version = opts.version || result.version
 				result.description = opts.description || result.description

-				initProject result, opts.path
+				console.log "Choose from popular modules to get you started:".bold
+				for i of popular_modules
+					popular_module = popular_modules[i]
+					module_i = parseInt(i)+1
+					console.log "#{module_i}) #{popular_module}"
+
+				addModule = () ->
+					prompt.get {
+						properties:
+							module:
+								description: 'Module (ex: 1, 2, official.readme)'
+					}, (err, module_result) ->
+						if err
+							console.log "\nMaybe next time."
+						else
+							if module_result.module.length
+								if !result.modules
+									result.modules = []
+								result.modules.push(module_result.module)
+								addModule()
+							else
+								initProject result, opts.path
+				addModule()

 module.exports = init


final fix

final fix

--- client/angular/components/discussion/form/discussion_form.coffee
+++ client/angular/components/discussion/form/discussion_form.coffee
@@ -9,6 +9,7 @@
   scope: {discussion: '='}
   templateUrl: 'generated/components/discussion/form/discussion_form.html'
   controller: ['$scope', ($scope) ->
+    $scope.canStartThread = true
     $scope.$watch 'discussion.groupId', (o, v)->
       return unless $scope.discussion.groupId
       $scope.maxThreads = $scope.discussion.group().parentOrSelf().subscriptionMaxThreads


show advanced settings menu in bottom too

--- client/landing/app/Applications/Ace.kdapplication/AppView.coffee
+++ client/landing/app/Applications/Ace.kdapplication/AppView.coffee
@@ -11,7 +11,7 @@

   constructor:(options, file)->

-    options.advancedSettings ?= no
+    options.advancedSettings ?= yes

     super


Renamed class for more consistency

Renamed class for more consistency

--- core/app/backbone/views/evidence/evidenceish_heading_view.coffee
+++ core/app/backbone/views/evidence/evidenceish_heading_view.coffee
@@ -1,5 +1,5 @@
 class window.EvidenceishHeadingView extends Backbone.Marionette.ItemView
-  className: 'discussion-evidenceish-heading'
+  className: 'comment-heading'
   template: 'evidence/evidenceish_heading'

   onRender: ->


rebase conflicts

rebase conflicts

--- src/scripts/modules/media/tabs/contents/contents.coffee
+++ src/scripts/modules/media/tabs/contents/contents.coffee
@@ -113,23 +113,6 @@
             if matched
               page.set('visible', matched)
               page.set('searchResult', snippet)
-              book = page.get('book')
-              bookId = "#{book.get('id')}@#{book.get('version')}"
-              pageId = "#{page.get('id')}@#{page.get('version')}"
-              BookSearchResults.fetch(
-                bookId: "#{bookId}/#{pageId}"
-                query: response.query.search_term
-              ).done((data) ->
-                html = data.results.items[0].html.replace('<q-match>', '<span class="q-match">').
-                replace('</q-match>', '</span class="q-match">')
-                $htmlNodes = $(html)
-                metaIndex = 0
-                # Heuristic: The actual content starts with a paragraph that has an id
-                ++metaIndex until $htmlNodes[metaIndex]?.id or metaIndex > $htmlNodes.length
-                $htmlNodes = $htmlNodes.slice(metaIndex)
-                html = $('<div>').append($htmlNodes).html()
-                page.set('searchHtml', html)
-              )
               expandContainers(page, true, true)
             return matched
       @loadHighlightedPage()


Hide install button for bundled packages

Closes #252

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -22,6 +22,8 @@
     @type = if @pack.theme then 'theme' else 'package'

     @handlePackageEvents()
+
+    @installButton.hide() if atom.packages.isBundledPackage(@pack.name)

     @installButton.on 'click', =>
       if @isInstalled()


Use Fabric.js Point in Position class

Use Fabric.js Point in Position class

--- position.coffee
+++ position.coffee
@@ -14,13 +14,14 @@
 #    You should have received a copy of the GNU Affero General Public License
 #    along with this program.  If not, see http://www.gnu.org/licenses/.

-define([], ->
+define(['lib/fabric'], ->

   class Position

-    constructor: (@pos, @direction, @time) ->
+    constructor: (pos, @direction, @time) ->
       @x = pos[0]
       @y = pos[1]
+      @pos = new fabric.Point(pos[0], pos[1])

   Position
 )


Change view management to be notified when panes are created

--- lib/mixins/view-management.coffee
+++ lib/mixins/view-management.coffee
@@ -1,4 +1,5 @@
 Mixin = require 'mixto'
+{TextEditor} = require 'atom'
 {CompositeDisposable} = require 'event-kit'
 {deprecate} = require 'grim'
 MinimapView = null
@@ -63,7 +64,7 @@
   # Internal: Destroys all views currently in use.
   destroyViews: ->
     view.destroy() for id, view of @minimapViews
-    @eachEditorViewSubscription?.dispose()
+    @observePaneSubscription?.dispose()
     @minimapViews = {}

   # Internal: Registers to each pane view existing or to be created and creates
@@ -73,46 +74,58 @@
     # the `eacheditorView` method. It returns a subscription object so we'll
     # store it and it will be used in the `deactivate` method to removes
     # the callback.
-    @eachEditorViewSubscription = atom.workspace.observeTextEditors (editor) =>
-      editorView = atom.views.getView(editor)
-      MinimapView ||= require '../minimap-view'
+    @observePaneSubscription = atom.workspace.observePanes (pane) =>
+      paneSubscriptions = new CompositeDisposable
+      paneSubscriptions.add pane.onDidDestroy ->
+        @updateAllViews()
+        paneSubscriptions.dispose()
+
+      paneSubscriptions.add pane.observeItems (item) =>
+        if item instanceof TextEditor
+          requestAnimationFrame => @onEditorAdded(item, pane)

-      editorId = editor.id
-      pane = atom.workspace.paneForItem(editor)
-      paneView = atom.views.getView(pane)
+      @updateAllViews()

-      return unless paneView?
+  onEditorAdded: (editor, pane) ->
+    editorView = atom.views.getView(editor)

-      if (view = @minimapViews[editorId])?
-        view.setEditorView editorView
-        view.detachFromPaneView()
-        view.attachToPaneView()
-        return
+    MinimapView ||= require '../minimap-view'

-      view = new MinimapView(editorView)
+    editorId = editor.id
+    paneView = atom.views.getView(pane)

-      @minimapViews[editorId] = view
+    return unless paneView?
+
+    if (view = @minimapViews[editorId])?
+      view.setEditorView editorView
+      view.detachFromPaneView()
+      view.attachToPaneView()
+      return
+
+    view = new MinimapView(editorView)
+
+    @minimapViews[editorId] = view
+
+    event = {view}
+    @emit('minimap-view:created', event)
+    @emitter.emit('did-create-minimap', event)
+
+    view.updateMinimapRenderView()
+
+    subscriptions = new CompositeDisposable
+    subscriptions.add editor.onDidDestroy =>
+      view = @minimapViews[editorId]

       event = {view}
-      @emit('minimap-view:created', event)
-      @emitter.emit('did-create-minimap', event)
+      if view?
+        @emit('minimap-view:will-be-destroyed', event)
+        @emitter.emit('will-destroy-minimap', event)

-      view.updateMinimapRenderView()
+        view.destroy()
+        delete @minimapViews[editorId]

-      subscriptions = new CompositeDisposable
-      subscriptions.add editor.onDidDestroy =>
-        view = @minimapViews[editorId]
+        @emit('minimap-view:destroyed', {view})
+        @emitter.emit('did-destroy-minimap', event)

-        event = {view}
-        if view?
-          @emit('minimap-view:will-be-destroyed', event)
-          @emitter.emit('will-destroy-minimap', event)
-
-          view.destroy()
-          delete @minimapViews[editorId]
-
-          @emit('minimap-view:destroyed', {view})
-          @emitter.emit('did-destroy-minimap', event)
-
-          if paneView.getActiveView()?.classList.contains('editor')
-            paneView.classList.add('with-minimap')
+        if paneView.getActiveView()?.classList.contains('editor')
+          paneView.classList.add('with-minimap')


Remove use of EditorView -> Editor delegates

These are going to be removed from the EditorView class.

--- lib/package-generator-view.coffee
+++ lib/package-generator-view.coffee
@@ -22,14 +22,16 @@
   attach: (@mode) ->
     @previouslyFocusedElement = $(':focus')
     @message.text("Enter #{mode} path")
-    placeholderName = "#{mode}-name"
-    packagesDirectory = _.last(atom.packages.getPackageDirPaths())
-    @miniEditor.setText(path.join(packagesDirectory, placeholderName))
-    pathLength = @miniEditor.getText().length
-    @miniEditor.setSelectedBufferRange([[0, pathLength - placeholderName.length], [0, pathLength]])
-
+    @setPathText("#{mode}-name")
     atom.workspaceView.append(this)
     @miniEditor.focus()
+
+  setPathText: (placeholderName) ->
+    {editor} = @miniEditor
+    packagesDirectory = _.last(atom.packages.getPackageDirPaths())
+    editor.setText(path.join(packagesDirectory, placeholderName))
+    pathLength = editor.getText().length
+    editor.setSelectedBufferRange([[0, pathLength - placeholderName.length], [0, pathLength]])

   detach: ->
     return unless @hasParent()


优化 git clone 流程

优化 git clone 流程

--- lib/project/create-project.coffee
+++ lib/project/create-project.coffee
@@ -12,7 +12,8 @@
   chameleonBox: null
   modalPanel: null
   repoDir: pathM.join desc.chameleonHome,'src','frameworks','butterfly-slim'
-  projectTempDir: pathM.join desc.chameleonHome,'src','templates','ProjectTemp'
+  frameworksDir: pathM.join desc.chameleonHome,'src','frameworks'
+  projectTempDir: pathM.join desc.chameleonHome,'src','ProjectTemp'
   repoURI: 'https://git.oschina.net/chameleon/butterfly-slim.git'
   LoadingMask: loadingMask

@@ -117,7 +118,7 @@
         Util.copy @projectTempDir, info.appPath, copySuccess # 创建项目根目录成功后 将空白项目的项目内容复制到根目录

     # 首先，判断本地是否有框架
-    Util.isFileExist @repoDir, (exists) =>
+    Util.isFileExist pathM.join(@frameworksDir, 'butterfly-slim'), (exists) =>
       if exists
         Util.createDir info.appPath, createSuccess #有，执行第二步：创建项目根目录
       else
@@ -128,7 +129,8 @@
             alert '项目创建失败：git clone失败，请检查网络连接'
             @modalPanel.item.children(".loading-mask").remove()

-        Util.getRepo(@repoDir, config.repoUri, success.bind(this)) #没有，执行 git clone，成功后执行第二步
+        Util.getRepo(@frameworksDir, config.repoUri, success.bind(this)) #没有，执行 git clone，成功后执行第二步
+

     LoadingMask = new @LoadingMask()
     @modalPanel.item.append(LoadingMask)


Fix toArray when @base isn't a property

Fix toArray when @base isn't a property

--- src/set/set_sort.coffee
+++ src/set/set_sort.coffee
@@ -71,7 +71,7 @@
     @fire('itemsWereRemoved', removedItems, removedIndexes)

   toArray: ->
-    @base.registerAsMutableSource()
+    @base.registerAsMutableSource?()
     @get('_storage').slice()

   forEach: (iterator, ctx) ->


Show position where error occurred

Show position where error occurred

--- lisp.coffee
+++ lisp.coffee
@@ -49,11 +49,19 @@
   isEOF: ->
     @pos == @code.length

+  current_pos: ->
+    headToCurrent = @code.substr(0, @pos)
+    {
+      row: headToCurrent.split("\n").length
+      column: @pos - headToCurrent.lastIndexOf("\n")
+    }
+
   expects: (pattern, throwing = false) ->
     valid = @code[@pos] && (pattern instanceof RegExp and pattern.test @code[@pos]) || pattern == @code[@pos...@pos + pattern.length]
     if !valid && throwing
-      current = if @isEOF() then 'EOF' else @code[@pos]
-      throw "unexpected \"#{current}\", expects \"#{pattern}\""
+      token = if @isEOF() then 'EOF' else @code[@pos]
+      pos = @current_pos()
+      throw "unexpected \"#{token}\", expects \"#{pattern}\" at #{pos.row}:#{pos.column} "

     return valid


Fixed live feed timestamp for wrong dates

--- site/static/js/live.coffee
+++ site/static/js/live.coffee
@@ -10,8 +10,9 @@
 		msPerYear = msPerDay * 365
 		elapsed = current - previous

-
-		if elapsed < msPerMinute
+		if elapsed < 0
+			return 'Incorrect time'
+		else if elapsed < msPerMinute
 			return Math.round(elapsed/1000) + ' seconds ago'
 		else if elapsed < msPerHour
 			return Math.round(elapsed/msPerMinute) + ' minutes ago'
@@ -67,7 +68,7 @@
 	updateTime: () =>

 		$('.time').each (i, e) ->
-			$(e).text window.live.timeDifference($(e).data('time'))
+			$(e).html window.live.timeDifference($(e).data('time'))

 	updateTerminal: (json) =>


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -24,7 +24,7 @@
   @name every
   @methodOf TimedEvents#
   @param {Number} n Number of frames to wait before executing the callback
-  @param {Function} fn Code to execute after the number of frames has passed
+  @param {Function} fn Code to execute after <code> of frames has passed
   ###
   every: (n, fn) ->
     if I.age.mod(n) is 0


aaaaaaaaaaa

aaaaaaaaaaa

--- modules/player/index.coffee
+++ modules/player/index.coffee
@@ -58,10 +58,6 @@
               return
             filters.push filter

-    omsg = undefined
-      # temp
-      #filters = [filters[0]]
-
     filterstr = " "
     filterstr += filter for filter in filters

@@ -80,9 +76,6 @@
     # Set events
     durationstr = if isFinite(qI.duration) then moment.utc(qI.duration * 1000).format("HH:mm:ss") else '∞'
     qI.once 'start', =>
-      if omsg
-        omsg.delete()
-        omsg = null
       msg.channel.sendMessage @hud.nowPlaying gdata, qI, true
         .then (m)=>
           if gdata.data.autoDel


Bugfix in clock hand

Bugfix in clock hand

--- components/clock/hand.cjsx
+++ components/clock/hand.cjsx
@@ -87,7 +87,7 @@

   # -- Render
   render: ->
-    style = prefixer(transform: "rotate(#{@state.angle}deg")
+    style = prefixer(transform: "rotate(#{@state.angle}deg)")
     style.height = @props.length - @state.knobWidth/2

     <div className={css.hand + ' ' + @props.className} style={style}>


fixing improper formatting in range-to-array expansion.

--- nodes.coffee
+++ nodes.coffee
@@ -524,7 +524,7 @@
     parts: []
     parts.push @from.compile o if @from isnt @fromVar
     parts.push @to.compile o if @to isnt @toVar
-    if parts.length then "${parts.join('; ')};\n$o.indent" else ''
+    if parts.length then "${parts.join('; ')};" else ''

   # When compiled normally, the range returns the contents of the *for loop*
   # needed to iterate over the values in the range. Used by comprehensions.
@@ -548,10 +548,10 @@
     result: o.scope.freeVariable()
     i:      o.scope.freeVariable()
     clause: "$from <= $to ?"
-    pre:    "\n${idt}${result} = [];${vars}"
+    pre:    "\n${idt}${result} = []; ${vars}"
     body:   "var $i = $from; $clause $i <$equals $to : $i >$equals $to; $clause $i += 1 : $i -= 1"
     post:   "{ ${result}.push($i) };\n${idt}return $result;\n$o.indent"
-    "(function(){${pre};\n${idt}for ($body)$post}).call(this)"
+    "(function(){${pre}\n${idt}for ($body)$post}).call(this)"

 #### SliceNode

@@ -1241,7 +1241,8 @@
     varPart:        ''
     body:           Expressions.wrap([@body])
     if range
-      sourcePart:   source.compileVariables o
+      sourcePart:   source.compileVariables(o)
+      sourcePart:   + "\n$o.indent" if sourcePart
       forPart:      source.compile merge o, {index: ivar, step: @step}
     else
       svar:         scope.freeVariable()
@@ -1266,7 +1267,7 @@
       body.unshift  literal "var $index = $ivar" if index
       body:         ClosureNode.wrap(body, true)
     else
-      varPart:      if @pattern then namePart else "${@idt(1)}$namePart;\n"
+      varPart:      (namePart or '') and (if @pattern then namePart else "${@idt(1)}$namePart;\n")
     if @object
       forPart:      "$ivar in $svar) { if (${utility('hasProp')}.call($svar, $ivar)"
     body:           body.compile(merge(o, {indent: @idt(1), top: true}))


:fire: example keymap for surround

:fire: example keymap for surround

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -406,18 +406,6 @@
   'ctrl-t': 'symbols-view:return-from-declaration'
   '"': 'vim-mode-plus:set-register-name'

-  # 'y s': 'vim-mode-plus:surround'
-  # 'y s w': 'vim-mode-plus:surround-word'
-
-  # 'd s': 'vim-mode-plus:delete-surround'
-  # 'd s': 'vim-mode-plus:delete-surround-any-pair'
-  # 'd s': 'vim-mode-plus:delete-surround-any-pair-allow-forwarding'
-
-  # 'y s c': 'vim-mode-plus:change-surround'
-  # 'y s c': 'vim-mode-plus:change-surround-any-pair'
-  # 'y s c': 'vim-mode-plus:change-surround-any-pair-allow-forwarding'
-
-  # 'y m s':  'vim-mode-plus:map-surround'
   # 'enter': 'vim-mode-plus:add-blank-line-below'
   # 'shift-enter': 'vim-mode-plus:add-blank-line-above'


make name prompt more generic

make name prompt more generic

--- root/init.coffee
+++ root/init.coffee
@@ -6,7 +6,7 @@
   {
     type: 'input',
     name: 'name',
-    message: 'What is the name of your sprout template?'
+    message: 'What is the name of your project?'
   },
   {
     type: 'input',


Fixed a bug with siblings

Fixed a bug with siblings

--- src/public/models/node.coffee
+++ src/public/models/node.coffee
@@ -65,6 +65,7 @@
 				@_unconnectedPeers = _(@_unconnectedPeers).without(peer)
 				@addPeer(peer)
 			)
+			peer.on('peer.addSibling', @_onAddSibling)

 			@_unconnectedPeers.push(peer)
 			return peer
@@ -94,9 +95,8 @@
 			if duplicatePeer = @getPeer(peer.id)
 				@removePeer(duplicatePeer)

-			peer.on('disconnected', ( peer ) =>
-				@removePeer(peer)
-			)
+			peer.on('disconnected', @removePeer)
+

 			@_peers.push(peer)
 			@trigger('peer.added', peer)
@@ -191,6 +191,7 @@
 				@_parent = null

 			peer.role = Peer.Role.Sibling
+			peer.emit("peer.addSibling", @id)

 		# Removes a peer as sibling node. Does not automatically close
 		# the connection but will make it a normal peer.
@@ -294,22 +295,23 @@
 					_superNodes = superNodes.slice(0)
 					@_chooseParent(superNodes)

-					# Set siblings of siblings
+					# Become a Supernode and become a Sibling
 					@on("hasParent", (hasParent) =>
 						for superNode in _superNodes
-							if hasParent
+							unless hasParent
+								@setSuperNode(true)
 								peer = @getPeer(superNode.id)
-							else
-								@setSuperNode(true)
-								peer = @getPeer(superNode.id)
-								peer.role = Peer.Role.Sibling
-
-					)
-
-
-
+								@addSibling(peer)
+					)

 			)
+
+
+		_onAddSibling: (id) =>
+			peer = @getPeer(id)
+			if peer
+				@addSibling(peer)
+
 		# Is called until a node connects to a Supernode
 		#
 		# @param superNodes [[Node]] an array of available superNodes
@@ -324,7 +326,6 @@
 							@trigger("hasParent", true)
 						else
 							@_chooseParent(superNodes)
-
 					)
 				)
 			else


Install packages for javascript development, HTML/CSS work, and a couple other languages

--- atom/dot-atom/packages.cson
+++ atom/dot-atom/packages.cson
@@ -7,20 +7,31 @@
   "atom-ide-ui"
   "atom-python-test"
   "atom-reverser"
+  "atom-wrap-in-tag"
   "atomic-rtags"
   "auto-update-plus"
   "autoclose-html"
+  "autocomplete-awk"
   "autocomplete-clang"
+  "autocomplete-fortran"
+  "autocomplete-json"
+  "autocomplete-modules"
   "autocomplete-paths"
   "autocomplete-python"
   "autocomplete-R"
+  "autocomplete-sql"
+  "autocomplete-xml"
   "build"
   "build-make"
   "busy"
   "busy-signal"
+  "city-lights-syntax"
+  "city-lights-ui"
   "clang-format"
   "code-peek"
   "copy-paste"
+  "emmet"
+  "emmet-jsx-css-modules"
   "ex-mode"
   "expand-region"
   "expose"
@@ -32,10 +43,14 @@
   "highlight-selected"
   "Hydrogen"
   "hydrogen-launcher"
+  "hyperclick"
   "ide-python"
   "imdone-atom"
   "intentions"
+  "js-hyperclick"
   "jumpy"
+  "language-awk"
+  "language-babel"
   "language-latex"
   "language-markdown"
   "language-mathematica"
@@ -43,13 +58,17 @@
   "language-weave"
   "linter"
   "linter-clang"
+  "linter-csslint"
+  "linter-eslint"
   "linter-flake8"
   "linter-lintr"
   "linter-markdown"
   "linter-pydocstyle"
   "linter-pylama"
   "linter-pylint"
+  "linter-sass-lint"
   "linter-ui-default"
+  "local-history"
   "MagicPython"
   "markdown-preview-plus"
   "markdown-preview-plus-opener"
@@ -64,16 +83,19 @@
   "pdf-view"
   "pigments"
   "platformio-ide-terminal"
+  "prettier-atom"
   "project-manager"
   "project-plus"
   "python-indent"
   "python-tools"
   "rbox"
+  "related"
   "set-syntax"
   "solarized-light-ui"
   "sort-lines"
   "switch-header-source"
   "symbols-tree-view"
+  "tab-foldername-index"
   "teletype"
   "toggle-quotes"
   "tree-view-git-branch"


TextEditorView.scrollToBufferPosition -> TextEditor

--- lib/conflict-marker.coffee
+++ lib/conflict-marker.coffee
@@ -187,5 +187,5 @@

   focusConflict: (conflict) ->
     st = conflict.ours.marker.getBufferRange().start
-    @editorView.scrollToBufferPosition st, center: true
+    @editor().scrollToBufferPosition st, center: true
     @editor().setCursorBufferPosition st, autoscroll: false


Shorten some of the vars for good looking

--- library/membrane/bilateral.coffee
+++ library/membrane/bilateral.coffee
@@ -82,22 +82,22 @@
     # channel. This is a client site counterpart of the providers. A
     # function that is supplied as an implementation is automatocally
     # externalized and transferred (by the `Screenplay`) to a client.
-    @uplink: (directives, implementation) ->
-        invalidFunc = "supply a function for the uplink"
-        implementation = _.find arguments, _.isFunction
+    @uplink: (directives, implement) ->
+        invalidFunc = "no function for the uplink"
+        implement = _.find arguments, _.isFunction
         directives = {} unless _.isPlainObject directives
-        assert _.isFunction(implementation), invalidFunc
-        p = @prototype; overwrapping = (container) ->
-            name = _.findKey p, (x) -> x is overwrapping
+        assert _.isFunction(implement), invalidFunc
+        p = @prototype; overwrap = (container) ->
+            name = _.findKey p, (x) -> x is overwrap
             assert socket = container.socket or container
             assert socket._events?, "no container/socket"
             return @createLinkage socket, name, directives
-        remoted = _.isObject(implementation.remote or null)
-        ext = if remoted then implementation else undefined
-        assert externalized = ext or external implementation
-        assert overwrapping.uplink = directives: directives
-        assert overwrapping.remote = externalized.remote
-        assert overwrapping.remote.source; overwrapping
+        remoted = _.isObject(implement.remote or null)
+        ext = if remoted then implement else undefined
+        assert externalized = ext or external implement
+        assert overwrap.uplink = directives: directives
+        assert overwrap.remote = externalized.remote
+        assert overwrap.remote.source; overwrap

     # This is a client site, bilateral bootloader that gets fired
     # on the client automatically (with `autocall`) and detects all