sync: made some client side changes that might fix some of the remaining weirdness (basically lock client while applying sync patch)

--- salvus/page/syncdoc.coffee
+++ salvus/page/syncdoc.coffee
@@ -92,43 +92,49 @@
             @opts.string = diffsync.dmp.patch_apply(p, @string())[0]
         else
             cm = @opts.cm
-            s = @string()
-            x = diffsync.dmp.patch_apply(p, s)
-            new_value = x[0]
-
-            next_pos = (val, pos) ->
-                # This functions answers the question:
-                # If you were to insert the string val at the CodeMirror position pos
-                # in a codemirror document, at what position (in codemirror) would
-                # the inserted string end at?
-                number_of_newlines = (val.match(/\n/g)||[]).length
-                if number_of_newlines == 0
-                    return {line:pos.line, ch:pos.ch+val.length}
-                else
-                    return {line:pos.line+number_of_newlines, ch:(val.length - val.lastIndexOf('\n')-1)}
-
-            pos = {line:0, ch:0}  # start at the beginning
-            diff = diffsync.dmp.diff_main(s, new_value)
-            for chunk in diff
-                #console.log(chunk)
-                op  = chunk[0]  # 0 = stay same; -1 = delete; +1 = add
-                val = chunk[1] # the actual text to leave same, delete, or add
-                pos1 = next_pos(val, pos)
-                switch op
-                    when 0 # stay the same
-                        # Move our pos pointer to the next position
-                        pos = pos1
-                        #console.log("skipping to ", pos1)
-                    when -1 # delete
-                        # Delete until where val ends; don't change pos pointer.
-                        cm.replaceRange("", pos, pos1)
-                        #console.log("deleting from ", pos, " to ", pos1)
-                    when +1 # insert
-                        # Insert the new text right here.
-                        cm.replaceRange(val, pos)
-                        #console.log("inserted new text at ", pos)
-                        # Move our pointer to just beyond the text we just inserted.
-                        pos = pos1
+            cm.setOption('readOnly', true)
+            try
+                s = @string()
+                x = diffsync.dmp.patch_apply(p, s)
+                new_value = x[0]
+
+                next_pos = (val, pos) ->
+                    # This functions answers the question:
+                    # If you were to insert the string val at the CodeMirror position pos
+                    # in a codemirror document, at what position (in codemirror) would
+                    # the inserted string end at?
+                    number_of_newlines = (val.match(/\n/g)||[]).length
+                    if number_of_newlines == 0
+                        return {line:pos.line, ch:pos.ch+val.length}
+                    else
+                        return {line:pos.line+number_of_newlines, ch:(val.length - val.lastIndexOf('\n')-1)}
+
+                pos = {line:0, ch:0}  # start at the beginning
+                diff = diffsync.dmp.diff_main(s, new_value)
+                for chunk in diff
+                    #console.log(chunk)
+                    op  = chunk[0]  # 0 = stay same; -1 = delete; +1 = add
+                    val = chunk[1] # the actual text to leave same, delete, or add
+                    pos1 = next_pos(val, pos)
+                    switch op
+                        when 0 # stay the same
+                            # Move our pos pointer to the next position
+                            pos = pos1
+                            #console.log("skipping to ", pos1)
+                        when -1 # delete
+                            # Delete until where val ends; don't change pos pointer.
+                            cm.replaceRange("", pos, pos1)
+                            #console.log("deleting from ", pos, " to ", pos1)
+                        when +1 # insert
+                            # Insert the new text right here.
+                            cm.replaceRange(val, pos)
+                            #console.log("inserted new text at ", pos)
+                            # Move our pointer to just beyond the text we just inserted.
+                            pos = pos1
+            catch e
+                console.log("BUG in patch_in_place")
+            cm.setOption('readOnly', false)
+

 codemirror_diffsync_client = (cm_session, content) ->
@@ -179,9 +185,9 @@
         @project_id = @opts.project_id
         @filename   = @opts.filename

-        @connect    = misc.retry_until_success_wrapper(f:@_connect) #, logname:'connect')
-        @sync       = misc.retry_until_success_wrapper(f:@_sync, min_interval:@opts.sync_interval) #, logname:'sync')
-        @save       = misc.retry_until_success_wrapper(f:@_save) #, logname:'save')
+        @connect    = misc.retry_until_success_wrapper(f:@_connect)#, logname:'connect')
+        @sync       = misc.retry_until_success_wrapper(f:@_sync, min_interval:@opts.sync_interval)#, logname:'sync')
+        @save       = misc.retry_until_success_wrapper(f:@_save)#, logname:'save')

         @connect (err) =>
             opts.cb(err, @)
@@ -323,9 +329,9 @@
             cursor_interval : 1000
             sync_interval   : 750   # never send sync messages up stream more often than this

-        @connect    = misc.retry_until_success_wrapper(f:@_connect)#  logname:'connect')
-        @sync       = misc.retry_until_success_wrapper(f:@_sync, min_interval:@opts.sync_interval)# logname:'sync')
-        @save       = misc.retry_until_success_wrapper(f:@_save) #logname:'save')
+        @connect    = misc.retry_until_success_wrapper(f:@_connect)#, logname:'connect')
+        @sync       = misc.retry_until_success_wrapper(f:@_sync, min_interval:@opts.sync_interval)#, logname:'sync')
+        @save       = misc.retry_until_success_wrapper(f:@_save)#, logname:'save')

         @filename    = @editor.filename
         @editor.save = @save
@@ -778,8 +784,8 @@
                 # since we just canceled the change.
                 @remove_cell_flags_from_changeObj(changeObj, ACTION_FLAGS)
                 @_apply_changeObj(changeObj)
-                @sync()
-                @process_sage_updates()
+                @sync () =>
+                    @process_sage_updates()

     init_worksheet_buttons: () =>
         buttons = @element.find(".salvus-editor-codemirror-worksheet-buttons")


Add autoSubmit script

Add autoSubmit script


Check for :hidden instead.

Check for :hidden instead.

--- app/assets/javascripts/keyboard.js.coffee
+++ app/assets/javascripts/keyboard.js.coffee
@@ -412,7 +412,7 @@
     @columns['feeds'].find('.selected').nextAll('li').find('.count').not('.hide').length

   selectNextUnreadFeed: ->
-    @item = @columns['feeds'].find('.selected').nextAll('li').find('.count').not('.hide').first().parents('li')
+    @item = @columns['feeds'].find('.selected').nextAll('li').find('.count').not(':hidden').first().parents('li')
     @selectItem()

   hasUnreadEntries: ->


reverse respimg support

reverse respimg support

--- app/assets/javascripts/priority.js.coffee
+++ app/assets/javascripts/priority.js.coffee
@@ -1,5 +1,5 @@
 #= require lazysizes
 #= require lazysizes/plugins/rias/ls.rias
 #= require lazysizes/plugins/bgset/ls.bgset
-#  require lazysizes/plugins/respimg/ls.respimg
+#= require lazysizes/plugins/respimg/ls.respimg
 #  require lazysizes/plugins/custommedia/ls.custommedia


Link latest project to single-project view

Signed-off-by: Max Fierke <0706025b2bbcec1ed8d64822f4eccd96314938d0@maxfierke.com>

--- app/assets/javascripts/projects.js.coffee
+++ app/assets/javascripts/projects.js.coffee
@@ -50,7 +50,7 @@
       if $lp.length > 0
         $.get "/projects.json", (data) ->
           proj = data[0]
-          $lp.find(".project-title a").text(proj.name).prop "href", "/projects/#project_" + proj.id
+          $lp.find(".project-title a").text(proj.name).prop "href", "/projects/" + proj.id
           $lp.find(".project-shortdesc").text proj.short_description
     init: ->
       # Highlight current nav item


added a handler for the enter

added a handler for the enter

--- app/assets/javascripts/the_role.js.coffee
+++ app/assets/javascripts/the_role.js.coffee
@@ -7,6 +7,11 @@
     e.parentNode.reset()
   $(e).find('.btn-success').click ->
     e.parentNode.submit()
+  $(e).children('span.b').find('input').keypress( (x) ->
+    if x.which == 13
+      e.parentNode.submit()
+      console.log('enter pressed')
+  )

 hideForm = (e) ->
   $(e).children('span.a').show()


:lipstick:

:lipstick:

--- src/less-compile-cache.coffee
+++ src/less-compile-cache.coffee
@@ -2,7 +2,6 @@

 _ = require 'underscore'
 LessCache = require 'less-cache'
-

 module.exports =
 class LessCompileCache


Prep InternalSocket for decaf

Prep InternalSocket for decaf

--- src/lib/InternalSocket.coffee
+++ src/lib/InternalSocket.coffee
@@ -15,6 +15,7 @@
 class InternalSocket extends EventEmitter
   regularEmitEvent: (event, data) ->
     @emit event, data
+    return

   debugEmitEvent: (event, data) ->
     try
@@ -33,6 +34,8 @@
         error: error
         metadata: @metadata

+    return
+
   constructor: (metadata = {}) ->
     super()
     @metadata = metadata
@@ -71,11 +74,13 @@
     return if @connected
     @connected = true
     @emitEvent 'connect', null
+    return

   disconnect: ->
     return unless @connected
     @connected = false
     @emitEvent 'disconnect', null
+    return

   isConnected: -> @connected

@@ -93,6 +98,7 @@
   send: (data) ->
     data = @dataDelegate() if data is undefined and typeof @dataDelegate is 'function'
     @handleSocketEvent 'data', data
+    return

   # ## Sending information packets without open bracket
   #
@@ -107,6 +113,7 @@
     @handleSocketEvent 'ip', ip, false
     if autoDisconnect and @isConnected() and @brackets.length is 0
       do @disconnect
+    return

   # ## Information Packet grouping
   #
@@ -139,9 +146,11 @@
   # intact through the component's processing.
   beginGroup: (group) ->
     @handleSocketEvent 'begingroup', group
+    return

   endGroup: ->
     @handleSocketEvent 'endgroup'
+    return

   # ## Socket data delegation
   #
@@ -152,6 +161,7 @@
     unless typeof delegate is 'function'
       throw Error 'A data delegate must be a function.'
     @dataDelegate = delegate
+    return

   # ## Socket debug mode
   #
@@ -161,6 +171,7 @@
   setDebug: (active) ->
     @debug = active
     @emitEvent = if @debug then @debugEmitEvent else @regularEmitEvent
+    return

   # ## Socket identifiers
   #
@@ -248,6 +259,7 @@
     @connected = true if event is 'connect'
     @connected = false if event is 'disconnect'
     @emitEvent event, payload
+    return

 exports.InternalSocket = InternalSocket


Add link to home for 404 page.

Add link to home for 404 page.

--- app/client/js/react/pages/notfound.coffee
+++ app/client/js/react/pages/notfound.coffee
@@ -3,14 +3,18 @@
 class app.react.pages.NotFound

   ###*
+    @param {app.Routes} routes
+    @param {app.react.Touch} touch
     @constructor
   ###
-  constructor: ->
+  constructor: (routes, touch) ->
     {div,h1,p} = React.DOM
+    {a} = touch.none 'a'

     @create = React.createClass

       render: ->
         div className: 'notfound',
           h1 null, "This page isn't available"
-          p null, "The link may be broken, or the page may have been removed."+          p null, 'The link may be broken, or the page may have been removed.'
+          a href: routes.home.createUrl(), 'Continue here please.'


Social: add existence check for function

--- client/Main/CommonViews/LikeView.coffee
+++ client/Main/CommonViews/LikeView.coffee
@@ -33,8 +33,8 @@
     @likeLink = new ActivityActionLink partial: "Like"

     # We need to getridoff this asap FIXME ~HK
-    if options.checkIfLikedBefore and KD.isLoggedIn()
-      data.checkIfLikedBefore (err, likedBefore)=>
+    if options.checkIfLikedBefore? and KD.isLoggedIn()
+      data.checkIfLikedBefore? (err, likedBefore)=>
         {useTitle} = @getOptions()
         if likedBefore
           @setClass "liked"


Apps: AppController: Let the apps feed works correctly

--- client/Social/Apps/AppController.coffee
+++ client/Social/Apps/AppController.coffee
@@ -25,10 +25,6 @@

     @on "LazyLoadThresholdReached", => @feedController.loadFeed()

-    @appsController = KD.getSingleton "kodingAppsController"
-
-    @appsController.on "AnAppHasBeenUpdated", @bound "updateApps"
-
   loadView:(mainView)->

     mainView.createCommons()
@@ -40,11 +36,12 @@
       feedId                : 'apps.main'
       itemClass             : AppsListItemView
       limitPerPage          : 10
+      delegate              : this
       useHeaderNav          : yes
       filter                :
         allApps             :
           title             : "All Apps"
-          noItemFoundText   : "There are no applications yet"
+          noItemFoundText   : "There is no application yet"
           dataSource        : (selector, options, callback)=>
             KD.remote.api.JApp.someWithRelationship selector, options, callback
         updates             :
