Improve to crop only images with top and bottom or left and right borders

--- components/GetBorderlessBox.coffee
+++ components/GetBorderlessBox.coffee
@@ -69,8 +69,9 @@
     c.params.max = 10 unless c.params.max
     c.params.avg = 10 unless c.params.avg
     threshold = c.params
-    diffPercentualTopBottom = 0.25
-    maxPercentualCrop = 0.5
+    diffPercentual = 0.25
+    maxPercentualCrop = 0.75
+    percentualConsideredBorder = 0.05

     # Convert to grayscale
     gray = []
@@ -147,33 +148,33 @@
       width: canvas.width
       height: canvas.height

-    # If there is not too much difference between top and bottom borders,
-    # then crop them
+    # Crop if there is not too much difference between top and bottom borders
     if (Math.abs bbox.y - (canvas.height - bbox.height)) <
-        (Math.max bbox.y, (canvas.height - bbox.height)) *
-        diffPercentualTopBottom
+        (Math.max bbox.y, (canvas.height - bbox.height)) * diffPercentual
       croppedBbox.y = bbox.y
       croppedBbox.height = bbox.height - croppedBbox.y

-    # Uncomment following lines if lateral crop is necessary
-    # if (Math.abs bbox.x - (croppedBbox.width - bbox.width)) <
-    #     (Math.max bbox.x, (croppedBbox.width - bbox.width)) * 0.75
-    #   croppedBbox.x = bbox.x
-    #   croppedBbox.width = bbox.width - croppedBbox.x
+    # Crop if there is not too much difference between left and right borders
+    if (Math.abs bbox.x - (canvas.width - bbox.width)) <
+        (Math.max bbox.x, (canvas.width - bbox.width)) * diffPercentual
+      croppedBbox.x = bbox.x
+      croppedBbox.width = bbox.width - croppedBbox.x

-    # verticalVariation = Math.min bbox.y, croppedBbox.height - bbox.height
-    # horizontalVariation = Math.min bbox.x, croppedBbox.width - bbox.width
+    # Calculate maximum variation in each direction
+    verticalVariation = Math.max bbox.y, canvas.height - croppedBbox.height
+    horizontalVariation = Math.max bbox.x, canvas.width - croppedBbox.width

-    # if horizontalVariation > verticalVariation
-    #   croppedBbox.x = horizontalVariation
-    #   croppedBbox.width = bbox.width - croppedBbox.x
-
-    # Check for invalid bboxes (e.g. images with only one color, small bboxes)
     newLength = (croppedBbox.height - croppedBbox.y) *
       (croppedBbox.width - croppedBbox.x)
-    if (newLength < (maxPercentualCrop * gray.length)) or
-        croppedBbox.width < 0 or
-        croppedBbox.height < 0
+
+    # Do not crop if:
+    # - The new image size is low than a certain maxPercentualCrop
+    if (newLength < ((1.0 - maxPercentualCrop) * gray.length)) or
+        # - There are horizontal AND vertical borders
+        ((verticalVariation > percentualConsideredBorder * canvas.height) and
+        (horizontalVariation > percentualConsideredBorder * canvas.width)) or
+        # - All the image was cropped
+        croppedBbox.width < 0 or croppedBbox.height < 0
       croppedBbox =
         x: 0
         y: 0


Parse server response to JSON.

Parse server response to JSON.

--- app/client/js/xhr.coffee
+++ app/client/js/xhr.coffee
@@ -6,7 +6,6 @@
 class app.Xhr

   ###*
-    TODO: Check connection etc.
     @param {app.Error} error
     @constructor
   ###
@@ -31,8 +30,8 @@
     @return {!goog.Promise}
   ###
   send: (method, url, json) ->
-    # TODO: Throw error or ignore pending requests with the same args.
     data = JSON.stringify json
     goog.labs.net.xhr
       .send method, url, data, Xhr.XHR_OPTIONS
+      .then (xhr) -> JSON.parse xhr.responseText
       .thenCatch @error.handle


dunce cap

dunce cap

--- apps/artwork/client/view.coffee
+++ apps/artwork/client/view.coffee
@@ -41,7 +41,7 @@
     'click .artwork-buy-button': 'buy'

   initialize: ({ @artwork }) ->
-    @showRails = if splitTest('merchandized_rails').outcome() is 'true' then true else false
+    @showRails = if splitTest('merchandized_rails').outcome() is 'true'
     @location = window.location
     @artist = @artwork.related().artist
     @artists = @artwork.related().artists


Only clone options when necessary (perf)

--- src/coffee/messenger.coffee
+++ src/coffee/messenger.coffee
@@ -407,7 +407,7 @@

             # if options were provided to this individual call, use them
             # don't clobber default options, use _.clone
-            sync_msgr_opts = _.clone msgr_opts
+            sync_msgr_opts = if options.messenger? then _.clone msgr_opts else msgr_opts
             if options.messenger?
                 _.extend sync_msgr_opts, options.messenger


Updating overlord to load ctrl.js directly

--- src/coffeeq_overlord.coffee
+++ src/coffeeq_overlord.coffee
@@ -33,7 +33,7 @@

 redis = require 'redis'
 helper = require './coffeeq_helpers'
-Ctrl =  require '../vendor/ctrl'
+Ctrl =  require '../vendor/ctrl/ctrl.js'

 class CoffeeQOverlord
   constructor: (options={}) ->


Rename setCallbacks to ObserveChanges

Rename setCallbacks to ObserveChanges

--- app/assets/javascripts/action_cable_notifications/collection.coffee
+++ app/assets/javascripts/action_cable_notifications/collection.coffee
@@ -50,10 +50,20 @@
     # Stores records that needs to be tracked when inserted into the collection
     @trackedRecords = []

+    @callbacks = {}
+
+    @observeChanges(callbacks)
+
     @callbacks?.initialize?.call(this)

-  # Sets the callbacks for collection after initialization
-  setCallbacks: (@callbacks) ->
+  # Update the callback stack for collection
+  observeChanges: (new_callbacks) ->
+    _.each(new_callbacks, (cb, name) =>
+      old_cb = @callbacks?[name]
+      @callbacks[name] = () ->
+        cb.apply(this, arguments)
+        old_cb?.apply(this, arguments)
+    )

   # Sync collection to ActionCable Channel
   syncToChannel: (@channel) ->


fix pdf document download link and preview.

--- app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
+++ app/assets/javascripts/hanuman/hanuman-cloudinary-uploads.js.coffee
@@ -16,7 +16,10 @@
     orderNameAttr = data.cloudinaryField.replace(/\[observation_documents_attributes]\[\d+]\[document]/, "[observation_documents_attributes][" + idx + "][sort_order]")
     hiddenNameAttr = data.cloudinaryField.replace(/\[observation_documents_attributes]\[\d+]\[document]/, "[observation_documents_attributes][" + idx + "][document]")
     # if document then overwrite  the filevalue
-    fileValue = dataObj.resource_type+"/"+dataObj.type+"/"+"v"+dataObj.version+"/"+dataObj.public_id+"#"+dataObj.signature
+    if data.result.format == "pdf"
+      fileValue = dataObj.resource_type+"/"+dataObj.type+"/"+"v"+dataObj.version+"/"+dataObj.public_id+".pdf"+"#"+dataObj.signature
+    else
+      fileValue = dataObj.resource_type+"/"+dataObj.type+"/"+"v"+dataObj.version+"/"+dataObj.public_id+"#"+dataObj.signature
   else if file == "video"
     regex = /\[observation_videos_attributes]\[\d+]\[video]/
     nameAttr = data.cloudinaryField.replace(/\[observation_videos_attributes]\[\d+]\[video]/, "[observation_videos_attributes][" + idx + "][description]")
@@ -24,7 +27,11 @@
     hiddenNameAttr = data.cloudinaryField.replace(/\[observation_videos_attributes]\[\d+]\[video]/, "[observation_videos_attributes][" + idx + "][video]")

   # this is simply appending the textarea and the hidden input. I am adding the hidden input right next to the img video-preview
-  $previewContainer.find("."+file+"-preview").last().append "
"+data.result.public_id+"
"
+  if data.result.format == undefined
+    file_id = data.result.public_id
+  else
+    file_id = data.result.public_id+"."+data.result.format
+  $previewContainer.find("."+file+"-preview").last().append "
"+file_id+"
"
   $previewContainer.find("."+file+"-preview").last().append "<textarea rows=2 cols=55 style='margin:20px 0 20px 0;' placeholder='Add "+file+" description here...' name="+nameAttr+"></textarea>"
   $previewContainer.find("."+file+"-preview").last().append "
<input type='number' value='"+sortOrder+"' name="+orderNameAttr+"></input>
"
   $previewContainer.find("."+file+"-preview").last().append "
Remove "+file+"
"
@@ -93,7 +100,6 @@
     $videoPreviewContainer.append "
" + $.cloudinary.video(data.result.public_id, format: data.result.format, version: data.result.version, crop: 'fill', width: 350) + "
"
     addTexareaForUpload("video", data, videoIdx, $videoPreviewContainer)
     poster = $(e.target).closest(".video-column").find('.video-preview:last').find("video").attr("poster")
-    $(e.target).closest(".video-column").find('.video-preview:last').find("video").attr("poster", poster.replace(/$|.mp4|.mov/, ".jpg"))
     videoIdx += 1

     if data.result.format != "mov" && data.result.format != "mp4"
@@ -126,13 +132,19 @@
     $(e.target).siblings('.progress').addClass('hidden')
     #setTimeout callback, 1000

-    publicId = data.result.public_id
+    # this if statement sets the pdf file's extension to png for a preview in upload
+    # publicId = data.result.public_id
+    if data.result.format == "pdf"
+      file_format ="png"
+    else
+      file_format = data.result.format

     $documentPreviewContainer = $(e.target).siblings('.document-preview-container')
-    $documentPreviewContainer.append "
" + $.cloudinary.image(data.result.public_id, format: data.result.format, version: data.result.version, crop: 'fill', width: 350).prop('outerHTML') + "
"
+    $documentPreviewContainer.append "
" + $.cloudinary.image(data.result.public_id, format: file_format, version: data.result.version, crop: 'fill', width: 350).prop('outerHTML') + "
"
     addTexareaForUpload("document", data, docIdx, $documentPreviewContainer)
-    if data.result.format != "jpg" && data.result.format != "png"
+    if data.result.format != "pdf" && data.result.format != "png" && data.result.format != "jpg"
       $('.document-preview img:last').attr('src', '/images/file-icon.png')
+
     docIdx += 1

   # validating upload format. If fomat to supported then handle error


inputvalidator/inputview/formview refactoring done, register form ready

--- components/forms/KDFormView.coffee
+++ components/forms/KDFormView.coffee
@@ -102,72 +102,23 @@
         input.once "ValidationResult", (result)=>
           inputCount++
           validInputs.push input if result
-          if inputs.length is validInputs.length
-            log "form is valid"
-            @emit "FormValidationPassed"
-          else
-            warn "form submit failed validation!"
-            @emit "FormValidationFailed"
+          if inputs.length is inputCount
+            if inputs.length is validInputs.length
+              @valid = yes
+              formData = $.extend {}, @getCustomData()
+              formData[inputView.inputName] = inputView.inputGetValue() for inputView in inputs
+              @formGetCallback()?.call @, formData, event
+              @emit "FormValidationPassed"
+            else
+              @valid = no
+              @emit "FormValidationFailed"
         input.validate null, event
