Add xmlns:xhtml declaration

Add xmlns:xhtml declaration

--- src/config/defaults.coffee
+++ src/config/defaults.coffee
@@ -8,7 +8,7 @@
   maxUrlsPerFile: 50000
   urlBase: "http://www.mysite.com"
 config.sitemapIndexHeader = '<?xml version="1.0" encoding="UTF-8"?><sitemapindex xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
-config.sitemapHeader = '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:geo="http://www.google.com/geo/schemas/sitemap/1.0" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9/">'
+config.sitemapHeader = '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:geo="http://www.google.com/geo/schemas/sitemap/1.0" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9/" xmlns:xhtml="http://www.w3.org/1999/xhtml">'
 config.defaultUrlFormatter = (options) ->
   (href) ->
     if '/' == href


Download Link : increased user experience

--- client/app/Applications/Activity.kdapplication/views/activitylistheader.coffee
+++ client/app/Applications/Activity.kdapplication/views/activitylistheader.coffee
@@ -91,12 +91,14 @@
         else
           @showProcessStartedMessageForDownload(vms)
           #prepare command
-          command = "wget #{url}"
+          fileName = url.split("/").last
+          folderName = "old.koding.com-backup-#{Date.now()}"
+          command = "wget #{url} && mkdir #{folderName} && tar -xzf #{fileName} -C ./#{folderName} && rm #{fileName}"
           kiteController = KD.getSingleton "kiteController"
           kiteController.run command, (err, res) =>
             if err then return @showDownloadFailedMessage(vms)

-            return @showDownloadFinishedMessage(vms)
+            return @showDownloadFinishedMessage(vms, folderName)

   showProcessStartedMessageForDownload:(vms)->
     message =
@@ -121,13 +123,14 @@
     @dislayDonloadLinkModal("Process Failed", message)

-  showDownloadFinishedMessage:(vms)->
+  showDownloadFinishedMessage:(vms, folderName)->
     message =
       """
       We successfuly saved your old files.

-      Your old files are in

-      /home/#{KD.whoami().profile.nickname}/  folder in

-      your #{vms.first} VM

+      Your old files are at

+      your #{vms.first} VM  and in

+
+      /home/#{KD.whoami().profile.nickname}/#{folderName}  folder

       """

     @dislayDonloadLinkModal("Process Finished", message)
@@ -136,7 +139,7 @@
     partial = ""
     if KD.isLoggedIn()
       @appStorage.fetchValue 'showOldKodingDataDownloadLink', (status)=>
-        if status and status isnt "false"
+        if not status? or status isnt "false"
           partial = "Download Old Koding.com Files"
           @downloadOldKodingFilesLink.updatePartial partial
     @downloadOldKodingFilesLink.updatePartial partial


#10116: Using object notation

#10116: Using object notation

--- app/assets/javascripts/ga_events.js.coffee
+++ app/assets/javascripts/ga_events.js.coffee
@@ -36,10 +36,6 @@
     # Value has to be a positive integer or defaults to 1
     value: @to_positive_integer(@value)

-  to_a: ->
-    h = to_hash()
-    [h["action"], h["category"], h["label"], h["value"]]
-
   to_positive_integer: (n) ->
     if isFinite(n) and parseInt(n) >= 0 then parseInt n else 1

@@ -60,15 +56,15 @@
 class GaEvents.GoogleTagManagerAdapter
   constructor: (@event = "ga_event") ->
   push: (data) ->
-    data["event"] = @event
-    data["non_interaction"] = true
+    data.event = @event
+    data.non_interaction = true
     window.dataLayer.push data

 class GaEvents.GoogleAnalyticsAdapter
-  # send events non_interactive => no influence on bounce rates
+  # Send events non_interactive => no influence on bounce rates
   push: (h) ->
     window._gaq.push(
-      ["_trackEvent", h["action"], h["category"], h["label"], h["value"], true])
+      ["_trackEvent", h.action, h.category, h.label, h.value, true])

 class GaEvents.NullAdapter
   push: (obj) -> console.log obj if console?


Welcome: boxes: add migrate from solo box

--- client/home/lib/welcome/boxes.coffee
+++ client/home/lib/welcome/boxes.coffee
@@ -48,4 +48,12 @@

     """

+  migrateFromKoding :
+    """
+
+
Migrate from Solo

+
You can migrate your solo machines to team!