@@ -115,45 +112,23 @@
           KD.remote.api.JApp.someWithRelationship selector, options, callback

     KD.getSingleton("appManager").tell 'Feeder', 'createContentFeedController', options, (controller)=>
-      for own name,listController of controller.resultsController.listControllers
-        listController.getListView().on 'AppWantsToExpand', (app)->
-          KD.getSingleton('router').handleRoute "/Apps/#{app.slug}", state: app

       @getView().addSubView controller.getView()
       @feedController = controller
+      @feedController.loadFeed()
       @emit 'ready'
-
-      {updateAppsButton} = @getView()
-      if controller.selection.name is 'updates'
-        updateAppsButton.emit 'UpdateView', 'updates'
-
-      controller.on 'FilterChanged', (filter)=>
-        @updateApps()  if filter is 'updates'
-        updateAppsButton.emit 'UpdateView', filter
-
-  putUpdateAvailableApps: (callback) ->
-    @appsController.fetchUpdateAvailableApps callback
-
-  updateApps:->
-    @utils.wait 100, => @feedController?.changeActiveSort "meta.modifiedAt"
-

   handleRoute:(route)->

     {app, lala} = route.params
     {JApp}      = KD.remote.api
     if app
-      log "slug:", slug = "#{lala}/#{app}"
+      log "slug:", slug = "Apps/#{lala}/#{app}"
       JApp.one {slug}, (err, app)=>
         log "FOUND THIS JAPP", err, app
+        if app then @showContentDisplay app

     log "HANDLING", route
-
-  createContentDisplay:(app, callback)->
-
-    contentDisplay = @showContentDisplay app
-    @utils.defer => callback contentDisplay
-

   showContentDisplay:(content)->


Terminal: with vm creation/removal terminal start tab is updated

--- client/Terminal/terminalstarttab.coffee
+++ client/Terminal/terminalstarttab.coffee
@@ -8,20 +8,26 @@

     @vmSessions = new KDCustomHTMLView tagName : 'ul'

+    @message = new KDCustomHTMLView
+
+    KD.singletons.notificationController.on 'NotificationHasArrived', ({event}) =>
+      if event in ["VMCreated", "VMRemoved"]
+        @wmWrapper = {}
+        @viewAppended()

   viewAppended:->

     super
-
+    @wmWrapper = {}
     @fetchVMs()
+    @prepareMessage()

   fetchVMs:->

     {vmController, kontrol} = KD.singletons

-    vmController.fetchVMs (err, vms)=>
-
+    vmController.fetchVMs yes, (err, vms)=>
       if err
         return new KDNotificationView title : "Couldn't fetch your VMs"

@@ -61,9 +67,10 @@
       vmList[vm.hostnameAlias] = vm

     {vmController:{terminalKites : kites}} = KD.singletons
+    {delegate} = @getOptions()
     for own alias, kite of kites
       vm = vmList[alias]
-      @vmSessions.addSubView new SessionStackView {kite: kite, alias: alias, vm: vm, delegate: this}
+      @vmSessions.addSubView new SessionStackView {kite: kite, alias: alias, vm: vm, delegate}

   pistachio:->


activity/flux: remove parent assignment from getter

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -90,21 +90,13 @@
     threads.map (thread) ->
       channelId = thread.get 'channelId'
       thread = thread.set 'flags', channelFlags.get channelId
-      thread.update 'messages', (msgs) ->
-        msgs.map (messageId) ->
-          message = messages.get messageId
-          if parentId = message.get 'parentId'
-            # FIXME: this string comparison shouldn't be here, but somehow
-            # undefined `parentId` s are returning as empty immutable lists,
-            # this needs to be investigated. ~Umut
-            if 'string' is typeof parentId
-              parent = messages.get parentId
-              message = message.set 'parent', parent
-          if message.has('__editedBody')
-            message = message.set 'body', message.get '__editedBody'
-            message = message.set 'payload', message.get '__editedPayload'
-          return message
-        .sortBy (m) -> m.get 'createdAt'
+      thread.update 'messages', (msgs) -> msgs.map (messageId) ->
+        message = messages.get messageId
+        if message.has('__editedBody')
+          message = message.set 'body', message.get '__editedBody'
+          message = message.set 'payload', message.get '__editedPayload'
+        return message
+      .sortBy (m) -> m.get 'createdAt'
 ]

 channelPopularMessages = [


sidebar: handled url access for channels and messages

--- client/activity/lib/routehandler.coffee
+++ client/activity/lib/routehandler.coffee
@@ -6,6 +6,7 @@
 handlers                  = require './routehandlers'
 lazyrouter                = require 'app/lazyrouter'
 isKoding                  = require 'app/util/isKoding'
+isSoloProductLite         = require 'app/util/issoloproductlite'
 { RoutingContext, match } = require 'react-router'

 reactivityRouteTypes = [
@@ -38,8 +39,11 @@
       then handleReactivity info, ctx
       # unless reactivity is enabled redirect reactivity routes to `Public`
       else ctx.handleRoute '/Activity/Public'
-    else handle type
-
+    else
+      if not isSoloProductLite()
+        handle type
+      else
+        return ctx.handleRoute ''

 ###*
  * Renders with reacth router.


Listen status bar menu related events on IDEView.

--- client/ide/views/tabview/ideview.coffee
+++ client/ide/views/tabview/ideview.coffee
@@ -10,10 +10,13 @@
   bindListeners: ->
     @on 'PlusHandleClicked', @bound 'createPlusContextMenu'

-    @tabView.on 'FileNeedsToBeOpened', @bound 'openFile'
-    @tabView.on 'VMTerminalRequested', @bound 'openVMTerminal'
-    @tabView.on 'VMWebPageRequested',  @bound 'openVMWebPage'
-    @tabView.on 'PaneDidShow',         => @updateStatusBar()
+    @tabView.on 'FileNeedsToBeOpened',    @bound 'openFile'
+    @tabView.on 'VMTerminalRequested',    @bound 'openVMTerminal'
+    @tabView.on 'VMWebPageRequested',     @bound 'openVMWebPage'
+    @tabView.on 'ShortcutsViewRequested', @bound 'createShortcutsView'
+
+    @tabView.on 'PaneDidShow', => # bound passes args also
+      @updateStatusBar()

     @once 'viewAppended', => KD.utils.wait 300, => @createEditor()


fix home.service spec

fix home.service spec

--- app/modules/home/home.service.spec.coffee
+++ app/modules/home/home.service.spec.coffee
@@ -73,11 +73,14 @@
     it "get work in progress by user", (done) ->
         userId = 3

+        project1 = {id: 1, name: "fake1", slug: "project-1"}
+        project2 = {id: 2, name: "fake2", slug: "project-2"}
+
         mocks.projectsService.getProjectsByUserId
             .withArgs(userId)
             .resolve(Immutable.fromJS([
-                {id: 1, name: "fake1", slug: "project-1"},
-                {id: 2, name: "fake2", slug: "project-2"}
+                project1,
+                project2
             ]))

         mocks.resources.userstories.listInAllProjects
@@ -125,28 +128,22 @@
                         userStories: [{
                             id: 1,
                             ref: 1,
-                            project: '1',
                             url: '/testing-project/us/1',
-                            projectName: 'fake1',
-                            blockedProject: undefined,
+                            project: project1,
                             _name: 'userstories'
                         }]
                         tasks: [{
                             id: 2,
                             ref: 2,
-                            project: '1',
+                            project: project1,
                             url: '/testing-project/tasks/1',
-                            projectName: 'fake1',
-                            blockedProject: undefined,
                             _name: 'tasks'
                         }]
                         issues: [{
                             id: 3,
                             ref: 3,
-                            project: '1',
                             url: '/testing-project/issues/1',
-                            projectName: 'fake1',
-                            blockedProject: undefined,
+                            project: project1,
                             _name: 'issues'
                         }]
                     }
@@ -154,28 +151,22 @@
                         userStories: [{
                             id: 1,
                             ref: 1,
-                            project: '1',
                             url: '/testing-project/us/1',
-                            projectName: 'fake1',
-                            blockedProject: undefined,
+                            project: project1,
                             _name: 'userstories'
                         }]
                         tasks: [{
                             id: 2,
                             ref: 2,
-                            project: '1',
                             url: '/testing-project/tasks/1',
-                            projectName: 'fake1',
-                            blockedProject: undefined,
+                            project: project1,
                             _name: 'tasks'
                         }]
                         issues: [{
                             id: 3,
                             ref: 3,
-                            project: '1',
                             url: '/testing-project/issues/1',
-                            projectName: 'fake1',
-                            blockedProject: undefined,
+                            project: project1,
                             _name: 'issues'
                         }]
                     }


Added another copperfield

Added another copperfield

--- scripts/copperfield.coffee
+++ scripts/copperfield.coffee
@@ -3,7 +3,8 @@
 # gotcha!

 copperfields = [
-  "http://i.imgur.com/asSfAoo.jpg"
+  "http://i.imgur.com/asSfAoo.jpg",
+  "http://i.imgur.com/0PjzWYh"
 ]
 module.exports = (robot) ->
     msg.send ""


testing auto deploy with codeship

testing auto deploy with codeship

--- scripts/mongo-brain.coffee
+++ scripts/mongo-brain.coffee
@@ -17,7 +17,7 @@
 #   None
 #
 # Author:
-#   ajacksified, maxbeatty
+#   ajacksified, maxbeatty

 Util = require "util"
 mongodb = require "mongodb"


Fix typo

Fix typo

--- scripts/training2nd.coffee
+++ scripts/training2nd.coffee
@@ -19,11 +19,11 @@
     res.send "「@boss お客様からMovableTypeに変更してほしいと頼まれました。」の形式です。"

   robot.respond /MovableTypeに変更したことを報告しに来ました。/i, (res) ->
-    res.reply "いやー、変えてもらったところを申し訳ないんだけどね。やはりWordpressのがよさそうで、Wordpressにしてほしいんだよ。MovableTypeがいいと言っていた社員もブログを書くだけでサーバを運用    するわけじゃないから、口出さないように言っておいたよ。それでは、よろしく。"
+    res.reply "いやー、変えてもらったところを申し訳ないんだけどね。やはりWordpressのがよさそうで、Wordpressにしてほしいんだよ。MovableTypeがいいと言っていた社員もブログを書くだけでサーバを運用するわけじゃないから、口出さないように言っておいたよ。それでは、よろしく。"
     res.send "「@boss お客様からWordpressに戻して100万PVに耐えられるようにしてほしいと頼まれました。」の形式です。"

   robot.respond /Wordpressに戻したことを報告しに来ました。/i, (res) ->
-    res.reply "じゃあ、早速使ってみますよ。それと、せっかく書いたのに消えてしまってはいやだか、バックアップをとれるようにしておいてください。"
+    res.reply "じゃあ、早速使ってみますよ。それと、せっかく書いたのに消えてしまってはいやだから、バックアップをとれるようにしておいてください。"
     res.send "「@boss お客様からバックアップを取るように頼まれました。」の形式です。"

   robot.respond /障害とその原因について報告しに来ました。.*/i, (res) ->


Don't try to connect outlet if current view is desteoyed

--- assets/scripts/app/views/repo/show.coffee
+++ assets/scripts/app/views/repo/show.coffee
@@ -22,7 +22,7 @@
         view.set('controller', @get('controller.container').lookup('controller:build'))
         Ember.run.next =>
           @set('_outlets', {}) if !@get('_outlets') && !@isDestroyed
-          @connectOutlet('pane',  view)
+          @connectOutlet('pane',  view) unless @isDestroyed
     ).observes('controller.repo.isLoaded')

   ReposEmptyView: Travis.View.extend


Only flatten to single array

Only flatten to single array

