update schemas for rental products and variants

--- common/schemas.coffee
+++ common/schemas.coffee
@@ -36,10 +36,12 @@
   'coords.x':
     label: 'Longitude'
     type: Number
+    decimal: true
     optional: true
   'coords.y'
     label: 'Latitude'
     type: Number
+    decimal: true
     optional: true
   metafields:
     type: [ReactionCore.Schemas.Metafield]
@@ -63,7 +65,7 @@
     currentLocationn:
       type: ReactionCore.Schemas.Location
       optional: true
-    events:
+    events:  # A place to store rental and service history
       type: [Object]
       optional: true
     'events.$.startDate':
@@ -74,6 +76,19 @@
       type: String
     'events.$.location':
       type: ReactionCore.Schemas.Location
+      optional: true
+    # rentalPrice:
+    #   type: [ReactionCore.Schemas.PriceBucket]
+    pricePerDay:
+      label: 'Daily Rate'
+      type: Number
+      decimal: true
+      min: 0
+    pricePerWeek:
+      label: 'Weekly Rate'
+      type: Number
+      decimal: true
+      min: 0
       optional: true
   }
 ])


StreetView: now pointing in the correct direction

--- app/_src/components/office-street-view.coffee
+++ app/_src/components/office-street-view.coffee
@@ -12,6 +12,7 @@
 	LOADED_CLASS_TIMEOUT = 700

 	constructor: (@$context) ->
+		console.log("initialize map")
 		@context = @$context.get(0);
 		@init()

@@ -33,8 +34,8 @@
       disableDefaultUI: true,
       scrollwheel: false,
       pov: {
-        heading: 165,
-        pitch: 0
+        heading: 336.54,
+        pitch: 20
       },
       zoom: 1
     }


more space for days in network graph

more space for days in network graph

--- app/assets/javascripts/branch-graph.js.coffee
+++ app/assets/javascripts/branch-graph.js.coffee
@@ -62,16 +62,16 @@
     cuday = 0
     cumonth = ""

-    r.rect(0, 0, 26, @barHeight).attr fill: "#222"
-    r.rect(26, 0, 20, @barHeight).attr fill: "#444"
+    r.rect(0, 0, 30, @barHeight).attr fill: "#222"
+    r.rect(30, 0, 25, @barHeight).attr fill: "#444"

     for day, mm in @days
       if cuday isnt day[0]
         # Dates
-        r.text(36, @offsetY + @unitTime * mm, day[0])
+        r.text(40, @offsetY + @unitTime * mm, day[0])
           .attr(
             font: "12px Monaco, monospace"
-            fill: "#DDD"
+            fill: "#BBB"
           )
         cuday = day[0]


write instructions for using crop_suggest.js in a view

--- app/assets/javascripts/crop_suggest.js.coffee
+++ app/assets/javascripts/crop_suggest.js.coffee
@@ -1,3 +1,10 @@
+# Uses JQuery's autocomplete to suggest a crop name in lieu of a
+# preposterously long select dropdown. To implement add code to
+# the view like this:
+#
+# = text_field_tag(:crop, @resource.crop.nil? ? "" : @resource.crop.name, :class => 'crop-suggest')
+# = f.hidden_field(:crop_id)
+
 jQuery ->
   if el = $( '.crop-suggest' )
     el.autocomplete


keep the action menus for now

keep the action menus for now

--- app/assets/javascripts/droom/popups.js.coffee
+++ app/assets/javascripts/droom/popups.js.coffee
@@ -172,6 +172,49 @@

+  # The action menu is a simple popup submenu used to hide editing links.
+  #
+  $.fn.action_menu = ->
+    @each ->
+      new ActionMenu(@)
+    @
+
+  class ActionMenu
+    @menus: $()
+    @remember: (menu) ->
+      @menus.push(menu)
+    @hideAll: () ->
+      menu.hide() for menu in @menus
+
+    constructor: (element) ->
+      @_link = $(element)
+      @_selector = "[data-for=\"#{@_link.attr('data-menu')}\"]"
+      @_link.click @toggle
+      ActionMenu.remember(@)
+
+    place: =>
+      pos = @_link.position()
+      $(@_selector).css
+        top: pos.top + 20
+        left: pos.left
+
+    toggle: (e) =>
+      if e
+        e.preventDefault()
+        e.stopPropagation()
+      if @_link.hasClass('up') then @hide() else @show()
+
+    show: (e) =>
+      @place()
+      ActionMenu.hideAll()
+      @_link.addClass('up')
+      $(@_selector).first().stop().slideDown 'fast'
+      $(document).bind "click", @hide
+
+    hide: (e) =>
+      $(@_selector).first().stop().slideUp 'fast', () =>
+        @_link.removeClass('up')
+      $(document).unbind "click", @hide


Switch another test to simulateLoad/simulateSave

--- test/save-restore.coffee
+++ test/save-restore.coffee
@@ -7,7 +7,7 @@
 postUrls =
   meFi: # the blue
     newPost: 'https://www.metafilter.com/contribute/post_good.mefi?pid=205463'
-    postPreview: 'post_preview.mefi'
+    postPreview: '/post_preview.mefi'
     commentPreview: '/contribute/post_comment_preview.mefi#commentpreview'
   askMe:
     newPost: 'https://ask.metafilter.com/contribute/post.cfm'
@@ -142,12 +142,12 @@
   if /#/.test(location['pathname'])
     throw new Error("makeLocation: Get that hash out of the pathname")

-  location['hash']     ?= ''
+  location['hash'] ?= ''
   if !/(^$)|^#/.test(location['hash'])
     throw new Error("makeLocation: Hash needs to be empty string or start with #: " +
       location['hash'])

-  location['href']     ?= location.protocol + location.hostname + location.pathname
+  location['href'] ?= location.protocol + location.hostname + location.pathname
   return location

 exports['test sessionStorage mock'] = (test) ->
@@ -242,34 +242,28 @@
 exports['preview mefi fpp'] = (test) ->
   sessionStorage = makeSessionStorage()

-  # First page: Save the comment
-  sr = saveRestore.makeSaveRestore
-    sessionStorage: sessionStorage
-    location: makeLocation
-      pathname: "/contribute/post_good.mefi"
-  saveOptions =
-    mdCommentText: "A comment"
-    mdExtendedText: "More inside"
-    linkId: null # no linkId for an FPP
-    formSubmitUrl: "/post_preview.mefi"
-  sr.saveMarkdownForPreview saveOptions
+  mdCommentText  = "A comment"
+  mdExtendedText = "More inside"
+
+  simulateSave
+    sessionStorage: sessionStorage
+    site: 'meFi'
+    type: 'post'
+    mdCommentText: mdCommentText
+    mdExtendedText: mdExtendedText

   test.strictEqual(sessionStorage.length, 1, "Data saved to session storage")

-  # Second page: Load the comment
-  sr = saveRestore.makeSaveRestore
-    sessionStorage: sessionStorage
-    location: makeLocation
-      pathname: "/post_preview.mefi"
-
-  savedDataJson = sr.restoreMarkdown
-    linkId: saveOptions.linkId
-    formSubmitUrl: saveOptions.formSubmitUrl
-
-  test.strictEqual(saveOptions.mdCommentText, savedDataJson.comment, "comment was restored correctly")
-  test.strictEqual(saveOptions.mdExtendedText, savedDataJson.extended, "extended was restored correctly")
-
-  storageKey = sr.getSessionStorageKey saveOptions.linkId, saveOptions.formSubmitUrl
+  loadResults = simulateLoad
+    sessionStorage: sessionStorage
+    site: 'meFi'
+    type: 'post'
+  storageKey    = loadResults.storageKey
+  savedDataJson = loadResults.savedDataJson
+
+  test.strictEqual(mdCommentText,  savedDataJson.comment,  "comment was restored correctly")
+  test.strictEqual(mdExtendedText, savedDataJson.extended, "extended was restored correctly")
+
   keyValue = sessionStorage.getItem(storageKey)
   test.strictEqual(keyValue, null, "Data was removed from session storage")
   test.strictEqual(sessionStorage.length, 0, "Nothing left in session storage")
@@ -331,7 +325,6 @@
     site: 'fanFare'
     type: 'comment'
   savedDataJson = loadResults.savedDataJson
-  sr            = loadResults.sr
   storageKey    = loadResults.storageKey

   test.strictEqual(mdCommentText, savedDataJson.comment, "comment was restored correctly")


The .json part confuses nginx and seems redundant

--- app/assets/javascripts/rails-assets.js.coffee
+++ app/assets/javascripts/rails-assets.js.coffee
@@ -75,7 +75,7 @@
     $scope.error = null
     $scope.gem = null