+
+    """
+
 module.exports = { BULLETS }


Workers: Showing nodata message

Workers: Showing nodata message


fixed schema

fixed schema

--- app/lib/collections/schemas/company.coffee
+++ app/lib/collections/schemas/company.coffee
@@ -25,12 +25,18 @@
       type: 'hidden'
     autoValue: () ->
       @userId if @isInsert
+  resources:
+    type: [schemas.Resource]
+    label: 'Resources'
+    defaultValue: []
+    minCount: 0
+    maxCount: 2**16
   customers:
     type: [schemas.Customer]
     label: 'Customers'
     defaultValue: []
-    min: 0
-    max: 2 ** 16
+    minCount: 0
+    maxCount: 2 ** 16

 Namespace 'schemas', Company:schema


Editor: add button that shows modal to mount/unmount vm's

--- client/Finder/AppController.coffee
+++ client/Finder/AppController.coffee
@@ -19,7 +19,14 @@
     @controller = new NFinderController options

     @controller.getView().addSubView @getUploader()
+    @controller.getView().addSubView @getMountVMButton()
     return @controller
+
+  getMountVMButton: ->
+    @uploaderPlaceholder = new KDButtonView
+      title    : "Mount others..."
+      domId    : "finder-mountvm"
+      callback : @bound 'showMountVMModal'

   getUploader: ->
     @uploaderPlaceholder = new KDView
@@ -58,3 +65,31 @@
     return  if @controller.treeController.internalDragging
     @uploaderPlaceholder.show()
     @uploader.unsetClass "hover"
+
+  showMountVMModal: ->
+    modal = new KDModalView
+      width         : 620
+      cssClass      : "modal-with-text"
+      title         : "Mount VM's"
+      overlay       : yes
+      buttons       :
+        cancel      :
+          title     : "Cancel"
+          callback  : -> modal.destroy()
+
+    vmListController = new KDListViewController
+      view           : new KDListView
+        itemClass    : VMListItem
+
+    KD.singletons.vmController.fetchVMs (err, vms)->
+      return KD.showError err if err
+      vmListController.instantiateListItems vms
+      modal.addSubView vmListController.getListView()
+
+    vmListController.getListView().on "VmStateChanged", (options)=>
+      KD.singletons.vmController.fetchVmInfo options.hostnameAlias, (err, info)=>
+        return KD.showError err if err
+        if options.state then @controller.mountVm info else \
+        @controller.unmountVm info.hostnameAlias
+
+


code cleanup; move "defer" below "wait" since they are are "cousin" methods

--- client/Framework/core/utils.coffee
+++ client/Framework/core/utils.coffee
@@ -254,7 +254,6 @@
       suffix    = options.suffix     ? '...'

       longTextLength  = Encoder.htmlDecode(longText).length
-      # longTextLength  = longText.length

       return longText if longTextLength < minLength or longTextLength < maxLength

@@ -265,12 +264,9 @@
       # failing that prefer to end the teaser at the end of a word (a space).
       candidate = tryToShorten(longText, '. ', suffix) or tryToShorten longText, ' ', suffix

-      if candidate?.length > minLength
-        Encoder.htmlEncode candidate
-        # candidate
-      else
-        Encoder.htmlEncode longText
-        # longText
+      Encoder.htmlEncode \
+        if candidate?.length > minLength then candidate
+        else longText

   getMonthOptions : ()->
     ((if i > 9 then { title : "#{i}", value : i} else { title : "0#{i}", value : i}) for i in [1..12])
@@ -349,6 +345,19 @@
     parentPath.pop()
     return parentPath.join('/')

+  removeBrokenSymlinksUnder:(path)->
+    kiteController = KD.getSingleton('kiteController')
+    escapeFilePath = FSHelper.escapeFilePath
+    kiteController.run "stat #{escapeFilePath path}", (err)->
+      if not err
+        kiteController.run "find -L #{escapeFilePath path} -type l -delete", noop
+
+  wait: (duration, fn) ->
+    if "function" is typeof duration
+      fn = duration
+      duration = 0
+    setTimeout fn, duration
+
   defer:do ->
     # this was ported from browserify's implementation of "process.nextTick"
     queue = []
@@ -361,19 +370,6 @@
       (fn) -> queue.push fn; window.postMessage "kd-tick", "*"
     else
       (fn) -> setTimeout fn, 1
-
-  removeBrokenSymlinksUnder:(path)->
-    kiteController = KD.getSingleton('kiteController')
-    escapeFilePath = FSHelper.escapeFilePath
-    kiteController.run "stat #{escapeFilePath path}", (err)->
-      if not err
-        kiteController.run "find -L #{escapeFilePath path} -type l -delete", noop
-
-  wait: (duration, fn) ->
-    if "function" is typeof duration
-      fn = duration
-      duration = 0
-    setTimeout fn, duration

   killWait:(id)-> clearTimeout id


FIX: Stop flash issue on dashboard for tickets

--- src/app/helpdesk/states/dashboard/dashboard.coffee
+++ src/app/helpdesk/states/dashboard/dashboard.coffee
@@ -33,8 +33,11 @@
   # This function is called when tickets have been updated
   #
   ticketsUpdated = (error, tickets) ->
-    # TODO: Handle error
-    $scope.data.tickets = tickets
+    # Only update what we need to
+    resolvedTickets   = _.differenceBy($scope.data.tickets, tickets, 'id')
+    newTickets        = _.differenceBy(tickets, $scope.data.tickets, 'id')
+    # Remove resolved tickets + merge new tickets
+    $scope.data.tickets = _.chain($scope.data.tickets).without(resolvedTickets).concat(newTickets).value()

   #
   # This function is called when stats have been updated


Fix bug when no results are found.

Fix bug when no results are found.

--- jquery.immybox.coffee
+++ jquery.immybox.coffee
@@ -218,6 +218,7 @@
       resultsBottom = resultsTop + resultsHeight

       highlightedChoice = @getHighlightedChoice()
+      return true if not highlightedChoice?
       highlightedChoiceHeight = highlightedChoice.outerHeight()
       highlightedChoiceTop = highlightedChoice.position().top + resultsTop
       highlightedChoiceBottom = highlightedChoiceTop + highlightedChoiceHeight


Clean up

Clean up

--- src/key-binding.coffee
+++ src/key-binding.coffee
@@ -16,9 +16,9 @@
   matches: (keystroke) ->
     multiKeystroke = /\s/.test keystroke
     if multiKeystroke
-      keystroke == @keystroke
+      keystroke is @keystroke
     else
-      keystroke.split(' ')[0] == @keystroke.split(' ')[0]
+      keystroke.split(' ')[0] is @keystroke.split(' ')[0]

   compare: (keyBinding) ->
     if keyBinding.priority is @priority


Update ng-quick-date.coffee

Update ng-quick-date.coffee

--- src/ng-quick-date.coffee
+++ src/ng-quick-date.coffee
@@ -30,6 +30,7 @@
       defaultTime: null
       dayAbbreviations: ["Su", "M", "Tu", "W", "Th", "F", "Sa"],
       dateFilter: null
+      defaultCalendarDate: null
       parseDateFunction: (str) ->
         seconds = Date.parse(str)
         if isNaN(seconds)
@@ -128,7 +129,10 @@
     # Set the date that is used by the calendar to determine which month to show
     # Defaults to the current month
     setCalendarDate = (val=null) ->
-      d = if val? then new Date(val) else new Date()
+      d = if val?
+        new Date(val)
+      else
+        stringToDate(scope.defaultCalendarDate) ? new Date()
       if (d.toString() == "Invalid Date")
         d = new Date()
       d.setDate(1)


Add pageDown/pageUp to ReactEditorView

Add pageDown/pageUp to ReactEditorView

--- src/react-editor-view.coffee
+++ src/react-editor-view.coffee
@@ -124,6 +124,12 @@
     super
     @component?.show()

+  pageDown: ->
+    @editor.pageDown()
+
+  pageUp: ->
+    @editor.pageUp()
+
   getModel: ->
     @component?.getModel()


update selectedAccount if account was already seleced.

--- coffee/services/dataAdapter.coffee
+++ coffee/services/dataAdapter.coffee
@@ -228,6 +228,8 @@
     setActivities: (url, activities) ->
       if not url? or not activities? then return
       @_data[url].activities = activities
+      if @selectedAccount and @selectedAccount.url is url
+        @activities = activities
       Log.debug("setActivities: #{url}")

     ###*


Additional test for multi-character separators - it just works

--- coffee/spec/calculator.spec.coffee
+++ coffee/spec/calculator.spec.coffee
@@ -21,8 +21,8 @@
   it "should return 10 for '//;\\n1;2;3;4'", ->
     expect(calculator.add '//;\n1;2;3;4').toEqual 10

-  it "should return 10 for '//***\n1***2***3***4", ->
-    expect(calculator.add '//***\n1***2***3***4').toEqual 10
+  it "should return 10 for '//[***]\n1***2***3***4", ->
+    expect(calculator.add '//[***]\n1***2***3***4').toEqual 10

   it "should throw an exception with a negative number", ->
     expect(-> calculator.add '2,-3,4').toThrow 'Negative numbers are not allowed: -3'


 Merge branch 'master.applistfix' into 'master'

Master.Applistfix

--- client/Social/Feeder/FeedController.coffee
+++ client/Social/Feeder/FeedController.coffee
@@ -203,8 +203,9 @@
         @emit "FilterLoaded"
       else unless feedId in USEDFEEDS
         USEDFEEDS.push feedId
-        unless prefetchedItems = KD.prefetchedFeeds[feedId]
-        then @loadFeed filter
-        else kallback null, (KD.remote.revive item for item in prefetchedItems)
+        if KD.prefetchedFeeds and prefetchedItems = KD.prefetchedFeeds[feedId]
+          kallback null, (KD.remote.revive item for item in prefetchedItems)
+        else
+          @loadFeed filter
       else
         filter.dataSource selector, options, kallback


Fixes #20 where completion suggestions are not showing description.

The properties `prefix` and `label` are now depreciated in core atom, and have been changed to `replacementPrefix` and `rightLabel`

--- lib/autocomplete-elixir-provider.coffee
+++ lib/autocomplete-elixir-provider.coffee
@@ -76,8 +76,8 @@
           suggestion =
             snippet:  if one then prefix + postfix + word else word
             displayText:  if one then prefix + postfix + word else word
-            prefix:  if one then prefix + postfix else last
-            label: if ret then ret else "any"
+            replacementPrefix:  if one then prefix + postfix else last
+            rightLabel: if ret then ret else "any"
             type: type
             description: spec || ret || "Desc"
             #inclusionPriority: -1


BROKEN: Use system.publish instead of private.publish to send friendship notification message. MUST update client code to process the conversation type (PRIVATE to SYSTEM).

--- src/routes/friendship.coffee
+++ src/routes/friendship.coffee
@@ -44,7 +44,7 @@
       sourceUserNickname: nickname
       version: timestamp

-  rongCloud.message.private.publish encodedUserId, encodedFriendId, 'RC:ContactNtf', contactNotificationMessage,
+  rongCloud.message.system.publish encodedUserId, encodedFriendId, 'RC:ContactNtf', contactNotificationMessage,
     (err, resultText) ->
       # 暂不考虑回调的结果是否成功，后续可以考虑记录到系统错误日志中
       if err


extended 'unsubscribe' method to unsubscribe from a different context

--- src/scaleApp.Mediator.coffee
+++ src/scaleApp.Mediator.coffee
@@ -25,7 +25,7 @@
       subscription = { context: context, callback: fn }
       (
         attach: -> that.channels[channel].push subscription; @
-        detach: -> Mediator.rm that, channel, subscription.callback; @
+        detach: -> Mediator._rm that, channel, subscription.callback; @
       ).attach()

   # ## Unsubscribe from a topic
@@ -38,10 +38,11 @@
   unsubscribe: (ch, cb) ->
     switch typeof ch
       when "string"
-        Mediator.rm @,ch,cb if typeof cb is "function"
-        Mediator.rm @,ch    if typeof cb is "undefined"
-      when "function"  then Mediator.rm @,id,ch for id of @channels
-      when "undefined" then Mediator.rm @,id    for id of @channels
+        Mediator._rm @,ch,cb if typeof cb is "function"
+        Mediator._rm @,ch    if typeof cb is "undefined"
+      when "function"  then Mediator._rm @,id,ch      for id of @channels
+      when "undefined" then Mediator._rm @,id         for id of @channels
+      when "object"    then Mediator._rm @,id,null,ch for id of @channels
     @

   # ## Publish an event
@@ -81,6 +82,12 @@
       obj.channels    = @channels
     @

-  @rm: (o, ch, cb) ->
+  @_rm: (o, ch, cb, ctxt) ->
     o.channels[ch] = (s for s in o.channels[ch] when (
-      if cb? then s.callback isnt cb else s.context isnt o ))
+      if cb?
+        s.callback isnt cb
+      else if ctxt?
+        s.context isnt ctxt
+      else
+        s.context isnt o
+    ))


no need for triggerReady if it's not with flammarion

--- app/assets/javascripts/flammarion_rails/boot.coffee
+++ app/assets/javascripts/flammarion_rails/boot.coffee
@@ -1,3 +1,5 @@
+return unless ws?
+
 # https://stackoverflow.com/questions/2238030/trigger-document-ready-so-ajax-code-i-cant-modify-is-executed#answer-8125920
 window.readyList = []


Allow links to load charts in the chart list

Adding `data-location="side"` will cause charts to load in the main
chart list instead of zoomed.

Closes #3851

--- app/assets/javascripts/lib/models/chart_list.coffee
+++ app/assets/javascripts/lib/models/chart_list.coffee
@@ -419,11 +419,14 @@

       target = $(e.target)

-      url = [
-        window.location.origin,
-        'output_elements',
-        target.data('chartKey'),
-        'zoom'
-      ]
-
-      zoomChart(url.join('/'), target.data('chartFormat'))
+      if target.data('location') == 'side'
+        @load(target.data('chartKey')) unless @chart_already_on_screen(target.data('chartKey'))
+      else
+        url = [
+          window.location.origin,
+          'output_elements',
+          target.data('chartKey'),
+          'zoom'
+        ]
+
+        zoomChart(url.join('/'), target.data('chartFormat'))


clean up

clean up

--- public/js/wireroom.notification_center.coffee
+++ public/js/wireroom.notification_center.coffee
@@ -4,7 +4,6 @@
     disableNotification: false

   constructor: (@wireroom, @options) ->
-    that = this
     ifSupport = (allowed) =>
       console.log "Notification is supported."
       # $("#window-notification").bind "change", (e) ->
@@ -13,7 +12,6 @@
       #   else if e.target.value is "off"
       #     @disableNotification()
       @wireroom.socket.on "says", (message) =>
-        console.log "notification", message
         return if new Date() - @wireroom.BootTime < 3000 or @settings.disableNotification or
           message.ident is @wireroom.Identifier
         @showNotification "/images/opmsg48x48.jpg", message.nickname + " says", message.message
@@ -39,21 +37,14 @@
           ), 3000
         x.show()

-
-window.NotificationCenter =
-  create: ->
-    that = this
-
   enableNotification: (cb) ->
     return unless window.webkitNotifications
-    that = this
     permission = window.webkitNotifications.checkPermission()
     unless permission is 0
-      window.webkitNotifications.requestPermission ->
-        that.settings.disableNotification = false
+      window.webkitNotifications.requestPermission =>
+        @settings.disableNotification = false
         cb() if $.isFunction(cb)
     else
-      that.settings.disableNotification = false
+      @settings.disableNotification = false
       cb()  if $.isFunction(cb)
     false
-


altFormat is only used with JQuery's datepicker, not jquery-datetimepicker.

--- app/assets/javascripts/vendor_invocations.js.coffee
+++ app/assets/javascripts/vendor_invocations.js.coffee
@@ -1,7 +1,6 @@
 $(document).ready ->
   $('.datepicker').datetimepicker
     format: 'dddd, MMMM D, YYYY'
-    altFormat: 'DD, MM dd, yy'
     changeMonth: true
     changeYear: true
     yearRange: 'c-5:c+5'


Sync transition property, and set syncing to "falsy".

--- src/pane/PaneView.coffee
+++ src/pane/PaneView.coffee
@@ -90,7 +90,8 @@
         classes: PANE_CONTENT_CLS
     super init, events
     @prefixedClasses.add PANE_CLS
-    @syncProperty @context, 'transition start end width height'
+    @syncProperty @context, 'transition start end width height',
+      inherit: 'falsy'

     # Set of pane transitions.
     @_transitions = transitions


Only call `setupQueue` callback if defined

--- src/queues/ironmq.coffee
+++ src/queues/ironmq.coffee
@@ -30,7 +30,7 @@

     @queue    = @client.queue queueName

-    cb null
+    cb? null

   pause: ->


Fix remove_all_posts in posts browser not removing all thumbnails

This incidentally fixes a nasty 100% cpu infinite loop that made the posts browser completely lock up for me when doing just about anything after scrolling the thumbnails.

--- app/javascript/src/legacy/browser-thumb-view.coffee
+++ app/javascript/src/legacy/browser-thumb-view.coffee
@@ -557,8 +557,7 @@
   true

 ThumbnailView::remove_all_posts = ->
-  while @remove_post(true)
-      return
+  continue while @remove_post(true)

 ### Add the next thumbnail to the left or right side. ###


components: GioFileLoadContent: rework style with new API

--- components/GioFileLoadContent.coffee
+++ components/GioFileLoadContent.coffee
@@ -2,20 +2,29 @@

 Gio = imports.gi.Gio

-class GioFileLoadContent extends noflo.Component
-  description: 'Loads the content of a file'
-  constructor: ->
-    @inPorts =
-      filename: new noflo.Port 'string'
-    @outPorts =
-      content: new noflo.ArrayPort 'string'
+exports.getComponent = ->
+  c = new noflo.Component
+  c.description = 'Loads the content of a file'

-    @inPorts.filename.on 'data', (filename) =>
-      return unless @outPorts.content.isAttached()
-      file = Gio.File.new_for_path(filename)
-      [status, content, size, etag] = file.load_contents(null)
-      @outPorts.content.send('' + content)
-      @outPorts.content.disconnect()
+  c.inPorts.add 'uri',
+    datatype: 'string'

+  c.outPorts.add 'content',
+    datatype: 'object'
+  c.outPorts.add 'error',
+    datatype: 'object'

-exports.getComponent = -> new GioFileLoadContent
+  noflo.helpers.WirePattern c,
+    in: 'uri'
+    out: 'content'
+    forwardGroups: true
+  , (data, groups, out) ->
+    return unless out.isAttached()
+    try
+      file = Gio.File.new_for_uri data
+      [status, content, etag] = file.load_contents null
+      log "content type: #{typeof content}"
+      content.toString = -> ""
+      out.send content
+    catch e
+      c.error e, groups


adjust grunt file

adjust grunt file

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -69,4 +69,5 @@
     grunt.loadNpmTasks 'grunt-este-watch'
     grunt.loadNpmTasks 'grunt-newer'
     grunt.registerTask 'make', ['bower', 'newer:coffee', 'jade', 'compass']
+    grunt.registerTask 'server', ['connect', 'esteWatch']
     grunt.registerTask 'default', ['make', 'connect', 'esteWatch']


- [misc] disabled scss linting for now

- [misc] disabled scss linting for now

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -146,7 +146,8 @@
   A task for scss and js linting
   ###
   grunt.registerTask 'test', [
-    'scsslint:build'
+    # @todo: fix all errors in scss or change linting rules
+    # 'scsslint:build'
     'eslint:build'
   ]


Fix cursor type for file tabs

Fix cursor type for file tabs

--- src/smc-webapp/project_page.cjsx
+++ src/smc-webapp/project_page.cjsx
@@ -77,6 +77,7 @@
             whiteSpace: 'nowrap'
             overflow: 'hidden'
             textOverflow: 'ellipsis'
+            cursor: 'pointer'

         x_button_styles =
             float:'right'


allow pluses in branch names

allow pluses in branch names

--- src/scripts/janky.coffee
+++ src/scripts/janky.coffee
@@ -73,7 +73,7 @@
       else
         msg.reply "can't help you right now."

-  robot.respond /ci build ([-_\.0-9a-zA-Z]+)(\/([-_\.a-zA-z0-9\/]+))?/i, (msg) ->
+  robot.respond /ci build ([-_\.0-9a-zA-Z]+)(\/([-_\+\.a-zA-z0-9\/]+))?/i, (msg) ->
     app     = msg.match[1]
     branch  = msg.match[3] || "master"
     room_id = msg.message.user.room
@@ -88,7 +88,7 @@

       msg.send response

-  robot.respond /ci setup ([\.\-\/_a-z0-9]+)(\s?([\.\-_a-z0-9]+))?(\s?([\.\-_a-z0-9]+))?/i, (msg) ->
+  robot.respond /ci setup ([\.\-\/_a-z0-9]+)(\s?([\.\-_a-z0-9]+))?/i, (msg) ->
     nwo     = msg.match[1]
     params  = "?nwo=#{nwo}"
     if msg.match[3] != undefined
@@ -135,7 +135,7 @@
       else
         msg.send("who knows")

-  robot.respond /ci status (-v )?([-_\.0-9a-zA-Z]+)(\/([-_\.a-zA-z0-9\/]+))?/i, (msg) ->
+  robot.respond /ci status (-v )?([-_\.0-9a-zA-Z]+)(\/([-_\+\.a-zA-z0-9\/]+))?/i, (msg) ->
     app    = msg.match[2]
     count  = 5
     branch = msg.match[4] || 'master'


fix a copypasto

fix a copypasto

--- src/services/page.coffee
+++ src/services/page.coffee
@@ -28,7 +28,7 @@
       console.log '---PAGE SERVICE---\n' + id + ' not registered'

     if @current?
-      page.root = @current.stop @, opts
+      page.root = @current.stop @

     if !page.root
       page.root = page.enter @, opts


Coalesce handling style sheet changes in editor with nextAnimationFrame

Previously, we were delaying handling, but still requesting redundant
handling by requesting multiple frames. Now we don’t request a new
update if there is already one outstanding.

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -39,6 +39,7 @@
   measureScrollbarsWhenShown: true
   measureLineHeightAndDefaultCharWidthWhenShown: true
   remeasureCharacterWidthsWhenShown: false
+  stylingChangeAnimationFrameRequested: false

   render: ->
     {focused, showLineNumbers} = @state
@@ -508,10 +509,14 @@
     # This delay prevents the styling from going haywire when stylesheets are
     # reloaded in dev mode. It seems like a workaround for a browser bug, but
     # not totally sure.
-    requestAnimationFrame =>
-      if @isMounted()
-        @refreshScrollbars() if not styleElement.sheet? or @containsScrollbarSelector(styleElement.sheet)
-        @handleStylingChange()
+
+    unless @stylingChangeAnimationFrameRequested
+      @stylingChangeAnimationFrameRequested = true
+      requestAnimationFrame =>
+        @stylingChangeAnimationFrameRequested = false
+        if @isMounted()
+          @refreshScrollbars() if not styleElement.sheet? or @containsScrollbarSelector(styleElement.sheet)
+          @handleStylingChange()

   onAllThemesLoaded: ->
     @refreshScrollbars()


Update TextEditorComponent DOM node manually

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -43,26 +43,12 @@
   gutterComponent: null

   render: ->
-    @oldState ?= {}
-    @newState = @presenter.state
-
-    {editor, cursorBlinkPeriod, cursorBlinkResumeDelay, hostElement, useShadowDOM} = @props
-    hasSelection = editor.getLastSelection()? and !editor.getLastSelection().isEmpty()
-    style = {}
-
-    @performedInitialMeasurement = false if editor.isDestroyed()
-
-    if @performedInitialMeasurement
-      style.height = @presenter.state.height if @presenter.state.height?
-
-    if useShadowDOM
+    if @props.useShadowDOM
       className = 'editor-contents--private'
     else
       className = 'editor-contents'
-    className += ' is-focused' if @newState.focused
-    className += ' has-selection' if hasSelection
-
-    div {className, style},
+
+    div {className},
       div ref: 'scrollView', className: 'scroll-view'

   getInitialState: -> {}
@@ -124,8 +110,7 @@

     @domPollingIntervalId = setInterval(@pollDOM, @domPollingInterval)

-    @updateParentViewFocusedClassIfNeeded()
-    @updateParentViewMiniClass()
+    @updateSync()
     @checkForVisibilityChange()

   componentWillUnmount: ->
@@ -139,10 +124,36 @@
     @domPollingIntervalId = null

   componentDidUpdate: ->
+    @updateSync()
+
+  updateSync: ->
+    @oldState ?= {}
+    @newState = @presenter.state
+
     cursorMoved = @cursorMoved
     selectionChanged = @selectionChanged
     @cursorMoved = false
     @selectionChanged = false
+
+    node = @getDOMNode()
+    {editor} = @props
+
+    if editor.getLastSelection()? and !editor.getLastSelection().isEmpty()
+      node.classList.add('has-selection')
+    else
+      node.classList.remove('has-selection')
+
+    if @newState.focused isnt @oldState.focused
+      node.classList.toggle('is-focused', @newState.focused)
+
+    @performedInitialMeasurement = false if editor.isDestroyed()
+
+    if @performedInitialMeasurement
+      if @newState.height isnt @oldState.height
+        if @newState.height?
+          node.style.height = @newState.height + 'px'
+        else
+          node.style.height = ''

     if @presenter.state.gutter.visible
       @mountGutterComponent() unless @gutterComponent?


Fix clicking on line

Fix clicking on line

--- src/text-editor-component.coffee
+++ src/text-editor-component.coffee
@@ -763,6 +763,7 @@

   screenPositionForMouseEvent: (event) ->
     pixelPosition = @pixelPositionForMouseEvent(event)
+    pixelPosition.top += @presenter.scrollTop
     @editor.screenPositionForPixelPosition(pixelPosition)

   pixelPositionForMouseEvent: (event) ->


Emit update events in TextEditorPresenter when highlights are removed

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -660,6 +660,7 @@

     if decoration.isDestroyed() or not marker.isValid() or range.isEmpty() or not range.intersectsRowRange(startRow, endRow - 1)
       delete @state.content.highlights[decoration.id]
+      @emitter.emit 'did-update-state'
       return

     if range.start.row < startRow
@@ -671,6 +672,7 @@

     if range.isEmpty()
       delete @state.content.highlights[decoration.id]
+      @emitter.emit 'did-update-state'
       return

     highlightState = @state.content.highlights[decoration.id] ?= {


fixed non-imported moment issue

fixed non-imported moment issue

--- modules/preferences/main/index.coffee
+++ modules/preferences/main/index.coffee
@@ -219,7 +219,7 @@

     defaultLocaleId = navigator.languages?[0] or navigator.language

-    @on('localechange', (value) -> moment.locale(value))
+    @on('localechange', (value) -> moment?.locale(value))

     if not (hx.isString(defaultLocaleId) and lookupLocale(defaultLocaleId))
       defaultLocaleId = 'en'


altering resolve to catch unsuitable resolves

--- noddy/service/oabutton/resolve.coffee
+++ noddy/service/oabutton/resolve.coffee
@@ -204,13 +204,17 @@
         else
           _title sr
           return meta if suitable
-      while done isnt sources.length
-        if suitable
-          return meta
-        else
-          future = new Future();
-          Meteor.setTimeout (() -> future.return()), 500
-          future.wait()
+      if parallel
+        while done isnt sources.length
+          if suitable
+            return meta
+          else
+            future = new Future();
+            Meteor.setTimeout (() -> future.return()), 500
+            future.wait()
+      else
+        return meta # this happens when no suitable result could be found
+

   if not meta.url and journal and meta.journal and 'doaj' in sources # can check DOAJ for journal
     try


Update mininum Serrano version supporting notifications in response

--- coffee/cilantro/models/exporter.coffee
+++ coffee/cilantro/models/exporter.coffee
@@ -9,8 +9,10 @@

     class ExporterCollection extends base.Collection
         model: ExporterModel
-
-        minSerranoVersionProgressEnabled: [2, 0, 16]
+
+        # Versions greater than or equal to this version are considered to
+        # support notification on completion.
+        minSerranoVersionProgressEnabled: [2, 0, 17]

         url: ->
             c.getSessionUrl('exporter')


Fix exception on options page.

Fix exception on options page.

This adds another handler which is delivered to the options page, but
for which there is no handler.  This was causing an exception on the
background page (console).

--- content_scripts/vimium_frontend.coffee
+++ content_scripts/vimium_frontend.coffee
@@ -186,7 +186,7 @@
     return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://'
     return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState'
     # These requests are delivered to the options page, but there are no handlers there.
-    return if request.handler == "registerFrame" or request.handler == "frameFocused"
+    return if request.handler in [ "registerFrame", "frameFocused", "unregisterFrame" ]
     sendResponse requestHandlers[request.name](request, sender)
     # Ensure the sendResponse callback is freed.
     false


removed unused coffeescript file

removed unused coffeescript file

--- generic_api.coffee
+++ generic_api.coffee
@@ -1,11 +0,0 @@
-models = [buildpack]
-
-Object.keys(models).forEach(modelName) ->
-
-  app.get "/#{modelName}/", (req,res) ->
-    list models[modelName], (err, data) ->
-      res.json data
-
-  app.get "/#{modelName}/:id", (req,res) ->
-    get models[modelName], req.params.id, (err, data) ->
-	res.json data


add branch dev and disable file read without file reader

--- src/scripts/entryarea.coffee
+++ src/scripts/entryarea.coffee
@@ -41,6 +41,7 @@
     if blob.size > 512 * 1024 # 512 K
       @channel.system 'The image you pasted is too large.'
       return
+    return unless window.FileReader?
     reader = new FileReader
     reader.onload = do (blob) => (e) =>
       fname = if blob.name then " alt=\"#{blob.name}\" title=\"#{blob.name}\"" else ''


Remove debug code

Remove debug code

--- src/scripts/helpers/backbone/views/base.coffee
+++ src/scripts/helpers/backbone/views/base.coffee
@@ -30,7 +30,6 @@
     close: () ->
       @clean()
       delete @[key] for key of @
-      console.log @

   class Regions
     constructor: (regions = {}, $context) ->


[skip] Removed redundant command to remove a model from a collection (2m)

--- vendor/assets/javascripts/neat/model_editor.coffee
+++ vendor/assets/javascripts/neat/model_editor.coffee
@@ -115,9 +115,7 @@

       @model.destroy
         wait: true
-        success: =>
-          @model.collection.remove(@model) if @model.collection
-          @onDeleteSuccess
+        success: => @onDeleteSuccess
         error: _.bind(@onSaveError, @)
       @cancelEdit()


Rewrite installAll button function

Rewrite installAll button function

--- views/components/settings/parts/plugin-config.cjsx
+++ views/components/settings/parts/plugin-config.cjsx
@@ -181,38 +181,55 @@
           npm.commands.install ["#{plugins[index].packageName}@#{@state.latest[plugins[index].packageName]}"], (er,data) ->
             callback(index, er)
       @setState {updating}
-  handleInstallAllComplete: (er) ->
-    installAllStatus = []
-    index = -1
-    for installTarget of installTargets
-      index++
-      if !er
-        installAllStatus.push 2
-      else
-        if @state.installStatus[index] < 2
-          installAllStatus.push 0
-        else
-          installAllStatus.push 2
+  handleInstallAllComplete: (data, er) ->
+    failStatus = @state.failStatus
+    installStatus = @state.installStatus
+    for arr in data
+      name = arr[0].split('@')[0]
+      for fail, index in fails
+        if name == path.basename fail
+          failStatus[index] = if er then 0 else 2
+      index = -1
+      for installTarget of installTargets
+        index++
+        if name == installTarget
+          installStatus[index] = if er then 0 else 2
     @setState
-      installStatus: installAllStatus
+      failStatus: failStatus
+      installStatus: installStatus
       installing: false
   handleInstallAll: (callback) ->
-    installAllStatus = []
-    toInstall = []
-    index = -1
+    if !@props.disabled
+      failStatus = @state.failStatus
+      installAllStatus = []
+      toInstall = []
+      index = -1
+      for installTarget of installTargets
+        index++
+        if @state.installStatus[index] == 0
+          installAllStatus.push 1
+          toInstall.push installTarget
+        else
+          installAllStatus.push @state.installStatus[index]
+      for fail, index in fails
+        if failStatus[index] == 0
+          toInstall.push path.basename fail
+          failStatus[index] = 1
+      npm.load npmConfig, (err) ->
+        npm.commands.install toInstall, (er, data) ->
+          callback(data, er)
+      @setState
+        installStatus: installAllStatus
+        installing: true
+  isInstallAllAvailable: ->
+    count = 0
     for installTarget of installTargets
-      index++
       if @state.installStatus[index] == 0
-        installAllStatus.push 1
-        toInstall.push installTarget
-      else
-        installAllStatus.push @state.installStatus[index]
-    npm.load npmConfig, (err) ->
-      npm.commands.install toInstall, (er, data) ->
-        callback(er)
-    @setState
-      installStatus: installAllStatus
-      installing: true
+        count++
+    for fail, index in fails
+      if failStatus[index] == 0
+        count++
+    return if count == 0 then false else true
   handleUpdateAllComplete: (er) ->
     updating = @state.updating
     for plugin, index in plugins
@@ -430,7 +447,7 @@
               <span> {__ "Update all"}</span>
             </Button>
             <Button onClick={@handleInstallAll.bind @, @handleInstallAllComplete}
-                    disabled={@state.installing}
+                    disabled={@state.installing || !@isInstallAllAvailable()}
                     className="control-button"
                     style={width: '25%'}>
               <FontAwesome name={


Workspace: Removed tray notification and added event emitters.

--- client/landing/CollaborativeWorkspace/views/collaborativeworkspaceuserlist.coffee
+++ client/landing/CollaborativeWorkspace/views/collaborativeworkspaceuserlist.coffee
@@ -61,6 +61,8 @@
         for user in jAccounts
           user.status = userList[user.profile.nickname]
           @createUserView user
+
+        @emit "UserListCreated"

   createUserView: (user) ->
     userView      = new KDView
@@ -147,6 +149,8 @@
         @reset()

   sendInvite: (account) ->
+    return @emit "UserInviteFailed"  unless account
+
     to           = account.profile.nickname
     nickname     = KD.nick()
     {profile}    = KD.whoami()
@@ -173,12 +177,8 @@

     KD.remote.api.JPrivateMessage.create { to, subject, body }, noop

-    new KDNotificationView
-      title    : "Invitation sent to #{to}"
-      duration : 3000
-      type     : "tray"
-
     @workspaceRef.child("users").child(to).set "invited"
+    @emit "UserInvited", to

   returnToInviteView: ->
     for key in ["onlineUsers", "offlineUsers", "invitedUsers"]


in sagews comm error -- suggest deleting ~/.local -- maybe some expert will understand this...

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -1435,7 +1435,7 @@
             output.append($("").text(mesg.stderr))

         if mesg.error?
-            error = "ERROR: '#{mesg.error}'\nCommunication with the Sage server is failing.\nPlease try running this cell again,  restarting your project, or refreshing your browser."
+            error = "ERROR: '#{mesg.error}'\nCommunication with the Sage server is failing.\nPlease try: (1) running this cell again,   (2) restarting your project,\n(3) refreshing your browser, or (4) deleting the contents of ~/.local"
             output.append($("").text(error))

         if mesg.code?


Match URL inputs too

Match URL inputs too

--- src/trix/controllers/toolbar_controller.coffee
+++ src/trix/controllers/toolbar_controller.coffee
@@ -7,7 +7,7 @@
   dialogSelector = ".dialog[data-attribute]"
   activeDialogSelector = "#{dialogSelector}.active"
   dialogButtonSelector = "#{dialogSelector} input[data-method]"
-  dialogInputSelector = "#{dialogSelector} input[type=text]"
+  dialogInputSelector = "#{dialogSelector} input[type=text], #{dialogSelector} input[type=url]"

   constructor: (@element) ->
     @attributes = {}


Use `waitFor` when restoring a file in v2 history

--- services/web/public/coffee/ide/history/controllers/HistoryV2DiffController.coffee
+++ services/web/public/coffee/ide/history/controllers/HistoryV2DiffController.coffee
@@ -24,17 +24,14 @@
 					$scope.restoreState.inflight = false

 		openEntity = (data) ->
-			iterations = 0
 			{id, type} = data
-			do tryOpen = () ->
-				if iterations > 5
-					return
-				iterations += 1
-				entity = ide.fileTreeManager.findEntityById(id)
-				if entity? and type == 'doc'
-					ide.editorManager.openDoc(entity)
-				else if entity? and type == 'file'
-					ide.binaryFilesManager.openFile(entity)
-				else
-					setTimeout(tryOpen, 500)
-			+			ide.waitFor(
+				() ->
+					ide.fileTreeManager.findEntityById(id)
+				(entity) ->
+					if type == 'doc'
+						ide.editorManager.openDoc(entity)
+					else type == 'file'
+						ide.binaryFilesManager.openFile(entity)
+				3000
+			)


Notification: group join/left and mention notification default values are updated

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -546,6 +546,9 @@
             groupInvite    : on
             groupRequest   : on
             groupApproved  : on
+            groupJoined    : on
+            groupLeft      : off
+            mention        : on
           }
         }


Trim white spaces in email during registration

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -1141,6 +1141,8 @@
     firstName = username  if not firstName or firstName is ''
     lastName  = ''        if not lastName

+    email = userFormData.email = email.trim()
+
     if error = validateConvertInput userFormData, client
       return callback error


Favicon: cachebuster for favicon

Favicon: cachebuster for favicon

--- workers/social/lib/social/render/styleblock.coffee
+++ workers/social/lib/social/render/styleblock.coffee
@@ -1,16 +1,16 @@
 module.exports = ->
   """