--- src/components/unsaved-state-mixin.cjsx
+++ src/components/unsaved-state-mixin.cjsx
@@ -20,7 +20,7 @@

 TransitionAssistant = {
   canTransition: -> not _.any(ACTIVE, (c) -> c._cannotTransition())
-  unsavedMessages: -> _.flatten _.invoke(ACTIVE, '_unsavedMessages')
+  unsavedMessages: -> _.flatten( _.invoke(ACTIVE, '_unsavedMessages'), 1)
   checkTransitionStateTo: (destination) ->

     new Promise (onProceed, onCancel) =>
@@ -29,7 +29,7 @@
       else
         body =

-            {for message, i in @unsavedMessages
+            {for message, i in @unsavedMessages()

{message}
}


Remove references to #visibility id that was changed to .visibility-select

--- cms/static/coffee/src/views/unit.coffee
+++ cms/static/coffee/src/views/unit.coffee
@@ -12,7 +12,7 @@

   initialize: =>
     @visibilityView = new CMS.Views.UnitEdit.Visibility(
-      el: @$('#visibility')
+      el: @$('.visibility-select')
       model: @model
     )

@@ -150,7 +150,7 @@
     )

   setVisibility: (event) ->
-    if @$('#visibility').val() == 'private'
+    if @$('.visibility-select').val() == 'private'
       target_url = '/unpublish_unit'
     else
       target_url = '/publish_draft'


reconcile create property from event data

--- imports/server/mqtt/docker/events.coffee
+++ imports/server/mqtt/docker/events.coffee
@@ -14,9 +14,13 @@
     when 'top'    then ignore
     else console.log 'events:unknown container status', msg.status

-  if mappedState
+  if mappedState and (labels = msg.Actor?.Attributes)
     console.log 'eventmsg', msg
-    reconciler.updateServiceState mappedState, msg.Actor?.Attributes
+    reconciler.updateServiceState mappedState, labels
+
+    if id = msg.id
+      console.log 'updateContainer id from event', id, labels
+      reconciler.updateContainerId id, labels

 handleImageEvent = (msg) ->
   if msg.status is 'pull' and (image = msg.Actor?.Attributes?.name)


Docs method for loading article

Docs method for loading article

--- js/outer.coffee
+++ js/outer.coffee
@@ -39,6 +39,9 @@
     load: ->
       if !HAML['categories']
         $.getScript("/assets/views/outer/docs/categories.hamlc")
+
+    article: ->
+      $.getScript("/assets/views/outer/docs/category/#{@name}.hamlc", => @init())

   # Pages
   home = new Page("home", "Continuous Integration made easy")


debouncing scroll handler

debouncing scroll handler

Former-commit-id: a8d32a9d1829dd3cce18b7ee9d7b0578ec640b24 [formerly 28b799eec266863fc043d8ba2fee270ee90a7d02] [formerly d8178ed45caacaa2775c61c54ac49353042837c1 [formerly 8e935e7dd627b2099516e253cd297f1d355b5fe0]] [formerly cb3fb9d32e29fecf8fdec2e62605ee1f1afe89c5 [formerly 196ac6dd7e38f2af0c749eaae8bac12de235e37b] [formerly aabfb71e6595e11f89ba5c7caa26b1251ff2b01c [formerly aabfb71e6595e11f89ba5c7caa26b1251ff2b01c [formerly ab1d353c9f52581241d2922c39015af1cd62a647]]]]
Former-commit-id: 78c59350eca7c8b553deb55d92e87aeb89511911 [formerly e3a8492a61e3c58002b6d83970af2d8f58016d98] [formerly 7b43afc7b0bfc5f862f9a0b174429681ff0a05cd [formerly 05f9302abae88e5ee3cf05e987e4a5258a405e77]]
Former-commit-id: 39fdb6b4bdd6b536da6a489c7bfef868217856fe [formerly 4e7a18e62fa0e0cb2efd0f4b3bf90b0e8e81d71f]
Former-commit-id: 4ddaa706cbf98a2e2d886b3c138c52e439f5371f
Former-commit-id: b5e582a24777686fb725372c5b4056c9e99aa489

--- @client/docker.coffee
+++ @client/docker.coffee
@@ -114,9 +114,8 @@
     serializer = new XMLSerializer()

     docker.register @key, =>
-      # This callback is invoked each scroll event handled by docker
-      # in order to get data about this docked element.
-
+      # This callback is invoked each time the dock is laid out by docker.
+      #
       # We can't use $el.height() to determine the height of the docking
       # component because there may be absolutely positioned elements
       # inside $el that we need to account for. For example, a Slider.
@@ -176,7 +175,7 @@
 # they know how to render.

 # For console output:
-debug = true
+debug = false

 docker =

@@ -204,7 +203,7 @@

     if !docker.listening_to_scroll_events

-      $(window).on "scroll.docker", docker.onScroll
+      $(window).on "scroll.docker", -> docker.user_scrolled = true
       $(window).on "resize.docker", docker.onResize

       # If the height of a docked component changes, we need to recalculate
@@ -213,6 +212,13 @@
       docker.check_resize_interval = setInterval docker.onCheckStickyResize, 500

       docker.listening_to_scroll_events = true
+
+      # Recompute layout if we've seen a scroll in past X ms
+      docker.interval = setInterval ->
+        if docker.user_scrolled
+          docker.user_scrolled = false
+          docker.onScroll()
+      , 100

   unregister : (key) ->
     delete docker.registry[key]


refactor proposal sorting

refactor proposal sorting

--- @client/filter.coffee
+++ @client/filter.coffee
@@ -65,18 +65,11 @@
 window.sorted_proposals = (proposals) ->

   sort = fetch 'sort_proposals'
-  set_sort() if !sort.func?
-
-
-  if sort.comp
-    comp = sort.comp
-    proposals = proposals.slice().sort (a,b) ->
-      return comp(a,b)
-
-  else
-    proposal_rank = sort.func
-    proposals = proposals.slice().sort (a,b) ->
-      return proposal_rank(b, sort.opinion_value) - proposal_rank(a, sort.opinion_value)
+
+  set_sort() if !sort.name?
+
+  comp = sort.comp
+  proposals = proposals.slice().sort comp

   # filter out filtered proposals
   filter_out = fetch 'filtered'
@@ -119,72 +112,83 @@

 sort_options = [
+
+  {
+    comp: (a, b) -> basic_proposal_scoring(b, ((o) -> o.stance)).avg - basic_proposal_scoring(a, ((o) -> o.stance)).avg
+    name: 'Average Score'
+    description: "Each proposal is scored by the average opinion score, where opinions are on [-1, 1]."
+  }, {
+    comp: (a, b) -> basic_proposal_scoring(b, ((o) -> o.stance)).sum - basic_proposal_scoring(a, ((o) -> o.stance)).sum
+    name: 'Total Score'
+    description: "Each proposal is scored by the sum of opinions, where opinions are on [-1, 1]."
+  }, {
+    name: 'Trending'
+    description: "'Total Score', except that newer opinions and topics are weighed more heavily."
+
+    comp: (a, b) ->
+      ov = (o) ->
+        ot = new Date(o.updated_at).getTime()
+        n = Date.now()
+        o.stance / (1 + Math.pow((n - ot) / 100000, 2))
+
+      val = (proposal) ->
+        sum = basic_proposal_scoring(proposal, ov).sum
+        n = Date.now()
+        pt = new Date(proposal.created_at).getTime()
+        sum / (1 + (n - pt) / 10000000000)  # decrease this constant to favor newer proposals
+
+      val(b) - val(a)
+  },
   {
     name: 'Alphabetically'
     comp: (a, b) -> a.name.localeCompare b.name
     description: "Sort alphabetically by the proposal's title"
-  },
-  {
-    func: (proposal, opinion_value) -> basic_proposal_scoring(proposal, opinion_value).avg
-    name: 'Average Score'
-    opinion_value: (o) -> o.stance
-    description: "Each proposal is scored by the average opinion score, where opinions are on [-1, 1]."
-  }, {
-    func: (proposal, opinion_value) -> basic_proposal_scoring(proposal, opinion_value).sum
-    name: 'Total Score'
-    opinion_value: (o) -> o.stance
-    description: "Each proposal is scored by the sum of opinions, where opinions are on [-1, 1]."
   }, {
-    func: (proposal, opinion_value) ->
-      sum = basic_proposal_scoring(proposal, opinion_value).sum
-      n = Date.now()
-      pt = new Date(proposal.created_at).getTime()
-      sum / (1 + (n - pt) / 10000000000)  # decrease this constant to favor newer proposals
-    name: 'Trending'
-    opinion_value: (o) ->
-      ot = new Date(o.updated_at).getTime()
-      n = Date.now()
-      o.stance / (1 + Math.pow((n - ot) / 100000, 2))
-    description: "'Total Score', except that newer opinions and topics are weighed more heavily."
-  }, {
-    func: (proposal) -> new Date(proposal.created_at).getTime()
+    comp: (a,b) -> new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
     name: 'Newest'
     description: "The proposals submitted most recently are shown first."
   }, {
     name: 'Unity'
-    func: (proposal, opinion_value) ->
-      stats = basic_proposal_scoring(proposal, opinion_value)
-      if stats.opinions.length > 1
-        Math.log(stats.opinions.length + 1) / stats.std_dev / (1 - Math.abs(stats.avg) + 1)
-      else
-        -1
-    opinion_value: (o) -> o.stance
     description: "Proposals where the community is most united for or against is shown highest."
+    comp: (a,b) ->
+      ov = (o) -> o.stance
+      val = (proposal) ->
+        stats = basic_proposal_scoring(proposal, ov)
+        if stats.opinions.length > 1
+          Math.log(stats.opinions.length + 1) / stats.std_dev / (1 - Math.abs(stats.avg) + 1)
+        else
+          -1
+      val(b) - val(a)
+
   }, {
     name: 'Difference'
-    func: (proposal, opinion_value) ->
-      stats = basic_proposal_scoring(proposal, opinion_value)
-      if stats.opinions.length > 1
-        stats.std_dev * Math.log(stats.opinions.length + 1)
-      else
-        -1
-    opinion_value: (o) -> o.stance
     description: "Proposals where the community is most split is shown highest."
+    comp: (a,b) ->
+      ov = (o) -> o.stance
+      val = (proposal) ->
+        stats = basic_proposal_scoring(proposal, ov)
+        if stats.opinions.length > 1
+          stats.std_dev * Math.log(stats.opinions.length + 1)
+        else
+          -1
+      val(b) - val(a)
+
   }, {
-    func: (proposal, opinion_value) -> basic_proposal_scoring
     name: 'Most Activity'
-    opinion_value: (o) -> 1 + (o.point_inclusions or []).length
     description: "Ranked by number of opinions and discussion."
+
+    comp: (a,b) ->
+      ov = (o) -> 1 + (o.point_inclusions or []).length
+      basic_proposal_scoring(b, ov).sum - basic_proposal_scoring(a, ov).sum
+
   }

 ]
-

 set_sort = ->
   sort = fetch 'sort_proposals'
-
-  if !sort.func?
+  if !sort.name?
     found = false
     loc = fetch('location')
     if loc.query_params?.sort_by
@@ -327,10 +331,10 @@
   render: ->

     sort = fetch 'sort_proposals'
-    set_sort() if !sort.func?
+    set_sort() if !sort.name?

     trigger = (e) =>
-      _.extend sort, sort_options[@local.focus]
+      _.extend sort, sort_options[@local.focus]
       save sort
       @local.sort_menu = false
       save @local
@@ -350,14 +354,12 @@
       @local.sort_menu = false
       save @local

-
     SPAN
       style:
         color: 'black'
         fontSize: 14

       "sort by "
-

       DIV
         ref: 'menu_wrap'


fix album.path index not being unique

fix album.path index not being unique

--- server/models/album.coffee
+++ server/models/album.coffee
@@ -30,8 +30,6 @@
 exports.albumSchema = albumSchema = new Schema
   path:
     type: String
-    required: true
-    index: true

   title:
     type: String
@@ -54,6 +52,7 @@
     type: mediaSpec
     required: false

+albumSchema.index {path: 1}, {unique: true}
 albumSchema.index {'pictures.path': 1}, {unique: true, sparse: true}
 albumSchema.index {'breadcrumb.path': 1}


Add Checkboxes to Markdown Lists

Add Checkboxes to Markdown Lists

Just begin your list item with `[ ]` or `[x]`. Like on Github.

--- lib/transforms/markdownComments.coffee
+++ lib/transforms/markdownComments.coffee
@@ -29,6 +29,12 @@

 # Instantiate custom renderer, will be used to collect headlines for TOC
 renderer = new marked.Renderer()
+
+# Add support for checkbox list items
+renderer.listitem = (text) ->
+  text = text.replace /^\[x\] /, '<input type="checkbox" checked disabled/> '
+  text = text.replace /^\[ \] /, '<input type="checkbox" disabled/> '
+  return "<li>#{text}</li>\n"

 ###
 # @method Set Heading Renderer


xhr: requests related with messages are added

--- client/app/lib/socialapicontroller.coffee
+++ client/app/lib/socialapicontroller.coffee
@@ -638,6 +638,47 @@
     return str.join "&"

   message:
+
+    byId : (options, callback) ->
+      { id } = options
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/message/#{id}"
+      , callback
+
+    delete : (options, callback) ->
+      { id } = options
+      doXhrRequest
+        type     : 'DELETE'
+        endPoint : "/api/social/message/#{id}"
+      , callback
+
+    edit : (options, callback) ->
+      { id } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/message/#{id}"
+        data     : options
+      , callback
+
+    bySlug : (options, callback) ->
+      { slug } = options
+      doXhrRequest
+        type     : 'GET'
+        endPoint : "/api/social/message/slug/#{slug}"
+      , callback
+
+    post : (options, callback) ->
+      { id } = options
+      doXhrRequest
+        type     : 'POST'
+        endPoint : "/api/social/channel/#{id}/message"
+        data     : options
+      , callback
+
+
+
+  message:
     byId                 : messageRequesterFn
       fnName             : 'byId'
       validateOptionsWith: ['id']


hub: don't send error events back to local_hub -- leads to nonsense

--- src/smc-hub/local_hub_connection.coffee
+++ src/smc-hub/local_hub_connection.coffee
@@ -300,6 +300,9 @@
                         return
                     when 'file_read_from_project'
                         # handle elsewhere by the code that requests the file
+                        return
+                    when 'error'
+                        # ignore -- don't care since handler already gone.
                         return
                     else
                         write_mesg(message.error(error:"unknown event '#{mesg.event}'"))
@@ -473,7 +476,7 @@
                             opts.cb(resp.error)
                         else
                             opts.cb(undefined, resp)
-                # As mentioned above -- there's no else -- if not timeout then
+                # As mentioned above -- there's no else -- if not timeout then
                 # we do not listen for a response.

     ####################################################


Implemented PPU scrolling logic.

Implemented PPU scrolling logic.

--- src/PPU.coffee
+++ src/PPU.coffee
@@ -20,6 +20,7 @@
     powerUp: ->
         @resetOAM()
         @resetRegisters()
+        @resetVariables()

     resetOAM: ->
         @objectAttributeMemory = (0 for [0..0x100]) # 256B
@@ -32,6 +33,13 @@
         @vramAddress = 0
         @vramReadBuffer = 0

+    resetVariables: ->
+        @selectedNameTableIndex = 0
+        @coarseXScroll = 0
+        @coarseYScroll = 0
+        @fineXScroll = 0
+        @fineYScroll = 0
+
     ###########################################################
     # Control register
     ###########################################################
@@ -111,12 +119,25 @@
     writeScroll: (value) ->
         @writeAddress value # Uses the same register as VRAM addressing

+    updateScrollingDataFromVRAMAddress: ->
+        @coarseXScroll =           @vramAddress        & 0x1E # Bits 0-4
+        @coarseYScroll =          (@vramAddress >>  5) & 0x1E # Bits 5-9
+        @selectedNameTableIndex = (@vramAddress >> 10) & 0x03 # Bits 10,11
+        @fineYScroll =            (@vramAddress >> 12) & 0x07 # Bits 12-14
+
+    updateVRAMAddressFromScrollingData: ->
+        @vramAddress = @coarseXScroll                | # Bits 0-4
+                       @coarseYScroll          <<  5 | # Bits 5-9
+                       @selectedNameTableIndex << 10 | # Bits 10,11
+                       @fineYScroll            << 12   # Bits 12-14
+
     ###########################################################
     # VRAM access
     ###########################################################

     writeAddress: (address) ->
         @vramAddress = (@vramAddress << 8 | address) & 0xFFFF
+        @updateScrollingDataFromVRAMAddress()
         address

     readData: ->
@@ -153,7 +174,7 @@
         @ppuMemory.write address, value

     isPalleteAddress: (address) ->
-        address & 0x3F00
+        (address & 0x3F00) == 0x3F00

     ###########################################################
     # Rendering
@@ -172,6 +193,21 @@
         @framebuffer

     tick: ->
-
+
+    incrementXScroll: ->
+        @fineXScroll = (@fineXScroll + 1) & 0x07
+        if @fineXScroll is 0 # Fine X scroll overflow
+            @coarseXScroll = (@coarseXScroll + 1) & 0x1E
+            if @coarseXScroll is 0 # Coarse X scroll overflow
+                @selectedNameTableIndex ^= 0x01 # Switch horizontal nametable
+        @updateVRAMAddressFromScrollingData()
+
+    incrementYScroll: ->
+        @fineYScroll = (@fineYScroll + 1) & 0x07
+        if @fineYScroll is 0 # Fine X scroll overflow
+            @coarseYScroll = (@coarseYScroll + 1) & 0x1E
+            if @coarseYScroll is 0 # Coarse X scroll overflow
+                @selectedNameTableIndex ^= 0x02 # Switch vertical nametable
+        @updateVRAMAddressFromScrollingData()

 module.exports = PPU


No trace.

No trace.

--- src/app.coffee
+++ src/app.coffee
@@ -388,7 +388,6 @@
                 removed: units

         _clearRadius: ->
-            console.trace()
             pos = @selectedPosition.value()
             if pos?
                 hasFilter = pos.get 'radiusFilter'


Humanize dates in views

Humanize dates in views

--- src/app.coffee
+++ src/app.coffee
@@ -28,7 +28,7 @@
     if Buffer.isBuffer(json[k])
       fields[k] = { path: "/crashreports/#{report.id}/files/#{k}" }
     else if v instanceof Date
-      fields[k] = moment(v).format('lll')
+      fields[k] = moment(v).fromNow()
     else
       fields[k] = if v? then v else 'not present'


(fix)change layer name back to original

(fix)change layer name back to original

--- src/app.coffee
+++ src/app.coffee
@@ -21,7 +21,7 @@
 # Setup data layers
 dataLayers = [
     new app.models.GeoJSONLayer
-      id: "earthfissures"
+      id: "earthFissures"
       layerName: "Earth Fissures"
       serviceUrl: app.geoserverUrl
       serviceType: "WFS"


adjust zoom levels and base layer

adjust zoom levels and base layer

--- src/app.coffee
+++ src/app.coffee
@@ -12,8 +12,8 @@
 app.map = new L.Map "map",
   center: center
   zoom: zoom
-  minZoom: 7
-  maxZoom: 14
+  minZoom: 6
+  maxZoom: 15

 app.geoserverUrl = "http://data.usgin.org/arizona/ows"

@@ -381,6 +381,7 @@
       layerName: "Road Map"
       apiKey: app.bingApiKey
       bingType: "Road"
+      active: true
   ,
     new app.models.BingLayer
       id: "Aerial"
@@ -393,7 +394,6 @@
       layerName: "Imagery with Labels"
       apiKey: app.bingApiKey
       bingType: "AerialWithLabels"
-      active: true
 ]

 app.baseLayerCollection = new app.models.LayerCollection baseLayers


Clear other units on search results load.

--- src/app.coffee
+++ src/app.coffee
@@ -375,6 +375,7 @@

         _search: (query) ->
             @selectedPosition.clear()
+            @clearUnits all: true
             @searchState.set 'input_query', query,
                 initial: true
             @searchState.trigger 'change', @searchState,


fix 400 issue

fix 400 issue

--- src/app.coffee
+++ src/app.coffee
@@ -69,11 +69,9 @@
     sqs.sendMessage message, (err, data) ->
       if err?
         console.error err
-        return res.send(500)
+        res.send(500)
       else
-        return res.send(200)
-
-    res.send(400)
+        res.send(200)
   else
     res.send(403)


add selector all pages that contain panels instances. /cc @procload

--- app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
+++ app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
@@ -288,7 +288,7 @@
       @options.pages[i]['panel_selector'] = el
       @active_panel = el
       @options.pages[i][''] = $(el).attr('id',"#{@options.pages[i]['id']}-panel")
-
+      $(el).parent().addClass('panels')
       $(el).amplePanels(@options.pages_options)
         .bind 'slide_horizontal', (e,d,dir) ->
           ref.load(i) if dir == 'next'


Consider a timeout as a failure

Consider a timeout as a failure

Otherwise the runner would be green even if timeouts ocurred.

--- lib/assets/javascripts/oojspec.js.coffee
+++ lib/assets/javascripts/oojspec.js.coffee
@@ -10,7 +10,7 @@
     @assertions.on 'pass',    => @stats.tests++; @stats.assertions++
     @assertions.on 'failure', => @stats.tests++; @stats.failures++
     #@runner.on 'context:start', => @stats.contexts++
-    @runner.on 'test:timeout', => @stats.timeouts++
+    @runner.on 'test:timeout', => @stats.timeouts++; @assertions.emit 'failure'
     @runner.on 'test:error', => @stats.errors++
     @runner.on 'test:deferred', => @stats.deferred++


Add an optional-title when search happened

--- client/app/Applications/Members.kdapplication/AppController.coffee
+++ client/app/Applications/Members.kdapplication/AppController.coffee
@@ -28,6 +28,7 @@
       filter                :
         everything          :
           title             : "All Members "
+          optional_title    : if @_searchValue then "Search results for #{@_searchValue} in all members" else null
           dataSource        : (selector, options, callback)=>
             if @_searchValue
               bongo.api.JAccount.byRelevance @_searchValue, options, callback


remove glitch when moving a key while timer is at 0, fix #25

--- src/scripts/core/Orchestrator.coffee
+++ src/scripts/core/Orchestrator.coffee
@@ -84,9 +84,14 @@
                 tween = TweenMax.to(item.values, tween_duration, val)
                 propertyTimeline.add(tween, key.time)

+            # Directly seek the property timeline to update the value.
             propertyTimeline.seek(seconds)
-          # force main timeline to refresh
-          seconds = seconds - 0.0000001
+          # Force main timeline to refresh but never try to go to < 0
+          # to prevent glitches when current time is 0.
+          if seconds > 0
+            seconds = seconds - 0.0000001
+          else
+            seconds = seconds + 0.0000001

       # Finally update the main timeline.
       @mainTimeline.seek(seconds)


timetravel diff:  when switching between view modes, reconfigure codemirror editor properly.

--- src/smc-webapp/editor_history.coffee
+++ src/smc-webapp/editor_history.coffee
@@ -174,12 +174,19 @@
             @element.find(".salvus-editor-history-diff-mode").show()
             @diff_slider.show()
             @slider.hide()
-        else
+            @set_doc_diff(@goto_diff()...)
+        else
+            for cm in @view_doc.codemirrors()
+                cm.setOption('lineNumbers', true)
+                cm.setOption('gutters', [])
+                cm.setValue('')
+                cm.setValue(@syncstring.version(@goto_revision(@revision_num)))
             @element.find("a[href=#hide-diff]").hide()
             @element.find("a[href=#show-diff]").show()
             @element.find(".salvus-editor-history-diff-mode").hide()
             @diff_slider.hide()
             @slider.show()
+            @set_doc(@goto_revision())

     set_doc: (time) =>
         if not time?
@@ -302,7 +309,7 @@
         if not num2?
             return
         if not num1?
-            num1 = @revision_num1 ? Math.max(0, num2 - 10)
+            num1 = @revision_num1 ? Math.max(0, Math.floor(num2/2))
         versions = @syncstring.all_versions()
         if not versions?
             # not yet initialized
@@ -385,7 +392,7 @@
             min     : 0
             max     : @length - 1
             step    : 1
-            values  : [Math.max(@revision_num - 10, 0), @revision_num]
+            values  : [Math.max(Math.floor(@revision_num/2), 0), @revision_num]
             range   : true
             slide  : (event, ui) => # TODO: debounce this
                 set_doc(@goto_diff(ui.values[0], ui.values[1]))


Moved localStorage private methods to the bottom

--- source/micro.localstorage.coffee
+++ source/micro.localstorage.coffee
@@ -1,18 +1,4 @@
-'use strict'
-
-_filter = (obj, predicate) ->
-  result = []
-  result.push(item) for item in obj when predicate item
-  result
-
-_filterOne = (obj, predicate) ->
-  return item for item in obj when predicate item
-  false
-
-_matches = (attrs) ->
-  (obj) ->
-    return false for key, val of attrs when attrs[key] isnt obj[key]
-    true
+"use strict"

 localDB =
   data: {}
@@ -57,4 +43,19 @@
     items.splice index, 1 if index > -1
     @write collection

+# -- Private methods -----------------------------------------------------------
+_filter = (obj, predicate) ->
+  result = []
+  result.push(item) for item in obj when predicate item
+  result
+
+_filterOne = (obj, predicate) ->
+  return item for item in obj when predicate item
+  false
+
+_matches = (attrs) ->
+  (obj) ->
+    return false for key, val of attrs when attrs[key] isnt obj[key]
+    true
+
 module.exports = localDB


Changed path for delete, retry actions on tasks

--- api.coffee
+++ api.coffee
@@ -7,8 +7,8 @@
 querystring = require 'querystring'

 # regular expression objects
-TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tasks\/?$/
-TASK_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tasks\/([a-zA-Z0-9\-\_\%]+)(\/[a-zA-Z_]*)?$/
+DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tasks\/?$/
+TASKS_REGEX = /^\/tasks\/([a-zA-Z0-9\-\_\%]+)(\/[a-zA-Z_]*)?$/
 TAGS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tags\/([a-zA-Z0-9\-\_\%]+)\/?$/
 PRESETS_REGEX = /^\/presets\/([a-zA-Z0-9\-\_\%]+)\/?$/
 FILES_REGEX = /^\/files\/([a-zA-Z0-9\-\_\%\ \.\/\(\)]+)\/?$/
@@ -159,9 +159,9 @@
         else
           response.writeHead 405, {'Allow': 'POST, DELETE'}
           response.end('405 Method Not Allowed')
-      else if TASKS_REGEX.test(urlParts.pathname)
+      else if DEVICE_TASKS_REGEX.test(urlParts.pathname)
         if request.method == 'POST'
-          deviceId = querystring.unescape(TASKS_REGEX.exec(urlParts.pathname)[1])
+          deviceId = querystring.unescape(DEVICE_TASKS_REGEX.exec(urlParts.pathname)[1])
           if body
             # queue given task
             task = sanitizeTask(deviceId, JSON.parse(body))
@@ -214,11 +214,10 @@
         else
           response.writeHead 405, {'Allow': 'POST'}
           response.end('405 Method Not Allowed')
-      else if TASK_REGEX.test(urlParts.pathname)
-        r = TASK_REGEX.exec(urlParts.pathname)
-        deviceId = querystring.unescape(r[1])
-        taskId = mongodb.ObjectID(querystring.unescape(r[2]))
-        action = try querystring.unescape(r[3])
+      else if TASKS_REGEX.test(urlParts.pathname)
+        r = TASKS_REGEX.exec(urlParts.pathname)
+        taskId = mongodb.ObjectID(querystring.unescape(r[1]))
+        action = try querystring.unescape(r[2])
         if not action? or action is '/'
           if request.method == 'DELETE'
             db.tasksCollection.remove({'_id' : taskId}, (err, removedCount) ->


Fixed context menu postioning issue by settings its left position relative to window.

--- components/contextmenu/contextmenu.coffee
+++ components/contextmenu/contextmenu.coffee
@@ -2,7 +2,8 @@

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

-    options.cssClass = @utils.curryCssClass "jcontextmenu", options.cssClass
+    options.cssClass  = @utils.curryCssClass "jcontextmenu", options.cssClass
+    options.menuWidth or= 172

     super options, data

@@ -35,16 +36,26 @@
     super

   positionContextMenu:()->
+    options     = @getOptions()
+    event       = options.event or {}
+    mainView    = @getSingleton 'mainView'

-    event       = @getOptions().event or {}
-    mainHeight  = @getSingleton('mainView').getHeight()
+    mainHeight  = mainView.getHeight()
+    mainWidth   = mainView.getWidth()

-    top         = @getOptions().y or event.pageY or 0
     menuHeight  = @getHeight()
+    menuWidth   = @getWidth()
+
+    top         = options.y or event.pageY or 0
+    left        = options.x or event.pageX or 0
+
     if top + menuHeight > mainHeight
-      top = mainHeight - menuHeight - 15
+      top  = mainHeight - menuHeight - 15
+
+    if left + menuWidth > mainWidth
+      left  = mainWidth - menuWidth

     @getDomElement().css
-      width     : "172px"
+      width     : "#{options.menuWidth}px"
       top       : top
-      left      : @getOptions().x or event.pageX or 0
+      left      : left


Add /env debug route, change host name

Add /env debug route, change host name

--- app.coffee
+++ app.coffee
@@ -1,5 +1,5 @@
 port = process.env.VMC_APP_PORT || 3000
-host = process.env.VCAP_APP_HOST || "localhost"
+host = process.env.APP_HOST || "localhost"

 require('zappajs') host, port, ->
   manifest = require './package.json'
@@ -45,6 +45,8 @@
   @get '/': ->
     @render 'landing': {passport: @session.passport}

+  @get '/env': -> @response.json process.env
+
   # Authenication
   @app.get '/auth/google', passport.authenticate 'google'
   @app.get '/auth/google/return', passport.authenticate 'google', { successRedirect: '/', failureRedirect: '/login' }
@@ -54,4 +56,3 @@

   @get '/auth/:provider/return': ->
     passport.authenticate @params.provider, { successRedirect: '/', failureRedirect: '/login' }
-  @get '/': -> 'Hello, World!'


Fix auth.json parsing

Fix auth.json parsing

Fixup for 8680414296cef45333be22b78a8d5a66ad4e4aa6

--- app.coffee
+++ app.coffee
@@ -272,7 +272,7 @@
 				if auth.groups?
 					(auth.groups[group][_package] = createComparator versions for _package, versions of packages) for group, packages of auth.groups
 				if auth.users?
-					(auth.users[user].packages[_package] = createComparator versions for _package, versions of userdata.packages) for username, userdata of auth.users when userdata.packages?
+					(auth.users[username].packages[_package] = createComparator versions for _package, versions of userdata.packages) for username, userdata of auth.users when userdata.packages?

 				debug "Updated auth"
 			catch err


increase body parser limit to 64kb

increase body parser limit to 64kb

the 16kb limit is not enough when the doc has too many escape characters

--- app.coffee
+++ app.coffee
@@ -31,8 +31,8 @@
 app.get  '/project/:project_id/ranges', HttpController.getAllRanges
 app.get  '/project/:project_id/doc/:doc_id', HttpController.getDoc
 app.get  '/project/:project_id/doc/:doc_id/raw', HttpController.getRawDoc
-# Add 16kb overhead for the JSON encoding
-app.post '/project/:project_id/doc/:doc_id', bodyParser.json(limit: Settings.max_doc_length + 16 * 1024), HttpController.updateDoc
+# Add 64kb overhead for the JSON encoding
+app.post '/project/:project_id/doc/:doc_id', bodyParser.json(limit: Settings.max_doc_length + 64 * 1024), HttpController.updateDoc
 app.del  '/project/:project_id/doc/:doc_id', HttpController.deleteDoc

 app.post  '/project/:project_id/archive', HttpController.archiveAllDocs


put all shutdown logic in beginShutdown, force program to exit after 2 mins

--- app.coffee
+++ app.coffee
@@ -103,8 +103,6 @@
 			else
 				res.send(503)

-
-
 app.get '*', (req, res)->
 	res.send 404

@@ -115,12 +113,16 @@
 beginShutdown = () ->
 	if appIsOk
 		appIsOk = false
-		server.close()
+		# hard-terminate this process if graceful shutdown fails
+		killTimer = setTimeout () ->
+			process.exit 1
+		, 120*1000
+		killTimer.unref?() # prevent timer from keeping process alive
+		server.close () ->
+			logger.log "closed all connections"
+			Metrics.close()
+			process.disconnect?()
 		logger.log "server will stop accepting connections"
-
-server.on "close", () ->
-	logger.log "closed all connections"
-	process.exit 1

 server.listen port, host, ->
 	logger.log("filestore store listening on #{host}:#{port}")


Feat/preview (#3)

Feat/preview (#3)

--- keymaps/language-coursescript.cson
+++ keymaps/language-coursescript.cson
@@ -1,3 +1,3 @@
 'atom-text-editor':
   'ctrl-c i': 'language-coursescript:insert-id'
-  'ctrl-c o': 'language-coursescript:preview'
+  'ctrl-c p': 'language-coursescript:preview'


remove empty app.coffee file

remove empty app.coffee file

--- test/sandbox/coffeescript/app.coffee
+++ test/sandbox/coffeescript/app.coffee
@@ -1 +0,0 @@
-# silence is golden


Syntax

Syntax

--- test/syno/SurveillanceStation.coffee
+++ test/syno/SurveillanceStation.coffee
@@ -37,8 +37,8 @@
           test.done()
       return

-  ## Not available on demo.synology.com ?
-  # Unknown error
+  # # Not available on demo.synology.com ?
+  # # Parameter invalid
   # getCameraCapability: (test)->
   #     @syno.ss.getCameraCapability {'vendor': 'AXIS', 'model': 'P3364-VE'}, (error, data)->
   #         test.ifError error
@@ -114,7 +114,7 @@
   #     return

   ## Not available on demo.synology.com
-  # Error: The requested method does not exist
+  ## Error: The requested method does not exist
   # getPTZCameraPatrolsSchedule: (test)->
   #     @syno.ss.getPTZCameraPatrolsSchedule {'cameraId': 4}, (error, data)->
   #         test.ifError error


refactor git-checkout-all-files

refactor git-checkout-all-files

--- lib/models/git-checkout-all-files.coffee
+++ lib/models/git-checkout-all-files.coffee
@@ -1,12 +1,8 @@
 git = require '../git'
 notifier = require '../notifier'

-gitCheckoutAllFiles = (repo) ->
-  git.cmd
-    args: ['checkout', '-f']
-    cwd: repo.getWorkingDirectory()
-    stdout: (data) ->
-      notifier.addSuccess "File changes checked out successfully!"
-      git.refresh()
-
-module.exports = gitCheckoutAllFiles
+module.exports = (repo) ->
+  git.cmd(['checkout', '-f'], cwd: repo.getWorkingDirectory())
+  .then (data) ->
+    notifier.addSuccess "File changes checked out successfully!"
+    git.refresh()


Allow backspace to keep the suggestion list open when it’s already open

--- lib/autocomplete-manager.coffee
+++ lib/autocomplete-manager.coffee
@@ -177,6 +177,12 @@
     # TODO: Should we *always* focus the editor? Probably not...
     @suggestionList?.hideAndFocusOn(@editorView)

+  requestHideSuggestionList: (command) ->
+    @hideTimeout = setTimeout(@hideSuggestionList, 0)
+
+  cancelHideSuggestionListRequest: ->
+    clearTimeout(@hideTimeout)
+
   # Private: Replaces the current prefix with the given match.
   #
   # match - The match to replace the current prefix with
@@ -219,7 +225,13 @@
   #
   # data - An {Object} containing information on why the cursor has been moved
   cursorMoved: ({textChanged}) =>
-    @hideSuggestionList() unless textChanged
+    # The delay is a workaround for the backspace case. The way atom implements
+    # backspace is to select left 1 char, then delete. This results in a
+    # cursorMoved event with textChanged == false. So we delay, and if the
+    # bufferChanged handler decides to show suggestions, it will cancel the
+    # hideSuggestionList request. If there is no bufferChanged event,
+    # suggestionList will be hidden.
+    @requestHideSuggestionList() unless textChanged

   # Private: Gets called when the user saves the document. Cancels the
   # autocompletion.
@@ -232,7 +244,12 @@
   # event - The change {Event}
   bufferChanged: ({newText, oldText}) =>
     return if @suggestionList.compositionInProgress
-    if atom.config.get('autocomplete-plus.enableAutoActivation') and (newText.trim().length is 1 or oldText.trim().length is 1)
+
+    autoActivationEnabled = atom.config.get('autocomplete-plus.enableAutoActivation')
+    wouldAutoActivate = newText.trim().length is 1 or (@suggestionList.isActive() and oldText.trim().length is 1)
+
+    if autoActivationEnabled and wouldAutoActivate
+      @cancelHideSuggestionListRequest()
       @contentsModified()
     else
       @hideSuggestionList()


add hook

add hook

--- lib/build/ios-build-view.coffee
+++ lib/build/ios-build-view.coffee
@@ -79,6 +79,17 @@
     @scheme.setText "chameleon-bundled"
     @BundleIdentifier.setText "com.foreveross.comeontom"
     @src.setText "exhibition/index.html"
+
+  destroy: ->
+    console.log  "ios-build-view destroy."
+    @remove()
+
+  attached: ->
+    console.log  "ios-build-view attached."
+
+  detached: ->
+    console.log  "ios-build-view detached."
+
   onClickIcon: ->
     openFile
       title: 'Select Icon Image'


fix prospectus upload

fix prospectus upload

--- lib/client/autoform-file.coffee
+++ lib/client/autoform-file.coffee
@@ -93,7 +93,7 @@
     theTemplate = t
     theEvent = e

-    if e.target.files[0].type = "image/png" || e.target.files[0].type = "image/jpg"
+    if e.target.files[0].type == "image/png" || e.target.files[0].type == "image/jpg"
       img = new Image
       img.onload = () ->
         if img.width < 1920 && img.height < 1080


Remove upvoters/downvoters from data model

--- lib/documents/annotation.coffee
+++ lib/documents/annotation.coffee
@@ -35,14 +35,6 @@
   #     _id
   #     name: ISO 639-1 dictionary
   #     slug: ISO 639-1 dictionary
-  #   upvoters: list of
-  #     _id: person id
-  #   downvoters: list of
-  #     _id: person id
-  # upvoters: list of
-  #   _id: person id
-  # downvoters: list of
-  #   _id: person id
   # local (client only): is this annotation just a temporary annotation on the client side

   @Meta
@@ -58,8 +50,4 @@
         tags: [@ReferenceField Tag, ['name', 'slug'], true, 'referencingAnnotations']
       tags: [
         tag: @ReferenceField Tag, ['name', 'slug']
-
-        # TODO: Field cannot be in a nested array (PeerDB)
-        #upvoters: [@ReferenceField Person]
-        #downvoters: [@ReferenceField Person]
       ]


Don't use jQuery

Don't use jQuery

Signed-off-by: Nathan Sobo <2e8aa918660411855c6d44d5bb2da677aa033255@github.com>

--- spec/command-palette-spec.coffee
+++ spec/command-palette-spec.coffee
@@ -1,5 +1,5 @@
 _ = require 'underscore-plus'
-{$, WorkspaceView} = require 'atom'
+{WorkspaceView} = require 'atom'
 CommandPalette = require '../lib/command-palette-view'

 describe "CommandPalette", ->
@@ -46,20 +46,6 @@
         for binding in keyBindings when binding.command == name
           expect(eventLi.find(".key-binding:contains(#{_.humanizeKeystroke(binding.keystrokes)})")).toExist()

-    it "displays all commands registered on the window", ->
-      editorEvents = atom.workspaceView.getActiveView().events()
-      windowEvents = $(window).events()
-      expect(_.isEmpty(windowEvents)).toBeFalsy()
-      for eventName, description of windowEvents
-        eventLi = palette.list.children("[data-event-name='#{eventName}']")
-        description = editorEvents[eventName] unless description
-        if description
-          expect(eventLi).toExist()
-          expect(eventLi.find('span')).toHaveText(description)
-          expect(eventLi.find('span').attr('title')).toBe(eventName)
-        else
-          expect(eventLi).not.toExist()
-
     it "focuses the mini-editor and selects the first command", ->
       expect(palette.filterEditorView.hasFocus()).toBeTruthy()
       expect(palette.find('.event:first')).toHaveClass 'selected'
@@ -101,7 +87,7 @@
   describe "when no element has focus", ->
     it "uses the root view as the element to display and trigger events for", ->
       atom.workspaceView.trigger 'command-palette:toggle'
-      $(':focus').blur()
+      document.activeElement.blur()
       atom.workspaceView.trigger 'command-palette:toggle'

       keyBindings = atom.keymap.findKeyBindings(atom.workspaceView.getActiveView())
@@ -116,7 +102,7 @@
   describe "when the body has focus", ->
     it "uses the root view as the element to display and trigger events for", ->
       atom.workspaceView.trigger 'command-palette:toggle'
-      $(document.body).focus()
+      document.body.focus()
       atom.workspaceView.trigger 'command-palette:toggle'
       keyBindings = atom.keymap.findKeyBindings(atom.workspaceView.getActiveView())
       for {name, displayName} in atom.commands.findCommands(target: atom.workspaceView[0])


Remove spare brackets

Remove spare brackets

--- spec/javascripts/setup.js.coffee
+++ spec/javascripts/setup.js.coffee
@@ -8,4 +8,3 @@

 Review.setupForTesting()
 Review.injectTestHelpers()
-})


Remap join lines to `meta-j`

Remap join lines to `meta-j`

--- keymaps/editor.cson
+++ keymaps/editor.cson
@@ -38,7 +38,7 @@
   'ctrl-meta-up': 'editor:move-line-up'
   'ctrl-meta-down': 'editor:move-line-down'
   'meta-D': 'editor:duplicate-line'
-  'ctrl-J': 'editor:join-line'
+  'meta-j': 'editor:join-line'
   'meta-<': 'editor:scroll-to-cursor'

 '.editor.mini':


fix normalize bug

fix normalize bug

--- src/dom.coffee
+++ src/dom.coffee
@@ -200,7 +200,9 @@
           DOM.removeNode(curNode)
         else if DOM.isTextNode(nextNode)
           nextNode = nextNode.nextSibling
-          DOM.setText(curNode, DOM.getText(curNode) + DOM.getText(curNode.nextSibling))
+          newText = DOM.getText(curNode) + DOM.getText(curNode.nextSibling)
+          DOM.setText(curNode, newText)
+          DOM.removeNode(curNode.nextSibling)
       curNode = nextNode

   isIE: (maxVersion) ->


:art: remove junk from the SymbolProvider specs

--- spec/symbol-provider-spec.coffee
+++ spec/symbol-provider-spec.coffee
@@ -29,7 +29,6 @@
       workspaceElement = atom.views.getView(atom.workspace)
       jasmine.attachToDOM(workspaceElement)

-
   afterEach ->
     atom.config.set('autocomplete-plus.defaultProvider', 'Fuzzy')

@@ -46,13 +45,8 @@
           atom.packages.activatePackage("autocomplete-plus").then (a) ->
             mainModule = a.mainModule

-      waitsFor ->
-        mainModule.autocompleteManager?.ready
-
       runs ->
         autocompleteManager = mainModule.autocompleteManager
-
-      runs ->
         advanceClock 1
         editorView = atom.views.getView(editor)

@@ -86,13 +80,8 @@
           atom.packages.activatePackage("autocomplete-plus").then (a) ->
             mainModule = a.mainModule

-      waitsFor ->
-        mainModule.autocompleteManager?.ready
-
       runs ->
         autocompleteManager = mainModule.autocompleteManager
-
-      runs ->
         advanceClock 1
         editorView = atom.views.getView(editor)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/number_extensions.coffee
+++ src/number_extensions.coffee
@@ -458,7 +458,7 @@

 I.rotation = 0.25.rotations

-1.rotation #
+1.rotation # =>
 </pre></code>

 @name turns


fix to setting too many timers at once on lyrics tracks

--- src/prong/track/lyrics.coffee
+++ src/prong/track/lyrics.coffee
@@ -17,8 +17,6 @@
         if i > 0 then i--
         return i

-
-
     lyrics = (selection, options) ->

         sequence = lyrics.sequence()
@@ -28,35 +26,49 @@
             .text(prong.trackName)

         height = selection.datum().height || sequence.trackHeight() || 128
-        display = selection.append('div').style('height', height + 'px')
-        #selection.text(' ');
+        display = selection.append('div')
+            .style('height', height + 'px')
+            .style('width', sequence.width() + 'px')
         display.classed('lyrics', true)

         uid = prong.uid()
         data = selection.datum().data
-        timeouts = []
+        timer = null
+        i = null

+        showLyric = -> display.text(data[i].text)

-        sequence.on 'play.lyrics' + uid, ->
+        setTimerForNextLyric = ->
+            hasNextLyric = data.length > (i + 1)
+            if hasNextLyric
+                nextLyric = data[i + 1]
+                currentTime = sequence.currentTime()
+                delay = (nextLyric.time - currentTime) * 1000
+                after = ->
+                    i++
+                    showLyric()
+                    setTimerForNextLyric()
+                timer = setTimeout(after, delay)
+
+        play = ->
             currentTime = sequence.currentTime()
-            # find the position of the first lyric to show
             i = lyricIndexAtTime(data, currentTime)
-            display.text(data[i].text)
-            for d in data.slice(i+1)
-                after = ->
-                    display.text(d.text)
-                timeouts.push(setTimeout(after, 1000 * (d.time - currentTime)))
+            showLyric()
+            setTimerForNextLyric()

+        stop = ->
+            if timer
+                clearTimeout(timer)

-        sequence.on 'stop.lyrics' + uid, ->
-            timeouts.map(clearTimeout)
-
-        # sequence.on('scrub.lyrics' + uid, ->
-        #     console.log('scrubbibg')
-
+        sequence.on 'play.lyrics' + uid, play
+        sequence.on 'stop.lyrics' + uid, stop
+        sequence.on 'loop.lyrics' + uid, (start) ->
+            stop()
+            play()
+
         sequence.timeline().on 'timeselect.lyrics' + uid, (time) ->
             i = lyricIndexAtTime(data, time)
-            display.text(data[i].text)
+            showLyric()

     lyrics.on = (type, listener) ->


Nix unused debug setting

Nix unused debug setting

--- src/jqt.coffee
+++ src/jqt.coffee
@@ -53,7 +53,6 @@
     addGlossToIcon: true
     backSelector: ".back, .cancel, .goback"
     cacheGetRequests: true
-    debug: true
     defaultAnimation: "slideleft"
     fixedViewport: true
     formSelector: "form"


better exception printing

better exception printing

--- src/jvm.coffee
+++ src/jvm.coffee
@@ -32,8 +32,11 @@
 show_stacktrace = (rs,e) ->
   e_type = rs.get_obj(e.exception.fields.cause).type
   detail_ref = e.exception.fields.detailMessage
-  detail = if detail_ref then rs.jvm2js_str rs.get_obj detail_ref else ''
-  console.error "Exception in thread \"main\" #{ext_classname e_type}: #{detail}"
+  if detail_ref
+    detail = rs.jvm2js_str rs.get_obj detail_ref
+    console.error "Exception in thread \"main\" #{detail}"
+  else
+    console.error "Exception in thread \"main\" #{ext_classname e_type}"
   for i in [e.stack.length-1..0] by -1
     entry = e.stack[i]
     console.error "\tat #{entry.cls}.#{entry.method}(#{entry.file}:#{entry.line}, code #{entry.op})"


New format for entry console log.

New format for entry console log.

--- src/log.coffee
+++ src/log.coffee
@@ -52,8 +52,10 @@
             new_entry = true

         if new_entry
-            console.log "%c#{if entry.active then 'working on' else 'distracted'} #{if entry.task then entry.task else 'UNKNOWN'} #{new Date(entry.date).toString()}",
-                "color: blue"
+            console.log "%c#{if entry.active then 'working on' else 'distracted'}
+                #{if entry.task then entry.task else if entry.active then 'UNKNOWN' else ''}
+                #{new Date(entry.date).toLocaleString()}",
+                "color:#{if entry.active then 'blue' else 'gray'}"
             @last_entry = entry
             @entries.push entry
             if save


showing 'In [group]' link only when in parent group

--- client/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
+++ client/app/Applications/Activity.kdapplication/views/activityitemchild.coffee
@@ -5,7 +5,10 @@

   constructor:(options, data)->

+    currentGroup = KD.getSingleton("groupsController").getCurrentGroup()
+
     @getContentGroupLinkPartial = (groupSlug, groupName)->
+      return "" if currentGroup?.slug is groupSlug
       """
       In #{groupName}
       """
@@ -14,7 +17,6 @@
       tagName     : "span"
       partial     : @getContentGroupLinkPartial(data.group, data.group)

-    currentGroup = KD.getSingleton("groupsController").getCurrentGroup()
     if currentGroup?.slug is data.group
       @contentGroupLink.updatePartial @getContentGroupLinkPartial(currentGroup.slug, currentGroup.title)
     else


Teamwork: Implemented previewFile for Teamwork.

--- client/app/Applications/Teamwork.kdapplication/Views/teamworkworkspace.coffee
+++ client/app/Applications/Teamwork.kdapplication/Views/teamworkworkspace.coffee
@@ -24,6 +24,17 @@
         joinedUser = snapshot.name()
         return if not joinedUser or joinedUser is KD.nick()
         @hidePlaygroundsButton()
+
+
+      if @amIHost()
+        # currently only for host.
+        # bc of clients need to know host's vmName and active pane's file data etc.
+        {viewerHeader} = @getActivePanel().paneLauncher.previewPane.previewer
+        viewerHeader.addSubView new KDButtonView
+          cssClass : "clean-gray workspace-previewer-button"
+          title    : "View active file"
+          callback : =>
+            @previewFile()

   createLoader: ->
     @container.addSubView @loader = new KDCustomHTMLView
@@ -93,3 +104,26 @@
       @getDelegate().showMarkdownModal()
     else
       Panel::showHintModal.call @getActivePanel()
+
+  previewFile: ->
+    activePanel     = @getActivePanel()
+    editor          = activePanel.getPaneByName "editor"
+    file            = editor.getActivePaneFileData()
+    path            = FSHelper.plainPath file.path
+    error           = "File must be under Web folder"
+    isLocal         = path.indexOf("localfile") is 0
+    isNotPublic     = not FSHelper.isPublicPath path
+    {previewPane}   = activePanel.paneLauncher
+    {defaultVmName} = KD.getSingleton "vmController"
+
+    return if isLocal or isNotPublic
+      error         = "This file cannot be previewed" if isLocal
+      new KDNotificationView
+        title       : error
+        cssClass    : "error"
+        type        : "mini"
+        duration    : 2500
+        container   : previewPane
+
+    url = path.replace "/home/#{@getHost()}/Web", defaultVmName
+    previewPane.openUrl url


amqp: Return in sendTo/subscribeToQueue() errors if not connected

Also handle not connected in ack/nackMessage

--- src/amqp.coffee
+++ src/amqp.coffee
@@ -70,6 +70,7 @@

   ## Sending/Receiving messages
   sendTo: (type, name, message, callback) ->
+    return callback new Error 'msgflo.amqp.sendTo():  Not connected' if not @channel
     # queue must exists
     data = new Buffer JSON.stringify message
     showLimit = 80
@@ -88,6 +89,7 @@

   subscribeToQueue: (queueName, handler, callback) ->
+    return callback new Error 'msgflo.amqp.subscribeToQueue():  Not connected' if not @channel
     debug 'subscribe', queueName
     # queue must exists
     deserialize = (message) =>
@@ -107,11 +109,13 @@

   ## ACK/NACK messages
   ackMessage: (message) ->
+    return if not @channel
     fields = message.amqp.fields
     debug 'ACK', fields.routingKey, fields.deliveryTag
     # NOTE: server will only give us new message after this
     @channel.ack message.amqp, false
   nackMessage: (message) ->
+    return if not @channel
     fields = message.amqp.fields
     debug 'NACK', fields.routingKey, fields.deliveryTag
     @channel.nack message.amqp, false, false


:memo: Note for S behavior spec

:memo: Note for S behavior spec

--- spec/operators-spec.coffee
+++ spec/operators-spec.coffee
@@ -186,8 +186,22 @@
       ensure 'escape', text: '12345\nabc\nABCDE'
       ensure 'u', text: "12345\nabcde\nABCDE", selectedText: ''

+    # Here is original spec I believe its not correct, if it says 'works'
+    # text result should be '\n' since S delete current line.
+    # Its orignally added in following commit, as fix of S(from description).
+    # But original SubstituteLine replaced with Change and MoveToRelativeLine combo.
+    # I believe this spec should have been failed at that time, but havent'.
+    # https://github.com/atom/vim-mode/commit/6acffd2559e56f7c18a4d766f0ad92c9ed6212ae
+    #
+    # it "works when the cursor's goal column is greater than its current column", ->
+    #   set text: "\n12345", cursor: [1, Infinity]
+    #   ensure 'kS', text: '\n12345'
+
     it "works when the cursor's goal column is greater than its current column", ->
       set text: "\n12345", cursor: [1, Infinity]
+      # Should be here, but I commented out before I have confidence.
+      # ensure 'kS', text: '\n'
+      # Folowing line include Bug ibelieve.
       ensure 'kS', text: '\n12345'
     # Can't be tested without setting grammar of test buffer
     xit "respects indentation", ->


Hydrator: fix resource hydration specs.

Hydrator: fix resource hydration specs.

--- src/OVirtResponseHydrator.coffee
+++ src/OVirtResponseHydrator.coffee
@@ -119,7 +119,7 @@
 #
 # - Detect resources.
 # - Delegate resource hydration to other hydrator instance.
-# - Save results in resources property.
+# - Replace node value with hydrated resource.
 #
 # ### Hydrate actions
 #


Selecting experiment selected in infrastructures tree view in invoked booster modal by default

--- app/assets/javascripts/infrastructures/infrastructures_tree.js.coffee
+++ app/assets/javascripts/infrastructures/infrastructures_tree.js.coffee
@@ -374,7 +374,8 @@
   boosterDialogPath: (infrastructure_name) ->
     # TODO: path as parameter
     "/infrastructure/get_booster_dialog?" + $.param({
-      infrastructure_name: infrastructure_name
+      infrastructure_name: infrastructure_name,
+      experiment_id: @experiment_id
     })

   commandConfirmDialogPath: (infrastructure_name, record_id, command) ->


json-import: 'no_upload' flag

json-import: 'no_upload' flag

--- src/routes/json-import.coffee
+++ src/routes/json-import.coffee
@@ -40,7 +40,10 @@
 			resMap[newId] = resMap[oldId]
 			delete resMap[oldId]

-_postFiles = (db, files, callback) ->
+_postFiles = (db, files, no_upload, callback) ->
+	if no_upload
+		log.warn "Skipping uploads as no_upload was given"
+		return callback()
 	if 'infolisFile' not of db
 		log.warn "No infolisFiles in the database"
 		return callback()
@@ -50,7 +53,7 @@
 		if fileId not of files
 			missingFiles.push fileId
 	if missingFiles.length > 0
-		return next "Did not provide these files: #{[missingFiles]}"
+		return callback new Error("Did not provide these files: #{[missingFiles]}")
 	# Post all files
 	Async.eachLimit Object.keys(db.infolisFile), 10, (fileId, cb) ->
 		log.silly files[fileId]
@@ -146,6 +149,7 @@
 		)
 		unless 'no-timestamp' of req.query
 			timestamp = "#{Date.now()}"
+		no_upload = 'no-upload' of req.query
 		form.parse req, (err, fields, files) ->
 			if err
 				log.error err
@@ -153,7 +157,7 @@
 			if not fields.db
 				return next "Must pass 'db'"
 			db = JSON.parse fields.db.toString()
-			_postFiles db, files, (err) ->
+			_postFiles db, files, no_upload, (err) ->
 				return next err if err
 				# replace ids
 				if timestamp then for resourceEndpoint in PUT_ORDER


Non-multiple-allowed questions are required

--- app/classifier/tasks/survey.cjsx
+++ app/classifier/tasks/survey.cjsx
@@ -66,6 +66,16 @@
   getInitialState: ->
     answers: {}

+  allFilledIn: ->
+    requiredIDs = for questionID in @props.task.questionsOrder
+      question = @props.task.questions[questionID]
+      if question.multiple
+        # Multiple-allowed questions aren't required.
+        continue
+      else
+        questionID
+    (true for id in requiredIDs when id not of @state.answers).length is 0
+
   render: ->
     choice = @props.task.choices[@props.choiceID]
     <div className="survey-choice-details">
@@ -91,7 +101,7 @@
             </label>}
         </div>}
       <button type="button" onClick={@props.onCancel}>Cancel</button>