-
-
-
-
-
-
-
-
-
-
-
-
-
-    #
-    # for inputItem in inputArray
-    #   inputItem = $(inputItem).closest(".kdinput")[0] if $(inputItem).hasClass "no-kdinput"
-    #   kdview = KD.getKDViewInstanceFromDomElement inputItem
-    #   inputItemInstances.push kdview if kdview
-    #
-    # # SIMPLIFY THIS
-    # validators = for inputItemInstance in inputItemInstances
-    #   f = (validator) ->
-    #     (callback) ->
-    #       if validator?
-    #         validator.validateAsync (data) =>
-    #           callback null, data
-    #       else
-    #         callback null, yes # inputs which not contain validator are valid
-    #   f inputItemInstance.inputValidator
-    #
-    # async.parallel validators, (err, resultSet) =>
-    #
-    #   resultSet = for results in resultSet
-    #     results.join '.' if $.isArray results
-    #   resultSet = resultSet.join '|'
-    #
-    #   if resultSet.search(/false/) < 0
-    #     # log "form submit passed validation!"
-    #     callback = @formGetCallback()
-    #     if callback?
-    #       # log "there is callback",@getDomElement().serializeArray(),@
-    #       formData = $.extend {},@getCustomData()
-    #       for inputData in @getDomElement().serializeArray()
-    #         formData[inputData.name] = inputData.value
-    #
-    #       callback.call @, formData,event
-    #     @valid = yes
-    #   else
-    #     warn "form submit failed validation!"
-    #     @propagateEvent KDEventType : "ValidationFailed"
-    #     @valid = no
-
-    # return no #propagations leads to window refresh

   focusFirstElement:->
     @$("input,select,button,textarea").first().trigger "focus"

-  formSetCallback:(callback)->
-    @formCallback = callback
+  formSetCallback:(callback)-> @formCallback = callback
+
   formGetCallback:()-> @formCallback


Stop having unfinished practice levels lock later levels in home version

Fixes #4196

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -373,10 +373,9 @@
     false

   annotateLevels: (orderedLevels) ->
-    previousIncompletePracticeLevel = false # Lock owned levels if there's a earlier incomplete practice level to play
     for level, levelIndex in orderedLevels
       level.position ?= { x: 10, y: 10 }
-      level.locked = not me.ownsLevel(level.original) or previousIncompletePracticeLevel
+      level.locked = not me.ownsLevel(level.original)
       level.locked = true if level.slug is 'kithgard-mastery' and @calculateExperienceScore() is 0
       level.locked = true if level.requiresSubscription and @requiresSubscription and me.get('hourOfCode')
       level.locked = false if @levelStatusMap[level.slug] in ['started', 'complete']
@@ -396,6 +395,7 @@
       level.unlocksItem = _.find(level.rewards, 'item')?.item
       level.unlocksPet = utils.petThangIDs.indexOf(level.unlocksItem) isnt -1

+
       if window.serverConfig.picoCTF
         if problem = _.find(@picoCTFProblems or [], pid: level.picoCTFProblem)
           level.locked = false if problem.unlocked or level.slug is 'digital-graffiti'