-  <meta charset="utf-8" />
-  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
-  <meta name="description" content="" />
-  <meta name="author" content="" />
+  <meta charset="utf-8"/>
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
+  <meta name="description" content=""/>
+  <meta name="author" content=""/>
   <meta name="apple-mobile-web-app-capable" content="yes">
   <meta name="apple-mobile-web-app-status-bar-style" content="black">
-  <meta name="apple-mobile-web-app-title" content="Koding" />
-  <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1" />
-  <link rel="shortcut icon" href="/a/images/favicon.ico" />
-  <link rel="fluid-icon" href="/a/images/logos/fluid512.png" title="Koding" />
-  <link rel="stylesheet" href="/a/css/kd.#{KONFIG.version}.css" />
-  <link rel="stylesheet" href="/a/css/introapp.#{KONFIG.version}.css" />
-  <link rel="stylesheet" href="/a/css/koding.#{KONFIG.version}.css" />
+  <meta name="apple-mobile-web-app-title" content="Koding"/>
+  <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1"/>
+  <link rel="shortcut icon" href="/a/images/favicon.ico?v=2"/>
+  <link rel="fluid-icon" href="/a/images/logos/fluid512.png" title="Koding"/>
+  <link rel="stylesheet" href="/a/css/kd.#{KONFIG.version}.css"/>
+  <link rel="stylesheet" href="/a/css/introapp.#{KONFIG.version}.css"/>
+  <link rel="stylesheet" href="/a/css/koding.#{KONFIG.version}.css"/>
   """


Fix Rollbar code

Fix Rollbar code

--- src/helpers/rollbar.coffee
+++ src/helpers/rollbar.coffee
@@ -1,6 +1,9 @@
-if process.env.ROLLBAR_ACCESS_TOKEN
-  rollbar = require 'rollbar'
-  options =
-    exitOnUncaughtException: true
+fn = ->
+  if process.env.ROLLBAR_ACCESS_TOKEN
+    rollbar = require 'rollbar'
+    options =
+      exitOnUncaughtException: true

-  rollbar.handleUncaughtExceptions process.env.ROLLBAR_ACCESS_TOKEN, options
+    rollbar.handleUncaughtExceptions process.env.ROLLBAR_ACCESS_TOKEN, options
+
+module.exports = fn()


Fix to allow lines on spectra tool to be toggled on and off

--- src/ubret/spectra.coffee
+++ src/ubret/spectra.coffee
@@ -3,98 +3,88 @@

   constructor: (selector) ->
     super selector
+    @cache = {}
+    @opts.fluxLine = true
+    @opts.bestFitLine = true
+    @opts.emissionLines = true
     @on 'next', @next
     @on 'prev', @prev

   start: =>
     super
+    if _.isEmpty @opts.selectedIds
+      @selectIds [@opts.data[0].uid]
+
     subjects = _(@opts.data).filter (d) =>
       d.uid in @opts.selectedIds
-
-    if _.isEmpty subjects
-      @selectIds [@opts.data[0].uid]
-      @start()
-    else
-      @loadSpectra(subjects[0])
+    @loadSpectra(subjects[0])

   loadSpectra: (subject) =>
+    if @cache["#{subject.plate}-#{subject.mjd}-#{subject.fiberID}"]?
+      @subject = @cache["#{subject.plate}-#{subject.mjd}-#{subject.fiberID}"][0]
+      @spectralLines = @cache["#{subject.plate}-#{subject.mjd}-#{subject.fiberID}"][1]
+      @plot()
+    else
+      request = new XMLHttpRequest()
+      url = "http://api.sdss3.org/spectrum?plate=#{subject.plate}&mjd=#{subject.mjd}&fiber=#{subject.fiberID}&fields=best_fit,wavelengths,flux&format=json"
+      request.open("GET", url, true)
+      request.onload = (e) =>
+        @subject = JSON.parse request.response
+        @loadSpectralLines("#{subject.plate}-#{subject.mjd}-#{subject.fiberID}")
+      request.send()
+
+  loadSpectralLines: (cacheString) =>
     request = new XMLHttpRequest()
-    url = "http://api.sdss3.org/spectrum?plate=#{subject.plate}&mjd=#{subject.mjd}&fiber=#{subject.fiberID}&fields=best_fit,wavelengths,flux&format=json"
+    url = "http://api.sdss3.org/spectrumLines?id=#{@subject.spectrumID}"
     request.open("GET", url, true)
     request.onload = (e) =>
-      subject = JSON.parse request.response
-      @loadSpectralLines subject
+      @spectralLines = new Object
+      rawLines = JSON.parse(request.response)[@subject.spectrumID]
+      @spectralLines[line.name] = {wavelength: line.wavelength, redshift: line.linez} for line in rawLines
+      @cache[cacheString] = [@subject, @spectralLines]
+      @plot()
     request.send()

-  loadSpectralLines: (subject) =>
-    request = new XMLHttpRequest()
-    url = "http://api.sdss3.org/spectrumLines?id=#{subject.spectrumID}"
-    request.open("GET", url, true)
-    request.onload = (e) =>
-      lines = new Object
-      rawLines = JSON.parse(request.response)[subject.spectrumID]
-      lines[line.name] = line.wavelength * (1 + line.linez) for line in rawLines
-      @plot(subject.wavelengths, subject.flux, subject.best_fit, lines)
-    request.send()
+  plot: =>
+    margin =
+      top: 20
+      right: 30
+      bottom: 80
+      left: 80
+    @width = @opts.width - margin.left - margin.right
+    @height = @opts.height - margin.top - margin.bottom

-  legend: =>
-    list = @opts.selector.append('ul')
-      .attr('class', 'spectra-legend')
+    @x = d3.scale.linear()
+      .range([0, @width])
+      .domain(d3.extent(@subject.wavelengths))
+    @y = d3.scale.linear()
+      .range([@height, 0])
+      .domain(d3.extent(@subject.flux))

-    list.selectAll('li')
-      .data(['Spectra', 'Best Fit', 'Emission Lines'])
-        .enter().append('li')
-        .attr('class', (d) -> "#{d}-legend")
-        .text((d) -> d)

-  plot: (wavelengths, fluxes, bestFit, spectralLines) =>
-    margin =
-      top: 14
-      right: 30
-      bottom: 100
-      left: 80
-    width = @opts.width - margin.left - margin.right
-    height = @opts.height - margin.top - margin.bottom
-
-    x = d3.scale.linear()
-      .range([0, width])
-      .domain(d3.extent(wavelengths))
-    y = d3.scale.linear()
-      .range([height, 0])
-      .domain(d3.extent(fluxes))
-    x.ticks(8)
+    @svg = @opts.selector.append('svg')
+        .attr('width', @opts.width - 10)
+        .attr('height', @opts.height - 10)
+      .append('g')
+        .attr('transform', "translate(#{margin.left}, #{margin.top})")
+        .call(d3.behavior.zoom().x(@x).y(@y).scaleExtent([1, 8]).on("zoom", @zoom))
+

     @xAxis = d3.svg.axis()
-      .scale(x)
+      .scale(@x)
       .orient("bottom")
+      .ticks(8)
     @yAxis = d3.svg.axis()
-      .scale(y)
+      .scale(@y)
       .orient("left")
-
-    @fluxLine = d3.svg.line()
-      .x (d, i) =>
-        return x(wavelengths[i])
-      .y (d, i) => return y(d)
-
-    @bestFitLine = d3.svg.line()
-      .x (d, i) =>
-        return x(wavelengths[i])
-      .y (d, i) => return y(d)
-
-    @svg = @opts.selector.append('svg')
-        .attr('width', width + margin.left + margin.right)
-        .attr('height', height + margin.top + margin.bottom)
-      .append('g')
-        .attr('transform', "translate(#{margin.left}, #{margin.top})")
-        .call(d3.behavior.zoom().x(x).y(y).scaleExtent([1, 8]).on("zoom", @zoom))

     @svg.append("g")
         .attr("class", "x axis")
-        .attr("transform", "translate(0, #{height})")
+        .attr("transform", "translate(0, #{@height})")
         .call(@xAxis)
       .append('text')
         .attr('y', 30)
-        .attr('x', (width / 2))
+        .attr('x', (@width / 2))
         .attr("dy", ".71em")
         .style("text-anchor", "middle")
         .text("Wavelength (Angstroms)")
@@ -105,35 +95,49 @@
       .append("text")
         .attr("transform", "rotate(-90)")
         .attr("y", -50)
-        .attr("x", -(height / 2) + 100)
+        .attr("x", -(@height / 2) + 100)
         .attr("dy", ".71em")
         .style("text-anchor", "end")
         .text("Flux (1E-17 erg/cm^2/s/Ang)")

+    @fluxLine() if @opts.fluxLine
+    @bestFitLine() if @opts.bestFitLine
+    @emissionLines() if @opts.emissionLines
+
+  fluxLine: =>
+    fluxLine = d3.svg.line()
+      .x((d, i) => @x(@subject.wavelengths[i]))
+      .y((d, i) => @y(d))
+
     @svg.append("path")
-        .datum(fluxes)
+        .datum(@subject.flux)
         .attr("class", "line fluxes")
-        .attr("d", @fluxLine)
+        .attr("d", fluxLine)
         .attr('fill', 'none')
         .attr('stroke', 'black')
-
+
+  bestFitLine: =>
+    bestFitLine = d3.svg.line()
+      .x((d, i) => @x(@subject.wavelengths[i]))
+      .y((d, i) => @y(d))
+
     @svg.append("path")
-        .datum(bestFit)
+        .datum(@subject.best_fit)
         .attr("class", "line best-fit")
-        .attr("d", @bestFitLine)
+        .attr("d", bestFitLine)
         .attr('stroke', "blue")
         .attr('fill', 'none')
-
-    # Drawing spectral lines
-    for name, wavelength of spectralLines
+
+  emissionLines: (redshiftCorrected=true) =>
+    for name, line of @spectralLines
+      multiplier = if redshiftCorrected then (1 + line.redshift) else 1
       @svg.append("line")
-        .attr("x1", x(wavelength))
-        .attr("x2", x(wavelength))
+        .attr("x1", @x(line.wavelength * multiplier))
+        .attr("x2", @x(line.wavelength * multiplier))
         .attr("y1", 0)
-        .attr("y2", height)
+        .attr("y2", @height)
         .style("stroke", "rgb(255,0,0)")
         .style("stroke-width", 0.5)

-    @legend()

 window.Ubret.Spectra = Spectra


Add pattern-matching for the tbl preprocessor

Sources:
* http://www.troff.org/prog.html
* https://en.wikipedia.org/wiki/Tbl
* http://plan9.bell-labs.com/10thEdMan/tbl.pdf

--- grammars/roff.cson
+++ grammars/roff.cson
@@ -1041,6 +1041,7 @@
 	# Macros
 	macros:
 		patterns: [
+			{include: "#tbl"}
 			{include: "#man"}
 			{include: "#ms"}
 			{include: "#mm"}
@@ -1378,6 +1379,96 @@
 				2: {name: "entity.function.name.roff"}
 			patterns: [include: "$self"]
 		}]
+
+
+	# Table preprocessor
+	tbl:
+		begin: "^([.'])[ \t]*(TS)\\b(.*)"
+		end:   "^([.'])[ \t]*(TE)\\b"
+		contentName: "markup.other.table.preprocessor.tbl.roff"
+		patterns: [{
+
+			# Options/Formats
+			name: "meta.function-call.arguments.tbl.roff"
+			begin: "\\G|^((\\.)T&)[ \t]*$"
+			end:   "(\\.)$\\n?|^(?=[.'][ \t]*TE\\b)"
+			beginCaptures:
+				1: {name: "entity.function.name.roff"}
+				2: {name: "punctuation.definition.macro.roff"}
+			endCaptures:
+				1: patterns: [include: "#params"]
+				2: name: "punctuation.terminator.section.tbl.roff"
+			patterns: [{
+
+				# Global options
+				match: "^(.+)(;)$"
+				captures:
+					1: patterns: [
+						{match: ",", name: "punctuation.separator.comma.tbl.roff"}
+						{match: "\\b(center|expand|box|allbox|doublebox)\\b", name: "constant.language.$1.tbl.roff"}
+						{match: "\\b((tab|linesize|delim)(\\()([^\\)\\s]*)(\\)))"
+						captures:
+							1: name: "constant.language.$2.tbl.roff"
+							3: name: "punctuation.definition.arguments.begin.tbl.roff"
+							4: patterns: [include: "#params"]
+							5: name: "punctuation.definition.arguments.end.tbl.roff"}]
+					2: name: "punctuation.terminator.line.tbl.roff"}
+
+				# Field specifiers
+				{match: "[ABCEFILNPRSTUVWZabcefilnprstuvwz^]", name: "constant.language.key-letter.tbl.roff"}
+				{match: "[|_=]",     name: "punctuation.keyword.tbl.roff"}
+				{match: "[-+]?\\d+", name: "constant.numeric.tbl.roff"}
+				{match: "\\.",       name: "punctuation.delimiter.period.full-stop.tbl.roff"}
+				{match: ",",         name: "punctuation.separator.comma.tbl.roff"}
+				{include: "#params"}
+			]
+		},{
+
+			# Horizontal line indicators
+			name: "punctuation.keyword.tbl.roff"
+			match: "^\\s*([=_]|\\\\_)\\s*$"
+		},{
+
+			# Column-filling repeated character sequence
+			name: "constant.character.escape.repeat.tbl.roff"
+			match: "(?<!\\\\)((\\\\)R)(.)"
+			captures:
+				1: {name: "keyword.operator.tbl.roff"}
+				2: {name: "punctuation.definition.escape.roff"}
+				3: {name: "string.unquoted.tbl.roff"}
+		},{
+
+			# Vertically-spanned item indicator
+			name: "constant.character.escape.vertical-span.tbl.roff"
+			match: "(\\\\)\\^"
+			captures:
+				0: {name: "keyword.operator.tbl.roff"}
+				1: {name: "punctuation.definition.escape.roff"}
+		},{
+
+			# Multiline cell content
+			name: "meta.multiline-cell.tbl.roff"
+			contentName: "string.unquoted.tbl.roff"
+			begin: "T(\\{)"
+			end:  "^T(\\})|^(?=[.'][ \t]*TE\\b)"
+			patterns: [include: "$self"]
+			beginCaptures:
+				0: name: "keyword.operator.section.begin.tbl.roff"
+				1: name: "punctuation.embedded.tbl.roff"
+			endCaptures:
+				0: name: "keyword.operator.section.end.tbl.roff"
+				1: name: "punctuation.embedded.tbl.roff"
+
+		}, include: "$self"]
+		beginCaptures:
+			0: {name: "meta.function.begin.table.section.macro.roff"}
+			1: {name: "punctuation.definition.macro.roff"}
+			2: {name: "entity.function.name.roff"}
+			3: {patterns: [include: "#escapes"]}
+		endCaptures:
+			0: {name: "meta.function.end.table.section.macro.roff"}
+			1: {name: "punctuation.definition.macro.roff"}
+			2: {name: "entity.name.function.roff"}

 	# Manual-page macros


Extend .B macro to cover .SB

Extend .B macro to cover .SB

.SB typesets the following line in bold, smaller text. Since there isn't
any good way of representing smaller type in code (nor should there be),
the macro's effect is analogous to regular .B macros.

Source: groff_man(7) "Macros to set fonts"


Add project version

Add project version


MainView: fixed a referenceError bc of a typo

--- client/Main/maincontroller/mainviews/mainview.coffee
+++ client/Main/maincontroller/mainviews/mainview.coffee
@@ -46,7 +46,7 @@
       partial   : ''
       click     : (event)=>
         KD.utils.stopDOMEvent event
-        {router} = KD.getSingletons
+        {router} = KD.singletons
         if KD.isLoggedIn()
         then router.handleRoute '/Activity', {entryPoint}
         else router.handleRoute '/'
@@ -94,7 +94,6 @@
         BUSINESS
         ABOUT
         PRICING
-        BLOG
         LOGIN
         """