-      <button type="button" onClick={@handleIdentification}>Identify</button>
+      <button type="button" disabled={not @allFilledIn()} onClick={@handleIdentification}>Identify</button>
     </div>

   handleAnswer: (questionID, answerID, e) ->


Don't send multiple status codes on insertFile error

--- app/coffee/FileController.coffee
+++ app/coffee/FileController.coffee
@@ -49,7 +49,8 @@
 			if err?
 				logger.log err: err, key: key, bucket: bucket, "error inserting file"
 				res.send 500
-			res.send 200
+			else
+				res.send 200

 	copyFile: (req, res)->
 		metrics.inc "copyFile"


Minor changes on model service.

Minor changes on model service.

--- app/coffee/services/model.coffee
+++ app/coffee/services/model.coffee
@@ -19,36 +19,38 @@

         class Model
             constructor: (name, data, dataTypes) ->
-                @_attrs = data
-                @_name = name
-                @_dataTypes = dataTypes
-
-                @_isModified = false
-                @_modifiedAttrs = {}
-
-                @initialize()
-                @applyCasts()
+                @._attrs = data
+                @._name = name
+                @._dataTypes = dataTypes
+
+                @.setAttrs(data)
+                @.initialize()

             applyCasts: ->
-                for attrName, castName of @_dataTypes
+                for attrName, castName of @._dataTypes
                     castMethod = service.casts[castName]
                     if not castMethod
                         continue