-    $http.post("/components.json", component: $scope.component).success((data, xhr) ->
+    $http.post("/components", component: $scope.component).success((data, xhr) ->
       $scope.gem = data
       $scope.converting = false
     ).error (data, status) ->


reorganize a few pieces of Gradebook.coffee

This has no functional change. It simply groups a few related
items together, mostly related to the grid.

refs GRADE-377

test plan:
* ensure Jenkins passes

Change-Id: I654948c79e0a1a1b3f98196aaf0cd85107f993d6
Reviewed-on: https://gerrit.instructure.com/131004
Tested-by: Jenkins
Reviewed-by: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>
Reviewed-by: Derek Bender <d24d8a7ee520e34b1cec38800225d9f36b552df7@instructure.com>
Product-Review: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>
QA-Review: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>

--- app/coffeescripts/gradezilla/Gradebook.coffee
+++ app/coffeescripts/gradezilla/Gradebook.coffee
@@ -395,6 +395,20 @@
         @updateColumnHeaders()
         @renderFilters()

+    # called from app/jsx/bundles/gradezilla.js
+    onShow: ->
+      $(".post-grades-button-placeholder").show()
+      return if @startedInitializing
+      @startedInitializing = true
+
+      @spinner = new Spinner() unless @spinner
+      $(@spinner.spin().el).css(
+        opacity: 0.5
+        top: '55px'
+        left: '50%'
+      ).addClass('use-css-transitions-for-show-hide').appendTo('#main')
+      $('#gradebook-grid-wrapper').hide()
+
     reloadStudentData: =>
       @setStudentsLoaded(false)
       @setSubmissionsLoaded(false)
@@ -475,19 +489,6 @@
       filteredVisibility = assignment.assignment_visibility.filter (id) -> id != hiddenSub.user_id
       assignment.assignment_visibility = filteredVisibility

-    onShow: ->
-      $(".post-grades-button-placeholder").show()
-      return if @startedInitializing
-      @startedInitializing = true
-
-      @spinner = new Spinner() unless @spinner
-      $(@spinner.spin().el).css(
-        opacity: 0.5
-        top: '55px'
-        left: '50%'
-      ).addClass('use-css-transitions-for-show-hide').appendTo('#main')
-      $('#gradebook-grid-wrapper').hide()
-
     gotCustomColumns: (columns) =>
       @gradebookContent.customColumns = columns
       columns.forEach (column) =>
@@ -504,12 +505,6 @@
           studentIds.push(student.id)

       @invalidateRowsForStudentIds(_.uniq(studentIds))
-
-    doSlickgridStuff: =>
-      @initGrid()
-      @initHeader()
-      @gridReady.resolve()
-      @loadOverridesForSIS()

     gotAllAssignmentGroups: (assignmentGroups) =>
       # purposely passing the @options and assignmentGroups by reference so it can update
@@ -531,7 +526,8 @@
     gotChunkOfStudents: (students) =>
       @courseContent.assignmentStudentVisibility = {}
       for student in students
-        student.enrollments = _.filter student.enrollments, @isStudentEnrollment
+        student.enrollments = _.filter student.enrollments, (e) ->
+          e.type == "StudentEnrollment" || e.type == "StudentViewEnrollment"
         isStudentView = student.enrollments[0].type == "StudentViewEnrollment"
         student.sections = student.enrollments.map (e) -> e.course_section_id

@@ -553,8 +549,13 @@
       else
         @gradebookGrid.render()

-    isStudentEnrollment: (e) =>
-      e.type == "StudentEnrollment" || e.type == "StudentViewEnrollment"
+    ## Post-Data Load Initialization
+
+    doSlickgridStuff: =>
+      @initGrid()
+      @initHeader()
+      @gridReady.resolve()
+      @loadOverridesForSIS()

     setupGrading: (students) =>
       # set up a submission for each student even if we didn't receive one
@@ -1051,51 +1052,6 @@

       @gradebookGrid.gridSupport.state.blur()

-    onGridInit: () ->
-      tooltipTexts = {}
-      # TODO: this "if @spinner" crap is necessary because the outcome
-      # gradebook kicks off the gradebook (unnecessarily).  back when the
-      # gradebook was slow, this code worked, but now the spinner may never
-      # initialize.  fix the way outcome gradebook loads
-      $(@spinner.el).remove() if @spinner
-      $('#gradebook-grid-wrapper').show()
-      @uid = @gradebookGrid.grid.getUID()
-      $('#content').focus ->
-        $('#accessibility_warning').removeClass('screenreader-only')
-      $('#accessibility_warning').focus ->
-        $('#accessibility_warning').blur ->
-          $('#accessibility_warning').remove()
-      @$grid = grid = $('#gradebook_grid')
-        .fillWindowWithMe({
-          onResize: => @gradebookGrid.grid.resizeCanvas()
-        })
-        .delegate '.slick-cell',
-          'mouseenter.gradebook' : @highlightColumn
-          'mouseleave.gradebook' : @unhighlightColumns
-          'mouseenter' : (event) ->
-            grid.find('.hover, .focus').removeClass('hover focus')
-            $(this).addClass (if event.type == 'mouseenter' then 'hover' else 'focus')
-          'mouseleave' : (event) ->
-            $(this).removeClass('hover focus')
-
-      @$grid.addClass('editable') if @options.gradebook_is_editable
-
-      @fixMaxHeaderWidth()
-      @gradebookGrid.grid.onColumnsResized.subscribe (e, data) =>
-        @$grid.find('.slick-header-column').each (i, elem) =>
-          $columnHeader = $(elem)
-          columnDef = $columnHeader.data('column')
-          return unless columnDef.type is "assignment"
-          if $columnHeader.outerWidth() <= columnWidths.assignment.min
-            @minimizeColumn($columnHeader) unless columnDef.minimized
-          else if columnDef.minimized
-            @unminimizeColumn($columnHeader)
-
-      @keyboardNav.init()
-      keyBindings = @keyboardNav.keyBindings
-      @kbDialog = new KeyboardNavDialog().render(KeyboardNavTemplate({keyBindings}))
-      $(document).trigger('gridready')
-
     sectionList: () ->
       _.values(@sections).sort((a, b) => (a.id - b.id))

@@ -1703,6 +1659,53 @@
           @getHeaderComponentRef(location.columnId)?.focusAtStart()

       @onGridInit()
+
+    onGridInit: () ->
+      tooltipTexts = {}
+      # TODO: this "if @spinner" crap is necessary because the outcome
+      # gradebook kicks off the gradebook (unnecessarily).  back when the
+      # gradebook was slow, this code worked, but now the spinner may never
+      # initialize.  fix the way outcome gradebook loads
+      $(@spinner.el).remove() if @spinner
+      $('#gradebook-grid-wrapper').show()
+      @uid = @gradebookGrid.grid.getUID()
+
+      $('#content').focus ->
+        $('#accessibility_warning').removeClass('screenreader-only')
+      $('#accessibility_warning').focus ->
+        $('#accessibility_warning').blur ->
+          $('#accessibility_warning').remove()
+
+      @$grid = grid = $('#gradebook_grid')
+        .fillWindowWithMe({
+          onResize: => @gradebookGrid.grid.resizeCanvas()
+        })
+        .delegate '.slick-cell',
+          'mouseenter.gradebook' : @highlightColumn
+          'mouseleave.gradebook' : @unhighlightColumns
+          'mouseenter' : (event) ->
+            grid.find('.hover, .focus').removeClass('hover focus')
+            $(this).addClass (if event.type == 'mouseenter' then 'hover' else 'focus')
+          'mouseleave' : (event) ->
+            $(this).removeClass('hover focus')
+
+      @$grid.addClass('editable') if @options.gradebook_is_editable
+
+      @fixMaxHeaderWidth()
+      @gradebookGrid.grid.onColumnsResized.subscribe (e, data) =>
+        @$grid.find('.slick-header-column').each (i, elem) =>
+          $columnHeader = $(elem)
+          columnDef = $columnHeader.data('column')
+          return unless columnDef.type is "assignment"
+          if $columnHeader.outerWidth() <= columnWidths.assignment.min
+            @minimizeColumn($columnHeader) unless columnDef.minimized
+          else if columnDef.minimized
+            @unminimizeColumn($columnHeader)
+
+      @keyboardNav.init()
+      keyBindings = @keyboardNav.keyBindings
+      @kbDialog = new KeyboardNavDialog().render(KeyboardNavTemplate({keyBindings}))
+      $(document).trigger('gridready')

     # Grid Event Handlers


[➦] Merged changes.

[➦] Merged changes.

--- vendor/assets/javascripts/jquery/plugins/referencesify/referencesies.js.coffee
+++ vendor/assets/javascripts/jquery/plugins/referencesify/referencesies.js.coffee
@@ -16,50 +16,57 @@

     return

-  # === Events ===
+  activateReference: (reference) ->
+    reference = $ reference

-  onInitialize: (slick, foo) =>
-    references       = slick.$slides
-    currentReference = $ references[0]
+    responsive = reference.hasClass 'responsive'

-    isResponsive = currentReference.hasClass 'responsive'
-
-    if isResponsive
-      mobileView = currentReference.find '.mobile.view'
+    if responsive
+      mobileView = reference.find '.mobile.view'

       mobileView.velocity 'fadeIn', {
-        duration: 1000
+        duration: 750
       } if do mobileView.present

     return

-  onBeforeChange: (slick, index, next) ->
-    references       = slick.$slides
-    currentReference = $ references[index]
+  deactivateReference: (reference) ->
+    reference = $ reference

-    isResponsive = currentReference.hasClass 'responsive'
+    responsive = reference.hasClass 'responsive'

-    if isResponsive
-      mobileView = currentReference.find '.mobile.view'
+    if responsive
+      mobileView = reference.find '.mobile.view'

       mobileView.velocity 'fadeOut', {
-        duration: 500
+        duration: 150
       } if do mobileView.present

     return

-  onAfterChange: (slick, index) ->
+  # === Events ===
+
+  onInitialize: (slick) =>
+    references       = slick.$slides
+    currentReference = $ references[0]
+
+    @activateReference currentReference
+
+    return
+
+  onBeforeChange: (slick, index, next) =>
     references       = slick.$slides
     currentReference = $ references[index]

-    isResponsive = currentReference.hasClass 'responsive'
+    @deactivateReference currentReference

-    if isResponsive
-      mobileView = currentReference.find '.mobile.view'
+    return

-      mobileView.velocity 'fadeIn', {
-        duration: 1000
-      } if do mobileView.present
+  onAfterChange: (slick, index) =>
+    references       = slick.$slides
+    currentReference = $ references[index]
+
+    @activateReference currentReference

     return


Fixed use of new namespace for App.Utils.parseAddressListLocal().

--- app/assets/javascripts/app/lib/app_post/utils.coffee
+++ app/assets/javascripts/app/lib/app_post/utils.coffee
@@ -964,8 +964,8 @@
         senders = App.Utils.parseAddressListLocal(article.from)
         if senders
           for sender in senders
-            if sender && sender.address && sender.address.match('@')
-              senderIsLocal = isLocalAddress(sender.address)
+            if sender && sender.match('@')
+              senderIsLocal = isLocalAddress(sender)

       # check if article recipient is local
       recipientIsLocal = false


[#81763904] remove unnecessary inline edit helper

--- app/assets/javascripts/helpers/inline_edit.js.coffee
+++ app/assets/javascripts/helpers/inline_edit.js.coffee
@@ -1,14 +0,0 @@
-# Idea inspired by: http://discuss.emberjs.com/t/dynamically-render-polymorphic-component/3184/6
-
-Ember.Handlebars.registerBoundHelper 'inlineEdit', (record, allUsers, participants, options) ->
-  componentName = "inline-edit-#{record.type}"
-  # The following cannot be passed in as options because they are simply stored
-  # on the options hash as strings, not objects. So we pass them in as
-  # arguments and then set them on options.hash.
-  options.hash.bodyPart = record
-  options.hash.allUsers = allUsers
-  options.hash.overlayParticipants = participants
-
-  helper = Em.Handlebars.resolveHelper(options.data.view.container, componentName)
-
-  helper.call(this, options)


trigger resize event for THREE.TrackballControls; fix #309

--- app/assets/javascripts/oxalis/view/plane_view.coffee
+++ app/assets/javascripts/oxalis/view/plane_view.coffee
@@ -169,7 +169,7 @@

   resize : ->

-    #Call this after the canvas was resized to fix the viewport
+    # Call this after the canvas was resized to fix the viewport
     canvas = $("#render-canvas")
     WIDTH = (canvas.width()-20)/2
     HEIGHT = (canvas.height()-20)/2
@@ -181,6 +181,11 @@
     @draw()

+    # notify THREE.TrackballControls
+    TDView = $("#TDView")
+    TDView.trigger($.Event('resizeCanvas'));
+
+
   scaleTrianglesPlane : (scale) =>

     @scaleFactor = scale


expose default all tabs

expose default all tabs

--- app/assets/javascripts/uploadcare/settings.js.coffee
+++ app/assets/javascripts/uploadcare/settings.js.coffee
@@ -142,7 +142,9 @@

   # Defaults (not normalized)
-  publicDefaults = {}
+  publicDefaults = {
+    allTabs: presets.tabs.all
+  }
   for own key, value of defaults
     publicDefaults[$.camelCase(key)] = value
   expose 'defaults', publicDefaults


Use the slack way of sending message

Use the slack way of sending message

--- scripts/magnum-ci.coffee
+++ scripts/magnum-ci.coffee
@@ -32,7 +32,7 @@
       user.room = payload.room if payload.room
       user.type = payload.type if payload.type

-      robot.send "#{payload.title}"
+      robot.messageRoom '#tool-mobile', "#{payload.title}"

     catch error
       console.log "travis hook error: #{error}. Payload: #{req.body.payload}"


remove fdescribe

remove fdescribe

--- spec/app/language-mode-spec.coffee
+++ spec/app/language-mode-spec.coffee
@@ -2,7 +2,7 @@
 Buffer = require 'text-buffer'
 EditSession = require 'edit-session'

-fdescribe "LanguageMode", ->
+describe "LanguageMode", ->
   [editSession, buffer, languageMode] = []

   afterEach ->


Explicitly set win32 installer description

--- tasks/pack.coffee
+++ tasks/pack.coffee
@@ -341,6 +341,7 @@
           signWithParams: signParams.join ' '
           setupIcon: './build/resources/win/setup.ico'
           iconUrl: mainManifest.icon.url
+          description: manifest.productName
           remoteReleases: remoteReleasesUrl
           copyright: manifest.copyright
           setupExe: manifest.name + '-' + manifest.version + '-win32-setup.exe'


Added support for json directory of available timbits

--- src/timbits.coffee
+++ src/timbits.coffee
@@ -53,6 +53,11 @@
 	@server.all '/', (req, res) ->
 		res.redirect '/timbits/help'

+	# route json page
+
+	@server.get '/timbits/json', (req, res) =>
+		res.json @box
+
 	# route help page
 	@server.all '/timbits/help', (req, res) =>
 		res.send ck.render(views.help, {box: @box} )
@@ -121,6 +126,8 @@
 	timbit.name = name
 	timbit.viewBase ?= name
 	timbit.maxAge ?= config.maxAge
+	timbit.listviews (views) =>
+		timbit.views = views
 	@box[name] = timbit

 	# configure help


More-consistent code layout

More-consistent code layout

--- spec/text-manipulation-spec.coffee
+++ spec/text-manipulation-spec.coffee
@@ -9,9 +9,11 @@
     it 'strips leading whitespace', ->
     expect(TextManipulation.stripLeadingWhitespace (leadingWhitespace+sourceFragment+trailingWhitespace))
       .toBe(sourceFragment+trailingWhitespace)
+
     it 'is identity when there is no leading whitespace', ->
     expect(TextManipulation.stripLeadingWhitespace (sourceFragment+trailingWhitespace))
       .toBe(sourceFragment+trailingWhitespace)
+
     it 'handles whitespace-only string', ->
     expect(TextManipulation.stripLeadingWhitespace (leadingWhitespace+trailingWhitespace))
       .toBe('')
@@ -20,9 +22,11 @@
     it 'strips trailing whitespace', ->
     expect(TextManipulation.stripTrailingWhitespace (leadingWhitespace+sourceFragment+trailingWhitespace))
       .toBe(leadingWhitespace+sourceFragment)
+
     it 'is identity when there is no trailing whitespace', ->
     expect(TextManipulation.stripTrailingWhitespace (leadingWhitespace+sourceFragment))
       .toBe(leadingWhitespace+sourceFragment)
+
     it 'handles whitespace-only string', ->
     expect(TextManipulation.stripTrailingWhitespace (leadingWhitespace+trailingWhitespace))
       .toBe('')
@@ -33,15 +37,19 @@
     it 'handles index at end of nested list', ->
       expect(TextManipulation.findMatchingOpeningBracket bufferText, [], 11, false)
        .toEqual({bracketIx: 0, ranges: [ [1,3] ]})
+
     it 'handles index at end of nested list contents', ->
       expect(TextManipulation.findMatchingOpeningBracket bufferText, [], 10, false)
        .toEqual({bracketIx: 3, ranges: [ [4,8] ]})
+
     it 'handles index at start of nested nested list contents', ->
       expect(TextManipulation.findMatchingOpeningBracket bufferText, [], 9, false)
        .toEqual({bracketIx: 8, ranges: [  ]})
+
     it 'handles index at start of nested nested list', ->
       expect(TextManipulation.findMatchingOpeningBracket bufferText, [], 8, false)
        .toEqual({bracketIx: 3, ranges: [ [4,8] ]})
+
     it 'handles index at start of list contents', ->
       expect(TextManipulation.findMatchingOpeningBracket bufferText, [], 1, false)
        .toEqual({bracketIx: 0, ranges: [  ]})
@@ -51,18 +59,23 @@
     it 'handles ignoredRange preceding nested list', ->
       expect(TextManipulation.findMatchingOpeningBracket nestedListText, [[2,3]], 11, false)
        .toEqual({bracketIx: 0, ranges: [ [1, 2], [3, 4], [9, 11] ]})
+
     it 'handles ignoredRange immediately preceding nested list', ->
       expect(TextManipulation.findMatchingOpeningBracket nestedListText, [[2,4]], 11, false)
        .toEqual({bracketIx: 0, ranges: [ [1, 2], [9, 11] ]})
+
     it 'handles ignoredRange spanning start of nested list', ->
       expect(TextManipulation.findMatchingOpeningBracket nestedListText, [[2,5]], 11, false)
        .toEqual(null)
+
     it 'handles ignoredRange following nested list', ->
       expect(TextManipulation.findMatchingOpeningBracket nestedListText, [[10, 11]], 11, false)
        .toEqual({bracketIx: 0, ranges: [ [1, 4], [9, 10] ]})
+
     it 'handles ignoredRange immediately following nested list', ->
       expect(TextManipulation.findMatchingOpeningBracket nestedListText, [[9,11]], 11, false)
        .toEqual({bracketIx: 0, ranges: [ [1, 4] ]})
+
     it 'handles index inside ignoredRange', ->
       expect(TextManipulation.findMatchingOpeningBracket nestedListText, [[3, 11]], 10, false)
        .toEqual({bracketIx: 0, ranges: [ [1, 3] ]})
@@ -73,15 +86,19 @@
     it 'handles index at start of list contents', ->
       expect(TextManipulation.findMatchingClosingBracket bufferText, [], 1, false)
        .toEqual({bracketIx: 13, ranges: [ [1,3], [11,13] ]})
+
     it 'handles index at start of nested list', ->
       expect(TextManipulation.findMatchingClosingBracket bufferText, [], 3, false)
        .toEqual({bracketIx: 13, ranges: [ [11,13] ]})
+
     it 'handles index at start of nested list contents', ->
       expect(TextManipulation.findMatchingClosingBracket bufferText, [], 4, false)
        .toEqual({bracketIx: 10, ranges: [ [6,10] ]})
+
     it 'handles index at end of nested list', ->
       expect(TextManipulation.findMatchingClosingBracket bufferText, [], 11, false)
        .toEqual({bracketIx: 13, ranges: [ [11,13] ]})
+
     it 'handles index at start of list', ->
       expect(TextManipulation.findMatchingClosingBracket bufferText, [], 0, false)
        .toEqual(null)
@@ -91,18 +108,23 @@
     it 'handles ignoredRange following nested list', ->
       expect(TextManipulation.findMatchingClosingBracket nestedListText, [[9, 11]], 2, false)
        .toEqual({bracketIx: 12, ranges: [ [2, 4], [11, 12] ]})
+
     it 'handles ignoredRange immediately following nested list', ->
       expect(TextManipulation.findMatchingClosingBracket nestedListText, [[10, 11]], 2, false)
        .toEqual({bracketIx: 12, ranges: [ [2, 4], [9, 10], [11, 12] ]})
+
     it 'handles ignoredRange spanning end of nested list', ->
       expect(TextManipulation.findMatchingClosingBracket nestedListText, [[8, 11]], 2, false)
        .toEqual(null)
+
     it 'handles ignoredRange preceding nested list', ->
       expect(TextManipulation.findMatchingClosingBracket nestedListText, [[2, 4]], 2, false)
        .toEqual({bracketIx: 12, ranges: [ [9, 12] ]})
+
     it 'handles ignoredRange immediately preceding nested list', ->
       expect(TextManipulation.findMatchingClosingBracket nestedListText, [[2, 3]], 2, false)
        .toEqual({bracketIx: 12, ranges: [ [3, 4], [9, 12] ]})
+
     it 'handles index inside ignoredRange', ->
       expect(TextManipulation.findMatchingClosingBracket nestedListText, [[2, 10]], 3, false)
        .toEqual({bracketIx: 12, ranges: [ [10, 12] ]})
@@ -113,9 +135,11 @@
     it 'works when surrounding a nested list', ->
       expect(TextManipulation.getEnclosingList bufferText, [], 3, 8)
         .toEqual({listRange: [1, 10], nonNestedRanges: [[1, 3], [8, 10]]})
+
     it 'works when inside a nested list', ->
       expect(TextManipulation.getEnclosingList bufferText, [], 5, 5)
         .toEqual({listRange: [4, 7], nonNestedRanges: [[4, 5], [5, 7]]})
+
     it 'fails on bracket mismatch', ->
       expect(TextManipulation.getEnclosingList '(  ]', 2, 2)
         .toEqual(null)
@@ -127,9 +151,11 @@
     it 'handles range inside one element', ->
       expect(TextManipulation.getListContainingRange bufferText, [], [1, 1])
         .toEqual({listRange: [1, 46], nonNestedRanges : [[1, 7], [12, 19], [39, 46]]})
+
     it 'handles range starting inside one element and ending inside another element', ->
       expect(TextManipulation.getListContainingRange bufferText, [], [1, 15])
         .toEqual({listRange: [1, 46], nonNestedRanges : [[1, 7], [12, 19], [39, 46]]})
+
     it 'handles range starting in one nested list and ending in a nested list inside another nested list', ->
       expect(TextManipulation.getListContainingRange bufferText, [], [8, 25])
         .toEqual({listRange: [1, 46], nonNestedRanges : [[1, 7], [12, 19], [39, 46]]})
@@ -237,12 +263,15 @@
     it 'handles index after ignore', ->
       expect(TextManipulation.backwardSkipIgnored ignoreRanges, 7)
         .toEqual(7)
+
     it 'handles index immediately after ignore', ->
       expect(TextManipulation.backwardSkipIgnored ignoreRanges, 6)
         .toEqual(4)
+
     it 'handles index inside ignore', ->
       expect(TextManipulation.backwardSkipIgnored ignoreRanges, 5)
         .toEqual(4)
+
     it 'handles index at start of ignore', ->
       expect(TextManipulation.backwardSkipIgnored ignoreRanges, 4)
         .toEqual(4)
@@ -253,12 +282,15 @@
     it 'handles index before ignore', ->
       expect(TextManipulation.forwardSkipIgnored ignoreRanges, 3)
         .toEqual(3)
+
     it 'handles index at start of ignore', ->
       expect(TextManipulation.forwardSkipIgnored ignoreRanges, 4)
         .toEqual(6)
+
     it 'handles index inside ignore', ->
       expect(TextManipulation.forwardSkipIgnored ignoreRanges, 5)
         .toEqual(6)
+
     it 'handles index immediately after ignore', ->
       expect(TextManipulation.forwardSkipIgnored ignoreRanges, 6)
         .toEqual(6)


move section to key bind consistency is obvious(for maintainability)

--- keymaps/vim-mode-plus.cson
+++ keymaps/vim-mode-plus.cson
@@ -283,6 +283,10 @@
   'ctrl-cmd-c': 'vim-mode-plus:change-occurrence'
   'cmd-d': 'vim-mode-plus:select-occurrence'

+'.platform-darwin atom-text-editor.vim-mode-plus-search':
+  'ctrl-cmd-c': 'vim-mode-plus:change-occurrence-from-search'
+  'cmd-d': 'vim-mode-plus:select-occurrence-from-search'
+
 # normal
 # -------------------------
 'atom-text-editor.vim-mode-plus.normal-mode':
@@ -478,10 +482,6 @@
   'shift-tab': 'vim-mode-plus:search-visit-prev'
   'tab': 'vim-mode-plus:search-visit-next'

-'.platform-darwin atom-text-editor.vim-mode-plus-search':
-  'ctrl-cmd-c': 'vim-mode-plus:change-occurrence-from-search'
-  'cmd-d': 'vim-mode-plus:select-occurrence-from-search'
-
 # If you want to map normal char to special operation.
 # Use `not(.literal-mode)` selector like below.
 # When you hit `ctrl-v`, it activate literal-mode.


Animation cleanup

Animation cleanup

--- src/animation.coffee
+++ src/animation.coffee
@@ -232,10 +232,7 @@

 		@_canceled = true

-		# @view.style["-webkit-animation-play-state"] = "paused"
-
-		@_cleanup false
-
+		# @view.style["-webkit-animation-play-state"] = "paused"
 		# @view.style["-webkit-animation-play-state"] = "running"

@@ -245,8 +242,6 @@
 			return

 		console.log "Animation[#{@animationId}].end #{@animationName}" if @debug
-
-		@_cleanup true

 		callback?()


Fixed paths

Fixed paths

--- src/animation.coffee
+++ src/animation.coffee
@@ -1,12 +1,12 @@
 _ = require "underscore"
-utils = require "../utils"
-css = require "../css"
+utils = require "./utils"
+css = require "./css"

 {EventEmitter} = require "./eventemitter"
-{Matrix} = require "../primitives/matrix"
-
-spring = require "../curves/spring"
-bezier = require "../curves/bezier"
+{Matrix} = require "./primitives/matrix"
+
+spring = require "./curves/spring"
+bezier = require "./curves/bezier"

 AnimationCounter = 0

@@ -344,4 +344,4 @@

-exports.Animation = Animation+exports.Animation = Animation


Add confirm prompt and update installing

--- src/upgrade.coffee
+++ src/upgrade.coffee
@@ -3,12 +3,14 @@
 _ = require 'underscore-plus'
 async = require 'async'
 optimist = require 'optimist'
+read = require 'read'
 request = require 'request'
 semver = require 'semver'

 Command = require './command'
 config = require './config'
 fs = require './fs'
+Install = require './install'
 tree = require './tree'

 module.exports =
@@ -23,27 +25,30 @@
     options = optimist(argv)
     options.usage """

-      Usage: apm upgrade [<package_name>...]
+      Usage: apm upgrade

-      Upgrade packages installed to ~/.atom/packages.
+      Upgrade out of date packages installed to ~/.atom/packages

-      All packages are upgraded if no package names are passed as arguments.
+      This command lists the out of date packages and then prompts to install
+      available updates.
     """
+    options.alias('c', 'confirm').boolean('confirm').default('confirm', true).describe('confirm', 'Confirm before installing updates')
     options.alias('h', 'help').describe('help', 'Print this usage message')
-    options.alias('c', 'confirm').boolean('confirm').default('confirm', true).describe('confirm', 'Confirm before install updates')
-    options.alias('l', 'list').boolean('list').describe('list', 'List the packages that have updates available')
-    options.alias('q', 'quiet').boolean('quiet').describe('quiet', 'Set the npm log level to warn')
+    options.alias('l', 'list').boolean('list').describe('list', 'List but don\'t install the outdated packages')

   getInstalledPackages: ->
     packages = []
-    for child in fs.list(@atomPackagesDirectory)
-      continue if fs.isSymbolicLinkSync(path.join(@atomPackagesDirectory, child))
-      try
-        metadata = JSON.parse(fs.readFileSync(path.join(@atomPackagesDirectory, child, 'package.json')))
-        packages.push(metadata)
-    packages = packages.filter ({name, version}={}) ->
-      name and semver.valid(version)
+    for name in fs.list(@atomPackagesDirectory)
+      if pack = @getIntalledPackage(name)
+        packages.push(pack)
     packages
+
+  getIntalledPackage: (name) ->
+    packageDirectory = path.join(@atomPackagesDirectory, name)
+    return if fs.isSymbolicLinkSync(packageDirectory)
+    try
+      metadata = JSON.parse(fs.readFileSync(path.join(packageDirectory, 'package.json')))
+      return metadata if metadata?.name and metadata?.version

   getInstalledAtomVersion: ->
     try
@@ -80,6 +85,33 @@
         else
           callback()

+  getAvailableUpdates: (packages, callback) ->
+    async.map packages, @getLatestVersion.bind(this), (error, updates) =>
+      return callback(error) if error?
+
+      updates = _.compact(updates)
+      updates.sort (updateA, updateB) ->
+        updateA.pack.name.localeCompare(updateB.pack.name)
+
+      callback(null, updates)
+
+  promptForConfirmation: (callback) ->
+    read {prompt: 'Would you like to install these updates? (yes)', edit: true}, (error, answer) ->
+      answer = if answer then answer.trim().toLowerCase() else 'yes'
+      callback(error, answer is 'y' or answer is 'yes')
+
+  installUpdates: (updates, callback) ->
+    installCommands = []
+    for {pack, latestVersion} in updates
+      do (pack, latestVersion) ->
+        installCommands.push (callback) ->
+          options =
+            callback: callback
+            commandArgs: ["#{pack.name}@#{latestVersion}"]
+          new Install().run(options)
+
+    async.waterfall(installCommands, callback)
+
   run: (options) ->
     {callback} = options
     options = @parseOptions(options.commandArgs)
@@ -87,15 +119,26 @@
     unless @getInstalledAtomVersion()
       return callback('Could not determine current Atom version installed')

-    if options.argv.list
-      packages = @getInstalledPackages()
-      async.map packages, @getLatestVersion.bind(this), (error, updates) ->
-        return callback(error) if error?
+    packages = @getInstalledPackages()
+    @getAvailableUpdates packages, (error, updates) =>
+      return callback(error) if error?

-        updates = _.compact(updates)
-        updates.sort (updateA, updateB) ->
-          updateA.name.localeCompare(updateB.name)
+      console.log "Package Updates Available".cyan + " (#{updates.length})"
+      tree updates, ({pack, latestVersion}) ->
+        "#{pack.name.yellow} #{pack.version.red} -> #{latestVersion.green}"

-        console.log "Package Updates Available".cyan + " (#{updates.length})"
-        tree updates, ({pack, latestVersion}) ->
-          "#{pack.name.yellow} #{pack.version.red} -> #{latestVersion.green}"
+      return callback() if options.argv.list
+      return callback() if updates.length is 0
+
+      console.log()
+      if options.argv.confirm
+        @promptForConfirmation (error, confirmed) =>
+          return callback(error) if error?
+
+          if confirmed
+            console.log()
+            @installUpdates(updates, callback)
+          else
+            callback()
+      else
+        @installUpdates(updates, callback)


Only create span class once

Only create span class once

Previously this was being generated on each match even though
the same classes were used for every match.

--- src/app/token.coffee
+++ src/app/token.coffee
@@ -71,30 +71,29 @@
       .replace(/</g, '<')
       .replace(/>/g, '>')

+    classes = []
+    classes.push('indent-guide') if hasIndentGuide
     if @isHardTab
+      classes.push('invisible') if invisibles.tab
+      classes.push('hard-tab')
+      classes = classes.join(' ')
       html = html.replace /^./, (match) ->
-        classes = []
-        classes.push('invisible') if invisibles.tab
-        classes.push('indent-guide') if hasIndentGuide
-        classes.push('hard-tab')
         match = invisibles.tab ? match
-        "#{match}"
+        "#{match}"
     else
       if hasLeadingWhitespace
+        classes.push('invisible') if invisibles.space
+        classes.push('leading-whitespace')
+        classes = classes.join(' ')
         html = html.replace /^[ ]+/, (match) ->
-          classes = []
-          classes.push('invisible') if invisibles.space
-          classes.push('indent-guide') if hasIndentGuide
-          classes.push('leading-whitespace')
           match = match.replace(/./g, invisibles.space) if invisibles.space
-          "#{match}"
+          "#{match}"
       if hasTrailingWhitespace
+        classes.push('invisible') if invisibles.space
+        classes.push('trailing-whitespace')
+        classes = classes.join(' ')
         html = html.replace /[ ]+$/, (match) ->
-          classes = []
-          classes.push('invisible') if invisibles.space
-          classes.push('indent-guide') if hasIndentGuide
-          classes.push('trailing-whitespace')
           match = match.replace(/./g, invisibles.space) if invisibles.space
-          "#{match}"
+          "#{match}"

     html


sml fx

sml fx

--- run.coffee
+++ run.coffee
@@ -23,6 +23,7 @@
   when "-v"
     p magenta "Input:", cyan process.argv[2]
     await (require ".").go process.argv[2], {}, defer err, res
+    throw err if err
     p yellow "Output:", green res

   when "--vv"


Fixed download stats calculations to make it converge to 100

--- server/lib/download.coffee
+++ server/lib/download.coffee
@@ -10,9 +10,9 @@
 STATS_MULTIPLIER = 1 - 1.0/100

 addStats = (type) ->
-    statistics[type] += 1
     for t of statistics
         statistics[t] *= STATS_MULTIPLIER
+    statistics[type] += 1

 export getStats = () ->
     return statistics


activity: Fixed typo.

activity: Fixed typo.

--- client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
+++ client/app/Applications/Activity.kdapplication/ListItems/ActivityListItemCodeSnippet.coffee
@@ -127,7 +127,7 @@
           file.save content, (err) ->

             notificationTitle = new KDView
-              partial : "Your file saved."
+              partial : "Your file is saved."

             notificationTitle.addSubView link = new KDCustomHTMLView
               tagName  : "a"


send user to `/datasets` on successful login

--- server/code/index.coffee
+++ server/code/index.coffee
@@ -315,7 +315,7 @@

 login = (req, resp) ->
   passport.authenticate("local",
-    successRedirect: "/"
+    successRedirect: "/datasets"
     failureRedirect: "/login"
     failureFlash: true
   )(req,resp)


Updated client assets

Updated client assets

--- gulp/clientAssets.coffee
+++ gulp/clientAssets.coffee
@@ -7,7 +7,7 @@
 # Copyright © 2015 Tranzit Development Team

 module.exports = (gulp, config) ->
-  gulp.task 'client-assets', ->
+  gulp.task 'client:assets', ->
     path = require 'path'
     imagemin = require 'gulp-imagemin'
     pngquant = require 'imagemin-pngquant'


Port 35729 is already in use by another process

https://github.com/toddmotto/fireshell/issues/39

--- gulp/tasks/server.coffee
+++ gulp/tasks/server.coffee
@@ -14,4 +14,4 @@
    gulp.src config.publicPath
     .pipe plugins.webserver
       port: 3000
-      livereload: true +      livereload: 22345


Added check for valid mywot API key

Added check for valid mywot API key

--- src/scripts/mywot.coffee
+++ src/scripts/mywot.coffee
@@ -55,28 +55,34 @@

 module.exports = (robot) ->
   robot.respond /mywot (.*)/i, (msg) ->
-    mywot_term = msg.match[1].toLowerCase()

-    robot.http(mywot_url + "hosts=#{mywot_term}/")
-      .get() (err, res, body) ->
+    if MYWOT_API_KEY?

-        mywot_json = JSON.parse body
+      mywot_term = msg.match[1].toLowerCase()

-        if mywot_json[mywot_term][0] == undefined
-          mywot_profile = "No MyWOT information found for #{mywot_term}"
+      robot.http(mywot_url + "hosts=#{mywot_term}/")
+        .get() (err, res, body) ->

-        else
-          mywot_trustworthiness = mywot_json[mywot_term][0]
-          mywot_childfriendliness = mywot_json[mywot_term][4]
-          mywot_categories = mywot_json[mywot_term]["categories"]
+          mywot_json = JSON.parse body

-          mywot_profile = """MyWot Result for #{mywot_term}
-          ---------------------------
-          - Trustworthiness: #{reputation(mywot_trustworthiness[0])} (Confidence: #{mywot_trustworthiness[1]}%)
-          - Child Safety:    #{reputation(mywot_childfriendliness[0])} (Confidence: #{mywot_childfriendliness[1]}%)
-          - Categories: """
+          if mywot_json[mywot_term][0] == undefined
+            mywot_profile = "No MyWOT information found for #{mywot_term}"

-          for key, value of mywot_categories
-            mywot_profile += "\n  - #{mywot_category_mapping[key]} (Confidence: #{value}%)"
+          else
+            mywot_trustworthiness = mywot_json[mywot_term][0]
+            mywot_childfriendliness = mywot_json[mywot_term][4]
+            mywot_categories = mywot_json[mywot_term]["categories"]

-        msg.send mywot_profile
+            mywot_profile = """MyWot Result for #{mywot_term}
+            ---------------------------
+            - Trustworthiness: #{reputation(mywot_trustworthiness[0])} (Confidence: #{mywot_trustworthiness[1]}%)
+            - Child Safety:    #{reputation(mywot_childfriendliness[0])} (Confidence: #{mywot_childfriendliness[1]}%)
+            - Categories: """
+
+            for key, value of mywot_categories
+              mywot_profile += "\n  - #{mywot_category_mapping[key]} (Confidence: #{value}%)"
+
+          msg.send mywot_profile
+
+    else
+      msg.send "MyWoT API key not configured. Get one at http://www.mywot.com/"


Stop exporting utils.getProps.

Stop exporting utils.getProps.

--- src/scripts/utils.coffee
+++ src/scripts/utils.coffee
@@ -148,7 +148,6 @@
   array: array
   createFormatMapper: createFormatMapper
   createPropertyMapper: createPropertyMapper
-  getProps: getProps
   getUserId: (userId) ->
     getUserId(userId).cuid || userId
   getUserProps: (user) ->


Middleware was backwards

Middleware was backwards

Action before authentication lol...

--- src/server/server.coffee
+++ src/server/server.coffee
@@ -46,7 +46,7 @@

 routes.forEach (rt) ->
 	auth = passport.authenticate "bearer", { session: false }
-	middleware = if !rt.auth then rt.fn else [ rt.fn, auth ]
+	middleware = if !rt.auth then rt.fn else [ auth, rt.fn ]
 	app[rt.verb] "/api#{rt.path}", middleware

 app.all "*", (req, res) ->


* [bug] the hasPerm should use the match function instead minimatch

--- src/mixins/roles.coffee
+++ src/mixins/roles.coffee
@@ -74,7 +74,7 @@
     #   Promise.resolve(true)
     # else
     if isObject @[permsFieldName]
-      Promise.resolve minimatch aPermName, @[permsFieldName]
+      Promise.resolve match aPermName, @[permsFieldName]
     else
       Promise.resolve(false)
     # return Promise.resolve(true) if @id is adminRole


Add many permission for the home

Add many permission for the home

--- server/lib/token.coffee
+++ server/lib/token.coffee
@@ -117,6 +117,12 @@
         "notification": "authorized"
         "photo": "authorized"
         "file": "authorized"
+        "background": "authorized"
+        "folder": "authorized"
+        "contact": "authorized"
+        "album": "authorized"
+        "event": "authorized"
+        "message": "authorized"
         "binary": "authorized"
         "user": "authorized"
         "device": "authorized"


Add backwards compatibility check for Stripe sub search

--- server/lib/utils.coffee
+++ server/lib/utils.coffee
@@ -38,6 +38,12 @@
           if userID? and sub.metadata?.id is userID
             unless subscription?.cancel_at_period_end is false
               subscription = sub
+
+          # Check for backwards compatible basic subscription search
+          # Only recipient subscriptions are currently searched for via userID
+          if userID? and not sub.metadata?.id and sub.plan?.id is 'basic'
+            subscription ?= sub
+
           return done(subscription) if subscription?.cancel_at_period_end is false

         if subscriptions.has_more


Warn if Meteor.settings fails.

Warn if Meteor.settings fails.

[#51845303]

--- server/spendflow.coffee
+++ server/spendflow.coffee
@@ -1,3 +1,6 @@
 @spendflowAutomaticNotes = "Managed automatically"

 Accounts.emailTemplates.from = "Kevin at Spendflow <kevin@spendflow.co>"
+
+if not Meteor.settings?.public?.analytics_api_key?
+  console.log "Oops, looks like the Segment.io API key is missing! Analytics won't work until you fix that."


Preprocessor improvements.

Preprocessor improvements.

* Added some better errors.
* Moved some functions from context to the preprocessor, makes calling them a bit easier.

--- src/preprocessor.coffee
+++ src/preprocessor.coffee
@@ -22,41 +22,54 @@
     # `indents` is an array of successive indentation characters.
     @indents = []
     @context = []
-    @context.peek = -> if @length then this[@length - 1] else null
-    @context.err = (c) ->
-      throw new Error "Unexpected " + inspect c
-    @context.observe = (c) ->
-      top = @peek()
+    @ss = new StringScanner ''
+
+  err: (c) ->
+    token =
       switch c
-        # opening token is closing token
-        when '"""', '\'\'\'', '"', '\'', '###', '`', '///', '/'
-          if top is c then do @pop
-          else @push c
-        # strictly opening tokens
-        when INDENT, '#', '#{', '[', '(', '{', '\\', 'regexp-[', 'regexp-(', 'regexp-{', 'heregexp-#', 'heregexp-[', 'heregexp-(', 'heregexp-{'
-          @push c
-        # strictly closing tokens
+        when INDENT
+          'INDENT'
         when DEDENT
-          (@err c) unless top is INDENT
-          do @pop
-        when '\n'
-          (@err c) unless top in ['#', 'heregexp-#']
-          do @pop
-        when ']'
-          (@err c) unless top in ['[', 'regexp-[', 'heregexp-[']
-          do @pop
-        when ')'
-          (@err c) unless top in ['(', 'regexp-(', 'heregexp-(']
-          do @pop
-        when '}'
-          (@err c) unless top in ['#{', '{', 'regexp-{', 'heregexp-{']
-          do @pop
-        when 'end-\\'
-          (@err c) unless top is '\\'
-          do @pop
-        else throw new Error "undefined token observed: " + c
-      this
-    @ss = new StringScanner ''
+          'DEDENT'
+        when TERM
+          'TERM'
+        else
+          inspect c
+    throw new Error "Unexpected " + token
+
+  peek: -> if @context.length then @context[@context.length - 1] else null
+
+  observe: (c) ->
+    top = @peek()
+    switch c
+      # opening token is closing token
+      when '"""', '\'\'\'', '"', '\'', '###', '`', '///', '/'
+        if top is c then do @context.pop
+        else @context.push c
+      # strictly opening tokens
+      when INDENT, '#', '#{', '[', '(', '{', '\\', 'regexp-[', 'regexp-(', 'regexp-{', 'heregexp-#', 'heregexp-[', 'heregexp-(', 'heregexp-{'
+        @context.push c
+      # strictly closing tokens
+      when DEDENT
+        (@err c) unless top is INDENT
+        do @context.pop
+      when '\n'
+        (@err c) unless top in ['#', 'heregexp-#']
+        do @context.pop
+      when ']'
+        (@err c) unless top in ['[', 'regexp-[', 'heregexp-[']
+        do @context.pop
+      when ')'
+        (@err c) unless top in ['(', 'regexp-(', 'heregexp-(']
+        do @context.pop
+      when '}'
+        (@err c) unless top in ['#{', '{', 'regexp-{', 'heregexp-{']
+        do @context.pop
+      when 'end-\\'
+        (@err c) unless top is '\\'
+        do @context.pop
+      else throw new Error "undefined token observed: " + c
+    @context

   p: (s) ->
     if s? then @emit 'data', s
@@ -68,7 +81,7 @@
     @ss.concat data unless isEnd

     until @ss.eos()
-      switch @context.peek()
+      switch @peek()
         when null, INDENT, '#{', '[', '(', '{'
           if @ss.bol() or @scan /// (?:[#{ws}]* \n)+ ///

@@ -95,7 +108,7 @@
                 # we lost an indent
                 @indents.splice indentIndex, 1
                 --indentIndex
-                @context.observe DEDENT
+                @observe DEDENT
                 @p "#{DEDENT}#{TERM}"
               else
                 # Some ambiguous dedent
@@ -108,10 +121,10 @@
             if @ss.check /// [#{ws}]+ [^#{ws}#] ///
               # an indent
               @indents.push @scan /// [#{ws}]+ ///
-              @context.observe INDENT
+              @observe INDENT
               @p INDENT

-          tok = switch @context.peek()
+          tok = switch @peek()
             when '['
               # safe things, but not closing bracket
               @scan /[^\n'"\\\/#`[({\]]+/
@@ -129,11 +142,11 @@
               @scan /[^\n'"\\\/#`[({]+/
               null
           if tok
-            @context.observe tok
+            @observe tok
             continue

           if tok = @scan /"""|'''|\/\/\/|###|["'`#[({\\]/
-            @context.observe tok
+            @observe tok
           else if tok = @scan /\//
             # unfortunately, we must look behind us to determine if this is a regexp or division
             pos = @ss.position()
@@ -142,83 +155,82 @@
               spaceBefore = ///[#{ws}]///.test lastChar
               nonIdentifierBefore = /[\W_$]/.test lastChar # TODO: this should perform a real test
             if pos is 1 or (if spaceBefore then not @ss.check /// [#{ws}=] /// else nonIdentifierBefore)
-              @context.observe '/'
-
+              @observe '/'
         when '\\'
-          if (@scan /[\s\S]/) then @context.observe 'end-\\'
+          if (@scan /[\s\S]/) then @observe 'end-\\'
           # TODO: somehow prevent indent tokens from being inserted after these newlines
         when '"""'
           @scan /(?:[^"#\\]+|""?(?!")|#(?!{)|\\.)+/
           @ss.scan /\\\n/
-          if tok = @scan /#{|"""/ then @context.observe tok
-          else if tok = @scan /#{|"""/ then @context.observe tok
+          if tok = @scan /#{|"""/ then @observe tok
+          else if tok = @scan /#{|"""/ then @observe tok
         when '"'
           @scan /(?:[^"#\\]+|#(?!{)|\\.)+/
           @ss.scan /\\\n/
-          if tok = @scan /#{|"/ then @context.observe tok
+          if tok = @scan /#{|"/ then @observe tok
         when '\'\'\''
           @scan /(?:[^'\\]+|''?(?!')|\\.)+/
           @ss.scan /\\\n/
-          if tok = @scan /'''/ then @context.observe tok
+          if tok = @scan /'''/ then @observe tok
         when '\''
           @scan /(?:[^'\\]+|\\.)+/
           @ss.scan /\\\n/
-          if tok = @scan /'/ then @context.observe tok
+          if tok = @scan /'/ then @observe tok
         when '###'
           @scan /(?:[^#]+|##?(?!#))+/
-          if tok = @scan /###/ then @context.observe tok
+          if tok = @scan /###/ then @observe tok
         when '#'
           @scan /[^\n]+/
-          if tok = @scan /\n/ then @context.observe tok
+          if tok = @scan /\n/ then @observe tok
         when '`'
           @scan /[^`]+/
-          if tok = @scan /`/ then @context.observe tok
+          if tok = @scan /`/ then @observe tok
         when '///'
           @scan /(?:[^[/#\\]+|\/\/?(?!\/)|\\.)+/
-          if tok = @scan /#{|\/\/\/|\\/ then @context.observe tok
-          else if @ss.scan /#/ then @context.observe 'heregexp-#'
-          else if tok = @scan /[\[]/ then @context.observe "heregexp-#{tok}"
+          if tok = @scan /#{|\/\/\/|\\/ then @observe tok
+          else if @ss.scan /#/ then @observe 'heregexp-#'
+          else if tok = @scan /[\[]/ then @observe "heregexp-#{tok}"
         when 'heregexp-['
           @scan /(?:[^\]\/\\]+|\/\/?(?!\/))+/
-          if tok = @scan /[\]\\]|#{|\/\/\// then @context.observe tok
+          if tok = @scan /[\]\\]|#{|\/\/\// then @observe tok
         when 'heregexp-#'
           @ss.scan /(?:[^\n/]+|\/\/?(?!\/))+/
-          if tok = @scan /\n|\/\/\// then @context.observe tok
+          if tok = @scan /\n|\/\/\// then @observe tok
         #when 'heregexp-('
         #  @scan /(?:[^)/[({#\\]+|\/\/?(?!\/))+/
-        #  if tok = @ss.scan /#(?!{)/ then @context.observe 'heregexp-#'
-        #  else if tok = @scan /[)\\]|#{|\/\/\// then @context.observe tok
-        #  else if tok = @scan /[[({]/ then @context.observe "heregexp-#{tok}"
+        #  if tok = @ss.scan /#(?!{)/ then @observe 'heregexp-#'
+        #  else if tok = @scan /[)\\]|#{|\/\/\// then @observe tok
+        #  else if tok = @scan /[[({]/ then @observe "heregexp-#{tok}"
         #when 'heregexp-{'
         #  @scan /(?:[^}/[({#\\]+|\/\/?(?!\/))+/
-        #  if tok = @ss.scan /#(?!{)/ then @context.observe 'heregexp-#'
-        #  else if tok = @scan /[}/\\]|#{|\/\/\// then @context.observe tok
-        #  else if tok = @scan /[[({]/ then @context.observe "heregexp-#{tok}"
+        #  if tok = @ss.scan /#(?!{)/ then @observe 'heregexp-#'
+        #  else if tok = @scan /[}/\\]|#{|\/\/\// then @observe tok
+        #  else if tok = @scan /[[({]/ then @observe "heregexp-#{tok}"
         when '/'
           @scan /[^[/\\]+/
-          if tok = @scan /[\/\\]/ then @context.observe tok
-          else if tok = @scan /\[/ then @context.observe "regexp-#{tok}"
+          if tok = @scan /[\/\\]/ then @observe tok
+          else if tok = @scan /\[/ then @observe "regexp-#{tok}"
         when 'regexp-['
           @scan /[^\]\\]+/
-          if tok = @scan /[\]\\]/ then @context.observe tok
+          if tok = @scan /[\]\\]/ then @observe tok
         #when 'regexp-('
         #  @scan /[^)/[({\\]+/
-        #  if tok = @scan /[)/\\]/ then @context.observe tok
-        #  else if tok = @scan /[[({]/ then @context.observe "regexp-#{tok}"
+        #  if tok = @scan /[)/\\]/ then @observe tok
+        #  else if tok = @scan /[[({]/ then @observe "regexp-#{tok}"
         #when 'regexp-{'
         #  @scan /[^}/[({\\]+/
-        #  if tok = @scan /[}/\\]/ then @context.observe tok
-        #  else if tok = @scan /[[({]/ then @context.observe "regexp-#{tok}"
+        #  if tok = @scan /[}/\\]/ then @observe tok
+        #  else if tok = @scan /[[({]/ then @observe "regexp-#{tok}"

     # reached the end of the file
     if isEnd
       @scan /// [#{ws}\n]* $ ///
-      while @context.length and INDENT is @context.peek()
-        @context.observe DEDENT
+      while @context.length and INDENT is @peek()
+        @observe DEDENT
         @p "#{DEDENT}#{TERM}"
       if @context.length
         # TODO: store offsets of tokens when inserted and report position of unclosed starting token
-        throw new Error 'Unclosed ' + (inspect @context.peek()) + ' at EOF'
+        throw new Error 'Unclosed ' + (inspect @peek()) + ' at EOF'
       @emit 'end'
       return


CoffeeLint

CoffeeLint

--- src/scripts/time.coffee
+++ src/scripts/time.coffee
@@ -4,20 +4,27 @@
 # Commands:
 #   ibizan in - Punch in at the current time and date
 #   ibizan out - Punch out at the current time and date
-#   ibizan in #project1 - Punch in at the current time and assigns the current project to #project1
-#   ibizan out #project1 #project2 - Punch out at the current time and splits the worked time since last in-punch between #project1 and #project2
+#   ibizan in #project1 - Punch in at the current time and assigns the current
+#                         project to #project1
+#   ibizan out #project1 #project2 - Punch out at the current time and splits
+#                                    the worked time since last in-punch between
+#                                    #project1 and #project2
 #   ibizan in 9:15 - Punch in at 9:15am today
 #   ibizan out 7pm yesterday - Punch out yesterday at 7pm
-#   ibizan in 17:00 #project3 - Punch in at 5pm and assigns the time until next out-punch to #project3
+#   ibizan in 17:00 #project3 - Punch in at 5pm and assigns the time until next
+#                               out-punch to #project3
 #   ibizan 1.5 hours - Append 1.5 hours to today's total time
 #   ibizan 2 hours yesterday - Append 2 hours on to yesterday's total time
-#   ibizan 3.25 hours tuesday #project1 - Append 3.25 hours on to Tuesday's total time and assigns it to #project1
+#   ibizan 3.25 hours tuesday #project1 - Append 3.25 hours on to Tuesday's
+#                                         total time and assigns it to #project1
 #   ibizan vacation today - Flags the user’s entire day as vacation time
 #   ibizan sick half-day - Flags half the user’s day as sick time
-#   ibizan vacation half-day yesterday - Flags half the user’s previous day (4 hours) as vacation time
+#   ibizan vacation half-day yesterday - Flags half the user’s previous day
+#                                        (4 hours) as vacation time
 #   ibizan sick Jul 6-8 - Flags July 6-8 of this year as sick time
-#   ibizan vacation 1/28 - 2/4 - Flags January 28th to February 4th of this year as vacation time.
-#
+#   ibizan vacation 1/28 - 2/4 - Flags January 28th to February 4th of this year
+#                                as vacation time.
+#
 #   ibizan hours - Replies with helpful info for hours? and hours [date]
 #   ibizan hours 8/4 - Replies with punches recorded on a given date
 #   ibizan hours? - Replies with the user's total time for today, with punches
@@ -26,12 +33,15 @@
 #   ibizan month? - Replies with the user's total time for the month
 #   ibizan year? - Replies with the user's total time for the year
 #   ibizan status - Replies with the user's Employee sheet info
-#   ibizan time - Replies with the current time in both Ibizan's default timezone and the user's timezone
+#   ibizan time - Replies with the current time in both Ibizan's default
+#                 timezone and the user's timezone
 #   ibizan timezone - Replies with the user's timezone
 #   ibizan timezone america/chicago - Sets the user's timezone
 #
 # Notes:
-#   All dates are formatted in MM/DD notation with no support for overriding year. Ibizan will extrapolate year from your ranges, even if it stretches over multiple years.
+#   All dates are formatted in MM/DD notation with no support for overriding
+#   year. Ibizan will extrapolate year from your ranges, even if it stretches
+#   over multiple years.
 #
 # Author:
 #   aaronsky
@@ -225,7 +235,8 @@
           Logger.removeReaction 'clock4', res.message
           user.directMessage "Undid your last punch, which was:
                               *#{lastPunchDescription}*\n\nYour most current
-                              punch is now: *#{user.lastPunch().description(user)}*",
+                              punch is now:
+                              *#{user.lastPunch().description(user)}*",
                              Logger
       )
       .catch(
@@ -283,7 +294,8 @@
       if not report[headers.logged]
         msg = "You haven't recorded any paid work time"
       else
-        msg = "You have *#{toTimeStr(report[headers.logged])} of paid work time*"
+        msg = "You have *#{toTimeStr(report[headers.logged])} of
+               paid work time*"
         loggedAny = true
       for kind in ['vacation', 'sick', 'unpaid']
         header = headers[kind]
@@ -291,7 +303,8 @@
           kind = 'unpaid work'
         if report[header]
           if not loggedAny
-            msg += ", but you have *#{toTimeStr(report[header])} of #{kind} time*"
+            msg += ", but you have *#{toTimeStr(report[header])} of
+                    #{kind} time*"
             loggedAny = true
           else
             msg += " and *#{toTimeStr(report[header])} of #{kind} time*"
@@ -326,11 +339,13 @@
         else
           attachments.push punch.slackAttachment()
     else if mode is 'month'
-      startOfMonth = moment.tz({hour: 0, minute: 0, second: 0}, tz).startOf("month")
+      startOfMonth =
+        moment.tz({hour: 0, minute: 0, second: 0}, tz).startOf("month")
       report = user.toRawPayroll(startOfMonth, now)
       dateArticle = "this month"
     else if mode is 'year'
-      startOfYear = moment.tz({hour: 0, minute: 0, second: 0}, tz).startOf("year")
+      startOfYear =
+        moment.tz({hour: 0, minute: 0, second: 0}, tz).startOf("year")
       report = user.toRawPayroll(startOfYear, now)
       dateArticle = "this year"
     else if mode is 'period'
@@ -339,7 +354,8 @@
         periodStart = periodStart.subtract(1, 'weeks')
       periodEnd = periodStart.clone().add(2, 'weeks')
       report = user.toRawPayroll(periodStart, periodEnd)
-      dateArticle = "this pay period (#{periodStart.format('M/DD')} to #{periodEnd.format('M/DD')})"
+      dateArticle = "this pay period (#{periodStart.format('M/DD')} to
+                     #{periodEnd.format('M/DD')})"
       for punch in user.punches
         if punch.date.isBefore(periodStart) or punch.date.isAfter(periodEnd)
           continue
@@ -348,7 +364,8 @@
         else
           attachments.push punch.slackAttachment()
     else
-      earlyToday = now.clone().hour(0).minute(0).second(0).subtract(1, 'minutes')
+      earlyToday =
+        now.clone().hour(0).minute(0).second(0).subtract(1, 'minutes')
       report = user.toRawPayroll(earlyToday, now)
       dateArticle = "today"
       for punch in user.punches
@@ -367,7 +384,8 @@
       if not report[headers.logged]
         msg = "You haven't recorded any paid work time"
       else
-        msg = "You have *#{toTimeStr(report[headers.logged])} of paid work time*"
+        msg = "You have *#{toTimeStr(report[headers.logged])} of
+               paid work time*"
         loggedAny = true
       for kind in ['vacation', 'sick', 'unpaid']
         header = headers[kind]
@@ -375,7 +393,8 @@
           kind = 'unpaid work'
         if report[header]
           if not loggedAny
-            msg += ", but you have *#{toTimeStr(report[header])} of #{kind} time*"
+            msg += ", but you have *#{toTimeStr(report[header])} of
+                    #{kind} time*"
             loggedAny = true
           else
             msg += " and *#{toTimeStr(report[header])} of #{kind} time*"
@@ -400,9 +419,11 @@
     user = Organization.getUserBySlackName res.message.user.name
     userTime = moment.tz(user.timetable.timezone.name)
     ibizanTime = moment.tz(TIMEZONE)
-    msg = "It's currently *#{userTime.format('h:mm A')}* in your timezone (#{userTime.format('z, Z')})."
+    msg = "It's currently *#{userTime.format('h:mm A')}* in your timezone
+           (#{userTime.format('z, Z')})."
     if userTime.format('z') != ibizanTime.format('z')
-      msg += "\n\nIt's #{ibizanTime.format('h:mm A')} in the default timezone (#{ibizanTime.format('z, Z')})."
+      msg += "\n\nIt's #{ibizanTime.format('h:mm A')} in the default timezone
+              (#{ibizanTime.format('z, Z')})."
     user.directMessage msg, Logger
     Logger.addReaction 'dog2', res.message

@@ -436,7 +457,8 @@
       tz = user.setTimezone(input)
       if tz
         userTime = moment.tz(user.timetable.timezone.name)
-        user.directMessage "Your timezone is now *#{user.timetable.timezone.name}*
+        user.directMessage "Your timezone is now
+                            *#{user.timetable.timezone.name}*
                             (#{userTime.format('z, Z')}).",
                            Logger
         Logger.addReaction 'dog2', res.message
@@ -467,7 +489,8 @@
       if scope is 'start'
         if not newtime.isBefore(user.timetable.end)
           user.directMessage "#{newtime.format('h:mm A')} is not before your
-                              current end time of #{user.timetable.start.format('h:mm A')}.",
+                              current end time of
+                              #{user.timetable.start.format('h:mm A')}.",
                              Logger
           Logger.addReaction 'x', res.message
           return
@@ -476,13 +499,15 @@
       else if scope is 'end'
         if not newtime.isAfter(user.timetable.start)
           user.directMessage "#{newtime.format('h:mm A')} is not after your
-                              current start time of #{user.timetable.start.format('h:mm A')}.",
+                              current start time of
+                              #{user.timetable.start.format('h:mm A')}.",
                              Logger
           Logger.addReaction 'x', res.message
           return
         else
           user.setEnd newtime
-      user.directMessage "Your active *#{scope}* time is now *#{newtime.format('h:mm A')}*.",
+      user.directMessage "Your active *#{scope}* time is now
+                          *#{newtime.format('h:mm A')}*.",
                          Logger
       Logger.addReaction 'dog2', res.message
     else


fix coverage partially

fix coverage partially

--- src/scriptscript.coffee
+++ src/scriptscript.coffee
@@ -40,9 +40,13 @@
     print_error err, a.d
     cb()
   await ss.tokenize input, {}, defer err, tok;    return handle_err(err) if err;  pp tok if a.t
+  ### !pragma coverage-skip-block ###
   await ss.parse tok, {}, defer err, ast;         return handle_err(err) if err;  pp ast if a.n
+  ### !pragma coverage-skip-block ###
   await ss.type_inference ast[0], {}, defer err;  return handle_err(err) if err;  pp ast if a.y
+  ### !pragma coverage-skip-block ###
   await ss.translate ast[0], {}, defer err, res;  return handle_err(err) if err;  cb null, res
+  ### !pragma coverage-skip-block ###

 try_eval = (err, res)->
   if err
@@ -69,6 +73,7 @@
       else
         cb null, res
       return
+    ### !pragma coverage-skip-block ###
     debug = input.startsWith ":d "
     input = input[3...] if debug
     await ss.go input, {}, defer ss_err, res


Don't clobber module by reassigning to path

--- src/app/atom-package.coffee
+++ src/app/atom-package.coffee
@@ -56,13 +56,14 @@
       @mainModule?.activateConfig?()

   activateResources: ->
-    keymap.add(path, map) for [path, map] in @keymaps
-    applyStylesheet(path, content) for [path, content] in @stylesheets
+    keymap.add(keymapPath, map) for [keymapPath, map] in @keymaps
+    applyStylesheet(stylesheetPath, content) for [stylesheetPath, content] in @stylesheets
     syntax.addGrammar(grammar) for grammar in @grammars
-    syntax.addProperties(path, selector, properties) for [path, selector, properties] in @scopedProperties
+    for [propertiesPath, selector, properties] in @scopedProperties
+      syntax.addProperties(propertiesPath, selector, properties)

   loadKeymaps: ->
-    @keymaps = @getKeymapPaths().map (path) -> [path, CSON.readFileSync(path)]
+    @keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath)]

   getKeymapPaths: ->
     keymapsDirPath = path.join(@path, 'keymaps')
@@ -72,7 +73,7 @@
       fsUtils.list(keymapsDirPath, ['cson', 'json'])

   loadStylesheets: ->
-    @stylesheets = @getStylesheetPaths().map (path) -> [path, loadStylesheet(path)]
+    @stylesheets = @getStylesheetPaths().map (stylesheetPath) -> [stylesheetPath, loadStylesheet(stylesheetPath)]

   getStylesheetPaths: ->
     stylesheetDirPath = path.join(@path, 'stylesheets')
@@ -112,8 +113,8 @@
   deactivateResources: ->
     syntax.removeGrammar(grammar) for grammar in @grammars
     syntax.removeProperties(path) for [path] in @scopedProperties
-    keymap.remove(path) for [path] in @keymaps
-    removeStylesheet(path) for [path] in @stylesheets
+    keymap.remove(keymapPath) for [keymapPath] in @keymaps
+    removeStylesheet(stylesheetPath) for [stylesheetPath] in @stylesheets

   requireMainModule: ->
     return @mainModule if @mainModule


Kill call to removed method

Kill call to removed method

--- src/app/edit-session.coffee
+++ src/app/edit-session.coffee
@@ -57,7 +57,6 @@
     @preserveCursorPositionOnBufferReload()

     @subscribe @displayBuffer, "changed", (e) =>
-      @updateAnchorPoints(e.bufferChange)
       @refreshAnchorScreenPositions() unless e.bufferDelta
       @trigger 'screen-lines-changed', e


Added logger and finished askForTrack

Added logger and finished askForTrack

--- source/Downloader.coffee
+++ source/Downloader.coffee
@@ -24,7 +24,7 @@
 # Download the given file name from
 # the given url.
 downloadTrack = (name, url) ->
-  winston.info('Called download track.')
+  winston.info('Going to download track.')
   URI =
     url: url
     headers:
@@ -51,4 +51,5 @@
     name = result['song']
     index = titles.indexOf(name)
     source = 'http://123savemp3.net' + downloads[index]
-
+    winston.info('Should download: ' + name)
+    downloadTrack(name, source)


Ensure cursor is hidden on scroll

Ensure cursor is hidden on scroll

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -1129,7 +1129,7 @@
     @cursorHideStyle = document.createElement("style")
     @cursorHideStyle.innerHTML = """
       body * {pointer-events: none !important; cursor: none !important;}
-      body {cursor: none !important;}
+      body, html {cursor: none !important;}
     """
     window.addEventListener "mousemove", @onMouseMove
     window.addEventListener "scroll", @onScroll


Buma field input name

Buma field input name

--- snippets/bulma_form.cson
+++ snippets/bulma_form.cson
@@ -4,9 +4,9 @@
     'prefix': 'bulma:field'
     'body': """

-        Label
+        Label

-          <input class="input" type="text" placeholder="Text input">
+          <input class="input" id="${1:name}" name="${1:name}" type="text" placeholder="Text input">

This is a help text


Custom keymappings for atom

Custom keymappings for atom

--- atom/dot-atom/keymap.cson
+++ atom/dot-atom/keymap.cson
@@ -33,4 +33,195 @@

 # Disable Enter key for confirming an autocomplete suggestion
 'atom-text-editor:not(mini).autocomplete-active':
-  'enter': 'editor:newline'
+    'enter': 'editor:newline'
+
+'.editor':
+    'ctrl-alt-b': 'atom-beautify:beautify-editor'
+    'ctrl-alt-g': 'fuzzy-grep:toggle'
+
+'body atom-text-editor.autocomplete-active':
+    'ctrl-p': 'autocomplete-plus:move-up'
+    'ctrl-n': 'autocomplete-plus:move-down'
+
+'atom-workspace:not([mini])':
+    'ctrl-shift-left': 'window:move-active-item-to-pane-on-left'
+    'ctrl-shift-right': 'window:move-active-item-to-pane-on-right'
+    'ctrl-shift-up': 'window:move-active-item-to-pane-above'
+    'ctrl-shift-down': 'window:move-active-item-to-pane-below'
+
+    'alt-x': 'vim-mode-plus:exchange-pane'
+    'alt-K': 'vim-mode-plus:move-pane-to-very-top'
+    'alt-J': 'vim-mode-plus:move-pane-to-very-bottom'
+    'alt-H': 'vim-mode-plus:move-pane-to-very-left'
+    'alt-L': 'vim-mode-plus:move-pane-to-very-right'
+
+    'alt-2': 'paner:split-pane-down-stay'
+    'alt-3': 'paner:split-pane-right-stay'
+
+    'ctrl-[': 'core:cancel'
+    'ctrl-g': 'core:cancel'
+
+    'ctrl-alt-e i': 'application:open-your-init-script'
+    'ctrl-alt-e c': 'application:open-your-config'
+    'ctrl-alt-e k': 'application:open-your-keymap'
+    'ctrl-alt-e s': 'application:open-your-stylesheet'
+    'ctrl-alt-e n': 'application:open-your-snippets'
+    'ctrl-alt-e p': 'project-folder:open-config'
+
+    'ctrl-i': 'cursor-history:next'
+    'ctrl-o': 'cursor-history:prev'
+    'ctrl-alt-i': 'cursor-history:next-within-editor'
+    'ctrl-alt-o': 'cursor-history:prev-within-editor'
+
+'atom-text-editor':
+    'shift-alt-enter': 'vim-mode-plus:maximize-pane-without-center'
+
+'atom-text-editor.vim-mode-plus:not(.insert-mode)':
+    'Q': 'vim-mode-plus:move-to-previous-subword'
+    'q': 'vim-mode-plus:move-to-next-subword'
+
+    'space T': 'narrow:fold'
+
+    'space o': 'narrow:symbols-by-current-word'
+    'space O': 'narrow:project-symbols-by-current-word'
+
+    'space r': 'narrow:symbols'
+    'space R': 'narrow:project-symbols'
+
+    'space l': 'narrow:scan'
+    'alt-l': 'narrow:scan-by-current-word'
+
+    'space s': 'narrow:search'
+    'ctrl-p': 'narrow:git-ls'
+    'space C': 'narrow:linter'
+
+    'space a': 'narrow:atom-scan'
+    'space A': 'narrow:atom-scan-by-current-word'
+    'space G': 'narrow:git-diff-all'
+
+    'g .': 'vim-mode-plus:add-preset-occurrence-from-last-occurrence-pattern'
+
+    'ctrl-/': 'vim-mode-plus:toggle-line-comments'
+
+    'space i': 'vim-mode-plus:insert-at-head-of-target'
+    'g i': 'vim-mode-plus:insert-at-start-of-target'
+    'g a': 'vim-mode-plus:insert-at-end-of-target'
+
+    'g d': 'vim-mode-plus:select-occurrence'
+
+    'g m': 'vim-mode-plus:mark'
+    'g M': 'vim-mode-plus:clear-persistent-selection'
+
+    'g enter': 'vim-mode-plus:create-persistent-selection'
+    'space enter': 'vim-mode-plus:clear-persistent-selection'
+
+    'g h': 'vim-mode-plus-user:quick-highlight'
+    'w': 'vim-mode-plus:move-to-next-smart-word'
+    'b': 'vim-mode-plus:move-to-previous-smart-word'
+    'e': 'vim-mode-plus:move-to-end-of-smart-word'
+
+    'g P': 'vim-mode-plus:put-before-with-auto-indent'
+    'g p': 'vim-mode-plus:put-after-with-auto-indent'
+
+    'g _': 'vim-mode-plus:snake-case'
+    'g %': 'vim-mode-plus:encode-uri-component'
+
+    'space p': 'vim-mode-plus:move-to-previous-fold-start-with-same-indent'
+    'space n': 'vim-mode-plus:move-to-next-fold-start-with-same-indent'
+    'g >': 'vim-mode-plus:rotate-arguments-of-inner-pair'
+    'g <': 'vim-mode-plus:rotate-arguments-backwards-of-inner-pair'
+
+    'space f f': 'fuzzy-finder:toggle-file-finder'
+    'space f g': 'fuzzy-grep:toggle'
+
+'atom-text-editor.vim-mode-plus.normal-mode':
+    'enter': 'narrow:search-by-current-word'
+
+    "g '": 'vim-mode-plus:move-to-next-string'
+
+    'space w k': 'window:focus-pane-above'
+    'space w j': 'window:focus-pane-below'
+    'space w h': 'window:focus-pane-on-left'
+    'space w l': 'window:focus-pane-on-right'
+    'space w w': 'window:focus-next-pane'
+    'space w p': 'window:focus-previous-pane'
+    'space w v': 'pane:split-right-and-copy-active-item'
+    'space w s': 'pane:split-down-and-copy-active-item'
+
+    'space g o': 'open-on-github:file'
+    'space g b': 'open-on-github:blame'
+    'space g h': 'open-on-github:history'
+    'space g i': 'open-on-github:issues'
+    'space g c': 'open-on-github:copy-url'
+    'space g r': 'open-on-github:branch-compare'
+    'space g g': 'open-on-github:repository'
+
+    'space g n': 'git-diff:move-to-next-diff'
+    'space g p': 'git-diff:move-to-previous-diff'
+    'space g d': 'git-diff:toggle-diff-list'
+
+    '_': 'toggle:there'
+
+    'space t f': 'vim-mode-plus:toggle-fold'
+    'space t i': 'user:toggle-show-invisible'
+    'space t I': 'editor:toggle-indent-guide'
+    'space t w': 'editor:toggle-soft-wrap'
+    'space t n': 'editor:toggle-line-numbers'
+
+    'S': 'vim-mode-plus:surround-smart-word'
+    's': 'vim-mode-plus:surround-smart-word'
+
+    'space j': 'jumpy:toggle'
+
+    'g f': 'open-this:here'
+    'ctrl-w f': 'open-this:split-down'
+    'ctrl-w F': 'open-this:split-right'
+
+    'space /': 'vim-mode-plus:clear-highlight-search'
+    'g x': 'link:open'
+
+'atom-text-editor.vim-mode-plus.insert-mode':
+    'ctrl-p': 'core:move-up'
+    'ctrl-n': 'core:move-down'
+    'j k': 'vim-mode-plus:activate-normal-mode' # jk to escape
+
+    'space tab': 'autocomplete-plus:activate'
+
+    'alt-e': 'editor:move-to-end-of-word'
+    'alt-b': 'editor:move-to-beginning-of-word'
+    'alt-d': 'editor:delete-to-end-of-word'
+
+    'alt-F': 'core:move-right'
+    'alt-B': 'core:move-left'
+
+    'ctrl-alt-f': 'vim-mode-plus:move-to-next-subword'
+    'ctrl-alt-b': 'vim-mode-plus:move-to-previous-subword'
+    'ctrl-alt-d': 'editor:delete-to-end-of-subword'
+    'ctrl-alt-w': 'editor:delete-to-beginning-of-subword'
+
+    'ctrl-y': 'clip-history:paste'
+    'alt-y': 'clip-history:paste-newer'
+    'ctrl-Y': 'clip-history:paste-last'
+    'ctrl-.': 'vim-mode-plus:insert-last-inserted'
+    'ctrl-{': 'vim-mode-plus:move-to-previous-fold-start'
+    'ctrl-}': 'vim-mode-plus:move-to-next-fold-start'
+
+'atom-text-editor.vim-mode-plus.visual-mode':
+
+    'ctrl-t': 'vim-mode-plus-user:move-selected-text-toggle-overwrite'
+    'ctrl-k': 'vim-mode-plus-user:move-selected-text-up'
+    'ctrl-j': 'vim-mode-plus-user:move-selected-text-down'
+    'ctrl-h': 'vim-mode-plus-user:move-selected-text-left'
+    'ctrl-l': 'vim-mode-plus-user:move-selected-text-right'
+
+    'alt-K': 'vim-mode-plus-user:duplicate-selected-text-up'
+    'alt-J': 'vim-mode-plus-user:duplicate-selected-text-down'
+    'alt-H': 'vim-mode-plus-user:duplicate-selected-text-left'
+    'alt-L': 'vim-mode-plus-user:duplicate-selected-text-right'
+
+    'T': 'try:paste'
+    'alt-/': 'vim-mode-plus:toggle-line-comments'
+    'alt-c': 'vim-mode-plus:yank'
+
+'atom-text-editor.vim-mode-plus.insert-mode.autocomplete-active':
+    'ctrl-g': 'autocomplete-plus:cancel'


Array#pipeline

Array#pipeline

--- src/array_extensions.coffee
+++ src/array_extensions.coffee
@@ -309,6 +309,25 @@
   return this

 ###*
+Pipe the input through each function in the array in turn. For example, if you have a
+list of objects you can perform a series of selection, sorting, and other processing
+methods and then receive the processed list. This array must contain functions that
+accept a single input and return the processed input. The output of the first function
+is fed to the input of the second and so on until the final processed output is returned.
+
+@name pipeline
+@methodOf Array#
+
+@param {Object} input The initial input to pass to the first function in the pipeline.
+@returns {Object} The result of processing the input by each function in the array.
+###
+Array::pipeline = (input) ->
+  for fn in this
+    input = fn(input)
+
+  return input
+
+###*
 Returns a new array with the elements all shuffled up.

 <code><pre>


Query SI every 5 seconds in case of an error

--- admin/static/coffee/namespaces/namespace.coffee
+++ admin/static/coffee/namespaces/namespace.coffee
@@ -216,7 +216,8 @@
             'click .cancel_btn': 'hide_add_index'
             'click .reconnect_link': 'init_connection'
             'click .close_hide': 'hide_alert'
-        interval: 60*1000 # Retrieve secondary indexes every minute
+        error_interval: 5*1000 # In case of an error, we try to retrieve the secondary index in 5 seconds
+        normal_interval: 60*1000 # Retrieve secondary indexes every minute

         initialize: =>
             @init_connection()
@@ -326,11 +327,14 @@

         # Retrieve secondary indexes with an interval
         set_interval_get_indexes: =>
-            @get_indexes()
-            @interval = setInterval @get_indexes, @interval # The connection times out every 5 minutes
-
-        get_indexes: =>
-            r.db(@db_name).table(@table).indexList().private_run @driver_handler.connection, @on_index_list
+            @get_indexes
+                set_timeout: true
+
+        get_indexes: (args) =>
+            if args?.set_timeout is true
+                r.db(@db_name).table(@table).indexList().private_run @driver_handler.connection, @on_index_list_repeat
+            else
+                r.db(@db_name).table(@table).indexList().private_run @driver_handler.connection, @on_index_list

         # Callback on indexList
         on_index_list: (err, result) =>
@@ -345,6 +349,23 @@
                 if not _.isEqual @secondary_indexes, secondary_indexes
                     @secondary_indexes = secondary_indexes
                     @render_content()
+
+        # Same as on_index_list except we set a timeout.
+        on_index_list_repeat: (err, result) =>
+            if err?
+                @loading = false
+                @render_content
+                    error: true
+                    index_list: true
+                @timeout = setTimeout @set_interval_get_indexes, @error_interval
+            else
+                @loading = false
+                secondary_indexes = result.sort()
+                if not _.isEqual @secondary_indexes, secondary_indexes
+                    @secondary_indexes = secondary_indexes
+                    @render_content()
+                @timeout = setTimeout @set_interval_get_indexes, @normal_interval
+

         on_fail_to_connect: =>
@@ -401,8 +422,8 @@
             @$(event.target).parent().parent().slideUp 'fast'

         destroy: =>
-            if @interval?
-                clearInterval @interval
+            if @timeout?
+                clearTimeout @timeout
             @db.off 'change:name', @save_name
             @model.off 'change:name', @save_name


Correction versions pre-release

Correction versions pre-release


one last test case

one last test case

--- test/query.coffee
+++ test/query.coffee
@@ -8,6 +8,9 @@
 describe 'query', ->

   before -> @clever = Clever 'DEMO_KEY', 'https://api.getclever.com'
+
+  it 'throws an error if you try to instantiate without an api key', ->
+    assert.throws -> Clever()

   it 'find with no arguments', (done) ->
     @clever.District.find (err, districts) =>


Remove console.log debug statement

Remove console.log debug statement

--- test/suite.coffee
+++ test/suite.coffee
@@ -346,7 +346,5 @@
         assert.equal typeof image.path, 'string'
         assert.equal typeof image.url, 'string'

-        console.log image.url
-
       done()


Test cases for settings + API adaptation

--- test/tests.coffee
+++ test/tests.coffee
@@ -9,6 +9,7 @@
         ['cache.test']
         ['network.test']
         ['fallback.test']
+        undefined
         createHash('md5').digest 'hex'
       )

@@ -78,6 +79,38 @@

         """

+    it 'should include SETTINGS section', ->
+      @appCache.settings = ["prefer-online"]
+      assert.equal @appCache.getManifestBody(),
+        """
+        CACHE:
+        cache.test
+
+        NETWORK:
+        network.test
+
+        FALLBACK:
+        fallback.test
+
+        SETTINGS:
+        prefer-online
+
+        """
+
+    it 'should exclude empty SETTINGS section', ->
+      @appCache.settings = {}
+      assert.equal @appCache.getManifestBody(),
+        """
+        CACHE:
+        cache.test
+
+        NETWORK:
+        network.test
+
+        FALLBACK:
+        fallback.test
+
+        """

   describe 'source()', ->
     it 'should include webpack build hash', ->


Clampable#clampToBounds

Clampable#clampToBounds

--- src/clampable.coffee
+++ src/clampable.coffee
@@ -26,7 +26,7 @@
   ###*
   @name clampToBounds
   @methodOf Clampable#
-  @param {Rectangle} bounds
+  @param {Rectangle} bounds The bound
   ###
   clampToBounds: (bounds) ->
     bounds ||= Rectangle x: 0, y: 0, width: App.width, height: App.height


collision with regular bounds

collision with regular bounds

--- src/collision.coffee
+++ src/collision.coffee
@@ -218,9 +218,10 @@
       unless target.xw? and target.yw?
         if target.width? and target.height?
           xw = target.width/2
+          y
           return Collision.rayRectangle source, direction,
             xw:
-            yw: target.height/2
+            yw:
         else
           error "Bounds object isn't a rectangle"


Documentation

Documentation

--- src/collision.coffee
+++ src/collision.coffee
@@ -35,8 +35,8 @@
   @param a The first rectangle
   @param b The second rectangle

-  @returns true if the rectangles overlap, false otherwise
-  @type boolean
+  @returns {Boolean} true if the rectangles overlap, false otherwise
+  @type Boolean
   ###
   rectangular: (a, b) ->
     a.x < b.x + b.width &&
@@ -77,8 +77,8 @@
   @param a
   @param b

-  @type boolean
-  @returns true is the circles overlap, false otherwise
+  @returns {Boolean} true is the circles overlap, false otherwise
+  @type Boolean
   ###
   circular: (a, b) ->
     r = a.radius + b.radius
@@ -97,8 +97,8 @@
   @param direction
   @param target

-  @type bool
-  @returns true if the line intersects the circle, false otherwise
+  @returns {Boolean} true if the line intersects the circle, false otherwise
+  @type Boolean
   ###
   rayCircle: (source, direction, target) ->
     radius = target.radius()


改进$.ajax的拦截代码

改进$.ajax的拦截代码

--- src/core/ajax.coffee
+++ src/core/ajax.coffee
@@ -17,7 +17,7 @@
 	if typeof url is "object" and not settings
 		settings = url

-	data = settings.data
+	data = settings?.data
 	if data
 		if typeof data is "object"
 			if data instanceof cola.Entity or data instanceof cola.EntityList


Random movement AI!

Random movement AI!

--- src/creatures.coffee
+++ src/creatures.coffee
@@ -4,11 +4,21 @@

 class exports.Creature extends Entity

+_ = require 'lodash'
+
+directions =
+	up: [0, -1]
+	down: [0, 1]
+	left: [-1, 0]
+	right: [1, 0]
+
 class exports.Dummy extends exports.Creature
-	tickRate: 10
+	tickRate: 3

 	tick: ->
-		Q 30
+		@move (_.sample _.values directions)...
+
+		Q 12

 class exports.Player extends exports.Creature
 	constructor: (g, m, x, y, @name, @speed = 12) ->
@@ -22,12 +32,7 @@
 		d = Q.defer()

 		@game.events.once 'key.*', (ch, key) =>
-			moveOffset = switch key.name
-				when 'up' then [0, -1]
-				when 'down' then [0, 1]
-				when 'left' then [-1, 0]
-				when 'right' then [1, 0]
-				else [0, 0]
+			moveOffset = directions[key.name] ? [0, 0]

 			@move moveOffset...


Replace all nbsp in string

Replace all nbsp in string

--- vendor/assets/javascripts/control.js.coffee
+++ vendor/assets/javascripts/control.js.coffee
@@ -123,7 +123,7 @@

   checkEquation: ->
     # Replace tokens with #id#.
-    equation = @input.html().replace(' ', '')
+    equation = @input.html().replace(/ /g, ' ')
     while (token = equation.match(@TOKEN_REGEX)) != null
       idReg = token[0].match(@ID_REGEX)
       id = idReg[0].replace('data-id="', '').replace('"', '')


Update task-panel.cjsx

Update task-panel.cjsx

No more BUGGGGG!

--- views/components/main/parts/task-panel.cjsx
+++ views/components/main/parts/task-panel.cjsx
@@ -309,7 +309,7 @@
           flag = updateQuestRecord('sally', null, 1)
           firstBattle = false
     return unless flag
-    for task in tasks when task.id < 100000 and questRecord[task.id]?
+    for task in tasks when task.id < 100000 and questGoals[task.id]?
       task.tracking = true
       task.percent = questRecord[task.id].count / questRecord[task.id].required
       task.progress = questRecord[task.id].count + ' / ' + questRecord[task.id].required
@@ -353,7 +353,7 @@
         flag = updateQuestRecord('sinking', {shipType: shipType}, 1) || flag
     if flag
       {tasks} = @state
-      for task in tasks when task.id < 100000 and questRecord[task.id]?
+      for task in tasks when task.id < 100000 and questGoals[task.id]?
         task.tracking = true
         task.percent = questRecord[task.id].count / questRecord[task.id].required
         task.progress = questRecord[task.id].count + ' / ' + questRecord[task.id].required


rejigger aloha initalization process

rejigger aloha initalization process

--- scripts/views/workspace/content/aloha-edit.coffee
+++ scripts/views/workspace/content/aloha-edit.coffee
@@ -6,10 +6,9 @@

   return class AlohaEditView extends Marionette.ItemView
     # **NOTE:** This template is not wrapped in an element
-    template: () -> throw 'BUG: You need to specify a template, modelKey, and optionally alohaOptions'
+    template: () -> throw 'BUG: You need to specify a template, modelKey'
     modelKey: null
-    alohaOptions: null
-    content: null
+    changed: false
     saveInterval: null

     templateHelpers: () ->
@@ -17,40 +16,44 @@

     initialize: () ->
       @isLoaded = false
+
       @model.load().done () =>
         @isLoaded = true
-        @render()
+        @render() if @changed

       @listenTo @model, "change:#{@modelKey}", (model, value, options) =>
         return if options.internalAlohaUpdate
-        @content = value
-        @render()
+        @changed = true

     onRender: () ->
       # update model after the user has stopped making changes
-      updateModel = =>
-        alohaId = @$el.attr('id')
-        alohaEditable = Aloha.getEditableById(alohaId)

-        if alohaEditable
-          editableBody = alohaEditable.getContents()
-          # Change the contents but do not update the Aloha editable area
-          @model.set(@modelKey, editableBody, {internalAlohaUpdate: true})
+      if @model.attributes.body
+        @$el.empty().append(@model.attributes.body) if @$el.find('.progress').length
+        updateModel = =>
+          alohaId = @$el.attr('id')
+          alohaEditable = Aloha.getEditableById(alohaId)
+
+          if alohaEditable
+            editableBody = alohaEditable.getContents()
+            # Change the contents but do not update the Aloha editable area
+            @model.set(@modelKey, editableBody, {internalAlohaUpdate: true})
+
+        #@saveInterval = setInterval(updateModel, 250) if not @saveInterval
+

-      @saveInterval = setInterval(updateModel, 250) if not @saveInterval
+        # Once Aloha has finished loading enable
+        @$el.addClass('disabled')
+
+        Aloha.ready =>

-      # Once Aloha has finished loading enable
-      @$el.addClass('disabled')
-      Aloha.ready =>
-        # Wait until Aloha is started before loading MathJax.
-        MathJax?.Hub.Configured()
+          @$el.mahalo() if @$el.mahalo
+          @$el.aloha()

-        # if aloha is on then disable it
-        @$el.mahalo() if @$el.mahalo
+          # Wait until Aloha is started before loading MathJax.
+          MathJax?.Hub.Configured()
+
+          # reenable everything
+          @$el.removeClass('disabled')

-        # reset the contents if necessary
-        @$el.empty().append(@content) if @content

-        # reenable everything
-        @$el.aloha(@alohaOptions)
-          .removeClass('disabled')


Documenting class and handling the new config file format.

--- src/toaster/toast.coffee
+++ src/toaster/toast.coffee
@@ -21,102 +21,107 @@
     @basepath = @toaster.basepath
     @builders = []

+    # if config is json
     if (config = @toaster.cli.argv["config"])?
       config = JSON.parse( config ) unless config instanceof Object
       @toast item for item in ( [].concat config )
+
+    # otherwise if it's file
     else
+
+      # evaluates it's path
       config_file = @toaster.cli.argv["config-file"]
       filepath = config_file || path.join @basepath, "toaster.coffee"

-      if @toaster.cli.argv.w
-        watcher = fsu.watch filepath
-        watcher.on 'change', (f)=>
-          now = ("#{new Date}".match /[0-9]{2}\:[0-9]{2}\:[0-9]{2}/)[0]
-          log "[#{now}] #{'Changed'.bold} #{filepath}".cyan
-          watcher.close()
-          @toaster.reset()
+      # if file doesn't exist
+      unless fs.existsSync filepath

-      if fs.existsSync filepath
-
-        contents = fs.readFileSync filepath, "utf-8"
-
-        try
-          code = cs.compile contents, {bare:1}
-        catch err
-          error err.message + " at 'toaster.coffee' config file."
-
-        fix_scope = /(^[\s\t]?)(toast)+(\()/mg
-        code = code.replace fix_scope, "$1this.$2$3"
-        eval code
-      else
+        # rise and error and aborts
         error "File not found: ".yellow + " #{filepath.red}\n" +
           "Try running:".yellow + " toaster -i".green +
-          " or type".yellow + " #{'toaster -h'.green} " +
+          " or type".yellow + " #toaster -h'".green +
           "for more info".yellow

-
-  toast:( srcpath, params = {} )=>
+        return null

-
-    if srcpath instanceof Object
-      params = srcpath
-    else if path.resolve srcpath != srcpath
-      folder = path.join @basepath, srcpath
+      # otherwise if file exists, go ahead and read it's contents
+      contents = fs.readFileSync filepath, "utf-8"
+
+      # now tries to compile it down to js
+      try
+        code = cs.compile contents, {bare:1}
+
+      # and if some error ocurrs, shows it and aborts
+      catch err
+        error "Error compiling `toaster.coffee` config file\n\n#{err}"
+        return proces.exit()
+
+      # if no errors, fix the `toast` call scope
+      fix_scope = /(^[\s\t]?)(toast)+(\()/mg
+      code = code.replace fix_scope, "$1this.$2$3"
+
+      # and finally execute it
+      eval code

-    if params.nature.browser?
-      params.nature.browser.minify ?= true
+  toast:( config = {} )=>

-      if params.nature.browser.release is null
-        error 'Release path not informed in config.'
-        return process.exit()
+    # normalize and validate all options in `toaster.coffee`
+
+    # ...: exclude - optional
+    config.exclude ?= []
+
+    # ...: bare - optional
+    config.bare ?= true # boolean
+
+    # ...: minify - optional
+    config.minify ?= true # boolean
+
+    # ...: dirs - mandatory
+    if config.dirs is null or config.dirs.length is 0
+      msg = 'Check your `toaster.coffee` config file, you need to inform at'
+      msg += 'least one dir in your config file.'
+      return error msg
+
+    for dir, i in config.dirs
+      dir = path.join @basepath, dir
+      if fs.existsSync dir
+        config.dirs[i] = dir
       else
-        dir = path.dirname params.nature.browser.release
-        unless fs.existsSync (path.join @basepath, dir)
-          error "Release folder does not exist:\n\t#{dir.yellow}"
-          return process.exit()
+        msg = 'Check your `toaster.coffee` config file, informed dir doens\'t '
+        msg += 'exist:\n\tleast one dir in your config file.'
+        return error msg

-    # configuration object shared between builders
-    if params.debug
-      debug = path.join @basepath, params.debug
+    # ...: release_dir - mandatory
+    if config.release_dir is null
+      msg = 'Check your `toaster.coffee` config file, `release_dir` must to'
+      msg += 'be informed.'
+      return error msg
     else
-      debug = null
+      config.release_dir ?= path.join @basepath, config.release_dir
+      unless fs.existsSync (path.dirname config.release_dir)
+        error "Release dir doesn't exist:\n\t#{dir.yellow}"
+        return null

-    config =
-        # src folders
-        src_folders: []
+    # ...:optimize - optional
+    if config.optimize?

-        # options
-        nature: params.nature
-        exclude: params.exclude ? []
-        bare: params.bare ? true
-        release: path.join @basepath, params.release
+      # ...: vendors - optional
+      if config.optimize.vendors?
+        for vname, vurl of config.optimize.vendors
+
+          continue if /^http/m.test vurl

+          vpath = path.join @basepath, vurl

-    # # compute vendors full path
-    # for v, i in config.vendors
-    #   vpath = config.vendors[i] = (path.resolve v)
-    #   if (path.resolve vpath) isnt vpath
-    #     config.vendors[i] = path.join @basepath, v
+          if fs.existsSync vpath
+            config.optimize.vendors[vname] = vpath
+          else

-    unless srcpath instanceof Object
-      srcpath = path.resolve( path.join @basepath, srcpath )
-      config.src_folders.push
-        path: srcpath
-        alias: params.alias || null
-
-    if params.folders?
-      for folder, alias of params.folders
-        if (path.resolve folder) != folder
-          folder = path.join @basepath, folder
-        config.src_folders.push {path: folder, alias: alias}
-
-    for item in config.src_folders
-      unless fs.existsSync item.path
-        error "Source folder doens't exist:\n\t#{item.path.red}\n" +
-            "Check your #{'toaster.coffee'.yellow} and try again." +
-            "\n\t" + (path.join @basepath, "toaster.coffee" ).yellow
-        return process.exit()
-
+            # error "Local vendor not found. #{dir}\nCheck your config."
+            msg = 'Check your `toaster.coffee` config file, local vendor was '
+            msg += 'not found:\n\t' + vpath
+            return error msg
+
     builder = new Builder @toaster, @toaster.cli, config
-    @builders.push builder
+    @builders.push builder


dont move shows to the top

dont move shows to the top

--- apps/search/collections/google_search_results.coffee
+++ apps/search/collections/google_search_results.coffee
@@ -21,6 +21,6 @@
   moveMatchResultsToTop: (query) ->
     models = @models
     for item, index in @models
-      if item.get('display').toLowerCase() == query.toLowerCase()
+      if item.get('display_model') != 'show' && item.get('display').toLowerCase() == query.toLowerCase()
         models.splice(0, 0, models.splice(index, 1)[0] );
     models


fix typo

fix typo

--- source/javascripts/data/WizardContent.coffee
+++ source/javascripts/data/WizardContent.coffee
@@ -9,7 +9,7 @@
       {
         content: [
           "
This tool will help you to easily create a customized Wikipedia classroom assignment and customized syllabus for your course.
"
-          "
When you’re finished, you'll have a ready-to-use lesson plan, with weekly assignments, published directly onto a sandbox page on Wikipedia where you can customeize it even further.
"
+          "
When you’re finished, you'll have a ready-to-use lesson plan, with weekly assignments, published directly onto a sandbox page on Wikipedia where you can customize it even further.
"
           "
Let’s start by filling in some basics about you and your course:
"
         ]
       }