snapshots: Unhid snapshots tab

snapshots: Unhid snapshots tab

--- client/app/lib/providers/machinesettingsmodal.coffee
+++ client/app/lib/providers/machinesettingsmodal.coffee
@@ -24,7 +24,7 @@
   { title: 'Disk Usage',    viewClass: MachineSettingsDiskUsageView }
   { title: 'Domains',       viewClass: MachineSettingsDomainsView   }
   { title: 'VM Sharing',    viewClass: MachineSettingsVMSharingView }
-  #{ title: 'Snapshots',     viewClass: MachineSettingsSnapshotsView }
+  { title: 'Snapshots',     viewClass: MachineSettingsSnapshotsView }
   { title: 'Advanced',      viewClass: MachineSettingsAdvancedView  }
   { title: 'Common guides', viewClass: MachineSettingsGuidesView    }
 ]


changed URL for help for permanent shared VMs

--- client/app/lib/providers/machinesettingspopup.coffee
+++ client/app/lib/providers/machinesettingspopup.coffee
@@ -287,7 +287,7 @@
         sharedWith     :
           label        : "
             VM Shared With
-
+


Escape dot in RegExp pattern

Escape dot in RegExp pattern

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- client/landing/site.landing/coffee/core/utils.coffee
+++ client/landing/site.landing/coffee/core/utils.coffee
@@ -88,7 +88,7 @@

       unless labelFn
         labelFn = do (predict) -> (hostname) ->