@@ -407,10 +407,6 @@
             #{problem.category} - #{problem.score} points
           """
           level.color = 'rgb(80, 130, 200)' if problem.solved
-
-      if @campaign?.levelIsPractice(level) and not level.locked and @levelStatusMap[level.slug] isnt 'complete' and
-      (not level.requiresSubscription or level.adventurer or not @requiresSubscription)
-        previousIncompletePracticeLevel = true

       level.hidden = level.locked
       if level.concepts?.length


Remove log

Remove log

--- src/Geocoder.coffee
+++ src/Geocoder.coffee
@@ -8,7 +8,6 @@
   appCode: hereAppCode

 geocoder = NodeGeocoder(options)
-console.log('>>> TaskRunner', TaskRunner)

 Geocoder =


detail map fix

detail map fix

--- js/views/map.coffee
+++ js/views/map.coffee
@@ -43,6 +43,8 @@
                 )
         if(@mapElement == "map")
           utils.map.instance.addControl(selectControl)
+        else
+          utils.detailMap.instance.addControl(selectControl)
         selectControl.activate()
         @addLayer(utils.poiLayer)


Remove unneeded .

Remove unneeded .

--- src/context-menu-manager.coffee
+++ src/context-menu-manager.coffee
@@ -18,7 +18,7 @@
       label: 'Inspect Element'
       command: 'application:inspect'
       executeAtBuild: (e) ->
-        @.commandOptions = x: e.pageX, y: e.pageY
+        @commandOptions = x: e.pageX, y: e.pageY
     ]

   # Public: Creates menu definitions from the object specified by the menu


Fix bug for TotalAmount.

Fix bug for TotalAmount.

--- src/coffee/export/orders.coffee
+++ src/coffee/export/orders.coffee
@@ -165,7 +165,7 @@
     shippingCost = @_toAmount(shippingInfo.price.centAmount)
     orderXML.e('ShippingCost').t(shippingCost)
     #<xs:element ref="PaymentCost" minOccurs="0"/>
-    totalAmount = order.totalPrice
+    totalAmount = order.totalPrice.centAmount
     totalAmount = order.taxedPrice.totalGross.centAmount if order.taxedPrice?.totalGross
     orderXML.e('TotalAmount').t(@_toAmount(totalAmount))
     #<xs:element ref="Comment" minOccurs="0"/>


Remove unused code.

Remove unused code.

--- bokehjs/src/coffee/common/ticking.coffee
+++ bokehjs/src/coffee/common/ticking.coffee
@@ -7,6 +7,7 @@
 # probably use a special scale.
 # TODO Add more comments.
 # TODO Add tests.
+# TODO There used to be a TODO: restore memoization.  So.... do that?

 define [
   "underscore",
@@ -618,58 +619,6 @@
         # (1 year - infinity)
         new AdaptiveScale([1.0, 2.0, 5.0], 10.0, ONE_YEAR, Infinity),
       ])
-
-  auto_interval_temp_old = (data_low, data_high) ->
-      """ Calculates the tick interval for a range.
-
-          The boundaries for the data to be plotted on the axis are::
-
-              data_bounds = (data_low,data_high)
-
-          The function chooses the number of tick marks, which can be between
-          3 and 9 marks (including end points), and chooses tick intervals at
-          1, 2, 2.5, 5, 10, 20, ...
-
-          Returns
-          -------
-          interval: float
-              tick mark interval for axis
-      """
-      data_range = float(data_high) - float(data_low)
-      desired_n_ticks = 6
-      ideal_interval = data_range / desired_n_ticks
-
-      ideal_magnitude = Math.pow(10, Math.floor(log10(ideal_interval)))
-      ideal_mantissa = ideal_interval / ideal_magnitude
-
-      allowed_mantissas = [0.5, 1.0, 2.0, 2.5, 5.0, 10.0]
-
-      # Reduce the set of allowed mantissas to only the closest two.
-      # FIXME Use binary search?
-      # FIXME This loop should always break, but just in case something weird
-      # happens with floating point, we'll set this default value.
-      index = allowed_mantissas.length - 1
-      for i in arange(1, allowed_mantissas.length)
-        if ideal_mantissa < allowed_mantissas[i]
-          index = i
-          break
-      # FIXME Is there some kind of slicing notation?
-      candidate_mantissas = [allowed_mantissas[index - 1],
-                             allowed_mantissas[index]]
-
-      # FIXME Use absolute value here!
-      errors = candidate_mantissas.map((mantissa) ->
-        return desired_n_ticks - (data_range / (mantissa * ideal_magnitude)))
-      best_mantissa = candidate_mantissas[argsort(errors)[0]]
-
-      interval = best_mantissa * ideal_magnitude
-
-      return interval
-
-  # TODO (bev) restore memoization
-  #auto_interval = memoize(auto_interval_temp)
-#   auto_interval = auto_interval_temp
-

   class BasicTickFormatter
     constructor: (@precision='auto', @use_scientific=true, @power_limit_high=5, @power_limit_low=-3) ->


Replaced pause by sleep and fixed bug related on requestAnimationFrame (slowdowns when pausing and running the mainloop).

--- engine/main_loop.coffee
+++ engine/main_loop.coffee
@@ -12,52 +12,52 @@
         @last_frame_durations = [16, 16, 16, 16, 16, 16, 16, 16, 16, 16]
         @_fdi = 0
         @timeout = context.MYOU_PARAMS.timeout
-        @timeout? and console.log 'WARNING: Timeout set: ' + @timeout
-        @pause_time = 0
         @last_time = 0
         @timeout_timer = @timeout
         @enabled = false
+        @stopped = false
         @context = context
         @_bound_tick = @tick.bind @
+        @_bound_run = @run.bind @

     run: ->
+        @stopped = false
         if @enabled
             return
-        requestAnimationFrame @_bound_tick
-        not @enabled and @timeout? and console.log 'Main loop running: ' + Math.floor(performance.now())
+        @req_tick = requestAnimationFrame @_bound_tick
         @enabled = true
-        @pause_time = 0
         @last_time = performance.now()
+        @reset_timeout()

     stop: ->
-        cancelAnimationFrame @_bound_tick
-        @pause_time = Infinity
+        if @req_tick?
+            cancelAnimationFrame @req_tick
+            @req_tick = null
+
         @enabled = false
+        @stopped = true

-    pause: (pause_time)->
-        if not pause_time?
-            console.log 'WARNING: Undefined pause_time.'
-        @pause_time = pause_time
+    sleep: (sleep_time)->
+        if @sleep_timeout?
+            clearTimeout(@sleep_timeout)
+            @sleep_timeout = null
+        if @enabled
+            @stop()
+        @sleep_timeout = setTimeout(@_bound_run, sleep_time)

     tick: ->
-        requestAnimationFrame @_bound_tick
         time = performance.now()
         @frame_duration = frame_duration = Math.min(time - @last_time, MAX_FRAME_DURATION)
         @last_time = time

-        if not @enabled or @pause_time > 0
-            @pause_time -= frame_duration
-            return
-
         if  @enabled and @timeout_timer <= 0
-            console.log  'Main loop paused: ' + Math.floor(time)
             @enabled = false
             return

+        @req_tick = requestAnimationFrame @_bound_tick
         @timeout_timer -= frame_duration
         @last_frame_durations[@_fdi] = frame_duration
         @_fdi = (@_fdi+1) % @last_frame_durations.length
-

         for scene in @context.loaded_scenes
             if not scene.enabled
@@ -77,8 +77,6 @@
                 step_world scene.world, frame_duration * 0.001
                 phy_to_ob scene.rigid_bodies

-
-
         evaluate_all_animations @context, frame_duration
         # for s in @context.active_sprites
         #     s.evaluate_sprite frame_duration
@@ -91,14 +89,12 @@
         @context.events.reset_frame_events()

     set_timeout: (timeout, reset=true)=>
-        'WARNING: Timeout set: ' + timeout
         @timeout = timeout
         if reset then @reset_timeout()

     reset_timeout: =>
-        if not @enabled
-            console.log 'Main loop running: ' + Math.floor( performance.now())
-
+        if @stopped
+            return
         @timeout_timer =  @timeout
         @enabled = true


:racehorse: Avoid setting the font size and line on each update

--- lib/minimap-editor-view.coffee
+++ lib/minimap-editor-view.coffee
@@ -22,12 +22,17 @@

   initialize: ->
     @lineOverdraw = atom.config.get('minimap.lineOverdraw')
+
     atom.config.observe 'minimap.lineOverdraw', =>
       @lineOverdraw = atom.config.get('minimap.lineOverdraw')

-    @lines.css 'line-height', atom.config.get('editor.lineHeight') + 'em'
     atom.config.observe 'editor.lineHeight', =>
-      @lines.css 'line-height', atom.config.get('editor.lineHeight') + 'em'
+      if @editorView?
+        @lines.css lineHeight: "#{@getLineHeight()}px"
+
+    atom.config.observe 'editor.fontSize', =>
+      if @editorView?
+        @lines.css fontSize: "#{@getFontSize()}px"

   destroy: ->
     @unsubscribe()
@@ -36,6 +41,10 @@
   setEditorView: (@editorView) ->
     @editor = @editorView.getModel()
     @buffer = @editorView.getEditor().buffer
+
+    @lines.css
+      lineHeight: "#{@getLineHeight()}px"
+      fontSize: "#{@getFontSize()}px"

     @subscribe @editor, 'screen-lines-changed.minimap', (changes) =>
       @pendingChanges.push changes
@@ -87,6 +96,7 @@

   getMinimapHeight: -> @getLinesCount() * @getLineHeight()
   getLineHeight: -> @lineHeight ||= parseInt @editorView.find('.lines').css('line-height')
+  getFontSize: -> @fontSize ||= parseInt @editorView.find('.lines').css('font-size')
   getLinesCount: -> @editorView.getEditor().getScreenLineCount()

   getMinimapScreenHeight: -> @minimapView.height() / @minimapView.scaleY
@@ -109,8 +119,6 @@
     firstVisibleScreenRow = @getFirstVisibleScreenRow()
     lastScreenRowToRender = firstVisibleScreenRow + @getMinimapHeightInLines() - 1
     lastScreenRow = @editor.getLastScreenRow()
-
-    @lines.css fontSize: "#{@editorView.getFontSize()}px"

     if @firstRenderedScreenRow? and firstVisibleScreenRow >= @firstRenderedScreenRow and lastScreenRowToRender <= @lastRenderedScreenRow
       renderFrom = Math.min(lastScreenRow, @firstRenderedScreenRow)


Clear old diffs after comitting in git

Clear old diffs after comitting in git

--- lib/react-git-diff-view.coffee
+++ lib/react-git-diff-view.coffee
@@ -10,7 +10,6 @@

     @subscribe @editorView, 'editor:path-changed', @subscribeToBuffer
     @subscribe atom.project.getRepo(), 'statuses-changed', =>
-      @decorations = {}
       @scheduleUpdate()
     @subscribe atom.project.getRepo(), 'status-changed', (path) =>
       @scheduleUpdate() if path is @editor.getPath()


App: more choices.

App: more choices.

--- src/app/index.coffee
+++ src/app/index.coffee
@@ -16,7 +16,18 @@
 			type: 'confirm'
 			message: 'Would you like to use Stylus?'
 			default: 'Y/n'
-			warning: 'Yes: Tâmia and base Stylus files will be placed into the tamia and styles directories.'
+		},
+		{
+			name: 'js'
+			type: 'confirm'
+			message: 'Would you like to use JavaScript?'
+			default: 'Y/n'
+		}
+		{
+			name: 'modernizr'
+			type: 'confirm'
+			message: 'Would you like to use Modernizr?'
+			default: 'Y/n'
 		}
 	]

@@ -24,11 +35,15 @@
 		return (@emit 'error', err)  if err

 		@styles = @ifYes props.styles
+		@js = @ifYes props.js
+		@modernizr = @ifYes props.modernizr

 		done()

 Generator::all = ->
 	args = @args
+	(@hookFor 'tamia:framework', args)
 	(@hookFor 'tamia:styles', args)  if @styles
+	(@hookFor 'tamia:modernizr', args)  if @modernizr


Widgets: Make editor tabs not closable.

Widgets: Make editor tabs not closable.

--- client/Workspace/panes/editorpane.coffee
+++ client/Workspace/panes/editorpane.coffee
@@ -44,6 +44,7 @@
       file                  = FSHelper.createFileFromPath path
       pane                  = new KDTabPaneView
         name                : file.name or "Untitled.txt"
+        closable            : no

       editor = @createEditorInstance file, content
       pane.addSubView editor


Don't trigger autocomplete if we're on a comment line.

--- services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -121,6 +121,8 @@
 			end = change.end
 			range = new Range(end.row, 0, end.row, end.column)
 			lineUpToCursor = @editor.getSession().getTextRange(range)
+			if lineUpToCursor.slice(0, 1) == '%'
+				return
 			commandFragment = getLastCommandFragment(lineUpToCursor)
 			# Check that this change was made by us, not a collaborator
 			# (Cursor is still one place behind)


add unload event propagation

add unload event propagation

--- library/membrane/duplex.coffee
+++ library/membrane/duplex.coffee
@@ -266,6 +266,7 @@
         @emit "socketing", this.socket, this.duplex, options
         failed = "failed to establish the Socket.IO connection"
         assert this.socket.emit, failed; this.socketFeedback()
+        $(window).unload => @emit "unload"; @socket.emit "unload"
         osc = (listener) => this.socket.on "connect", listener
         osc => @socket.emit "screening", _.pick(@, @snapshot), =>
             assert @consumeProviders; @consumeProviders @socket


conventions in the matches method

conventions in the matches method

--- library/nucleus/restful.coffee
+++ library/nucleus/restful.coffee
@@ -106,7 +106,7 @@
     # Can be used from the outside, but generally should not be done.
     unsupported: (request, response, next) ->
         assert methodNotAllowed = 405
-        assert codes = http.STATUS_CODES
+        assert codes = http.STATUS_CODES or {}
         assert message = codes[methodNotAllowed]
         doesJson = response.accepts(/json/) or no
         response.writeHead methodNotAllowed, message
@@ -121,13 +121,13 @@
     # that were used for configuring the class of this service.
     # It is async, so be sure to call the `decide` with boolean!
     matches: (request, response, decide) ->
-        conditions = @constructor.condition?()
-        conditions = [] unless _.isArray conditions
-        identify = @constructor?.identify().underline
-        p = (i, c) -> i.limitation request, response, c
+        conditions = try @constructor.condition()
+        conditions = Array() unless _.isArray conditions
+        identify = try @constructor?.identify().underline
+        p = (i, cn) -> i.limitation request, response, cn
         fails = "service #{identify} fails some conditions"
         return super request, response, (decision) =>
-            return decide no unless decision
+            return decide no unless decision is yes
             async.every conditions, p, (confirms) ->
                 return decide yes if confirms
                 logger.debug fails.yellow


updateUser throw error

updateUser throw error

--- backend/database-controller.coffee
+++ backend/database-controller.coffee
@@ -54,6 +54,7 @@

   updateUser: (user, callback) ->
     id = user.id
+    callback(throw new Error "Error updating user, no id specified") if !id
     connection.query {
       sql: "UPDATE users SET username=?, password=?, firstname=?, lastname=?, street=?, zip=?, location=? WHERE id=#{id}",
       values: [


Activity: passing collab payload to the messages

--- client/Social/Activity/views/privatemessage/replyinputwidget.coffee
+++ client/Social/Activity/views/privatemessage/replyinputwidget.coffee
@@ -39,8 +39,8 @@

   empty: -> @input.empty()

-
-  getPayload: ->
+  # until we have a different type for collab messages - SY
+  getPayload: -> return collaboration : yes  if @getOptions().collaboration

   reset: (unlock = yes) ->


fix event handler leak as browsers reload

unfortunately, this goes beyond just a simple "fix" as the old solution
was suffering from a chicken/egg problem where I got to chose between
leaking event handlers whenever the reload happens (leaking events on
the redis client), or alternatively, whenever an event is published
(leaking events on the http connection)

Fun-Fact: I've already changed all slides about this three times. This
will be the fourth :-)

--- long-polling/lib/server.coffee
+++ long-polling/lib/server.coffee
@@ -99,6 +99,10 @@
 listen_address = cfg.listen_address ? '120.0.0.1'
 redis_connections = {}

+unsubscribe = (channel, cb)->
+  return if not redis_connections[channel]
+  redis_connections[channel].removeListener('message', cb)
+
 subscribe = (channel, cb)->
   if not redis_connections[channel]
     c = redis.createClient 6379, cfg['redis-server'] || 'localhost'
@@ -107,12 +111,10 @@
       c.subscribe 'channels:'+channel
       c.on 'close', ()->
         delete redis_connection[c]
-      c.once "message", (chn, message)->
-        cb(message)
+      c.on "message", cb
   else
     c = redis_connections[channel]
-    c.once "message", (chn, message)->
-      cb(message)
+    c.on "message", cb

 exports.run = ->
   http.createServer((req, res)->
@@ -138,27 +140,33 @@
     req.on 'close', ->
       clear_waiting()

-    handle_request = ()->
+    handle_subscription = (c, message)->
       handler channel, last_event_id, (err, evts)->
-        return http_error res, 500, 'Failed to get event data: ' + err if err
+        return http_error 500, 'Failed to get event data' if err
+        abort_processing = write res, evts, true
+        last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
+        if abort_processing
+          unsubscribe channel, handle_subscription
+          clear_waiting()
+          res.end()

-        last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
-        if waiting() or (evts and evts.length > 0)
-          abort_processing = write(res, evts, not waiting());
-          if abort_processing or waiting()
-            res.end()
-            return
+    handler channel, last_event_id, (err, evts)->
+      return http_error res, 500, 'Failed to get event data: ' + err if err

-        set_waiting()
-        subscribe channel, (message)->
-          handler channel, last_event_id, (err, evts)->
-            return http_error 500, 'Failed to get event data' if err
-            abort_processing = write res, evts, true
-            return clear_waiting() if abort_processing
-            last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
-            process.nextTick ()->
-              handle_request()
-    handle_request()
+      last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
+      if waiting() or (evts and evts.length > 0)
+        abort_processing = write(res, evts, not waiting());
+        if waiting() or abort_processing
+          unsubscribe channel, handle_subscription
+          res.end()
+          return true
+
+      set_waiting()
+      subscribe channel, handle_subscription
+      req.on "close", ->
+        unsubscribe channel, handle_subscription
+        clear_waiting()
+
   ).listen listen_port, listen_address

   console.log "Server running at http://#{listen_address}:#{listen_port}/"


fixed typo in comment.

fixed typo in comment.

--- core/app/backbone/views/channels/favourite_topic_button_view.coffee
+++ core/app/backbone/views/channels/favourite_topic_button_view.coffee
@@ -3,7 +3,7 @@
   initialize: ->
     @user = currentUser

-    # TODO: invent some way to unsure favourite topics are actually loaded.
+    # TODO: invent some way to ensure favourite topics are actually loaded.
     # the current view seems to assume it already is loaded, so it wouldn't
     # need a loading state
     @model.set loaded: true


Set up a class for it in web-view-attributes

--- atom/renderer/lib/web-view/web-view-attributes.coffee
+++ atom/renderer/lib/web-view/web-view-attributes.coffee
@@ -167,6 +167,11 @@
   constructor: (webViewImpl) ->
     super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl

+# Attribute specifies HTTP referrer.
+class UserAgentAttribute extends WebViewAttribute
+  constructor: (webViewImpl) ->
+    super webViewConstants.ATTRIBUTE_HTTPREFERRER, webViewImpl
+
 # Attribute that set preload script.
 class PreloadAttribute extends WebViewAttribute
   constructor: (webViewImpl) ->


Minor optimizations.

Minor optimizations.

--- src/MappedCollection2.coffee
+++ src/MappedCollection2.coffee
@@ -272,30 +272,29 @@
     hooks = @_hooks[name]

     # If no hooks, nothing to do.
-    return unless hooks?
+    return unless hooks? and (n = hooks.length) isnt 0

     # Flags controlling the run.
-    stopped = false
-    actionPrevented = false
+    notStopped = true
+    actionNotPrevented = true

     # Creates the event object.
     event = {
-      stopPropagation: -> stopped = true
+      stopPropagation: -> notStopped = false

       # TODO: Should `preventDefault()` imply `stopPropagation()`?
-      preventDefault: -> actionPrevented = true
+      preventDefault: -> actionNotPrevented = false
     }

     i = 0
-    n = hooks.length
-    while not stopped and i < n
+    while notStopped and i < n
       hook.call ctx, event
       ++i

     # TODO: Is exception handling necessary to have the wanted
     # behavior?

-    return not actionPrevented
+    return actionNotPrevented

   _updateItem: (rule, item) ->
     return unless @_runHook 'beforeUpdate', item


express.io -> express (encoding problems)

--- src/server.coffee
+++ src/server.coffee
@@ -6,7 +6,7 @@
 Accepts       = require 'accepts'
 Fs            = require 'fs'
 Merge         = require 'merge'
-Express       = require 'express.io'
+Express       = require 'express'
 Mongoose      = require 'mongoose'
 BodyParser    = require 'body-parser'
 TSON          = require 'tson'


Simple heroku test

Simple heroku test

--- src/server.coffee
+++ src/server.coffee
@@ -1,7 +1,7 @@
 express = require 'express'
 server  = express()

-server.get('/', (req, res) -> res.send('hello') )
+server.get('/', (req, res) -> res.send('hello 123') )

 module.exports = server


explicit pass in for test

explicit pass in for test

--- components/auction_reminders/view.coffee
+++ components/auction_reminders/view.coffee
@@ -1,4 +1,5 @@
 _ = require 'underscore'
+_s = require 'underscore.string'
 Backbone = require 'backbone'
 ClockView = require '../clock/view.coffee'
 template = -> require('./template.jade') arguments...
@@ -35,6 +36,7 @@
   render: ->
     @$el.html template
       auction: @model
+      _s: _s
     @postRender()
     this


check for error callback

check for error callback

--- src/arangodb.coffee
+++ src/arangodb.coffee
@@ -743,7 +743,9 @@
     aql = aql.remove(returnVariable).in('@@collection')

     @execute model, 'query', aql, bindVars, (err, result) ->
-      callback and callback err, {count: result.extra.stats.writesExecuted}
+      if callback
+        return callback err if err
+        callback null, {count: result.extra.stats.writesExecuted}

   ###
     Count the number of instances for the given model
@@ -768,7 +770,8 @@
     aql = aql.collectWithCountInto(returnVariable).return(returnVariable)

     @execute model, 'query', aql, bindVars, (err, result) ->
-      callback err, result._result[0]
+      return callback err if err
+      callback null, result._result[0]

   ###
     Update properties for the model instance data


DOM utils: Methods getRect, getComputedStyle and getDimensions now support that the 'element' has a 'DOM' attribute.

--- src/base/dom.coffee
+++ src/base/dom.coffee
@@ -943,9 +943,13 @@
 			return node.parentNode.replaceChild(new_node, node)

 	@getRect: (docElem) ->
+		if docElem.hasOwnProperty('DOM')
+			docElem = docElem.DOM
 		docElem.getBoundingClientRect()

 	@getComputedStyle: (docElem) ->
+		if docElem.hasOwnProperty('DOM')
+			docElem = docElem.DOM
 		window.getComputedStyle(docElem)

 	@setStyle: (docElem, style, append="px") ->
@@ -1010,6 +1014,9 @@
 				width: window.innerWidth
 				height: window.innerHeight
 			}
+
+		if docElem.hasOwnProperty('DOM')
+			docElem = docElem.DOM

 		cs = @getComputedStyle(docElem)
 		rect = @getRect(docElem)


builder: fix cssVarMap not returning sprite name

--- client/builder/lib/index.coffee
+++ client/builder/lib/index.coffee
@@ -530,6 +530,9 @@
               console.error chalk.red('sprites') + ': stylus hates it when you have dots in image filenames, fix this: ' + sprite.name
               throw 'throwing cowardly'

+            sprite.name = "#{rname}_#{manifest.name}_#{sprite.name}"
+            return sprite
+
         s = vfs.src(dir).pipe spritesmith opts_
         return {
           name   : manifest.name


Reload button on review page

Reload button on review page

--- client/components/Review.coffee
+++ client/components/Review.coffee
@@ -128,13 +128,23 @@
         @setState
             results: @state.results[...-1]

+    componentDidUpdate: () ->
+        @state =
+            results: (r for r in props.data.ok when r.userList != "unknown")
+
     render: () ->
-        if @state.results.length == 0
-            return
Ревьювить больше нечего, обновите страницу

-            <ConnectedReviewResult result={@state.results[@state.results.length-1]} handleDone={@gotoNext}/>
-            {@state.results.length > 1 &&    # prefetch next submit
-

-                    <ConnectedReviewResult result={@state.results[@state.results.length-2]} handleDone={@gotoNext}/>
-
}
+            <Button onClick={@props.handleReload}>Обновить</Button>
+            {
+            if @state.results.length == 0
+
Ревьювить больше нечего, обновите страницу

+            else
+

+                    <ConnectedReviewResult result={@state.results[@state.results.length-1]} handleDone={@gotoNext}/>
+                    {@state.results.length > 1 &&    # prefetch next submit
+

+                            <ConnectedReviewResult result={@state.results[@state.results.length-2]} handleDone={@gotoNext}/>
+
}
+

+            }


AppController: add isHostOnline method

AppController: add isHostOnline method

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1885,6 +1885,30 @@
         @handleParticipantKicked targetUser

+  getCollaborationHost: -> if @amIHost then KD.nick() else @collaborationHost
+
+
+  isHostOnline: ->
+
+    host = @getCollaborationHost()
+
+    filtered = @participants
+      .asArray()
+      .filter (its) -> its.nickname is host
+
+    return no  unless filtered.length
+
+    { sessionId } = filtered.first
+
+    return no  unless sessionId
+
+    final = @rtm
+      .getCollaborators()
+      .filter (its) -> its.sessionId is sessionId
+
+    return final.length > 0
+
+
   listenPings: ->

     pingInterval = 1000 * 5


Collaboration: Return handling change if context is not exists.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -885,6 +885,9 @@
     if not myWatchMap or myWatchMap.keys().length is 0 or origin in myWatchMap.keys()
       log 'i need to handle this change...', change

+      {context} = change
+      return unless context
+
       if change.type is 'ContentChange'
         string = @rtm.getFromModel @realTimeDoc, change.context.file.path


AppController: Added quit method.

AppController: Added quit method.

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -1635,7 +1635,15 @@
           callback : => @modal.destroy()

     @showModal options
+  quit: ->
+
+    KD.utils.killRepeat @autoSaveInterval
+    KD.utils.killRepeat @pingInterval
+    @rtm?.dispose()
     @rtm = null
+
+    KD.singletons.router.handleRoute '/IDE'
+    KD.singletons.appManager.quit this

   showSessionEndedModal: (content) ->


IDE: bind double click event to filesPane, when it is collapsed it will expand the sidebar

--- client/ide/AppController.coffee
+++ client/ide/AppController.coffee
@@ -112,6 +112,7 @@
         appView.emit 'KeyViewIsSet'

         @createInitialView()
+        @bindCollapseEvents()

         {@finderPane} = @workspace.panel.getPaneByName 'filesPane'

@@ -131,6 +132,21 @@
       if routeInfo.path.indexOf('/IDE') is -1
         if mainView.isSidebarCollapsed
           mainView.toggleSidebar()
+
+  bindCollapseEvents: ->
+
+    { panel } = @workspace
+
+    filesPane = @workspace.panel.getPaneByName 'filesPane'
+
+    # We want double click to work
+    # if only the sidebar is collapsed. ~Umut
+    filesPane.on 'dblclick', (event) =>
+      KD.utils.stopDOMEvent event
+      @expandSidebar()  if @isSidebarCollapsed
+
+    baseSplit = panel.layout.getSplitViewByName 'BaseSplit'
+    baseSplit.resizer.on 'dblclick', @bound 'toggleSidebar'

   setActiveTabView: (tabView) ->
     @activeTabView = tabView


No `do` for you

No `do` for you

--- app/assets/javascripts/backbone/geochat.js.coffee
+++ app/assets/javascripts/backbone/geochat.js.coffee
@@ -18,4 +18,4 @@

   # initialize router
   window.router = new Geochat.Routers.ChannelsRouter
-  do Backbone.history.start
+  Backbone.history.start()


encoding bug fix

encoding bug fix

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -69,26 +69,21 @@
         data[1].value = settings.path + data[1].value #the key
         data

-  build_content_object = ($uploadForm, file, result) ->
-    domain = $uploadForm.attr('action')
-    content = {}
-    if result # Use the S3 response to set the URL to avoid character encodings bugs
-      path             = $('Key', result).text()
-      split_path       = path.split('/')
-      content.url      = domain + path
-      content.filename = split_path[split_path.length - 1]
-      content.filepath = split_path.slice(0, split_path.length - 1).join('/')
-    else # IE8 and IE9 return a null result object so we use the file object instead
-      path             = settings.path + $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
-      content.url      = domain + path + '/' + file.name
-      content.filename = file.name
-      content.filepath = path
+    build_content_object = ($uploadForm, file, result) ->
+      content = {}
+      if result # Use the S3 response to set the URL to avoid character encodings bugs
+        content.url            = $(result).find("Location").text()
+        content.filepath       = $('<a />').attr('href', content.url)[0].pathname
+      else # IE <= 9 retu      rn a null result object so we use the file object instead
+        domain                 = $uploadForm.attr('action')
+        content.filepath       = $uploadForm.find('input[name=key]').val().replace('/${filename}', '')
+        content.url            = domain + content.filepath + '/' + encodeURIComponent(file.name)

-    content.filename   = file.name
-    content.filesize   = file.size if 'size' of file
-    content.filetype   = file.type if 'type' of file
-    content = $.extend content, settings.additional_data if settings.additional_data
-    content
+      content.filename         = file.name
+      content.filesize         = file.size if 'size' of file
+      content.filetype         = file.type if 'type' of file
+      content = $.extend content, settings.additional_data if settings.additional_data
+      content

   #public methods
   @initialize = ->


paths with one slash are local paths other are absolute paths with or without schema

--- app/assets/javascripts/uploadcare/utils.js.coffee
+++ app/assets/javascripts/uploadcare/utils.js.coffee
@@ -116,12 +116,20 @@
     new RegExp("^" + parts.join('.+') + "$", flags)

   ns.normalizeUrl = (url) ->
+    # google.com/ → http://google.com
+    # /google.com/ → /google.com
+    # //google.com/ → http://google.com
+    # http://google.com/ → http://google.com
     scheme = document.location.protocol
     if scheme != 'http:'
       scheme = 'https:'

-    if url.match /^\/\//
-      url = "#{scheme}#{url}"
+    parts = url.match /^([^:/?#]+:)?\/?(\/)?/
+    if not parts[1]  # if no scheme
+      if parts[2]  # if two slashes
+        url = "#{scheme}#{url}"
+      else if not parts[0] # if no one slash
+        url = "#{scheme}//#{url}"
     url.replace(/\/+$/, '')

   ns.fitText = (text, max) ->


Fixes little issue with projects/assignments

Since we deal with assignments now, the information about the project_id
has to be plucked from the assignment

--- app/assets/javascripts/views/users/show.js.coffee
+++ app/assets/javascripts/views/users/show.js.coffee
@@ -26,8 +26,8 @@

   initialize: ->
     @userInfoTemplate = Handlebars.compile(@templates.userInfo)
-    userProjects = @model.get('projects').map (project) ->
-      window.StaffPlan.projects.get(project.project_id).get('name')
+    userProjects = @model.get('assignments').map (assignment) ->
+      window.StaffPlan.projects.get(assignment.project_id).get('name')
     @$el.html @userInfoTemplate({user: @model.attributes, projects: userProjects})

     @render()


Cleaner way of moving the scale to the bottom of the chart

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -55,7 +55,7 @@

         # translate the main x axis
         d3.select(@element).select('g.axis .domain')
-            .attr('transform', "translate(0, #{options.height - 14})")
+            .attr('transform', "translate(0, #{options.height - 13})scale(1, -1)")

         # datasets
         @root.append('g')


Set smoke to same z-index as entity

Set smoke to same z-index as entity

--- app/scripts/lazerscripts/stage1/stage1boss.coffee
+++ app/scripts/lazerscripts/stage1/stage1boss.coffee
@@ -19,11 +19,12 @@
     }[version]
     @sequence(
       @blast(@location(),
-        radius: 10
-        duration: 480
-        z: @entity.z - 3
-        alpha: options.alpha
-        lightness: 1.0
+        =>
+          radius: 10
+          duration: 480
+          z: @entity.z - 3
+          alpha: options.alpha
+          lightness: 1.0
         ->
           rotation: @rotation + 1
           alpha: Math.max(0, @alpha - .003)
@@ -480,20 +481,6 @@
           @while(
             @wait 300
             @smoke()
-            #@sequence(
-              #@blast(@location(),
-                #radius: 10
-                #duration: 480
-                #z: -10
-                #alpha: .8
-                #->
-                  #rotation: @rotation + 1
-                  #alpha: Math.max(0, @alpha - .003)
-                  #lightness: -> Math.max(.2, @lightness - .05)
-                  #y: @y - (Math.random() * 2)
-              #)
-              #@wait -> 40 + (Math.random() * 50)
-            #)
           )
         )
       )
@@ -502,21 +489,6 @@
       @while(
         @moveTo(x: 1.1, speed: 300)
         @smoke('light')
-        #@sequence(
-          #@blast(@location(),
-            #radius: 10
-            #duration: 480
-            #z: -150
-            #alpha: .4
-            #lightness: 1.0
-            #->
-              #rotation: @rotation + 1
-              #alpha: Math.max(0, @alpha - .003)
-              #lightness: Math.max(.2, @lightness - .05)
-              #y: @y - (Math.random() * 2)
-          #)
-          #@wait -> 140 + (Math.random() * 50)
-        #)
       )
     )

@@ -582,7 +554,7 @@
       @sendToBackground(0.9, -100)
       @parallel(
         @while(
-          @moveTo(x: -.15, speed: 400)
+          @moveTo(x: -.15, y: .5, speed: 400)
           @smoke()
         )
         @scale(0.7, duration: 3000)


Fixed action on container

Fixed action on container

--- src/chocolate.coffee
+++ src/chocolate.coffee
@@ -1,12 +1,14 @@
-class_body     = 'choco-body'
-class_error    = 'choco-error'
-class_hover    = 'choco-hover'
-class_item     = 'choco-item'
-class_loading  = 'choco-loading'
-class_selected = 'choco-selected'
-class_show     = 'choco-show'
+choco = 'choco'
+class_body     = "#{choco}-body"
+class_error    = "#{choco}-error"
+class_hover    = "#{choco}-hover"
+class_item     = "#{choco}-item"
+class_loading  = "#{choco}-loading"
+class_selected = "#{choco}-selected"
+class_show     = "#{choco}-show"

 class Chocolate
+
   existActions = ['next', 'prev', 'close']

   env = {}
@@ -30,16 +32,15 @@
     @storage = new ChocolateStorage @options.repeat

     template = templates.overlay
-    template = template.replace '{{spinner}}', templates.spinner
     template = template.replace '{{thumbnails}}', if @options.thumbnails then templates.thumbnails else ''

     @overlay = beforeend(document.body, template)[0]

-    containers = ['container', 'leftside', 'rightside', 'slider']
+    containers = ['leftside', 'rightside', 'slider']
     containers.push 'thumbnails' if @options.thumbnails

     for container in containers
-      @[container] = @overlay.querySelector ".choco-#{container}"
+      @[container] = @overlay.querySelector ".#{choco}-#{container}"

     @add images if images

@@ -47,7 +48,7 @@

     addHandler @overlay, 'click', =>
       @close()
-    , '.choco-close'
+    , ".#{choco}-close"

     for container in ['overlay', 'leftside', 'rightside']
@@ -121,10 +122,9 @@
     loading = hasClass item.slide, class_loading
     if loading
       loadImage item, (success) =>
-        item.size = item.slide.querySelector('.choco-slide-container').offsetWidth
-        console.log item.size
-        @leftside.style.width = (env.w - item.size) / 2 + 'px'
-        @rightside.style.width = (env.w - item.size) / 2 + 'px'
+        @updateSides item
+    else
+      @updateSides item

     true

@@ -175,6 +175,15 @@

+  updateSides: (item) ->
+    if not item.size
+      item.size = item.slide.querySelector(".#{choco}-slide-container").offsetWidth
+    @leftside.style.width = (env.w - item.size) / 2 + 'px'
+    @rightside.style.width = (env.w - item.size) / 2 + 'px'
+
+
+
+
   initTouch: -> true

@@ -184,12 +193,21 @@
     data = chocolate.storage.add data
     return unless data

+    data.thumbnail = beforeend(chocolate.thumbnails, mustache templates['thumbnails-item'], data)[0]
+    addHandler data.thumbnail, 'click', -> chocolate.select data
+
     data.slide = beforeend(chocolate.slider, mustache templates['slide'], data)[0]
     data.slide.classList.add class_loading
-    data.thumbnail = beforeend(chocolate.thumbnails, mustache templates['thumbnails-item'], data)[0]
-    data.img = data.slide.querySelector '.choco-slide-image'
-
-    addHandler data.thumbnail, 'click', -> chocolate.select data
+
+    data.img = data.slide.querySelector ".#{choco}-slide-image"
+
+    method = chocolate.options.actions.container if chocolate.options.actions.container in existActions
+
+    if method
+      addHandler data.slide, 'click', ->
+        chocolate[method]()
+      , ".#{choco}-slide-container"
+

     if image

@@ -260,7 +278,7 @@

       if chocolate.options.actions[container] is 'close'
         addHandler chocolate[container], ['mouseenter', 'mouseleave'], ->
-          toggleClass chocolate.overlay, class_hover, '.choco-close'
+          toggleClass chocolate.overlay, class_hover, ".#{choco}-close"

@@ -268,7 +286,7 @@
   getEnv = ->
     return env unless needResize
     if isOpen
-      slide = opened.slider.querySelector '.choco-slide'
+      slide = opened.slider.querySelector ".#{choco}-slide"
       return unless slide
       style = getStyle slide
       h = toInt(style 'height') -


Improve simulate shipping function

Improve simulate shipping function

--- src/checkout.coffee
+++ src/checkout.coffee
@@ -251,7 +251,7 @@
           quantity: 0
         })
       @updateItems(itemsToRemove, expectedOrderFormSections)
-
+
   # Clone an item to one or more new items like it
   cloneItem: (itemIndex, newItemsOptions, expectedFormSections = @_allOrderFormSections) =>
     @_updateOrderForm
@@ -359,11 +359,19 @@
     @sendAttachment('shippingData', {address: address})

   # Simulates shipping using a list of items, a postal code and a country.
-  simulateShipping: (items, postalCode, country, salesChannel) =>
-    dataRequest =
-      items: items
-      postalCode: postalCode
-      country: country
+  simulateShipping: (args...) =>
+    [firstArg,secondArg,country,salesChannel] = arguments
+    dataRequest = []
+    if Array.isArray( firstArg )
+      dataRequest =
+        items: firstArg
+        postalCode: secondArg
+        country: country
+    else
+      dataRequest =
+        shippingData: firstArg
+        orderFormId: secondArg
+        country: country

     salesChannelQueryString = ''
     if salesChannel


Short circuit if array is empty

Short circuit if array is empty

--- src/checkout.coffee
+++ src/checkout.coffee
@@ -225,6 +225,9 @@

   # Sends a request to remove items from the OrderForm.
   removeItems: (items, expectedOrderFormSections = @_allOrderFormSections) =>
+    if items and items.length is 0
+      return @getOrderForm(expectedOrderFormSections)
+
     itemsToRemove = []
     for item, i in items
       itemsToRemove.push({
@@ -237,6 +240,9 @@
   removeAllItems: (expectedOrderFormSections = @_allOrderFormSections) =>
     @getOrderForm(['items']).then (orderForm) =>
       items = orderForm.items
+      if items and items.length is 0
+        return orderForm
+
       itemsToRemove = []
       for item, i in items
         itemsToRemove.push({


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -222,6 +222,7 @@
             yw: target.height/2
         else
           error "Bounds object isn't a rectangle"
+
           return

       xw = target.xw


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -48,10 +48,12 @@

     @name collide
     @methodOf Collision
-    @param {Object|Array} groupA An object or set of objects to check collisions with
-    @param {Object|Array} groupB An objcet or set of objects to check collisions with
-    @param {Function} callback The callback to call when an object of groupA collides
+    @param {Object|Array|String} groupA An object or set of objects to check collisions with
+    @param {Object|Array|String} groupB An objcet or set of objects to check collisions with
+    @param {Function} callback The callback to call when an object of groupA collides
     with an object of groupB: (a, b) ->
+    @param {Function} [detectionMethod] An optional detection method to determine when two
+    objects are colliding.
     ###
     collide: (groupA, groupB, callback, detectionMethod=collides) ->
       if Object.isString(groupA)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/collision.coffee
+++ src/collision.coffee
@@ -221,7 +221,7 @@
             xw: target.width/2
             yw: target.height/2
         else
-          error "Bounds object"
+          error "Bounds object "
           return

       xw = target.xw


support CMYK gradients

support CMYK gradients

--- lib/gradient.coffee
+++ lib/gradient.coffee
@@ -1,15 +1,32 @@
+{number} = require './object'
+
 class PDFGradient
   constructor: (@doc) ->
     @stops = []
     @embedded = no
     @transform = [1, 0, 0, 1, 0, 0]
-    @_colorSpace = 'DeviceRGB'

   stop: (pos, color, opacity = 1) ->
+    color = @doc._normalizeColor(color)
+
+    if @stops.length is 0
+      if color.length is 3
+        @_colorSpace = 'DeviceRGB'
+      else if color.length is 4
+        @_colorSpace = 'DeviceCMYK'
+      else if color.length is 1
+        @_colorSpace = 'DeviceGray'
+      else
+        throw new Error('Unknown color space')
+    else if (@_colorSpace is 'DeviceRGB' and color.length isnt 3) or
+            (@_colorSpace is 'DeviceCMYK' and color.length isnt 4) or
+            (@_colorSpace is 'DeviceGray' and color.length isnt 1)
+      throw new Error('All gradient stops must use the same color space')
+
     opacity = Math.max(0, Math.min(1, opacity))
-    @stops.push [pos, @doc._normalizeColor(color), opacity]
+    @stops.push [pos, color, opacity]
     return this
-
+
   setTransform: (m11, m12, m21, m22, dx, dy) ->
     @transform = [m11, m12, m21, m22, dx, dy]
     return this
@@ -65,7 +82,7 @@
       Type: 'Pattern'
       PatternType: 2
       Shading: shader
-      Matrix: (+v.toFixed(5) for v in @matrix)
+      Matrix: number(v) for v in @matrix

     pattern.end()


Refactor context unit spec to only cover error cases

--- src/context/context.spec.coffee
+++ src/context/context.spec.coffee
@@ -1,9 +1,8 @@
-describe.skip 'Context', ->
+describe 'Context', ->
   Context = null

   class RepositoryMock
   eventBusStub = null
-  eventricStub = null

   beforeEach ->
     eventBusStub =
@@ -11,41 +10,7 @@
       subscribeToDomainEventWithAggregateId: sandbox.stub()
       publishDomainEvent: sandbox.stub()

-    eventric = require '..'
-    eventricStub =
-      get: -> {}
-      log:
-        debug: ->
-      getStores: -> []
-      publish: ->
-      defaults: eventric.defaults
-      eachSeries: eventric.eachSeriesprojectionService
-      EventBus: sandbox.stub().returns eventBusStub
-      Repository: RepositoryMock
-
     Context = require './'
-
-
-  describe '#initialize', ->
-
-    it 'should instantiate registered projections', ->
-      context = new Context 'exampleContext', eventricStub
-      class ProjectionStub
-        stores: ['inmemory']
-      context.addProjection 'SomeProjection', ProjectionStub
-      context.initialize()
-      .then ->
-        context.getProjectionStore 'inmemory', 'SomeProjection', (err, projectionStore) ->
-          expect(projectionStore).to.deep.equal {}
-
-
-    it 'should instantiate and initialize all registered adapters', (done) ->
-      context = new Context 'exampleContext', eventricStub
-      AdapterFactory = sandbox.stub()
-      context.addAdapter 'Adapter', AdapterFactory
-      context.initialize ->
-        expect(AdapterFactory).to.have.been.calledWithNew
-        done()

   describe '#command', ->
@@ -73,19 +38,6 @@
             done()

-    describe 'has a registered handler', ->
-      it 'should execute the command handler', (done) ->
-        commandStub = sandbox.stub()
-        someContext = new Context 'exampleContext', eventricStub
-        someContext.initialize ->
-          someContext.addCommandHandler 'doSomething', commandStub
-
-          params = foo: 'bar'
-          someContext.command 'doSomething', params
-          expect(commandStub.calledWith params, sinon.match.func).to.be.true
-          done()
-
-
   describe '#query', ->
     someContext = null
     beforeEach ->
@@ -106,28 +58,3 @@
           .catch (error) ->
             expect(error).to.be.an.instanceOf Error
             done()
-
-
-    describe 'given the query has a matching queryhandler', ->
-      it 'should call the queryhandler function', (done) ->
-        queryStub = sandbox.stub().yields null, 'result'
-        someContext.addQueryHandler 'getSomething', queryStub
-        someContext.initialize ->
-          someContext.query 'getSomething'
-          .then (result) ->
-            expect(result).to.equal 'result'
-            expect(queryStub).to.have.been.calledWith
-            done()
-
-
-  describe '#emitDomainEvent', ->
-    beforeEach (done) ->
-      someContext = new Context 'ExampleContext', eventricStub
-      someContext.defineDomainEvent 'WhatSoEver', ->
-      someContext.initialize ->
-        someContext.emitDomainEvent 'WhatSoEver'
-        done()
-
-
-    it 'should publish the DomainEvent on the EventBus', ->
-      expect(eventBusStub.publishDomainEvent).to.have.been.called


Commit bêbado

Commit bêbado

--- src/coffee/portal-notify-me.coffee
+++ src/coffee/portal-notify-me.coffee
@@ -18,7 +18,7 @@
 			Form: 'form'
 			Name: '.notifyme-client-name'
 			Email: '.notifyme-client-email'
-			SkuId: 'Title.notifyme-skuid'
+			SkuId: '.notifyme-skuid'
 			Loading: '.notifyme-loading-message'
 			Success: 'fieldset.success'
 			Error: 'fieldset.error'


simplify

simplify

--- lib/operator.coffee
+++ lib/operator.coffee
@@ -624,34 +624,37 @@
   @extend()

   pasteLinewise: (selection, text) ->
-    newRange = super(selection, text)
-
-    neededIndent = @editor.suggestedIndentForBufferRow(newRange.start.row)
+    newRange = super
+
+    [startRow, endRow] = [newRange.start.row, newRange.end.row]
+    suggestedIndentLevel = @editor.suggestedIndentForBufferRow(startRow)

     # We must calculate the min here to ensure we're not deleting non-space characters from the line.
     # This occurs if the register contents has greater indentation on the first line than the others,
     # such as:
     #      varOne: value
     # varFortyTwo: value
-    actualIndent = Number.MAX_SAFE_INTEGER
-    for bufferRow in [newRange.start.row..newRange.end.row - 1]
-      if @editor.lineTextForBufferRow(bufferRow) isnt ''
-        actualIndent = Math.min(actualIndent, getIndentLevelForBufferRow(@editor, bufferRow))
+    minIndentLevel = null
+    for row in [startRow...endRow] when not isEmptyRow(@editor, row)
+      indentLevel = getIndentLevelForBufferRow(@editor, row)
+      if minIndentLevel?
+        minIndentLevel = Math.min(minIndentLevel, indentLevel)
+      else
+        minIndentLevel = indentLevel

     # The user put blank lines only, prevent autoIndent
-    return newRange if actualIndent is Number.MAX_SAFE_INTEGER
-
-    indentDelta = neededIndent - actualIndent
+    return newRange unless minIndentLevel?
+
+    indentDelta = suggestedIndentLevel - minIndentLevel

     if indentDelta > 0
       indentText = @editor.buildIndentString(indentDelta)
-      for bufferRow in [newRange.start.row..newRange.end.row - 1]
-        insertTextAtBufferPosition(@editor, [bufferRow, 0], indentText)
+      for row in [startRow...endRow]
+        insertTextAtBufferPosition(@editor, [row, 0], indentText)
     else if indentDelta < 0
       charsToRemove = @editor.buildIndentString(Math.abs(indentDelta)).length
-      for bufferRow in [newRange.start.row..newRange.end.row - 1]
-        @editor.setTextInBufferRange([[bufferRow, 0], [bufferRow, charsToRemove]], '')
-
+      for row in [startRow...endRow]
+        @editor.setTextInBufferRange([[row, 0], [row, charsToRemove]], '')
     return newRange

 class PutAfterWithAutoIndent extends PutBeforeWithAutoIndent


fix: substitutions quotes (#165)

fix: substitutions quotes (#165)

--- lib/renderer.coffee
+++ lib/renderer.coffee
@@ -121,17 +121,21 @@

   for preElement in $.merge(html.filter('pre'), html.find('pre'))
     codeBlock = $(preElement.firstChild)
-    fenceName = codeBlock.attr('class')?.replace(/^language-/, '') ? defaultLanguage

-    highlighter ?= new Highlights(registry: atom.grammars)
-    highlightedHtml = highlighter.highlightSync
-      fileContents: codeBlock.text()
-      scopeName: scopeForFenceName(fenceName)
+    # Exclude text node to highlights
+    # Because this creates a rendering bug with quotes substitutions #102
+    if codeBlock[0]?.nodeType isnt Node.TEXT_NODE
+      fenceName = codeBlock.attr('class')?.replace(/^language-/, '') ? defaultLanguage

-    highlightedBlock = $(highlightedHtml)
-    # The `editor` class messes things up as `.editor` has absolutely positioned lines
-    highlightedBlock.removeClass('editor').addClass("lang-#{fenceName}")
-    highlightedBlock.insertAfter(preElement)
-    preElement.remove()
+      highlighter ?= new Highlights(registry: atom.grammars)
+      highlightedHtml = highlighter.highlightSync
+        fileContents: codeBlock.text()
+        scopeName: scopeForFenceName(fenceName)
+
+      highlightedBlock = $(highlightedHtml)
+      # The `editor` class messes things up as `.editor` has absolutely positioned lines
+      highlightedBlock.removeClass('editor').addClass("lang-#{fenceName}")
+      highlightedBlock.insertAfter(preElement)
+      preElement.remove()

   html


Update atom config

Update atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -33,6 +33,7 @@
       "repositories"
       "tmp"
       "vendor"
+      ".DS_Store"
     ]
     projectHome: "/Users/thrillhause/Projects"
     telemetryConsent: "no"
@@ -44,6 +45,7 @@
     fontFamily: "Hack"
     fontSize: 15
     invisibles: {}
+    scrollPastEnd: true
     showIndentGuide: true
     softWrapAtPreferredLineLength: true
   "go-plus":
@@ -68,6 +70,8 @@
     ]
   stylefmt:
     formatOnSave: true
+  "tree-view":
+    hideIgnoredNames: true
   welcome:
     showOnStartup: false
 ".js.jsx.react.source":


:racehorse: Cache parsed snippets

:racehorse: Cache parsed snippets

--- lib/snippets.coffee
+++ lib/snippets.coffee
@@ -14,6 +14,8 @@

   activate: ->
     @userSnippetsPath = null
+    @snippetIdCounter = 0
+    @snippetsCache = new Map
     @scopedPropertyStore = new ScopedPropertyStore
     @subscriptions = new CompositeDisposable
     @subscriptions.add atom.workspace.addOpener (uri) =>
@@ -178,6 +180,7 @@
       for name, attributes of snippetsByName
         {prefix, body} = attributes
         attributes.name = name
+        attributes.id = @snippetIdCounter++
         if typeof body is 'string'
           unparsedSnippetsByPrefix[prefix] = attributes
         else if not body?
@@ -212,10 +215,14 @@
     for prefix, attributes of unparsedSnippetsByPrefix
       continue if typeof attributes?.body isnt 'string'

-      {name, body, bodyTree, description, descriptionMoreURL} = attributes
-      bodyTree ?= @getBodyParser().parse(body)
-      snippet = new Snippet({name, prefix, bodyTree, description, descriptionMoreURL, bodyText: body})
-      snippets[prefix] = snippet
+      {id, name, body, bodyTree, description, descriptionMoreURL} = attributes
+
+      unless @snippetsCache.has(id)
+        bodyTree ?= @getBodyParser().parse(body)
+        snippet = new Snippet({id, name, prefix, bodyTree, description, descriptionMoreURL, bodyText: body})
+        @snippetsCache.set(id, snippet)
+
+      snippets[prefix] = @snippetsCache.get(id)
     snippets

   priorityForSource: (source) ->


Wrap syntax errors in front matter to output file path

--- parsers/front_matter_parser.coffee
+++ parsers/front_matter_parser.coffee
@@ -21,7 +21,14 @@
     process.stdout.write "."
     return if fileIsIgnored(file.name)

-    output = frontMatter.parse fs.readFileSync(file.fullPath, 'utf-8')
+    fileText = fs.readFileSync(file.fullPath, 'utf-8')
+    try
+      frontMatter.parse fileText
+    catch err
+      console.log err.stack
+      return console.log "This occurred in #{file.fullPath}"
+
+    output = frontMatter.parse fileText
     fileAttrs = _.keys output.attributes

     attrDiff    = _.difference requiredAttrs, fileAttrs


fix subtree

fix subtree

--- lib/ui/views.coffee
+++ lib/ui/views.coffee
@@ -39,7 +39,7 @@
           children.map((x) => @render(x, opts)).forEach (x) ->
             body.appendChild x

-  subtree: ({label, child}) ->
+  subtree: ({label, child}, opts) ->
     @render (if child.type == "tree"
       type: "tree"
       head: @tags.span [label, child.head]


:bug: Make linter work with incremental updates Fixes #1087

--- lib/validate.coffee
+++ lib/validate.coffee
@@ -50,7 +50,7 @@
       else
         result.filePath = null
       result.range = Range.fromObject result.range if result.range?
-      result.key = JSON.stringify(result)
+      result.key = (message.text or message.html) + '$' + message.type + '$' + (message.class or '') + '$' + (message.name or '') + '$' + message.filePath + '$' + (if message.range then message.range.start.column + ':' + message.range.start.row + ':' + message.range.end.column + ':' + message.range.end.row else '')
       result.linter = linter.name
       Validate.messages(result.trace, linter) if result.trace and result.trace.length
     return


:art:

:art:

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -763,7 +763,7 @@
       else if character is ' '
         screenExtent += 1
       else
-        return screenExtent
+        break
     screenExtent

   # Walk backwards through the line, looking for the first non


changed the result and error emissions from serially and concurrently

--- src/event-channel.coffee
+++ src/event-channel.coffee
@@ -108,13 +108,18 @@

   serially: (builder) ->
     functions = []
-    go = (fn) -> (functions.push fn)
+    go = (fn) ->
+      functions.push fn
     builder go
     events = @source()
     (arg) ->
+      results = []
+      count = 0
       _fn = (arg) ->
+        results.push arg if arg?
         fn = functions.shift()
         if fn?
+          count++
           try
             rval = fn(arg)
             if rval instanceof EventChannel
@@ -126,7 +131,7 @@
           catch error
             events.emit "error", error
         else
-          events.emit "success", arg
+          events.emit "success", results
       _fn( arg )
       return events

@@ -138,7 +143,7 @@
     events = @source()
     (arg) ->
       _fn = (arg) ->
-        results = {}; errors = []
+        results = {}; errors = {}
         called = 0; returned = 0
         finish = ->
           returned++
@@ -149,10 +154,12 @@
               _error = new Error "concurrently: unable to complete"
               _error.errors = errors
               events.emit "error", _error
-        error = (_error) ->
-          errors.push _error
-          # TODO: If we're going to finish() here, why bother with an array
-          # of errors ... ?
+        record_error = (name, _error) ->
+          if name
+            errors[name] = _error
+          else
+            errors.unnamed_actions ||= []
+            errors.unnamed_actions.push _error
           finish()
         return arg if functions.length is 0
         for [name, fn] in functions
@@ -165,11 +172,12 @@
               rval = fn( arg )
               if rval instanceof EventChannel
                 rval.on "success", success
-                rval.on "error", error
+                rval.on "error", (error) ->
+                  record_error name, error
               else
                 success rval
             catch _error
-              error _error
+              record_error name, _error
       _fn( arg )
       events


modal setposition animation fix for initial positioning

--- client/Framework/components/modals/KDModalView.coffee
+++ client/Framework/components/modals/KDModalView.coffee
@@ -19,11 +19,15 @@

     super options, data

+    @setClass "initial"
     @putOverlay options.overlay                   if options.overlay
     @setClass "fx"                                if options.fx
     @setTitle options.title                       if options.title
     @setContent options.content                   if options.content
     @addSubView options.view,".kdmodal-content"   if options.view
+
+    @on "viewAppended", =>
+      @utils.wait 500, => @unsetClass "initial"

     KDView.appendToDOMBody @

@@ -43,16 +47,17 @@
     @_windowDidResize()

     # @getSingleton("windowController").setKeyView @ ---------> disabled because KDEnterinputView was not working in KDmodal
-    $(window).on "keydown.modal",(e)=>
+    $(window).one "keydown.modal",(e)=>
       @destroy() if e.which is 27

     @on "childAppended", @setPositions.bind @
+
     @listenWindowResize()

   setDomElement:(cssClass)->

     @domElement = $ "
-

+

@@ -116,8 +121,6 @@
       newPosition.left = $(window).width() - @modalWidth - position.right - 20 if position.right #20 is the padding FIX
       @$().css newPosition
       @$().css opacity : 1
-      @utils.wait 500, =>
-        @unsetClass "initial"

   _windowDidResize:->
     @setPositions()


privatemessage: remove error notifications for participant added/removed channel events

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -183,7 +183,7 @@

     socialapi.cacheable typeConstant, id, (err, channel) =>

-      return KD.showError err  if err
+      return warn err  if err

       channel.isParticipant    = yes
       channel.participantCount = participantCount
@@ -202,7 +202,7 @@
     @removeItem id

     socialapi.cacheable typeConstant, id, (err, channel) =>
-      return KD.showError err  if err
+      return warn err  if err

       channel.isParticipant    = no
       channel.participantCount = participantCount


sidebar: apply a callback queue for fetchWorkspaces

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -403,14 +403,17 @@
   workspacesFetched  = no
   fetchingWorkspaces = no

-  fetchWorkspaces: (callback = noop) ->
+  fetchWorkspaces: do (callbackQueue = []) -> (callback) ->

     activitySidebar = this

     return callback null, KD.userWorkspaces  if workspacesFetched
-    return  if fetchingWorkspaces
+    return callbackQueue.push callback       if fetchingWorkspaces

     fetchingWorkspaces = yes
+
+    # put first callback to queue as well.
+    callbackQueue.push callback

     KD.remote.api.JWorkspace.fetchByMachines()

@@ -445,11 +448,14 @@
           KD.userWorkspaces     = userWorkspaces
           workspacesFetched     = yes
           activitySidebar.updateMachineTree()
-          callback null, userWorkspaces
+
+          callbackQueue.forEach (fn) -> fn null, userWorkspaces
+          callbackQueue = []

       .error (rest...) ->
         fetchingWorkspaces = no
-        callback rest...
+        callbackQueue.forEach (fn) -> fn rest...
+        callbackQueue = []


Sidebar: Slugify workspace name before create.

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -605,10 +605,10 @@
     layout = {}

     if not name or not machineUId
-      return warn 'Missing options for create new workspace'
+      return warn 'Missing options to create a new workspace'

     unless rootPath
-      rootPath       = "/home/#{KD.nick()}/Workspaces/#{name}"
+      rootPath       = "/home/#{KD.nick()}/Workspaces/#{KD.utils.slugify name}"
       emptyWorkspace = yes

     data    = { name, machineUId, machineLabel, rootPath, layout }


GroupsController: setDefaultTemplate: create activity only when stackTemplate content is updated

--- client/app/lib/maincontroller/groupscontroller.coffee
+++ client/app/lib/maincontroller/groupscontroller.coffee
@@ -186,14 +186,16 @@
         # Re-call create default stack flow to make sure it exists
         computeController.createDefaultStack yes

-        id = currentGroup.socialApiDefaultChannelId
-
-        ActivityFlux = require 'activity/flux'
-        ActivityFlux.actions.message.createMessage id, "
-          I've just updated the Team stack template, please take a backup of
-          your existing data and re-initialize your stack to use the latest
-          version.
-        "
+
+        if stackTemplate._updated
+          id = currentGroup.socialApiDefaultChannelId
+
+          ActivityFlux = require 'activity/flux'
+          ActivityFlux.actions.message.createMessage id, "
+            I've just updated the Team stack template, please take a backup of
+            your existing data and re-initialize your stack to use the latest
+            version.
+          "

         # Warn other group members about stack template update
         currentGroup.sendNotification 'StackTemplateChanged', stackTemplate._id


image is converted to image-collection when edit app mounts

--- client/apps/edit/components/section_list/index.coffee
+++ client/apps/edit/components/section_list/index.coffee
@@ -26,6 +26,19 @@
   onNewSection: (section) ->
     @setState editingIndex: @props.sections.indexOf section

+  convertImages: (section) ->
+    newSection = {
+      type: 'image_collection'
+      layout: section.attributes.layout || 'overflow_fillwidth'
+      images: [ {
+        type: section.get('type')
+        url: section.get('url')
+        caption: section.get('caption')
+      } ]
+    }
+    section.attributes = newSection
+    return section
+
   render: ->
     div {},
       div {
@@ -35,6 +48,8 @@
       },
         SectionTool { sections: @props.sections, index: -1 }
         @props.sections.map (section, i) =>
+          if section.get('type') is 'image'
+            section = @convertImages section
           [
             SectionContainer {
               sections: @props.sections


finder minor

finder minor

--- client/landing/app/MainApp/sidebar/sidebarview.coffee
+++ client/landing/app/MainApp/sidebar/sidebarview.coffee
@@ -78,10 +78,11 @@
       cssClass  : "finder-resize-handle"

     @finderController = new NFinderController
-      fsListeners : yes
-      initialPath : "/Users/#{profile.nickname}/Sites/#{profile.nickname}.koding.com/website" # obsolete, make it work this way
-      initDelay   : 5000
-      useStorage  : yes
+      fsListeners       : yes
+      initialPath       : "/Users/#{profile.nickname}/Sites/#{profile.nickname}.koding.com/website" # obsolete, make it work this way
+      initDelay         : 5000
+      useStorage        : yes
+      addOrphansToRoot  : no

     @finder = @finderController.getView()


TEST"TST-272 remove console.log

TEST"TST-272 remove console.log

--- client/test/lib/helpers/virtualmachineshelpers.coffee
+++ client/test/lib/helpers/virtualmachineshelpers.coffee
@@ -151,7 +151,6 @@
   removeAccessFromSharedMachine: (browser, callback) ->
     member = utils.getUser no, 1
     removeUser = "#{sharedUserlist} .remove"
-    console.log('remove')
     browser
       .waitForElementVisible membersList, 20000
       .moveToElement sharedUserlist, 0, 0


reference to problem element not present when creating cm editor instance,fallback to store it in window object for now

--- common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -6,8 +6,6 @@
     @id = @el.data('problem-id')
     @element_id = @el.attr('id')
     @url = @el.data('url')
-    @lang_choice = ''
-    @editors = []
     # has_timed_out and has_response are used to ensure that are used to
     # ensure that we wait a minimum of ~ 1s before transitioning the check
     # button from disabled to enabled
@@ -54,6 +52,7 @@
 	var value = _this.$(this).find('option:selected').text(),
 	    $mode =  _this.$(this).val();
 	_this.lang_choice =  $mode;
+    //alert(_this.lang_choice);
 	if(window.localStorage){
 	  window.localStorage.setItem('cm_lang_choice',$mode);
 	}
@@ -64,7 +63,7 @@
 	if(window.cmeditor){
 	  window.cmeditor.setOption('mode',$mode);
 	}else{
-	  alert('cmeditor is set afterwards, cool he will get the lang from _this.lang_choice');
+	  console.log('cmeditor is set afterwards, cool he will get the lang from _this.lang_choice');
 	}

     }).trigger('change');
@@ -590,11 +589,10 @@
       return display

     cminput: (container) =>
+      alert container
       element = $(container).find("textarea")
       tabsize = element.data("tabsize")
-      mode = @lang_choice
-      if mode == ''
-          mode = element.data("mode")
+      mode = element.data("mode")
       linenumbers = element.data("linenums")
       readOnly = element.data("readonly")
       spaces = Array(parseInt(tabsize) + 1).join(" ")


no id on connection;

no id on connection;

--- components/connect/client/connect_results_view.coffee
+++ components/connect/client/connect_results_view.coffee
@@ -22,16 +22,14 @@
     id = target.data('id')

     if target.hasClass('is-connected')
-      analytics.track.click 'Connection removed',
-        label: analytics.modelNameAndIdToLabel @model.get('class'), @model.id
+      analytics.track.click 'Connection removed'

       reqOpts =
         type: 'DELETE'
         url: "#{sd.API_URL}/channels/#{id}/blocks/#{@block.id}"
     else
       mediator.shared.recent_connections.create @collection.get(id).toJSON()
-      analytics.track.click 'Connection created',
-        label: analytics.modelNameAndIdToLabel @model.get('class'), @model.id
+      analytics.track.click 'Connection created'
       reqOpts =
         type: "POST"
         url: "#{sd.API_URL}/channels/#{id}/connections"


resize elements vertically on window resize

--- src/frontend/room.coffee
+++ src/frontend/room.coffee
@@ -136,4 +136,18 @@
 # launch it.
 $(document).ready ->
     if window.Voicious.WebRTCRunnable
+        # Resize all elements vertically when window is resized
+        # (middle-row elements must fill all space between header and footer)
+        resizeAll = () ->
+            container     = ($ '.container-fluid')[0]
+            rows          = ($ container).find '.row-fluid'
+            summedHeight  = 0
+            for row in rows
+                if (($ row).attr 'id') isnt 'middle-row'
+                    summedHeight += do ($ row).innerHeight
+            jqMiddleRow   = ($ '#middle-row')
+            summedPadding = (parseInt (jqMiddleRow.css 'padding-top')) + (parseInt (jqMiddleRow.css 'padding-bottom'))
+            jqMiddleRow.height (do ($ window).innerHeight) - summedHeight - summedPadding
+        do resizeAll
+        ($ window).on 'resize', resizeAll
         room = new Room window.modules


Updated template cache for production

Updated template cache for production

--- app/assets/javascripts/sprangular/interceptors/railsAssets.coffee
+++ app/assets/javascripts/sprangular/interceptors/railsAssets.coffee
@@ -4,7 +4,9 @@

   $provide.factory 'railsAssetsInterceptor', ->
     request: (config) ->
-      if assetUrl = Env.templates[config.url]
+      url = config.url.gsub(/^\/assets\//, '')
+
+      if assetUrl = Env.templates[url]
         config.url = assetUrl

       config


add page:update trigger

add page:update trigger

--- app/assets/javascripts/widgit/admin/plugins/icon_editor.js.coffee
+++ app/assets/javascripts/widgit/admin/plugins/icon_editor.js.coffee
@@ -27,6 +27,7 @@
       @update()

   update: ->
+    $(document).trigger 'page:update'
     @$parent.resource('update', 'icon', @icon)

   makeModal: ->


Clean up indents

Clean up indents

--- data/prefixes.coffee
+++ data/prefixes.coffee
@@ -158,7 +158,7 @@
           props:  ['display']
           browsers: browsers
   prefix 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
-          browsers:   browsers
+          browsers: browsers
   prefix 'flex-direction', 'flex-wrap', 'flex-flow', 'justify-content',
          'order', 'align-items', 'align-self', 'align-content',
           browsers: browsers
@@ -167,7 +167,7 @@
   add 'display-flex', 'inline-flex',
        browsers: browsers
   add 'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
-       browsers:   browsers
+       browsers: browsers
   add 'flex-direction', 'flex-wrap', 'flex-flow', 'justify-content',
       'order', 'align-items', 'align-self', 'align-content',
        browsers: browsers


.

Former-commit-id: 46f0802c91f63d2d77dffcc276c074641f62aefe [formerly 739a44238bde74aa10c9026c138015dd5024dac6] [formerly 81306ac11dea77ba52c2f38b472dfd319f3f21b6 [formerly 1803997cf7b94eea4f5cb945060d0a8ca41b7069]] [formerly a4018e8683e3c5b94295dce0b51825a33ffebaee [formerly cb7d61c58dd6227e6d500819d00ff05864b5e725] [formerly c940e13bbe9445ad5854896ee006d626fc078c8d [formerly c940e13bbe9445ad5854896ee006d626fc078c8d [formerly 9b9c97491c3ea70ba2b3d2eb58679db49d254d55]]]]
Former-commit-id: 1ca40dd66dfb7243396282077333bf63d7d49fda [formerly 1c068f260596602f128d51f5b940abb0b1f6db68] [formerly 81113aab759044e963fb7330a35aa9d287f4a1d6 [formerly bc400d7ee461fb301cb3039d7d23cc355000418e]]
Former-commit-id: 836071f09d91888525dd1f55b28ce5f1c2b198cf [formerly 8e7d0d9ba5459f18b59eda49168eb44ab1bf6785]
Former-commit-id: c1d21a6cd81c7cd9765d7ba38045c7ec84523d99
Former-commit-id: cd721feb2757c24876a9e9f73158e09ebb1f18c3

--- @client/third_party_auth.coffee
+++ @client/third_party_auth.coffee
@@ -5,11 +5,12 @@

     if provider == 'google'
       provider = 'google_oauth2'
-      vanity_url = location.host.split('.').length == 1
-      if !vanity_url
-        document.domain = location.host.replace(/^.*?([^.]+\.[^.]+)$/g,'$1')
-      else
-        document.domain = document.domain # make sure it is explitly set
+
+    vanity_url = location.host.split('.').length == 1
+    if !vanity_url
+      document.domain = location.host.replace(/^.*?([^.]+\.[^.]+)$/g,'$1')
+    else
+      document.domain = document.domain # make sure it is explitly set

     if provider == 'twitter'


Escape row values in constructed query string.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -81,7 +81,8 @@
   for child in $('#collection_form').children()
     if $(child).attr('id')
       column_names.push $(child).attr('id')
-      row_values.push $(child).val()
+      # wrap row values in single quotes and backslash-escape single-quotes
+      row_values.push "'#{$(child).val().replace(/'/g,"\\\'")}'"
   console.log "INSERT INTO #{collection} #{column_names.join(',')} VALUES #{row_values.join(',')}"
   clear_collection_form()
   $('#collection_form').after $('<div>').attr('class','alert alert-success').attr('id','submit_success').append('Submitted.')


Revert "Platform specified files extensions now stays in the file name"

This reverts commit e0d2abf67771e76dc2db93bf1bc7180a5fea328a.

--- cli/tasks/build/parse/index.coffee
+++ cli/tasks/build/parse/index.coffee
@@ -46,8 +46,10 @@
 		val = "`require('#{path}')`"
 		obj.path = undefined
 		if linkType
-			unless PLATFORM_TYPES[platform][linkType]
-				return false
+			if PLATFORM_TYPES[platform][linkType]
+				obj.name = name
+			else
+				return false;

 		obj.file = val
 		return true


Tweaking title position

Tweaking title position

--- src/game/stages/start_stage.coffee
+++ src/game/stages/start_stage.coffee
@@ -29,31 +29,36 @@
     @ctx.fillRect(0, @height / 2, @width, @height / 2)

     @ctx.fillStyle = 'red'
-    @ctx.fillText('Red Guard', beginW, beginH)
+    @drawText('Red Guard', beginW, beginH)

     @ctx.fillStyle = 'white'
-    @ctx.fillText('W', beginW, beginH + heightInc)
-    @ctx.fillText('S', beginW, beginH + heightInc * 2)
-    @ctx.fillText('D', beginW, beginH + heightInc * 3)
+    @drawText('W', beginW, beginH + heightInc)
+    @drawText('S', beginW, beginH + heightInc * 2)
+    @drawText('D', beginW, beginH + heightInc * 3)

-    @ctx.fillText('CONTROLS', middle, beginH - heightInc)
-    @ctx.fillText('- Move Up -', middle, beginH + heightInc)
-    @ctx.fillText('- Move Down - ', middle, beginH + heightInc * 2)
-    @ctx.fillText('- Fire -', middle, beginH + heightInc * 3)
+    @drawText('CONTROLS', middle, beginH - heightInc)
+    @drawText('- Move Up -', middle, beginH + heightInc)
+    @drawText('- Move Down - ', middle, beginH + heightInc * 2)
+    @drawText('- Fire -', middle, beginH + heightInc * 3)

     @ctx.fillStyle = 'cyan'
-    @ctx.fillText('Blue Guard', endWidth, beginH)
+    @drawText('Blue Guard', endWidth, beginH)

     @ctx.fillStyle = 'white'
-    @ctx.fillText('UP', endWidth, beginH + heightInc)
-    @ctx.fillText('DOWN', endWidth, beginH + heightInc * 2)
-    @ctx.fillText('LEFT', endWidth, beginH + heightInc * 3)
+    @drawText('UP', endWidth, beginH + heightInc)
+    @drawText('DOWN', endWidth, beginH + heightInc * 2)
+    @drawText('LEFT', endWidth, beginH + heightInc * 3)

     Effects.anaglyph(
-      @title, '72px Georgia', @ctx, @width / 2, @height / 4, 5)
+      @title, '72px Georgia', @ctx, @width / 2, @height / 5, 5)
     Effects.anaglyph(
-      @entryText, '36px Georgia', @ctx,  @width / 2, @height / 2 - heightInc, 3)
+      @entryText, '36px Georgia', @ctx,  @width / 2, @height * 2 / 5, 3)

     @ctx.restore()

+  drawText: (string, x, y) ->
+    @ctx.fillText(string, x, y)

+
+
+


Style fixes

Style fixes

--- src/dropzone.coffee
+++ src/dropzone.coffee
@@ -280,10 +280,12 @@
     # The dimensions are going to be used like this:
     #
     #     var info = @options.resize.call(this, file);
-    #     ctx.drawImage(img, info.srcX, info.srcY, info.srcWidth, info.srcHeight, info.trgX, info.trgY, info.trgWidth, info.trgHeight);
+    #     ctx.drawImage(img, info.srcX, info.srcY, info.srcWidth, info.srcHeight, info.trgX,
+    #       info.trgY, info.trgWidth, info.trgHeight);
     #
     #  srcX, srcy, trgX and trgY can be omitted (in which case 0 is assumed).
-    #  trgWidth and trgHeight can be omitted (in which case the options.thumbnailWidth / options.thumbnailHeight are used)
+    #  trgWidth and trgHeight can be omitted (in which case the options.thumbnailWidth /
+    #  options.thumbnailHeight are used)
     resize: (file) ->
       info =
         srcX: 0
@@ -360,11 +362,14 @@
         file.previewTemplate = file.previewElement # Backwards compatibility

         @previewsContainer.appendChild file.previewElement
-        node.textContent = file.name for node in file.previewElement.querySelectorAll("[data-dz-name]")
-        node.innerHTML = @filesize file.size for node in file.previewElement.querySelectorAll("[data-dz-size]")
+        node.textContent = file.name for node in \
+          file.previewElement.querySelectorAll("[data-dz-name]")
+        node.innerHTML = @filesize file.size for node in file.previewElement.querySelectorAll(
+          "[data-dz-size]")

         if @options.addRemoveLinks
-          file._removeLink = Dropzone.createElement """#{@options.dictRemoveFile}"""
+          file._removeLink = Dropzone.createElement \
+            """#{@options.dictRemoveFile}"""
           file.previewElement.appendChild file._removeLink

         removeFileEvent = (e) =>