-                    @_attrs[attrName] = castMethod(@_attrs[attrName])
-
+                    @._attrs[attrName] = castMethod(@._attrs[attrName])

             getIdAttrName: ->
                 return "id"

             getUrl: ->
-                return "#{url(@_name)}/#{@getAttrs()[@getIdAttrName()]}"
+                return "#{url(@_name)}/#{@.getAttrs()[@.getIdAttrName()]}"

             getAttrs: (patch=false) ->
                 if patch
-                    return _.extend({}, @_modifiedAttrs)
-
-                return _.extend({}, @_attrs, @_modifiedAttrs)
+                    return _.extend({}, @._modifiedAttrs)
+                return _.extend({}, @._attrs, @._modifiedAttrs)
+
+            setAttrs: (attrs) ->
+                @._attrs = attrs
+                @._modifiedAttrs = {}
+
+                @.applyCasts()
+                @._isModified = false

             initialize: () ->
                 self = @
@@ -147,7 +149,7 @@
                     defered.resolve(self)

                 promise.error (data, status) ->
-                    defered.reject()
+                    defered.reject(data)

                 return defered.promise


Small style fixes in AppView

Small style fixes in AppView

--- app/coffeescripts/AppView.coffee
+++ app/coffeescripts/AppView.coffee
@@ -3,7 +3,7 @@
 # instantiating and triggering the Network to be drawn
 class window.sirius.AppView extends Backbone.View
   $a = window.sirius