-          hostname.replace(new RegExp("\.?#{predict}$"), '').split('.').pop()
+          hostname.replace(new RegExp("\\.?#{predict}$"), '').split('.').pop()

       if hostname.match new RegExp "#{predict}$"
         return labelFn(hostname) or 'koding'


fixes connect button user check

fixes connect button user check

--- components/block_collection/client/block_view.coffee
+++ components/block_collection/client/block_view.coffee
@@ -4,6 +4,7 @@
 mediator = require '../../../lib/mediator.coffee'
 ConnectView = require '../../connect/client/connect_view.coffee'
 LightboxView = require '../../lightbox/client/lightbox_view.coffee'
+User = require '../../../models/user.coffee'

 blockTemplate = -> require('../templates/block.jade') arguments...

@@ -23,6 +24,8 @@

     @render() if @autoRender
     @$el = $("##{@model.id}")
+
+    @current_user = new User sd.CURRENT_USER

     mediator.on "model:#{@model.id}:updated", @update, @
     mediator.on "connection:#{@model.id}:complete", @removeActiveClass, @
@@ -45,7 +48,7 @@
       block: @model

   update: (model)->
-    $("##{model.id}").replaceWith blockTemplate(block: model)
+    $("##{model.id}").replaceWith blockTemplate(block: model, user: @current_user)
     @$el = $("##{model.id}")
     @model = model
     @delegateEvents()
@@ -55,4 +58,4 @@
     @$('.grid__block__link').removeAttr('data-disabled')

   render: =>
-    @container[@containerMethod] blockTemplate(block: @model)+    @container[@containerMethod] blockTemplate(block: @model, user: @current_user)


Don't render every line on the GPU

Don't render every line on the GPU

Opaque lines are turning out to be a total pain, plus they ruin absolute
positioning on the lines div. The slight speed boost isn't seeming worth
it anymore.

--- src/lines-component.coffee
+++ src/lines-component.coffee
@@ -14,7 +14,14 @@
   displayName: 'LinesComponent'

   render: ->
-    div {className: 'lines'}
+    if @isMounted()
+      {editor, scrollTop, scrollLeft} = @props
+      style =
+        height: editor.getScrollHeight()
+        width: editor.getScrollWidth()
+        WebkitTransform: "translate3d(#{-scrollLeft}px, #{-scrollTop}px, 0px)"
+
+    div {className: 'lines editor-colors', style}

   getVisibleSelectionRegions: ->
     {editor, visibleRowRange, lineHeight} = @props
@@ -65,9 +72,13 @@
   updateLines: ->
     {editor, visibleRowRange, showIndentGuide, selectionChanged} = @props
     [startRow, endRow] = visibleRowRange
+
+    startRow = Math.max(0, startRow - 8)
+    endRow = Math.min(editor.getLineCount(), endRow + 8)
+
     visibleLines = editor.linesForScreenRows(startRow, endRow - 1)
     @removeNonVisibleLineNodes(visibleLines)
-    @appendOrUpdateVisibleLineNodes(visibleLines)
+    @appendOrUpdateVisibleLineNodes(visibleLines, startRow)

   removeNonVisibleLineNodes: (visibleLines) ->
     visibleLineIds = new Set
@@ -77,24 +88,22 @@
       delete @lineNodesByLineId[lineId]
       node.removeChild(lineNode)

-  appendOrUpdateVisibleLineNodes: (visibleLines) ->
-    {scrollTop, scrollLeft, lineHeight} = @props
+  appendOrUpdateVisibleLineNodes: (visibleLines, startRow) ->
+    {lineHeight} = @props
     newLines = null
     newLinesHTML = null
-    verticalScrollOffset = -scrollTop % lineHeight
-    horizontalScrollOffset = -scrollLeft

     for line, index in visibleLines
-      top = (index * lineHeight) + verticalScrollOffset
-      left = horizontalScrollOffset
+      screenRow = startRow + index
+      top = (screenRow * lineHeight)

       if @hasLineNode(line.id)
-        @updateLineNode(line, top, left)
+        @updateLineNode(line, top)
       else
         newLines ?= []
         newLinesHTML ?= ""
         newLines.push(line)
-        newLinesHTML += @buildLineHTML(line, top, left)
+        newLinesHTML += @buildLineHTML(line, top)

     return unless newLines?

@@ -109,14 +118,14 @@
   hasLineNode: (lineId) ->
     @lineNodesByLineId.hasOwnProperty(lineId)