@@ -378,7 +383,8 @@
             else
               @removeFile file

-        removeLink.addEventListener "click", removeFileEvent for removeLink in file.previewElement.querySelectorAll("[data-dz-remove]")
+        removeLink.addEventListener "click", removeFileEvent for removeLink in \
+          file.previewElement.querySelectorAll("[data-dz-remove]")

     # Called whenever a file is removed.
     removedfile: (file) ->
@@ -402,7 +408,8 @@
       if file.previewElement
         file.previewElement.classList.add "dz-error"
         message = message.error if typeof message != "String" and message.error
-        node.textContent = message for node in file.previewElement.querySelectorAll("[data-dz-errormessage]")
+        node.textContent = message for node in file.previewElement.querySelectorAll(
+          "[data-dz-errormessage]")

     errormultiple: noop

@@ -1289,7 +1296,8 @@
 Dropzone.isBrowserSupported = ->
   capableBrowser = yes

-  if window.File and window.FileReader and window.FileList and window.Blob and window.FormData and document.querySelector
+  if window.File and window.FileReader and window.FileList and window.Blob and window.FormData and \
+      document.querySelector
     unless "classList" of document.createElement "a"
       capableBrowser = no
     else
@@ -1326,7 +1334,8 @@
     element = document.querySelector el
   else if el.nodeType?
     element = el