-
+
   initialize: ->
     #change underscores symbols for handling interpolation to {{}}
     _.templateSettings = {interpolate : /\{\{(.+?)\}\}/g }
@@ -38,16 +38,15 @@
       mapTypeId: google.maps.MapTypeId.ROADMAP
       mapTypeControl: false
       zoomControl: true
-      zoomControlOptions: {
+      zoomControlOptions:
         style: google.maps.ZoomControlStyle.DEFAULT,
         position: google.maps.ControlPosition.TOP_LEFT
-      }
     }
     #attach the map to the namespace
     $a.map = new google.maps.Map document.getElementById("map_canvas"), mapOptions

   # This creates the context menu as well as adds the listeners for map area of the application.
-  # Currently we have zoom in and zoom out as well as center the map.
+  # Currently we have zoom in and zoom out as well as center the map.
   _contextMenu: () ->
     contextMenuOptions = {}
     contextMenuOptions.menuItems= $a.main_context_menu
@@ -64,11 +63,11 @@
   # This creates the layers menu bar
   _layersMenu: () ->
     lmenu = new $a.LayersMenuView({className: 'dropdown-menu bottom-up', id : 'l_list', parentId: 'lh', menuItems: $a.layers_menu})
-
+
   # creates a DOM document for the models xml to written to.
   # if no scenario has been loaded show a message indicating this.
   # The two files passed to the writeAndDownloadXML method are scenario.php and scenario-download-php.