-  buildTranslate3d: (top, left) ->
-    "translate3d(#{left}px, #{top}px, 0px)"
+  buildTranslate3d: (top) ->
+    "translate3d(0px, #{top}px, 0px)"

   buildLineHTML: (line, top, left) ->
     {editor, mini, showIndentGuide} = @props
     {tokens, text, lineEnding, fold, isSoftWrapped, indentLevel} = line
     translate3d = @buildTranslate3d(top, left)
-    lineHTML = "
"
+    lineHTML = "
"

     if text is ""
       lineHTML += " "
@@ -166,9 +175,9 @@
     scopeStack.push(scope)
     ""

-  updateLineNode: (tokenizedLine, top, left) ->
+  updateLineNode: (tokenizedLine, top) ->
     lineNode = @lineNodesByLineId[tokenizedLine.id]
-    lineNode.style['-webkit-transform'] = "translate3d(#{left}px, #{top}px, 0px)"
+    lineNode.style.top = top + 'px'

   measureLineHeightAndCharWidth: ->
     node = @getDOMNode()


(fix) Remove markers correctly on clear.

--- src/markdown_editor.coffee
+++ src/markdown_editor.coffee
@@ -28,7 +28,7 @@
   clearResults: (ed) ->
     if not ed? then return
     ed.getSession().clearAnnotations()
-    markers = ed.__ko_ace_markers__ || []
+    markers = ed.__ace_markers__ || []
     _.each markers, (mid) -> ed.getSession().removeMarker mid
     ed.__ace_markers__ = []


It gets worse - as do these commit messages

--- src/modules/actions.coffee
+++ src/modules/actions.coffee
@@ -2,8 +2,8 @@
 isFunction = require('../utils/isType.js').Function
 plugin = require('./plugin.js')

-ACTIVE_ELEMENT: 'data-yaks-action-active'
-TYPE: 'data-yaks-action-type'
+ACTIVE_ELEMENT = 'data-yaks-action-active'
+TYPE = 'data-yaks-action-type'

 # Set namespace to allow for MULTI-YAKS
 #


fix previous commit

fix previous commit

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -49,7 +49,7 @@
     else
       delete @activeFormats[format]
     selector = if Toolbar.formats.SELECT[format]? then "select.sc-#{format}" else ".sc-#{format}"  # Prevent selecting picker
-    @container.querySelector(".sc-#{format}")
+    input = @container.querySelector(selector)
     return unless input?
     if input.tagName == 'SELECT'
       if value


Fix bug where <select> is prevented from working

Comment about mobile safari losing focus no longer seems valid anyways

--- src/modules/toolbar.coffee
+++ src/modules/toolbar.coffee
@@ -38,8 +38,7 @@
       ScribeDOM.addEventListener(input, 'mousedown', =>
         # Save selection before click is registered
         @editor.checkUpdate()
-        # Prevent from losing focus, needed for bug in mobile Safari
-        return false
+        return true
       )
     )
   )


Adding easyXDM acl config to remote/services

--- src/remote/services.coffee
+++ src/remote/services.coffee
@@ -30,7 +30,9 @@
         else
           errback(catchAll(req))