-  throw new Error "Invalid `#{name}` option provided. Please provide a CSS selector or a plain HTML element." unless element?
+  throw new Error "Invalid `#{name}` option provided. Please provide a CSS selector or a plain " +
+    "HTML element." unless element?
   return element

 Dropzone.getElements = (els, name) ->
@@ -1342,7 +1351,8 @@
   else if els.nodeType?
     elements = [ els ]

-  throw new Error "Invalid `#{name}` option provided. Please provide a CSS selector, a plain HTML element or a list of those." unless elements? and elements.length
+  throw new Error "Invalid `#{name}` option provided. Please provide a CSS selector, a plain " +
+    "HTML element or a list of those." unless elements? and elements.length

   return elements

@@ -1369,7 +1379,8 @@
   for validType in acceptedFiles
     validType = validType.trim()
     if validType.charAt(0) == "."
-      return yes if file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) != -1
+      return yes if file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length -
+        validType.length) != -1
     else if /\/\*$/.test validType
       # This is something like a image/* mime type
       return yes if baseMimeType == validType.replace /\/.*$/, ""


:racehorse: Test for prefix without calling path.join

Directory::relativize is called many times by the fuzzy finder
and using path.join possibly multiple times per call was consuming
much of the time take to show the fuzzy finder view.

--- src/directory.coffee
+++ src/directory.coffee
@@ -73,11 +73,11 @@

     if fullPath is @getPath()
       ''