-  # This will change when we know what we are running on the backend. The first file taks the xml string
+  # This will change when we know what we are running on the backend. The first file taks the xml string
   # generated from the models and saves it to a file. The second parameter creates the correct headers to download
   # this file to the client
   saveScenario: ->
@@ -78,9 +77,9 @@
      else
         $a.broker.trigger("app:show_message:info", "No scenario loaded")

-  openScenario: ->
+  openScenario: ->
     $("#uploadField").click()
-
+
   # displayMap takes the uploaded file data parses the xml into the model objects, and creates the MapNetworkView
   _displayMap: (fileText) ->
     try
@@ -99,7 +98,7 @@

   _messagePanel: ->
     new $a.MessagePanelView()
-
+
   _setKeyDownEvents: (e) ->
     # Open Local Network ALT-A
     # We set ALT_DOWN to false here because
@@ -107,29 +106,29 @@
     if $a.ALT_DOWN and e.keyCode == 65
       $("#uploadField").click()
       $a.ALT_DOWN = false
-
+
     # Save Local Network
     if $a.ALT_DOWN and e.keyCode == 83
       $("#save-local-network").click()
       $a.ALT_DOWN = false
-
+
     # Set multi-select of map elements with the shift key
     $a.SHIFT_DOWN = true if e.keyCode == 16
-
+
     # Set alt key down in order to set up quick key for opening files
     $a.ALT_DOWN = true if e.keyCode == 18
-

-  _setKeyUpEvents: (e) ->
+
+  _setKeyUpEvents: (e) ->
     # Turn off shift and alt down flags where appropriate
     $a.SHIFT_DOWN = false if e.keyCode == 16
     $a.ALT_DOWN = false  if e.keyCode == 18
-
+
   fadeIn: ->
     $('.container').fadeIn(200)
     $('#lh').fadeIn(200)
     $('#mh').fadeIn(200)
-
+
   _treeMenuToggle: () ->
     toggleTree = document.createElement "button"
     toggleTree.innerHTML = " < "
@@ -139,7 +138,7 @@
       $a.broker.trigger('map:toggle_tree', 0)

   _attachEvents: ->
-
+
     $('#expand-all').click( ->
       all_checks = $('.expand-tree')
       btn = document.getElementById('expand-all')
@@ -150,7 +149,7 @@
         checkBox.checked = false for checkBox in all_checks
         btn.innerHTML = '+'
       )
-
+
   toggleTree: (display) =>
     button = document.getElementById 'collapseTree'
     if button.innerHTML == ' > ' and (display == 0 or display == false)
@@ -163,4 +162,4 @@
       $('#right_tree').show(200)
       align = right: '22%'
       $('#collapseTree').animate(align, 200)
-
+


bring format_number back in

bring format_number back in

--- app/controllers/stats_box.coffee
+++ app/controllers/stats_box.coffee
@@ -2,6 +2,7 @@
 template = require '../views/stats_box'
 Api = require 'zooniverse/lib/api'
 Project = require "zooniverse/models/project"
+num = require "../lib/format_number"

 class StatsBox extends Controller
   className: 'snapshot-chicago-stats'
@@ -23,16 +24,16 @@
       @totalSubjectCount = groups
         .map (group) -> +group.stats.total
         .reduce (total, count) -> total + count
-      @totalImages.text @totalSubjectCount
+      @totalImages.text num(@totalSubjectCount)

   update: =>
     project = Project.current

     @updateSubjectTotal() unless @totalSubjectCount
-    @completeCount = project.complete_count
+    @completeCount = num(project.complete_count)
     @complete.text @percentComplete() + "%"