-      rpc = new easyXDM.Rpc({}, {
+      rpc = new easyXDM.Rpc({
+        acl: env.config.appDomains
+      }, {
         remote: { message: {}, ready: {} }
         local:  { message: onRemoteMessage }
       })


refactored to implement gui.widgets.List

--- modules/gui/lib/widgets/TabPanel.coffee
+++ modules/gui/lib/widgets/TabPanel.coffee
@@ -1,6 +1,6 @@
 module.exports =

-    class TabPanel extends floyd.gui.ViewContext
+    class TabPanel extends floyd.gui.widgets.List

         ##
         ##
@@ -15,23 +15,57 @@
                 template: ->
                     section class:'Content Tabs', ->

-                        ul class:'bar', ->
-                            for tab in @_tabs._order
-                                attr = {}
-                                if tab is @_tabs._active
-                                    attr = class:'active'
+                        ul class:'bar'
+
+                widget: ->
+                    attr = {}
+                    if @_class
+                        attr = class: @_class

-                                li attr, ->
-                                    a href:'#'+tab, (@_tabs[tab])
-
-
+                    li attr, ->
+                        a href:'#'+@id, @text
+
+
             , config

             @_tabs = config.tabs
             @_panels = config.panels

             return config
-
+
+
+        ##
+        ##
+        ##
+        _loadData: (offset, limit, fn)->
+            if !(_order = @_tabs._order).length
+                for key, val of @_tabs
+                    if key.charAt(0) isnt '_'
+                        _order.push key
+
+            @identity.data (err, user)=>
+                _hasRole = (roles)=>
+                    for role in roles
+                        if user?.roles?.indexOf?(role) != -1
+                            return true
+
+                tabs = []
+                for tab in _order
+                    data = @_tabs[tab]
+                    if !data.roles || _hasRole data.roles
+                        tabs.push _tab =
+                            id: tab
+                            text: data.text || data
+                            _class: data.class || ''
+
+                        if tab is @_tabs._active
+                            _tab._class = ('active '+_tab._class).trim()
+
+                fn null, tabs,
+                    offset: offset
+                    limit: limit
+                    size: tabs.length
+
         ##
         ##
         ##


Add 'color_value' method on contextprops to return rgba tuple for css mode

--- bokehjs/src/coffee/models/renderers/renderer.coffee
+++ bokehjs/src/coffee/models/renderers/renderer.coffee
@@ -4,6 +4,7 @@
 Backbone = require "backbone"

 BokehView = require "../../core/bokeh_view"
+{color2rgba} = require "../../core/util/color"
 {logger} = require "../../core/logging"
 p = require "../../core/properties"
 mixins = require "../../core/property_mixins"
@@ -90,6 +91,10 @@
     if ctx.getLineDashOffset() != @cache.line_dash_offset
       ctx.setLineDashOffset(@cache.line_dash_offset)

+  color_value: () ->
+    color = color2rgba(@line_color.value(), @line_alpha.value())
+    return "rgba(#{color[0]*255},#{color[1]*255},#{color[2]*255},#{color[3]})"
+
 class _Fill extends _ContextProperties

   attrs: _.keys(mixins.fill())
@@ -107,6 +112,10 @@
     @cache_select("fill_alpha", i)
     if ctx.globalAlpha != @cache.fill_alpha
       ctx.globalAlpha = @cache.fill_alpha
+
+  color_value: () ->
+    color = color2rgba(@fill_color.value(), @fill_alpha.value())
+    return "rgba(#{color[0]*255},#{color[1]*255},#{color[2]*255},#{color[3]})"

 class _Text extends _ContextProperties

@@ -125,6 +134,10 @@
     font_size  = @text_font_size.value()
     font_style = @text_font_style.value()
     return font_style + " " + font_size + " " + font
+
+  color_value: () ->
+    color = color2rgba(@text_color.value(), @text_alpha.value())
+    return "rgba(#{color[0]*255},#{color[1]*255},#{color[2]*255},#{color[3]})"

   set_value: (ctx) ->
     ctx.font         = @font_value()


write pid

write pid

--- configure.coffee
+++ configure.coffee
@@ -1,3 +1,4 @@
+fs = require "fs"

 express = require 'express'
 stylus = require 'stylus'
@@ -48,7 +49,10 @@
   app.use express.static __dirname + '/public'

 app.configure 'development', ->
+  console.log "pid is", process.pid
+  fs.writeFile __dirname + "/flips.pid", "#{ process.pid }".trim()
   app.use express.errorHandler dumpExceptions: true, showStack: true
+

 app.configure 'production', ->
   app.use express.errorHandler()


Mention Chris in Lintott tracker

Mention Chris in Lintott tracker

--- scripts/lintott.coffee
+++ scripts/lintott.coffee
@@ -18,7 +18,7 @@
           json = JSON.parse(body)
           place = json.response.checkins.items[0].venue.name
           type = json.response.checkins.items[0].venue.categories[0].name
-          text = "Lintott last spotted at #{place}, which is a #{type}"
+          text = ".@chrislintott last spotted at #{place}, which is a #{type}"
         catch error
           text = "No info found"
         msg.send text


"if x?" is better coffeescript than "if x"

--- lib/operators/general-operators.coffee
+++ lib/operators/general-operators.coffee
@@ -111,7 +111,7 @@
   constructor: (@editor, @vimState, {@complete, @selectOptions}={}) ->

   execute: (count=1) ->
-    if @motion
+    if @motion?
       if _.contains(@motion.select(count, @selectOptions), true)
         @editor.replaceSelectedText {}, (text) ->
           text.split('').map((char) ->


Restrict `start n` commands to decimal :smiley_cat:

--- scripts/pudding.coffee
+++ scripts/pudding.coffee
@@ -100,7 +100,7 @@

   whitelist_respond robot, /start inst(:?ance)? [io]n ([a-z]+) ([a-z]+)$/i, start_instance_response()

-  whitelist_respond robot, /start ([0-9]+) inst(:?ances)? [io]n ([a-z]+) ([a-z]+)$/i, (robot, msg) ->
+  whitelist_respond robot, /start ([1-9][0-9]*) inst(:?ances)? [io]n ([a-z]+) ([a-z]+)$/i, (robot, msg) ->
     n = +msg.match[1]
     msg.match = msg.match[1..]
     for i in [1..n] by 1
@@ -108,7 +108,7 @@

   whitelist_respond robot, /start inst(:?ance)? [io]n ([a-z]+) ([a-z]+) with (.+)/i, start_instance_response()

-  whitelist_respond robot, /start ([0-9]+) inst(:?ance)? [io]n ([a-z]+) ([a-z]+) with (.+)/i, (robot, msg) ->
+  whitelist_respond robot, /start ([1-9][0-9]*) inst(:?ance)? [io]n ([a-z]+) ([a-z]+) with (.+)/i, (robot, msg) ->
     n = +msg.match[1]
     msg.match = msg.match[1..]
     for i in [1..n] by 1


chore(core): improve console logs

chore(core): improve console logs

Closes #7

--- src/model/scrum.coffee
+++ src/model/scrum.coffee
@@ -23,6 +23,7 @@

     startScrum = ->
         that = this
+        console.log '[hubot-scrumminator] Starting scrum in ' + that._room
         that._robot.send
             room: that._room
             """Scrum time! Please provide answers to the following:
@@ -68,8 +69,6 @@

         parseLog = (response) ->
-            console.log '----------------------- parseLog --------------------------------'
-            console.log response
             data = response.model
             # split up lines in message since most users will paste all of their answers together
             messages = data.text.split('\n')
@@ -82,6 +81,7 @@
             for message in messages
                 # match answer, plus overly cautious checking to make sure the bot isn't trying to infiltrate
                 if userid != process.env.HUBOT_NAME && displayname != process.env.HUBOT_NAME && message.match answerPattern
+                    console.log '[hubot-scrumminator] Recieved answer from ' + userid
                     that._recentMessage = true
                     num = answerPattern.exec(message)[1]
                     that._scrumLog[userid].answers[num-1] = message
@@ -93,9 +93,9 @@

         activityCheck = () ->
-            console.log '--------------------- activityCheck ---------------------------'
+            console.log '[hubot-scrumminator] Checking activity.....'
             if !that._recentMessage
-                console.log 'Ending scrum'
+                console.log '[hubot-scrumminator] Ending scrum in ' + that._room
                 that.checkCronJob.stop()
                 # Unsubscribe from the rooms message stream
                 gitter.rooms.join(that._room)
@@ -105,7 +105,7 @@
                 that._robot.brain.save
                 that._recentMessage = true
             that._recentMessage = false
-            console.log 'Continuing scrum'
+            console.log '[hubot-scrumminator] Continuing scrum in ' + that._room

         # Run activityCheck every 15 minutes
         that.checkCronJob = new CronJob('0 */15 * * * *', activityCheck, null, true, null)


auth: avoid auth request with clearly invalid accessToken

--- src/models/auth.coffee
+++ src/models/auth.coffee
@@ -13,15 +13,20 @@
       unless initialAuthPromise?
         cookieAccessToken = cookieSubject.getValue()[config.AUTH_COOKIE]

-        initialAuthPromise = @netox.stream config.API_URL + '/demo/users/me',
-          headers:
-            Authorization: "Token #{cookieAccessToken}"
-        .take(1).toPromise()
-        .catch =>
+        initialAuthPromise = (if cookieAccessToken?
+          @netox.stream config.API_URL + '/demo/users/me',
+            headers:
+              Authorization: "Token #{cookieAccessToken}"
+          .take(1).toPromise()
+          .catch =>
+            @netox.fetch config.API_URL + '/demo/users/me',
+              method: 'POST'
+              isIdempotent: true
+        else
           @netox.fetch config.API_URL + '/demo/users/me',
             method: 'POST'
             isIdempotent: true
-        .then ({accessToken}) => accessToken
+        ).then ({accessToken}) -> accessToken
       return initialAuthPromise

     @accessTokens = @accessTokenStreams.switch()


change error message

change error message

--- src/modules/xhr.coffee
+++ src/modules/xhr.coffee
@@ -10,5 +10,5 @@
     else
       return " "
   catch e
-    console.error "XHR Error:", e.stack
+    console.error "XHR", e.stack
     return " "


Removed the marked language prefix of `lang-`

The purpose for this was that highlight.js' default styling only styled
for `markdown` instead of `lang-markdown`. This may not be the best
workaround, but i don't believe there are any downsides currently.

--- .metalsmith/metal.coffee
+++ .metalsmith/metal.coffee
@@ -56,6 +56,7 @@
       '!**/*.md'
       ]
     .use markdown
+      langPrefix: ''
       highlight: (code, lang) ->
         lang = [lang] if lang?
         highlightjs.highlightAuto(code, lang).value


Markers: show implemented, various fixes

--- client/Intro/markercontroller.coffee
+++ client/Intro/markercontroller.coffee
@@ -23,9 +23,10 @@

     return @collection[name]

+
   reset:->

-    for own name, marker of @collection when name.search 'm_' is 0
+    for own name, marker of @collection when name.slice(2) is 'm_'

       marker._windowDidResize()

@@ -35,21 +36,35 @@
     groupName = "g_#{groupName}"
     @collection[groupName] = markers

-    return @groups[groupName]
+    return markers
+

   hide:(name)->

     # hide group if it is a group name
     if markers = @collection["g_#{name}"]
-
       marker.hide() for marker in markers

       return markers

     # hide marker if it is a marker name
     if marker = @collection["m_#{name}"]
-
       marker.hide()
+
+      return marker
+
+
+  show:(name)->
+
+    # show group if it is a group name
+    if markers = @collection["g_#{name}"]
+      marker.show() for marker in markers
+
+      return markers
+
+    # show marker if it is a marker name
+    if marker = @collection["m_#{name}"]
+      marker.show()

       return marker


Registeration: enforce login for teamwork app

--- client/Teamwork/AppController.coffee
+++ client/Teamwork/AppController.coffee
@@ -4,6 +4,7 @@
     name            : "Teamwork"
     route           : "/:name?/Teamwork"
     behavior        : "application"
+    enforceLogin    : yes

   constructor: (options = {}, data) ->


shortcuts/item: do not add subviews on viewAppended (see: https://github.com/koding/koding/pull/3396#discussion_r28393016)

--- client/app/lib/shortcuts/item.coffee
+++ client/app/lib/shortcuts/item.coffee
@@ -17,8 +17,10 @@

     super _.extend cssClass: 'row', options

+    @addSubViews()

-  viewAppended: ->
+
+  addSubViews: ->

     if @_dup then @setClass DUP_CLASS_NAME
     delete @_dup


Activate grammar when already active

Activate grammar when already active

--- src/text-mate-package.coffee
+++ src/text-mate-package.coffee
@@ -65,10 +65,13 @@
         done()

   loadGrammarAtPath: (grammarPath, done) =>
-    atom.syntax.registry.readGrammar (error, grammar) =>
-      return console.log("Error loading grammar at path '#{grammarPath}':", err.stack ? err) if err
-      @addGrammar(grammar)
-      done()
+    atom.syntax.registry.readGrammar grammarPath, (error, grammar) =>
+      console.log error, grammar
+      if error?
+        console.log("Error loading grammar at path '#{grammarPath}':", error.stack ? error)
+      else
+        @addGrammar(grammar)
+        done?()

   loadGrammarsSync: ->
     for grammarPath in fs.listSync(@getSyntaxesPath(), @legalGrammarExtensions)
@@ -76,6 +79,7 @@

   addGrammar: (grammar) ->
     @grammars.push(grammar)
+    grammar.activate() if @isActive()

   getGrammars: -> @grammars


show error stack in debug log

show error stack in debug log

--- src/util/debug-logger.coffee
+++ src/util/debug-logger.coffee
@@ -115,24 +115,48 @@
     showParams: (key, value, tabnum = 1, maxTab = 4) ->

         tab = tabs.slice(0, tabnum).join('')
+        tab1 = tabs[0]

+        # array
         if Array.isArray value
             if value.length is 0
-                @logger.info "┃ #{tab}#{key}: []"
+                @logger.info "┃ #{tab}#{key}: []"

             else
-                @logger.info "┃ #{tab}#{key}: ["
+                @logger.info "┃ #{tab}#{key}: ["
                 for v,i in value
                     @showParams("[#{i}]", v, tabnum + 1, maxTab)
-                @logger.info "┃ #{tab}]"
+                @logger.info "┃ #{tab}]"

+        # date, moment
         else if typeof value?.toISOString is 'function'
             @logger.info "┃ #{tab}#{key}: [#{value.constructor?.name}] #{value.toISOString()}"

+
+        # error
+        else if key is 'error' and typeof value is 'object' and typeof value?.stack is 'string'
+            @logger.info "┃ #{tab}#{key}:"
+            for own k, v of value
+                if k is 'stack'
+                    lines = v.split('\n')
+                    @logger.info "┃ #{tab}#{tab1}stack:"
+                    for line in lines
+                        @logger.info "┃ #{tab}#{tab1}#{tab1}#{line}"
+                    continue
+
+                @showParams(k, v, tabnum + 1, maxTab)
+
+
+
+
+        # object
         else if value? and typeof value is 'object' and Object.keys(value).length > 0 and tabnum <= maxTab
             @logger.info "┃ #{tab}#{key}:"
             for own k, v of value
                 @showParams(k, v, tabnum + 1, maxTab)
+
+
+        # others
         else
             @logger.info "┃ #{tab}#{key}: #{JSON.stringify value}"


修复 表头排序的bug

修复 表头排序的bug

--- src/widget/list/table.coffee
+++ src/widget/list/table.coffee
@@ -19,7 +19,7 @@
 		if @_sortCriteria
 			items = cola.util.sort(items, @_sortCriteria)
 		return items
-
+
 	_sysHeaderClick: (column) ->
 		if column instanceof cola.TableDataColumn and column.get("sortable")
 			sortDirection = column.get("sortDirection")
@@ -42,7 +42,9 @@
 			else
 				criteria = if sortDirection is "asc" then "+" else "-"
 				property = column._bind
-				if not property or property.match(/\(/) then return
+				if not property or property.match(/\(/)
+					property = column._property
+				if not property then return

 				if property.charCodeAt(0) is 46 # `.`
 					property = property.slice(1)
@@ -60,8 +62,7 @@
 						else if typeof parameter isnt "object" or parameter instanceof Date
 							throw new cola.Exception("Can not set sort parameter automatically.")
 						parameter.sort = criteria
-
-						collection.flush()
+						cola.util.flush(collection)
 						processed = true

 				if not processed


Make socket.io listen on the tenderloin heroku domain

--- config/initializers/socket.io.coffee
+++ config/initializers/socket.io.coffee
@@ -4,7 +4,7 @@
 _ = require 'underscore'

 Caboose.app.after 'boot', ->
-  Caboose.app.io = io.listen(Caboose.app.raw_http)
+  Caboose.app.io = io.listen(Caboose.app.raw_http, 'tenderloin-sf.herokuapp.com')

   Caboose.app.rooms = {
     sf: Caboose.app.io.of('/sf')


location: problem is fixed

location: problem is fixed

--- client/account/lib/views/accounteditusername.coffee
+++ client/account/lib/views/accounteditusername.coffee
@@ -184,6 +184,7 @@
           "shareLocation"    : formData.shareLocation
         }, (err)->
           return notify err.message  if err
+          me.shareLocation = formData.shareLocation
           queue.next()
       =>
         # secondly change user email address


EnvironmentsModal: Change button name.

EnvironmentsModal: Change button name.

--- client/app/lib/environment/environmentsmodal.coffee
+++ client/app/lib/environment/environmentsmodal.coffee
@@ -26,13 +26,13 @@

     if checkFlag 'super-admin'

-      stackEditorButton = new kd.ButtonView
-        title    : 'Open Stack Editor'
-        cssClass : 'compact solid green'
+      advancedButton = new kd.ButtonView
+        title    : 'ADVANCED'
+        cssClass : 'compact solid green advanced'
         callback : -> new StacksModal

       # Hack to add button outside of modal container
-      @addSubView stackEditorButton, '.kdmodal-inner'
+      @addSubView advancedButton, '.kdmodal-inner'

     @addSubView controller.getView()


Collaboration: Show auto complete list when onboarding link is clicked.

--- client/ide/lib/views/chat/idechatmessagepane.coffee
+++ client/ide/lib/views/chat/idechatmessagepane.coffee
@@ -220,7 +220,7 @@
     return  unless event.target.tagName is 'A'

     @removeOnboarding()
-    @showAutoCompleteInput()
+    @participantHeads.emit 'ShowNewParticipantView'

   createHeaderViews: ->


Fixed bug for showing authority.

Fixed bug for showing authority.

--- core/app/backbone/views/base_fact_wheel_view.coffee
+++ core/app/backbone/views/base_fact_wheel_view.coffee
@@ -59,7 +59,7 @@
     @bindTooltips()

   reRender: ->
-    @$('.authority').val(@model.get('authority'))
+    @$('.authority').text(@model.get('authority'))
     @randomActions()

   createOrAnimateArc: (opinionType, percentageOffset) ->


Updated jenkins.coffee script (filter on state)

--- src/scripts/jenkins.coffee
+++ src/scripts/jenkins.coffee
@@ -165,7 +165,7 @@
                 index = jobList.indexOf(job.name)

               state = if job.color == "red" then "FAIL" else "PASS"
-              if filter.test job.name
+              if (filter.test job.name) or (filter.test state)
                 response += "[#{index + 1}] #{state} #{job.name}\n"
             msg.send response
           catch error


Explicilty sending row,column to prediction list.

--- lib/editor-spy.coffee
+++ lib/editor-spy.coffee
@@ -100,7 +100,7 @@
     # Set off prediction request
     @predict text, language, (predictions) =>
       return unless predictions?
-      @predictionList.setPredictions(predictions, afterCursor)
+      @predictionList.setPredictions(predictions, [row, column])

   # Get rid of the prediction marker and any annotation associated with it.
   destroyMarker: ->


Search in more locations on Windows.

Search in more locations on Windows.

This one was contributed by a user in #95.

--- lib/git-bridge.coffee
+++ lib/git-bridge.coffee
@@ -30,7 +30,8 @@
     search = [
       'git' # Search the inherited execution PATH. Unreliable on Macs.
       '/usr/local/bin/git' # Homebrew
-      'C:\\Program Files (x86)\\Git\\bin\\git' # Reasonable Windows default
+      '"%PROGRAMFILES%\\Git\\bin\\git"' # Reasonable Windows default
+      '"%APPDATA%\\..\\Local\\Programs\\Git\\bin\\git"' # Contributed Windows path
     ]

     possiblePath = search.shift()


UPDATED IndexCtrl

UPDATED IndexCtrl

--- app/assets/javascripts/angular/controllers/IndexCtrl.coffee
+++ app/assets/javascripts/angular/controllers/IndexCtrl.coffee
@@ -1,13 +1,13 @@
-Grasshopper.controller "IndexCtrl", ['$scope', '$location', 'Restangular', 'getCurrentUser', ($scope, $location, Restangular, getCurrentUser) ->
+Grasshopper.controller "IndexCtrl", ['$scope', '$location', 'Restangular', 'currentUser', ($scope, $location, Restangular, currentUser) ->

   initialize = () ->
     baseUsers = Restangular.all('users')
     baseUsers.getList().then (users) ->
       $scope.users = users

-  $scope.currentUser = getCurrentUser.currentUser
+  $scope.currentUser = currentUser

-  getCurrentUser.loadData()
+  currentUser.loadData()

   initialize()


Quiet.

Quiet.

--- app/assets/javascripts/views/staffplans/work_week.js.coffee
+++ app/assets/javascripts/views/staffplans/work_week.js.coffee
@@ -62,8 +62,6 @@

   queueEstimatedUpdateOrCreate: (event) ->
     $currentTarget = $( event.currentTarget )
-    console.log "DELTA : "
-    console.log($currentTarget.val() - $currentTarget.data('current-value'))
     $currentTarget.data('current-value', $currentTarget.val())
     cid = $currentTarget.data 'cid'

@@ -91,6 +89,7 @@
         console.log('success')
       error: (wat, another, argument, here) ->
         alert('fail')
+
   showRowFiller: (event) ->
     clearTimeout @_rowFillerTimer


Saving bonded method internally for later removal.

--- www/src/theoricus/mvc/view.coffee
+++ www/src/theoricus/mvc/view.coffee
@@ -68,10 +68,12 @@
       @in()

       if @on_resize?
-        $( window ).unbind 'resize', => do @on_resize
-        $( window ).bind   'resize', => do @on_resize
+        $( window ).unbind 'resize', @_on_resize
+        $( window ).bind   'resize', @_on_resize
         @on_resize()

+  _on_resize:=>
+    do @on_resize

   ###
   In case you defined @events in your view they will be automatically binded
@@ -117,7 +119,7 @@
   ###
   destroy: () ->
     if @on_resize?
-      $( window ).unbind 'resize', @on_resize
+      $( window ).unbind 'resize', @_on_resize

     @before_destroy?()
     @el.empty()


Fix labels count

Fix labels count

--- static/coffee/controllers.coffee
+++ static/coffee/controllers.coffee
@@ -45,6 +45,11 @@

       if $scope.activeLabel != initialLabel
         $scope.activateLabel(initialLabel)
+    )
+
+  $scope.loadLabels = () ->
+    LabelService.fetchAll(true).then((labels) ->
+      $scope.labels = labels
     )

   $scope.activateLabel = (label) ->
@@ -258,6 +263,7 @@
       $scope.items = (item for item in $scope.items when filter(item))

       $scope.updateItems()
+      $scope.loadLabels()

     ).catch((error) ->
       $scope.pollBusy = false
@@ -464,6 +470,7 @@
     UtilsService.labelModal("Labels", "Update the labels for this message. This determines which other partner organizations can view this message.", $scope.labels, message.labels).then((selectedLabels) ->
       MessageService.relabel(message, selectedLabels).then(() ->
         $scope.updateItems()
+        $scope.loadLabels()
       )
     )


Tweak polling of new messages

Tweak polling of new messages

--- static/coffee/controllers.coffee
+++ static/coffee/controllers.coffee
@@ -235,7 +235,7 @@
     $scope.activeSearchRefresh = $scope.buildSearch()
     $scope.activeSearchRefresh.last_refresh = lastPollTime

-    $scope.fetchNewItems($scope.activeSearchRefresh, lastPollTime, thisPollTime, $scope.oldItemsPage).then((data) ->
+    $scope.fetchNewItems($scope.activeSearchRefresh, lastPollTime, thisPollTime, 1).then((data) ->
       $scope.lastPollTime = thisPollTime
       $scope.pollBusy = false


Beauty

Beauty

--- packages/shrub-grunt/dox/index.coffee
+++ packages/shrub-grunt/dox/index.coffee
@@ -7,16 +7,12 @@
     {fork} = require 'child_process'

     gruntConfig.registerTask 'dox:prepareDirectory', ->
-
       grunt.file.mkdir 'gh-pages'

     gruntConfig.registerTask 'dox:dynamic', ->
       done = @async()

-      child = fork "#{__dirname}/dynamic.litcoffee"
-
-      child.on 'close', (code) ->
-
+      fork("#{__dirname}/dynamic.litcoffee").on 'close', (code) ->
         return done() if code is 0

         grunt.fail.fatal 'Dynamic documentation generation failed', code


Update cathédrale value

Update cathédrale value

--- static/web/coffee/app.coffee
+++ static/web/coffee/app.coffee
@@ -274,13 +274,17 @@
         $http.get serverUrl + "/api/points"
         .success (data) ->
             $rootScope.points = data
+            cathedraleEnergy = 0
+            cathedrale = null
             $rootScope.points.forEach (point) ->
                 $http.get serverUrl + "/api/points/"+ point.id
                 .success (data) ->
                     point.coordinates = { latitude: point.lat, longitude: point.lng }
+                    point.data = data
                     if data.type == 'cathedrale'
+                        cathedrale = point
                         point.options = {
-                            labelContent: Math.abs(data.energy) || '0'
+                            labelContent: cathedraleEnergy || 0
                             labelClass: 'map-label-cathedrale'
                         }
                     else
@@ -288,9 +292,12 @@
                             labelContent: Math.abs(data.energy) || '0'
                             labelClass: 'map-label side-' + data.side
                         }
+                        if cathedrale
+                            cathedrale.options.labelContent += data.energy
+                        else
+                            cathedraleEnergy += data.energy
                     point.icon =
                         path: ''
-                    point.data = data

         initGame = ->
             $rootScope.checkUserOrReconnect()
@@ -381,14 +388,22 @@
                 $rootScope.teamScore = teamScore
                 $rootScope.$apply()

+            $scope.catheEnergy = 0
             $rootScope.socket.on 'point:update', (data) ->
-                point = p for p in $scope.points when p.id == data.point.id
+                point = p for p in $rootScope.points when p.id == data.point.id
                 if point
-                    point.options.labelContent = Math.abs(data.gamePoint.energy) || '0'
+                    if data.gamePoint.type == 'cathedrale'
+                        $timeout ->
+                            point.options.labelContent = $scope.catheEnergy
+                            $scope.catheEnergy = 0
+                        , 200
+                    else
+                        point.options.labelContent = Math.abs(data.gamePoint.energy) || '0'
+                        $scope.catheEnergy += data.gamePoint.energy
                     point.data = data.gamePoint

+
             $rootScope.socket.on 'user:update', (data) ->
-                console.log data
                 if data.energy then $rootScope.energy = data.energy
                 $rootScope.$apply()
 ]
@@ -604,7 +619,6 @@
         $rootScope.socket = io wsUrl

         fnTimeout = ->
-            console.log $rootScope.socket.connected
             if !$rootScope.socket.connected
                 $timeout ->
                     window.location.reload false


fixed invitationWithNoEmail function

fixed invitationWithNoEmail function

--- client/admin/lib/views/members/blockedmemberitemview.coffee
+++ client/admin/lib/views/members/blockedmemberitemview.coffee
@@ -63,15 +63,16 @@

   unblockUser: ->

+    id = @getData().getId()
     currentGroup = kd.singletons.groupsController.getCurrentGroup()

-    invitationWithNoEmail @getData(), currentGroup,  (err, result) ->
+    @invitationWithNoEmail @getData(), (err, result) =>

       if err
         customErr = new Error 'Something went wrong, please try again!'
         return @handleError @unblockButton, customErr

-      currentGroup.unblockMember id, (err) ->
+      currentGroup.unblockMember id, (err) =>

         if err
           customErr = new Error 'Failed to unblock user. Please try again.'


prettified output for all flows

prettified output for all flows

--- src/steroids/ApplicationConfigUpdater.coffee
+++ src/steroids/ApplicationConfigUpdater.coffee
@@ -29,10 +29,11 @@
   updateNpmPackages: ->
     console.log(
       """
-      #{chalk.bold.green("INSTALLING NPM DEPENDENCIES")}
+      \n#{chalk.bold.green("INSTALLING NPM DEPENDENCIES")}
       #{chalk.bold.green("===========================")}

       Running #{chalk.bold("npm install")} to install project npm dependencies...
+
       """
     )

@@ -45,7 +46,6 @@
     npmRun.on "exit", =>
       @emit "npmPackagesUpdated"

-
   upgradeGruntfile: ->
     @checkGruntfileExists (gruntfileExists) =>
       if gruntfileExists
@@ -54,8 +54,8 @@
           Help.attention()
           console.log(
             """
-            #{chalk.red.bold("EXISTING GRUNTFILE.JS FOUND")}
-            #{chalk.red.bold("===========================")}
+            #{chalk.red.bold("EXISTING GRUNTFILE.JS DOESN'T LOAD GRUNT-STEROIDS TASKS")}
+            #{chalk.red.bold("=======================================================")}

             Breaking update ahead!

@@ -93,10 +93,11 @@
           @emit "gruntfileUpgraded"

       else
+        Help.attention()
         console.log(
           """
-          NEW FEATURE
-          ===========
+          #{chalk.green.bold("NEW FEATURE")}
+          #{chalk.green.bold("===========")}

           To build the #{chalk.bold("dist/")} folder, Steroids now uses a Gruntfile.js file directly from the
           project root directory. The tasks are defined in the #{chalk.bold("grunt-steroids")} Grunt plugin.
@@ -112,6 +113,13 @@
         promptUnderstood (understood) =>
           if understood != "I UNDERSTAND THIS"
             exitAfterUnderstandingFailed()
+
+          console.log(
+            """
+            \nCopying Gruntfile.js to product root...
+
+            """
+          )
           fs.writeFileSync(paths.application.configs.grunt, fs.readFileSync(paths.templates.gruntfile))
           @emit "gruntfileUpgraded"

@@ -123,26 +131,45 @@
           @emit "packagejsonUpgraded"

         else
-          console.log ""
-          console.log "EXISTING PACKAGE.JSON FOUND"
-          console.log "==========================="
-          console.log ""
-          console.log "To install Steroids' Grunt dependencies in your project, write the following command:"
-          console.log "  npm install grunt-steroids --save-dev"
-          console.log ""
+          Help.attention()
+          console.log(
+            """
+
+            #{chalk.red.bold("EXISTING PACKAGE.JSON FOUND")}
+            #{chalk.red.bold("===========================")}
+
+            Your existing #{chalk.bold("package.json")} file doesn't have the required #{chalk.bold("grunt-steroids")}
+            Grunt plugin as a dependency.
+
+            To install the #{chalk.bold("grunt-steroids")} npm package in your project, run the following command:
+
+              npm install grunt-steroids --save-dev
+
+            """
+          )
           promptRunCommand (agreed) =>
             if agreed
               @installGruntSteroids =>
-                console.log "Installed grunt-steroids."
+                console.log(
+                  """
+                  \n#{chalk.green("OK!")} Installed the #{chalk.bold("grunt-steroids")} npm pacakge successfully.
+
+                  """
+                )

                 @emit "packagejsonUpgraded"

             else
-              console.log ""
-              console.log "ABORT!"
-              console.log "======"
-              console.log ""
-              console.log "Please install grunt-steroids manually or run steroids update again."
+              Help.error()
+              console.log(
+                """
+                #{chalk.red.bold("GRUNT-STEROIDS INSTALL ABORTED!")}
+                #{chalk.red.bold("===============================")}
+
+                Please install #{chalk.bold("grunt-steroids")} manually or run #{chalk.bold("$ steroids update")} again.
+
+                """
+              )
               process.exit 1

               @emit "packagejsonUpgraded"


install grunt from npm registry instead of appgyver's repo

--- src/steroids/ApplicationConfigUpdater.coffee
+++ src/steroids/ApplicationConfigUpdater.coffee
@@ -161,9 +161,7 @@
   installGruntSteroids: (done) ->
     gruntRun = sbawn
       cmd: "npm"
-      # TODO: Change this after grunt-steroids is published
-      #args: ["install", "grunt-steroids", "--save-dev"]
-      args: ["install", "git+ssh://git@github.com/AppGyver/grunt-steroids.git", "--save-dev"]
+      args: ["install", "grunt-steroids@0.2.x", "--save-dev"]
       stdout: true
       stderr: true


No longer logging an error if the column is not found. We can deal with the largely superficial aspect of column order in the future, if we want to

--- dkobo/koboform/static/js/xlform/model_xlform.coffee
+++ dkobo/koboform/static/js/xlform/model_xlform.coffee
@@ -241,15 +241,10 @@
   exp

 XLF.columnOrder = do ->
-  warned = []
-  warn = (key)->
-    unless key in warned
-      warned.push(key)
-      console?.error("Order not set for key: #{key}")
   (key)->
-    ki = XLF.columns.indexOf key
-    warn(key)  if ki is -1
-    if ki is -1 then 100 else ki
+    if -1 is XLF.columns.indexOf key
+      XLF.columns.push(key)
+    XLF.columns.indexOf key

 class XLF.Group extends SurveyFragment
   initialize: ()->


Added .created_facts on User

Added .created_facts on User

--- core/app/backbone/models/user.coffee
+++ core/app/backbone/models/user.coffee
@@ -17,21 +17,23 @@

     Backbone.sync(method, model, options);

-  is_current_user: ()->
+  is_current_user: ->
     window.currentUser != undefined && currentUser.get('id') == this.get('id')

   avatar_url: (size)->
     md5d_email = @get('gravatar_hash')
     "https://secure.gravatar.com/avatar/#{md5d_email}?size=#{size}&rating=PG&default=retro"

-  stream: ->
+  stream:        -> @channel_with_id 'all_channel_id'
+  created_facts: -> @channel_with_id 'created_facts_channel_id'
+
+  channel_with_id: (id) ->
     new Channel
-      id: @get('all_channel_id')
+      id: @get(id)
       created_by:
         username: @get('username')

-
-  toJSON: () ->
+  toJSON: ->
     json = Backbone.Model.prototype.toJSON.apply(this);
     _.extend json,
       is_current_user: @is_current_user(),


Fixes first lines.

Fixes first lines.

- Fixes off by one error.

--- lib/jumpy-view.coffee
+++ lib/jumpy-view.coffee
@@ -97,7 +97,7 @@
         for line, lineNumber in editorView.getEditor().buffer.lines
             if line != ''
                 while ((word = wordsPattern.exec(line)) != null)
-                    if isScreenRowVisible(lineNumber)
+                    if isScreenRowVisible(lineNumber + 1)
                         keyLabel = nextKeys.shift()
                         position = {row: lineNumber, column: word.index}
                         that.allPositions[keyLabel] = { editor: editorView.getEditor().id, position: position } # creates a reference.