-    else if fullPath.indexOf(path.join(@getPath(), path.sep)) is 0
+    else if @isPathPrefixOf(@getPath(), fullPath)
       fullPath.substring(@getPath().length + 1)
     else if fullPath is @getRealPathSync()
       ''
-    else if fullPath.indexOf(path.join(@getRealPathSync(), path.sep)) is 0
+    else if @isPathPrefixOf(@getRealPathSync(), fullPath)
       fullPath.substring(@getRealPathSync().length + 1)
     else
       fullPath
@@ -139,3 +139,7 @@
     if @watchSubscription?
       @watchSubscription.close()
       @watchSubscription = null
+
+  # Private: Does given full path start with the given prefix?
+  isPathPrefixOf: (prefix, fullPath) ->
+    fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep


Modal

Modal

- make close button flat in CUI.__ng__

--- src/elements/Modal/Modal.coffee
+++ src/elements/Modal/Modal.coffee
@@ -22,6 +22,7 @@
 		@__addHeaderButton "cancel",
 			class: "ez5-modal-close-button"
 			icon:  "close"
+			appearance: if CUI.__ng__ then "flat" else "normal"
 			onClick: (ev) =>
 				@doCancel(ev)


Fix deltaX and deltaY initialization

Fix deltaX and deltaY initialization

--- src/fattable.coffee
+++ src/fattable.coffee
@@ -361,8 +361,6 @@
         supportedEvent = if document.createElement "div".onwheel? then "wheel" else if document.onmousewheel then "mousewheel" else "DOMMouseScroll"

         getDelta = (->
-                    deltaX = 0
-                    deltaY = 0
                     switch supportedEvent
                         when "wheel"
                             (evt)->
@@ -376,11 +374,15 @@
                                 [deltaX, deltaY]
                         when "mousewheel"
                             (evt)->
+                                deltaX = 0
+                                deltaY = 0
                                 deltaX = evt.wheelDeltaX ? 0
                                 deltaY = evt.wheelDeltaY ? evt.wheelDelta
                                 [deltaX, deltaY]
                         when "DOMMouseScroll"
                             (evt)->
+                                deltaX = 0
+                                deltaY = 0
                                 if evt.axis == evt.HORIZONTAL_AXI then deltaX = -50.0*evt.detail else deltaY = -50.0*evt.detail
                                 [deltaX, deltaY]
         )()