-    @classificationCount.text project.classification_count
-    @userCount.text project.user_count
+    @classificationCount.text num(project.classification_count)
+    @userCount.text num(project.user_count)

   percentComplete: ->
     return 0 unless @totalSubjectCount


Create a provider if was not created earlier

--- src/steroids/Providers.coffee
+++ src/steroids/Providers.coffee
@@ -89,9 +89,39 @@
       (error) =>
         console.log "SandboxDB provider not found"
         # console.log error
-        self.addProvider(provider_name)
-    )
-    deferred.promise
+        self.addProvider(provider_name).then (val)->
+          console.log "pr added"
+          deferred.resolve(val)
+          # TODO: remove the remove when finished
+          self.removeProvider("appgyver_sandbox").then (res)->
+            console.log 'pr removed'
+    )
+    deferred.promise
+
+  addProvider: (provider_name, data) =>
+    deferred = q.defer()
+
+    console.log "Adding a provider"
+
+    data = data || @_getDefaultProviderData(provider_name)
+
+    @config_api.post "/app/#{@getAppId()}/service_providers.json", data, (err, req, res, obj) =>
+      if obj['uid']
+        deferred.resolve "Provider successfully added!"
+        console.log(obj)
+      else
+        deferred.reject err
+
+      @config_api.close()
+
+    deferred.promise
+
+
+  _getDefaultProviderData: (provider_name) =>
+    return {
+      providerTypeId: 6    # appgyver_sandbox id specified in config api
+      name: provider_name
+    }

   # Should be used only when multiple providers are implemented
   # TODO: Remove when not needed


Indicamos para qué sirve el enlace.

Indicamos para qué sirve el enlace.

--- app/scripts/quitar_imagen.coffee
+++ app/scripts/quitar_imagen.coffee
@@ -3,7 +3,7 @@
     src = imagen.attr("src")
     contenedor = $('

')
     imagen.wrap(contenedor).before(
-      $('Quitar').click ->
+      $('Quitar').click ->
         textarea = $("textarea")
         textarea.val(textarea.val().replace("!" + src + "!", "")).keydown()
         false


Audited text-match-engines for 80 chars

Audited text-match-engines for 80 chars

--- src/text_match_engines.coffee
+++ src/text_match_engines.coffee
@@ -18,7 +18,8 @@
       pattern = pattern.toLowerCase()
     while (i = text.indexOf pattern) > -1
       do =>
-#        console.log "Found '" + pattern + "' @ " + i + " (=" + (index + i) + ")"
+#        console.log "Found '" + pattern + "' @ " + i +
+#            " (=" + (index + i) + ")"
         results.push
           start: index + i
           end: index + i + pLen
@@ -102,7 +103,8 @@
   #
   # If no match is found, the function returns null.
   search: (text, pattern, expectedStartLoc = 0, options = {}) ->
-#    console.log "In dtm search. text: '" + text + "', pattern: '" + pattern + "', expectedStartLoc: " + expectedStartLoc + ", options:"
+#    console.log "In dtm search. text: '" + text + "', pattern: '" + pattern +
+#       "', expectedStartLoc: " + expectedStartLoc + ", options:"
 #    console.log options
     if expectedStartLoc < 0 then throw new Error "Can't search at negative indices!"

@@ -139,7 +141,8 @@
 #                uncheckedMidSection: Math.max 0, matchLen - startLen - endLen
 #                lengthError: matchLen - pLen
 #          else
-#            console.log "Sorry, matchLen (" + matchLen + ") is not between " + 0.5*pLen + " and " + 1.5*pLen
+#            console.log "Sorry, matchLen (" + matchLen + ") is not between " +
+#                0.5*pLen + " and " + 1.5*pLen
 #        else
 #          console.log "endSlice ('" + endSlice + "') not found"
 #      else
@@ -175,7 +178,8 @@
   # You don't need to call the functions below this point manually

   searchForSlice: (text, slice, expectedStartLoc) ->
-#    console.log "searchForSlice: '" + text + "', '" + slice + "', " + expectedStartLoc
+#    console.log "searchForSlice: '" + text + "', '" + slice + "', " +
+#        expectedStartLoc

     r1 = @dmp.match_main text, slice, expectedStartLoc
     startIndex = r1.index


dialog is in header

dialog is in header

--- src/load.coffee
+++ src/load.coffee
@@ -61,8 +61,6 @@
   level_world = world.add_child create_level(game_levels, level)
   for child in layout
     level_world.add_child child(level_world)
-
-  # world.add_child dialog

   world.send(my.key.setup)
   world


Fixed wrong selected_reward after render.

--- src/main.coffee
+++ src/main.coffee
@@ -198,7 +198,7 @@
         auto_highlight: true
     render: ->
         log_reward = log.get_reward()
-        selected_reward = Math.max settings.reward_pack, Math.min(selected_reward, log_reward)
+        selected_reward = Math.min(selected_reward, log_reward)
         auto_highlight = @state.auto_highlight and (auto_hide_time != Infinity)
         if not @state.writing_working_on
             working_on_value = log.last_task


Fix main collapsibles to work with jQuery Mobile 1.3.1

The upgrade to jQM 1.3.1 revealed an error in how the main collapsibles
were coded.

Signed-off-by: Steven Chan <e44f0ea69ce310c26f3dae7dc40dfba581c2c7de@sitka.bclibraries.ca>

--- src/main.coffee
+++ src/main.coffee
@@ -52,12 +52,11 @@
 		# This means the search bar acts as part of the collapsible set of summary bars,
 		# even though it is not located inside the container
 		# that defines the summary bars as a collapsible set.
-		toggle = if $(@).is(':visible') then 'collapse' else 'expand'
 		$('.account_summary .ui-collapsible-heading').on 'click', ->
-			$('#opac_search').trigger toggle
+			$('#opac_search').trigger 'collapse'
 			return # need to bubble up click event for jQM
 		$('#opac_search').on 'click', ->
-			$('.account_summary').each -> $(@).trigger toggle
+			$('.account_summary').each -> $(@).trigger 'collapse'
 			return # need to bubble up click event for jQM

 		# Upon showing a page, we focus on a specific element that depends on


OS X Dock stuff.

OS X Dock stuff.

--- src/main.coffee
+++ src/main.coffee
@@ -11,3 +11,6 @@
 app.on 'window-all-closed', ->
   if process.platform is not 'darwin'
     app.quit()
+
+app.on 'activate-with-no-open-windows', ->
+  window = new SpringseedWindow()


Remove require

Remove require

--- src/msgc.coffee
+++ src/msgc.coffee
@@ -1,5 +1,3 @@
-{Robot, Adapter, TextMessage, EnterMessage, LeaveMessage, CatchAllMessage} = require "hubot"
-
 class Msgc extends Adapter

   constructor: ->


Remove redundant items.length check.

Remove redundant items.length check.

--- src/pane.coffee
+++ src/pane.coffee
@@ -302,7 +302,7 @@
   addItemsToStack: (itemStackIndices) ->
     if @items.length > 0
       if itemStackIndices.length is 0 or itemStackIndices.length isnt @items.length or itemStackIndices.indexOf(-1) >= 0
-        itemStackIndices = (i for i in [0..@items.length-1]) unless @items.length is 0
+        itemStackIndices = (i for i in [0..@items.length-1])
       for itemIndex in itemStackIndices
         @addItemToStack(@items[itemIndex])


Added back to inbox link

Added back to inbox link

--- app/talk/inbox-conversation.cjsx
+++ app/talk/inbox-conversation.cjsx
@@ -77,6 +77,7 @@
   render: ->
     if @props.user

+        <Link to="inbox">Back to Inbox</Link>

{@state.conversation?.title}

         {if @state.recipients.length


Client: Activity: Added new props to ChannelParticipantsInput. event handlings, placeholder etc..

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -261,7 +261,13 @@
   renderAddNewParticipantInput: ->

     <div className={@getNewParticipantInputClassNames()}>
-      <input placeholder='type a @username and hit enter' />
+      <input ref='ChannelParticipantsInput'
+        onKeyDown   = { @bound 'onKeyDown' }
+        onChange    = { @bound 'onChange' }
+        placeholder = 'type a @username and hit enter'
+        value       = { @state.value }
+        ref         = 'textInput'
+      />
     </div>


Add handler to invoke feedback modal

Add handler to invoke feedback modal

--- apps/contact/client/index.coffee
+++ apps/contact/client/index.coffee
@@ -1,11 +1,16 @@
 openMultiPageModal = require '../../../components/multi_page_modal/index.coffee'
 SpecialistView = require '../../../components/contact/general_specialist.coffee'
+FeebackView = require '../../../components/contact/feedback.coffee'

 module.exports.init = ->
   $('.js-contact-specialist').click (e) ->
     e.preventDefault()
     new SpecialistView

+  $('.js-contact-feedback').click (e) ->
+    e.preventDefault()
+    new FeebackView
+
   $('.js-multi-page-modal').click (e) ->
     e.preventDefault()
     openMultiPageModal $(this).data('id')


detach all views, even if the file has not yet been loaded

--- lib/agda.coffee
+++ lib/agda.coffee
@@ -41,8 +41,7 @@

     @on 'deactivate', =>
       @active = false
-      if @loaded
-        @view.detach()
+      @view.detach()

     @on 'goal-manager:buffer-modified', => @saveBuffer()

@@ -160,7 +159,7 @@
       @view.attachInputBox @executable.normalizeCommand if @loaded

   input: ->
-    # @view.attachInputMethod()
+    @view.attachInputMethod()
     @inputMethod.activate()

 module.exports = Agda


RootView talks about packages and packageModules, not extensions

Now you call `rootView.activatePackage`, etc

--- spec/tree-view-spec.coffee
+++ spec/tree-view-spec.coffee
@@ -13,7 +13,7 @@
     rootView = new RootView(require.resolve('fixtures/tree-view'))
     project = rootView.project

-    rootView.activateExtension(TreeView)
+    atom.loadPackage("tree-view")
     treeView = rootView.find(".tree-view").view()
     treeView.root = treeView.find('> li:first').view()
     sampleJs = treeView.find('.file:contains(tree-view.js)')
@@ -51,7 +51,7 @@
         rootView.deactivate()

         rootView = new RootView
-        rootView.activateExtension(TreeView)
+        rootView.activatePackage(TreeView)
         treeView = rootView.find(".tree-view").view()

       it "does not create a root node", ->
@@ -74,7 +74,7 @@
   describe "when the prototypes deactivate method is called", ->
     it "calls the deactivate on tree view instance", ->
       spyOn(treeView, "deactivate").andCallThrough()
-      rootView.deactivateExtension(TreeView)
+      rootView.deactivatePackage(TreeView)
       expect(treeView.deactivate).toHaveBeenCalled()

   describe "serialization", ->
@@ -89,7 +89,7 @@
       newRootView = RootView.deserialize(rootView.serialize())
       rootView.deactivate() # Deactivates previous TreeView

-      newRootView.activateExtension(TreeView)
+      newRootView.activatePackage(TreeView)

       newTreeView = newRootView.find(".tree-view").view()

@@ -106,7 +106,7 @@
       rootView.deactivate() # Deactivates previous TreeView

       newRootView.attachToDom()
-      newRootView.activateExtension(TreeView)
+      newRootView.activatePackage(TreeView)

       newTreeView = newRootView.find(".tree-view").view()
       expect(newTreeView).toMatchSelector ':focus'
@@ -589,7 +589,7 @@

       rootView = new RootView(rootDirPath)
       project = rootView.project
-      rootView.activateExtension(TreeView)
+      rootView.activatePackage(TreeView)
       treeView = rootView.find(".tree-view").view()
       dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
       dirView.expand()


use slideDown/slideUp

use slideDown/slideUp

--- app/assets/javascripts/effective_panel_select/effective_panel_select.js.coffee
+++ app/assets/javascripts/effective_panel_select/effective_panel_select.js.coffee
@@ -21,7 +21,7 @@
     constructor: (el, options) ->
       @panel = $(el)
       @input = @panel.find("input[type='hidden']")
-      @label = @panel.find('span.selected')
+      @label = @panel.find('.selection-title')
       @selector = @panel.children('.selector')
       @tabList = @selector.find('ul.nav').first()
       @tabContent = @selector.find('.tab-content').first()
@@ -75,13 +75,14 @@
       if @panel.hasClass('expanded') then @collapse() else @expand()

     expand: ->
-      @panel.addClass('expanded')
       @invade() if @options.invade
+      @selector.slideDown 'fast', => @panel.addClass('expanded')

     collapse: ->
-      @panel.removeClass('expanded')
-      @reset() if @options.resetOnCollapse
-      @retreat() if @options.invade
+      @selector.slideUp 'fast', =>
+        @panel.removeClass('expanded')
+        @reset() if @options.resetOnCollapse
+        @retreat() if @options.invade

     # Invade the nearest '.row'
     invade: ->