 Merge branch 'master.minorFixes' into 'master'

Master.Minor Fixes

--- client/Social/Members/ContentDisplays/profileview.coffee
+++ client/Social/Members/ContentDisplays/profileview.coffee
@@ -354,24 +354,25 @@
       input.setEditingMode yes
       input.focus()

-    @firstName.on "NextTabStop",     => focus @lastName
-    @firstName.on "PreviousTabStop", => focus @bio
-    @lastName.on "NextTabStop",      => focus @bio
-    @lastName.on "PreviousTabStop",  => focus @firstName
-    @bio.on "NextTabStop",           => focus @firstName
-    @bio.on "PreviousTabStop",       => focus @lastName
-
-    @firstName.on "click", -> @setEditingMode yes
-    @lastName.on "click",  -> @setEditingMode yes
-    @bio.on "click",       -> @setEditingMode yes
-
-    @firstName.on "EnterPressed", save
-    @lastName.on "EnterPressed", save
-    @bio.on "EnterPressed", save
-
-    @firstName.on "BlurHappened", save
-    @lastName.on "BlurHappened", save
-    @bio.on "BlurHappened", save
+    if @memberData.getId() is KD.whoami().getId()
+      @firstName.on "NextTabStop",     => focus @lastName
+      @firstName.on "PreviousTabStop", => focus @bio
+      @lastName.on "NextTabStop",      => focus @bio
+      @lastName.on "PreviousTabStop",  => focus @firstName
+      @bio.on "NextTabStop",           => focus @firstName
+      @bio.on "PreviousTabStop",       => focus @lastName
+
+      @firstName.on "click", -> @setEditingMode yes
+      @lastName.on "click",  -> @setEditingMode yes
+      @bio.on "click",       -> @setEditingMode yes
+
+      @firstName.on "EnterPressed", save
+      @lastName.on "EnterPressed", save
+      @bio.on "EnterPressed", save
+
+      @firstName.on "BlurHappened", save
+      @lastName.on "BlurHappened", save
+      @bio.on "BlurHappened", save

     avatarOptions  =
       showStatus      : yes


Home: changed button title

Home: changed button title

--- client/landing/site.hackathon/coffee/home/AppView.coffee
+++ client/landing/site.hackathon/coffee/home/AppView.coffee
@@ -70,7 +70,7 @@

       @signUpForm = new HomeRegisterForm
         cssClass    : 'login-form register no-anim'
-        buttonTitle : 'LET\'S DO THIS '
+        buttonTitle : 'SIGN UP'
         callback    : (formData) =>
           router.requireApp 'Login', (controller) =>
             controller.getView().showExtraInformation formData, @signUpForm


Social: add fetcher function for popular posts into request class

--- workers/social/lib/social/models/socialapi/requests.coffee
+++ workers/social/lib/social/models/socialapi/requests.coffee
@@ -108,6 +108,13 @@
     return callback {message: "Request is not valid for listing popular topics"}

   url = "#{SOCIAL_API_URL}/popular/topics/#{data.type}"
+  get url, data, callback
+
+fetchPopularPosts = (data, callback)->
+  if not data.groupName or not data.type or not data.channelName
+    return callback {message: "Request is not valid for listing popular topics"}
+
+  url = "#{SOCIAL_API_URL}/popular/posts/#{data.channelName}/#{data.type}"
   get url, data, callback

 fetchPinnedMessages = (data, callback)->
@@ -199,6 +206,7 @@
   fetchPinnedMessages
   pinMessage
   unpinMessage
+  fetchPopularPosts
   fetchPopularTopics
   addReply
   unlikeMessage


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/engine_selector.coffee
+++ test/engine_selector.coffee
@@ -1,4 +1,6 @@
 module "Engine"
+
+test "#c"

 test "#each", ->
   engine = Engine()


Reorder some imports

Reorder some imports

--- library/nucleus/cakefile.coffee
+++ library/nucleus/cakefile.coffee
@@ -23,14 +23,13 @@
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ###

-
-_ = require "lodash"
+fs = require "fs"
+paths = require "path"
 assert = require "assert"
 colors = require "colors"
 logger = require "winston"
-paths = require "path"
-fs = require "fs"

+_ = require "lodash"
 {rmdirSyncRecursive} = require "wrench"
 {spawn} = require "child_process"


Fix some mistakes in the remote module

Fix some mistakes in the remote module

--- library/visualize/remote.coffee
+++ library/visualize/remote.coffee
@@ -100,8 +100,8 @@
         return @$sourcing unless sourcing?
         invalid = "Sourcing is not correct"
         throw new Error invalid unless correct
-        @emit "sourcing", this, @$sourcing, sourcing
-        @$sourcing = sourcing.toString()
+        @emit "sourcing", @$sourcing, sourcing
+        @$sourcing = sourcing

 # A class that represents a wrapped remote classes. It accepts the
 # class wrapped in function and then stores it for later. Then when
@@ -116,11 +116,11 @@
     # will be executed on a remote side, with differen environment.
     constructor: (objectable) ->
         detected = _.isFunction objectable
-        passed = -> objectable.length is 0
-        objectable = "The argument is not a function"
+        barebones = objectable.length is 0
+        inconsistent = "The argument is not a function"
         wrapping = "The wrapper must not have arguments"
-        throw new Error objectable unless detected
-        throw new Error wrapping unless passed()
+        throw new Error inconsistent unless detected
+        throw new Error wrapping unless barebones
         @sourcing objectable; this

     # Generation of string representation of the object instantiation


Add support for exponential notation in Pic


clear filter

clear filter

--- jample/app/assets/javascripts/components/jample/index.js.cjsx
+++ jample/app/assets/javascripts/components/jample/index.js.cjsx
@@ -60,6 +60,7 @@
             Filter
             <input type="text" onKeyUp={@debounce} id="filter_input" className="form-control" />
             <button type="button" className="btn btn-info" onClick={@set_filter}>Go</button>
+            <button type="button" className="btn btn-info" onClick={@clear_filter}>Clear</button>
             {@state.current_filter_size} Tracks
           </div>
         </div>
@@ -183,6 +184,11 @@
     console.log("set_filter_to_current_patch_track")
     currentTrack = @state.track_set[@state.currentPatch]
     $('#filter_input').val(currentTrack._id.$oid)
+    @set_filter()
+
+  clear_filter: ->
+    console.log("clear_filter")
+    $('#filter_input').val("")
     @set_filter()

   init_16_patches_as_sequence: ->


Initial Help Index

Initial Help Index

--- help/index.coffee
+++ help/index.coffee
@@ -21,17 +21,20 @@
 module.exports = """
 Hello #{userdata.username}, to receive help in one of the following areas
 simply type "help " followed by the thing you want help with, and then hit
-enter. For example, type "help this".
+enter. For example, type "help this" and then press enter on your keyboard.

 The following is a list of the current available help topics:

-  help this             What the heck of this?
-  help koding           What is Koding?
+  help this             What the heck is this?
   help ftp              Connect to your VM through FTP
   help programs         What comes pre installed on Koding?
   help mysql            Connect to MySQL on Koding
   help postgresql       Connect to PostgreSQL on Koding
   help mongodb          Connect to MongoDB on Koding

-As always, the community is here to
+As always, additional help can be found on Koding University at:
+  http://learn.koding.com
+
+Or from the Koding community, at:
+  https://koding.com/Activity
 """


camel case

camel case

--- node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
+++ node_modules_koding/kite-amqp/lib/kite-amqp/index.coffee
@@ -54,27 +54,27 @@
   getGroupName:(groupName)->
     "#{@publicKey}-kite-kite-#{groupName}"

-  receivedCommand: (from, cmdid, cmd, args)->
+  receivedCommand: (from, cmdId, cmd, args)->
     console.log "receivedCommand", arguments
     if cmd.indexOf('reply-') > -1
-      cmdid = cmd.split('reply-')[1]
+      cmdId = cmd.split('reply-')[1]
       #console.log ">>>> reply cmd", cmd, args
-      @waitingReplies[cmdid]?.call @, false, {args:args, from:from}
+      @waitingReplies[cmdId]?.call @, false, {args:args, from:from}
       return

     fn = @commands[cmd]
     if fn
       ret = fn.call @, {args:args, from:from}, (err, ret)=>
-        @sendCommand(from, "reply-#{cmdid}", [err, ret])
+        @sendCommand(from, "reply-#{cmdId}", [err, ret])
       return

     if @api.propertyIsEnumerable(cmd) and 'function' is typeof @api[cmd]
       fn = @api[cmd]
       fn.call @kite, {args:args, from:from}, (err, ret)=>
-        @sendCommand(from, "reply-#{cmdid}", [err, ret])
+        @sendCommand(from, "reply-#{cmdId}", [err, ret])
       return

-    @sendCommand(from, "reply-#{cmdid}", ['function not found', false])
+    @sendCommand(from, "reply-#{cmdId}", ['function not found', false])

   queueCommand:(channel, cmd, args, callback=null)->


Adapted read more translations

Adapted read more translations

--- app/assets/javascripts/partystreusel/translations.js.coffee
+++ app/assets/javascripts/partystreusel/translations.js.coffee
@@ -3,13 +3,13 @@
     readmore:
       button_text:
         open: 'Read more'
-        close: 'Read less'
+        close: 'Show less'

   de:
     readmore:
       button_text:
         open: 'Mehr lesen'
-        close: 'Weniger lesen'
+        close: 'Weniger anzeigen'

   fr:
     readmore:


fix disappearing file bug

fix disappearing file bug

test plan:
 - have two folders and one file in a folder
 - preview a file
 - when you close the preview, both files should still be there

fixes CNVS-22597
refs CNVS-22758

Change-Id: Idc3320b089b8e3cb6bce340999b10f8f8bb45ad9
Reviewed-on: https://gerrit.instructure.com/62376
Reviewed-by: Ryan Shaw <ea3cd978650417470535f3a4725b6b5042a6ab59@instructure.com>
Tested-by: Jenkins
QA-Review: Charles Kimball <46fea98154686b8132aa2745574cc8021812cb41@instructure.com>
Product-Review: Jeremy Stanley <b3f594e10a9edcf5413cf1190121d45078c62290@instructure.com>

--- app/coffeescripts/react_files/components/FilePreview.coffee
+++ app/coffeescripts/react_files/components/FilePreview.coffee
@@ -62,7 +62,7 @@
                 props.currentFolder.files.models
       folders = props.currentFolder.folders.models

-      items = _.extend files, folders
+      items = files.concat folders
       otherItems =  items.filter (item) ->
                       return true unless onlyIdsToPreview
                       item.id in onlyIdsToPreview


update title in orange header when it changes

--- scripts/views/workspace/content/layouts/editor.coffee
+++ scripts/views/workspace/content/layouts/editor.coffee
@@ -21,6 +21,10 @@
       # other changes, so we need this feedback here.
       @$el.parent().parent().parent().parent().find(
         '#module-title-indicator').text(@model.get('title'))
+
+      @listenTo @model, "change:title", =>
+        @$el.parent().parent().parent().parent().find(
+          '#module-title-indicator').text(@model.get('title'))

       # Focus the editor. This has to be done here, because @$el isn't attached
       # to the DOM before this. We also have to wait until the content is


Add a delayed sort on model changes

Add a delayed sort on model changes

like dateLastModified

--- scripts/views/workspace/content/search-results.coffee
+++ scripts/views/workspace/content/search-results.coffee
@@ -7,6 +7,38 @@
   'cs!views/workspace/menu/toolbar-search'
   'hbs!templates/workspace/content/search-results'
 ], ($, _, Backbone, Marionette, SearchResultsItemView, searchView, searchResultsTemplate) ->
+
+  SORT_TIMEOUT = 250 # milliseconds
+
+  SORT_COMPARATOR = (modelA, modelB) ->
+    compareAttribute = (name, desc=false) ->
+      valueA = modelA.get(name)
+      valueB = modelB.get(name)
+
+      if valueA and valueB
+        if valueA < valueB
+          ret = -1
+        else if valueA > valueB
+          ret = 1
+        else
+          ret = 0
+
+        # Flip if descending order
+        ret = 0 - ret if desc
+
+      else if valueA
+        ret = -1
+      else if valueB
+        ret = 1
+      else
+        ret = 0
+
+      return ret
+
+    return compareAttribute('dateLastModifiedUTC', true) or
+           compareAttribute('mediaType') or
+           compareAttribute('title') or
+           compareAttribute('id')

   return class SearchResultsView extends Marionette.CompositeView
     template: searchResultsTemplate
@@ -18,6 +50,20 @@
       @contents = @collection # Keep a reference to the original collection

       @listenTo(searchView, 'search', @filter)
+
+
+      @collection.comparator = SORT_COMPARATOR
+      @collection.sort()
+
+      @listenTo @collection, 'change', () =>
+        if not @sorting
+          @sorting = setTimeout ( () =>
+            @collection.sort()
+            @sorting = false
+            ), SORT_TIMEOUT
+
+      @listenTo @collection, 'sort', () => @reorder()
+

     filter: (query) ->
       if not query
@@ -31,3 +77,17 @@
         @collection = new Backbone.Collection(filtered);

       @render()
+
+    # Instead of re-rendering each view when sorting, just update the ordering
+    # of the rendered elements in the DOM
+    reorder: () ->
+
+      @collection.each (childModel, index) =>
+        $containerChildren = @$itemViewContainer.children()
+
+        childView = @children.findByModel(childModel)
+        $el = childView.$el
+        childIndex = $containerChildren.index($el)
+
+        if index != childIndex
+          $el.insertBefore($containerChildren.eq(index))


Update classification's link to subject(s)

--- app/pages/project/classify.cjsx
+++ app/pages/project/classify.cjsx
@@ -60,7 +60,7 @@
         links:
           project: @props.project.id
           workflow: workflow.id
-          set_member_subject: subject.id
+          subjects: [subject.id]
       classification.update metadata: ->
         classification.metadata.workflow_version = workflow.version
         classification.metadata


remove useless variable

remove useless variable

--- BackofficeBundle/Resources/public/coffee/areaView.coffee
+++ BackofficeBundle/Resources/public/coffee/areaView.coffee
@@ -46,7 +46,6 @@
     return

   sendRemoveArea: ->
-    that = this
     $.ajax
       url: @area.get('links')._self_delete
       method: 'POST'


Fix device images for file urls

Fix device images for file urls

--- framer/Components/DeviceComponent.coffee
+++ framer/Components/DeviceComponent.coffee
@@ -305,6 +305,10 @@
 		# We want to get these image from our public resources server
 		resourceUrl = "//resources.framerjs.com/static/DeviceResources"

+		# If we are running a local copy of Framer from the drive, get the resource online
+		if Utils.isFileUrl(window.location.href)
+			resourceUrl = "http://#{resourceUrl}"
+
 		# If we're running Framer Studio and have local files, we'd like to use those.
 		# For now we always use jp2 inside framer stusio
 		if Utils.isFramerStudio() and window.FramerStudioInfo
@@ -559,6 +563,10 @@

 		# We want to get these image from our public resources server
 		resourceUrl = "//resources.framerjs.com/static/DeviceResources"
+
+		# If we are running a local copy of Framer from the drive, get the resource online
+		if Utils.isFileUrl(window.location.href)
+			resourceUrl = "http://#{resourceUrl}"

 		# If we're running Framer Studio and have local files, we'd like to use those
 		if Utils.isFramerStudio() and window.FramerStudioInfo and Utils.framerStudioVersion() >= newDeviceMinVersion


AvatarView: minor whitespace

AvatarView: minor whitespace

--- client/Main/CommonViews/avatarviews/avatarview.coffee
+++ client/Main/CommonViews/avatarviews/avatarview.coffee
@@ -4,8 +4,8 @@

     options.cssClass       or= ""
     options.size           or=
-      width                   : 50
-      height                  : 50
+      width                  : 50
+      height                 : 50
     options.detailed        ?= no
     options.showStatus     or= no
     options.statusDiameter or= 5


Notification: use cacheable for fetching account data

--- client/Main/notifications/notificationlistitem.coffee
+++ client/Main/notifications/notificationlistitem.coffee
@@ -54,23 +54,20 @@
     @actors = []
     options = @getOptions()
     {latestActors} = @getData()
-    promises = latestActors.map (actorId) =>
-      new Promise (resolve, reject) =>
-        KD.remote.api.JAccount.one _id: actorId, (err, actor) =>
-          return reject err  if err
-          @actors.push actor
-          resolve()
+    constructorName = 'JAccount'
+    origins = latestActors.map (id) -> {id, constructorName}

-    Promise.all(promises).then =>
-      @participants = new options.linkGroupClass {group:@actors}
-      @avatar       = new options.avatarClass
-        size     :
-          width  : 40
-          height : 40
-        origin   : @actors[0]
-
-    .catch (err) ->
-      warn err.description
+    new Promise (resolve, reject) =>
+      KD.remote.cacheable origins, (err, actors) =>
+        return reject err if err?
+        @actors = actors
+        @participants = new options.linkGroupClass {group:actors}
+        @avatar       = new options.avatarClass
+          size     :
+            width  : 40
+            height : 40
+          origin   : @actors[0]
+        resolve()

   viewAppended: ->
     promises = []
@@ -118,7 +115,8 @@
           {message} = KD.singletons.socialapi
           message.byId {id: data.targetId}, (err, message)=>
             return reject err  if err or not message
-            KD.remote.api.JAccount.one _id: message.accountOldId, (err, origin)=>
+
+            KD.remote.cacheable 'JAccount', message.account._id, (err, origin)=>
               return reject err  if err or not origin

               adjective = if message.account._id is KD.whoami()?.getId() then "your"


Activity: set unread count if item exists

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

     item = listController.itemForId id

-    item.setUnreadCount unreadCount  if item.unreadCount
+    item.setUnreadCount unreadCount  if item?.unreadCount

   getListController: (type) ->


VueI18n: force Vue to rerender after locale change. There is a intermittent bug where non en locales sometimes dont render in all components when the page loads

--- vue/src/shared/services/session.coffee
+++ vue/src/shared/services/session.coffee
@@ -37,6 +37,7 @@
         loadedLocales.push(locale)
         i18n.setLocaleMessage(locale, data)
         setI18nLanguage(locale)
+        Vue.forceUpdate()

 export default new class Session
   defaultFormat: ->


Fix bootbox confirm

Fix bootbox confirm

--- app/assets/javascripts/ab_admin/core/confirmation.js.coffee
+++ app/assets/javascripts/ab_admin/core/confirmation.js.coffee
@@ -1,6 +1,6 @@
 $.rails.allowAction = (element) ->
-  message = "
#{element.data("confirm")}
"
+  message = element.data("confirm")
   return true unless message
-  bootbox.confirm message, (answer) ->
+  bootbox.confirm "
#{message}
", (answer) ->
     element.data('confirm', null).click() if answer
   false


whoops  ( ﾟ ヮﾟ)

whoops  ( ﾟ ヮﾟ)

--- resources/assets/coffee/_classes/sticky-header.coffee
+++ resources/assets/coffee/_classes/sticky-header.coffee
@@ -36,7 +36,6 @@

   onScroll: =>
-    console.log('scroll')
     @pin()
     @stickOrUnstick()


Add italic to html container editor

Add italic to html container editor

--- app/assets/javascripts/catalog-admin/summernote_init.coffee
+++ app/assets/javascripts/catalog-admin/summernote_init.coffee
@@ -2,5 +2,15 @@
   $("[data-provider='summernote']").summernote
     height: 300
     maximumImageFileSize: 262144 # 256KB
+    toolbar: [
+      ['style', ['style']],
+      ['font', ['bold', 'italic', 'underline', 'clear']],
+      ['fontname', ['fontname']],
+      ['color', ['color']],
+      ['para', ['ul', 'ol', 'paragraph']],
+      ['table', ['table']],
+      ['insert', ['link', 'picture', 'video']],
+      ['view', ['fullscreen', 'codeview', 'help']],
+    ]

 $(document).ready(init)


Onboarding: modal copy change

Onboarding: modal copy change

--- client/ide/views/modals/onboardingmodalcontent.coffee
+++ client/ide/views/modals/onboardingmodalcontent.coffee
@@ -18,7 +18,7 @@

-          Welcome to your awesome dev environment
+          Welcome to Koding! This 2min video will give you an overview of your new cloud-based development environment. Get ready to say goodbye to localhost!

         {{> @getStartedButton }}


Add comments make it in correct order

Add comments make it in correct order

--- client/landing/app/MainApp/sidebar/sidebarview.coffee
+++ client/landing/app/MainApp/sidebar/sidebarview.coffee
@@ -10,6 +10,7 @@
     @_finderExpanded  = no
     @_popupIsActive   = no

+    # Avatar area
     @avatar = new AvatarView
       tagName    : "div"
       cssClass   : "avatar-image-wrapper"
@@ -21,6 +22,10 @@
     @avatarAreaIconMenu = new AvatarAreaIconMenu
       delegate     : @

+    @statusLEDs = new KDView
+      cssClass : 'status-leds'
+
+    # Main Navigations
     @navController = new NavigationController
       view           : new NavigationList
         type         : "navigation"
@@ -31,6 +36,7 @@

     @nav = @navController.getView()

+    # Main Navigations Footer Menu
     @footerMenuController = new NavigationController
       view           : new NavigationList
         type         : "footer-menu"
@@ -41,17 +47,22 @@

     @footerMenu = @footerMenuController.getView()

+    # Finder Header
     @finderHeader = new KDCustomHTMLView
       tagName   : "h2"
       pistachio : "{{#(profile.nickname)}}.#{location.hostname}"
     , account

+    # File Tree
     @finderController = new NFinderController
       useStorage        : yes
       addOrphansToRoot  : no

     @finder = @finderController.getView()
-
+    KD.registerSingleton "finderController", @finderController
+    @finderController.on 'ManageResources', @bound 'toggleResources'
+
+    # Finder Bottom Controls
     @finderBottomControlsController = new KDListViewController
       view        : new FinderBottomControls
       wrapper     : no
@@ -78,13 +89,7 @@
           callback?()
       ]

-    KD.registerSingleton "finderController", @finderController
     @listenWindowResize()
-
-    @statusLEDs = new KDView
-      cssClass : 'status-leds'
-
-    @finderController.on 'ManageResources', @bound 'toggleResources'

   resetAdminNavController:->
     @utils.wait 1000, =>


Remove duplicate "no-data" messages in curtailment chart

--- app/assets/javascripts/d3/solar_curtailment_curve.coffee
+++ app/assets/javascripts/d3/solar_curtailment_curve.coffee
@@ -32,6 +32,7 @@
     # are removed to ensure a full redraw.
     initialDraw: (xScale, yScale, area, line) ->
       @svg.selectAll('g.serie').remove()
+      @svg.selectAll('g.no-data').remove()

       super(xScale, yScale, area, line)


Default quantity to null rather than zero

--- app/assets/javascripts/darkswarm/services/cart.js.coffee
+++ app/assets/javascripts/darkswarm/services/cart.js.coffee
@@ -57,6 +57,6 @@
     create_line_item: (variant)->
       variant.line_item =
         variant: variant
-        quantity: 0
+        quantity: null
         max_quantity: null
       @line_items.push variant.line_item


Remove backwards compat check for pending item API

--- spec/results-view-spec.coffee
+++ spec/results-view-spec.coffee
@@ -421,30 +421,18 @@
       runs ->
         expect(atom.workspace.getActivePaneItem().getPath()).toContain('sample.')

-    if atom.workspace.getActivePane().getPendingItem?
-      it "opens the file containing the result in pending state when the search result is single-clicked", ->
-        pathNode = resultsView.find(".search-result")[0]
-        pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, which: 1))
-        editor = null
-        waitsFor ->
-          editor = atom.workspace.getActiveTextEditor()
-
-        runs ->
-          expect(atom.workspace.getActivePane().getPendingItem()).toBe editor
-          expect(atom.views.getView(editor)).toHaveFocus()
-
-      it "opens the file containing the result in non-pending state when the search result is double-clicked", ->
-        pathNode = resultsView.find(".search-result")[0]
-        pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 1))
-        pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 2))
-        editor = null
-        waitsFor ->
-          editor = atom.workspace.getActiveTextEditor()
-        waitsFor ->
-          atom.workspace.getActivePane().getPendingItem() is null
-
-        runs ->
-          expect(atom.views.getView(editor)).toHaveFocus()
+    it "opens the file containing the result in non-pending state when the search result is double-clicked", ->
+      pathNode = resultsView.find(".search-result")[0]
+      pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 1))
+      pathNode.dispatchEvent(buildMouseEvent('mousedown', target: pathNode, detail: 2))
+      editor = null
+      waitsFor ->
+        editor = atom.workspace.getActiveTextEditor()
+      waitsFor ->
+        atom.workspace.getActivePane().getPendingItem() is null
+
+      runs ->
+        expect(atom.views.getView(editor)).toHaveFocus()

     describe "when `openProjectFindResultsInRightPane` option is true", ->
       beforeEach ->


When loading entries, if the feed to which an entry belongs is not in the scope, load it via Ajax from the server.

Before this commit, a reload of all feeds was triggered. Now only the missing feed is loaded, which is less resource intensive.

--- app/assets/javascripts/ng-services/ng-EntrySvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-EntrySvc.js.coffee
@@ -139,8 +139,8 @@
         return feed.title
       else
         # If an entry is retrieved without a corresponding feed in the scope,
-        # immediately load feeds from the server to retrieve it
-        feedsFoldersSvc.load_feeds()
+        # immediately load it from the server
+        feedsFoldersSvc.load_feed entry.feed_id
         return ''

     #--------------------------------------------


new channel is closed by default

new channel is closed by default

--- components/new_channel/client/new_channel_view.coffee
+++ components/new_channel/client/new_channel_view.coffee
@@ -19,7 +19,7 @@
     @model = new Channel
       base_class: 'Channel'
       class: 'Channel'
-      status: 'public'
+      status: 'closed'
       user: mediator.shared.current_user.attributes
       length: 0


[WIP] remove fixed header from filters view

--- frontend/coffeescript/app/viz/filters_view.coffee
+++ frontend/coffeescript/app/viz/filters_view.coffee
@@ -14,35 +14,7 @@
         events:
           'click .js-sort': 'sort_table'

-        detect_scroll: ( event ) ->
-
-            return if @$el.is ':hidden'
-
-            scrollTop  = $( event.currentTarget ).scrollTop()
-            scrollLeft = $( event.currentTarget ).scrollLeft()
-
-            offsetTop = @$el.position().top
-
-            # Copy the header row of the table into the scroller div.
-            header     = $('#fixed-header')
-            header_row = $('tr:first-child', @el)
-            $('table tr', header)
-              .empty()
-              .append header_row.html()
-            header.css( 'width', header_row.width() )
-
-            # If we've scrolled past the header row of the table, make our
-            # fixed header visible
-            if scrollTop > offsetTop
-                header.css( { left: "-#{scrollLeft}px" } ).show()
-            else
-                # Otherwise just hide the sucker.
-                header.hide()
-
-            @
-
         sort_table: ->
-          console.log 'sorting table'
           column = $(event.target)

           field = column.data 'field'
@@ -53,43 +25,35 @@
           # Set the new sort order.
           # Sort order changes from None -> Descending -> Ascending
           if sort_order is 'none'
-            console.log 'sorting (descendening)'
             sort_order = 'desc'
-            sort_icon.removeClass( 'icon-sort icon-sort-up' ).addClass( 'icon-sort-down' )
+            sort_icon.removeClass('icon-sort icon-sort-up').addClass('icon-sort-down')
           else if sort_order is 'desc'
-            console.log 'sorting (ascending)'
             sort_order = 'asc'
-            sort_icon.removeClass( 'icon-sort-down' ).addClass( 'icon-sort-up' )
+            sort_icon.removeClass('icon-sort-down').addClass('icon-sort-up')
           else if sort_order is 'asc'
-            console.log 'not sorting'
             sort_order = null
-            sort_icon.removeClass( 'icon-sort-up' ).addClass( 'icon-sort' )
+            sort_icon.removeClass('icon-sort-up').addClass('icon-sort')

           column.data('order', sort_order )

           @collection.sort_fetch( field: field, order: sort_order )

-        detect_pagination: ( event ) ->
-
-            # Don't load another page while the page is being requested from the
-            # server
-            if @page_loading? and @page_loading
-                return
-
-            return if @$el.is ':hidden'
-
-            view_height = @$el.height()
-            scroll_height = $( event.currentTarget ).scrollTop() + $( event.currentTarget ).height()
-
-            if scroll_height + 100 > view_height
-                @page_loading = true
-                @collection.next( success: => @page_loading = false )
-
-            @
-
-
-        onShow: ->
-            ($ '.fixed-header' ).show()
+
+        detect_pagination: (event) ->
+          # Don't load another page while the page is being requested from the
+          # server
+          return if @page_loading
+
+          return if @$el.is ':hidden'
+
+          view_height = @$el.height()
+          scroll_height = $( event.currentTarget ).scrollTop() + $( event.currentTarget ).height()
+
+          if scroll_height + 100 > view_height
+              @page_loading = true
+              @collection.next( success: => @page_loading = false )
+
+          @

         showView: ->
             ($ '#filters-viz').show()
@@ -126,9 +90,7 @@
         initialize: () ->
             DataTableView::initialize.apply(@, arguments)

-            # Bind events to handle fixed-header rendering, sorting, and pagination
-            # FIXME: scroll events should be bound to $el, not parent container
-            $('#vizContainer').scroll( { view: @ }, (event) => @detect_scroll(event) )
+            # Bind events to handle pagination
             $('#vizContainer').scroll( { view: @ }, (event) => @detect_pagination(event) )

             # Set the location of the data div and change it when we resize


test(macModal): Fixed e2e tests

test(macModal): Fixed e2e tests

--- test/e2e/modal.spec.coffee
+++ test/e2e/modal.spec.coffee
@@ -1,9 +1,6 @@
 describe "Mac Modal e2e test", ->
-  ptor = null
-
   beforeEach ->
     browser.get "/test/e2e/modal.html"
-    ptor = protractor.getInstance()
     browser.waitForAngular()

   it "should show the registered modal", ->
@@ -32,7 +29,7 @@

     modal = element(By.id("keyboard-modal"))

-    ptor.actions().sendKeys(protractor.Key.ESCAPE).perform()
+    browser.actions().sendKeys(protractor.Key.ESCAPE).perform()

     expect(modal.isDisplayed()).toBeFalsy()

@@ -40,7 +37,6 @@
     button = element(By.id("open-overlay-btn"))
     button.click()

-    driver = protractor.getInstance().driver
-    modal  = driver.findElement(By.id("overlay-modal"))
-    driver.executeScript("arguments[0].click()", modal).then ->
+    modal = browser.findElement(By.id("overlay-modal"))
+    browser.driver.executeScript("arguments[0].click()", modal).then ->
       expect(modal.isDisplayed()).toBeFalsy()


engine: fix test

engine: fix test


enh: default rule, support loading rule only

--- flight-app.coffee
+++ flight-app.coffee
@@ -193,9 +193,10 @@
     catch e
       alert "Failed to parse: #{e}"
     @isim.clear()
-    @isim.put parsed.pattern, cx, cy
-    @colors = parsed.colors
-    @isim.simulator.rule = parsed.rule if parsed.rule?
+    if parsed.pattern.length > 0
+      @isim.put parsed.pattern, cx, cy
+      @colors = parsed.colors
+    @isim.simulator.rule = parsed.rule ? makeRule "1,2,4,8:rot90"
     @_updateClearBtnIcon()
     @_clearBackground()
     window.scrollTo 0, 0


Better UX around scrolling.

Better UX around scrolling.

--- foreground.coffee
+++ foreground.coffee
@@ -18,9 +18,10 @@
         Common.log "  #{key} #{value}" unless key == "name"

     chrome.runtime.sendMessage name: "icon", show: true if @config.selectors?
-    @selectElement element, false if element
-    # Disabled.  It's not clear that the UX is better like this on not.
-    # @onScroll focusDelay: 0
+    if element
+      @selectElement element, false
+    else if element = @getElements("down")[0]
+       @selectElement element, false

   deactivate: ->
     chrome.runtime.sendMessage name: "icon", show: false
@@ -225,6 +226,7 @@
   # If we scroll, and there's already a selected element, and that element goes out of the viewport, then we
   # select the top-most visible selectable element.
   onScroll: do ->
+    delay = 50
     timer = null

     cancel = ->
@@ -232,14 +234,13 @@
         clearTimeout timer; timer = null

     (event) ->
-      delay = event?.focusDelay ? 200
       cancel()
-      unless @element and Common.isInViewport @element
-        if element = @getElements("down")[0]
-          timer = Common.setTimeout delay, =>
-            timer = null
-            unless @element and Common.isInViewport @element
-              @selectElement element, false
+      timer = Common.setTimeout delay, =>
+        timer = null
+        if @element and not Common.isInViewport @element
+          @clearSelection()
+        if not @element and element = @getElements("down")[0]
+          @selectElement element, false

 Wrapper =
   interface: null


Paper over random order of keys in test.

--- test/unit/model/user.coffee
+++ test/unit/model/user.coffee
@@ -130,12 +130,17 @@
         correctArgs.should.be.true

       it "posts to luxurypigbox with ehg's and ickletest's ssh keys", ->
-        # :TODO: this test is sensitive to the order the keys arrive in
-        # (whether a/b/c first or d/e/f first), it shouldn't be
+        # Note: the "keys" argument in the form is sensitive to some
+        # essentially random dictionary iteration order. We naughtily test
+        # both plausible orders.
         correctArgs = @request.calledWith
           uri: "#{process.env.CU_BOX_SERVER}/luxurypigbox/sshkeys"
           form:
             keys: '["d","e","f","a","b","c"]'
+        correctArgs ||= @request.calledWith
+          uri: "#{process.env.CU_BOX_SERVER}/luxurypigbox/sshkeys"
+          form:
+            keys: '["a","b","c","d","e","f"]'
         correctArgs.should.be.true

   describe 'Validation', ->


Tests for display_artists_section

Tests for display_artists_section

--- apps/partner2/test/client/overview_layout_factory.coffee
+++ apps/partner2/test/client/overview_layout_factory.coffee
@@ -28,7 +28,7 @@
   describe 'gallery_three', ->
     beforeEach ->
       @profile = new Profile fabricate 'partner_profile', full_bio: 'full_bil'
-      @partner = new Partner fabricate 'partner', profile_layout: 'gallery_three'
+      @partner = new Partner fabricate 'partner', profile_layout: 'gallery_three', display_artists_section: true

     it 'returns modules properly for profile_banner_display: "Shows", profile_artists_layout: "Grid"', ->
       @partner.set profile_banner_display: 'Shows'
@@ -131,7 +131,7 @@
   describe 'gallery_two', ->
     beforeEach ->
       @profile = new Profile fabricate 'partner_profile', full_bio: 'full bio here'
-      @partner = new Partner fabricate 'partner', profile_layout: 'gallery_two'
+      @partner = new Partner fabricate 'partner', profile_layout: 'gallery_two', display_artists_section: true

     it 'returns modules properly for profile_banner_display: "Shows", profile_artists_layout: "Grid"', ->
       @partner.set profile_banner_display: 'Shows'
@@ -231,6 +231,11 @@
         }
       ]

+    it 'does not return the "Artists" module when display_artists_section is false', ->
+      @partner.set display_artists_section: false
+      modules = factory(@partner, @profile)
+      modules.should.containDeep [{ name: 'artists', component: undefined }]
+
   describe 'gallery_one', ->
     beforeEach ->
       @profile = new Profile fabricate 'partner_profile', full_bio: 'full bio here'


remove some more specs for phantom crash

--- exercises/test/all-tests.coffee
+++ exercises/test/all-tests.coffee
@@ -1,6 +1,6 @@
 # require './components/app.spec'
 require './components/exercise/controls.spec'
-require './components/exercise.spec'
+# require './components/exercise.spec'

 # testsContext = require.context("./", true, /\.spec\.(cjsx|coffee)$/)
 # testsContext.keys().forEach(testsContext)


Add flash shortcuts to Modal

Add flash shortcuts to Modal

--- vendor/assets/javascripts/fustrate/components/modal.coffee
+++ vendor/assets/javascripts/fustrate/components/modal.coffee
@@ -229,6 +229,15 @@
   closeOnBackgroundClick: ->
     true

+  flashSuccess: (message, { icon } = {}) ->
+    new Fustrate.Components.Flash.Success(message, icon: icon)
+
+  flashError: (message, { icon } = {}) ->
+    new Fustrate.Components.Flash.Error(message, icon: icon)
+
+  flashInfo: (message, { icon } = {}) ->
+    new Fustrate.Components.Flash.Info(message, icon: icon)
+
   _cacheHeight: =>
     @settings._cachedHeight = @modal.show().height()


Sidebar: Removed newItemsCounter from ActivityLink itself.

--- client/app/MainApp/navigation/navigationactivitylink.coffee
+++ client/app/MainApp/navigation/navigationactivitylink.coffee
@@ -8,12 +8,10 @@

     appManager = KD.getSingleton "appManager"

-    @newActivityCount = 0
-
     @count = new KDCustomHTMLView
       tagName   : "span"
       cssClass  : "main-nav-icon transparent"
-      partial   : "#{@newActivityCount}"
+      partial   : ""
       click     : =>
         @setActivityLinkToDefaultState()
         appManager.tell "Activity", "unhideNewItems"
@@ -39,16 +37,14 @@
       @setActivityLinkToDefaultState()

   updateNewItemsCount: (itemCount) ->
-    newItemsCount = itemCount or= @newActivityCount
-    return if newItemsCount is 0
+    return if itemCount is 0
+    @count.updatePartial itemCount
     @count.show()
     @icon.hide()
-    @count.updatePartial newItemsCount

   setActivityLinkToDefaultState: ->
     @icon.show()
     @count.hide()
-    @newActivityCount = 0

   viewAppended:->
     @setTemplate @pistachio()


Fix pinning master

Fix pinning master

--- admin/static/coffee/namespaces/replicas.coffee
+++ admin/static/coffee/namespaces/replicas.coffee
@@ -811,12 +811,17 @@
             else
                 new_replica_affinities[new_primary] = 0

+            if (not @model.get('ack_expectations')[new_primary]?) or @model.get('ack_expectations')[new_primary] is 0
+                new_ack = {}
+                new_ack[new_primary] = 1
+

             data =
                 primary_uuid: new_primary
                 primary_pinnings: primary_pinnings
                 replica_affinities: new_replica_affinities
+                ack_expectations: (new_ack if new_ack?)

             @data_cached = data
             $.ajax
@@ -832,7 +837,7 @@
         on_success_pin: =>
             data_to_set =
                 replica_affinities: @model.get('replica_affinities')
-            data_to_set = _.extend @data_cached, data_to_set
+            data_to_set = _.extend data_to_set, @data_cached
             @model.set data_to_set
             @model.trigger 'change:primary_uuid'
             @turn_primary_on()


logging

logging

--- coffeescript/lib/initialConnection/initialConnection.coffee
+++ coffeescript/lib/initialConnection/initialConnection.coffee
@@ -43,6 +43,8 @@
             winston.verbose dataString
             currentRace?.addCrash data.data, data.gameTick ? 0
             winston.verbose "Max Angle is now #{currentRace.maxAngle}"
+          else if msgType in ['gameEnd', 'tournamentEnd', 'spawn']
+            winston.verbose dataString
           else if msgType in ['lapFinished', 'gameStart', 'joinRace', 'finish', 'turboAvailable', 'createRace']
             winston.verbose dataString
           else


keep recent  new member buckets to 4 instead of 3

--- workers/social/lib/social/models/activity/cache.coffee
+++ workers/social/lib/social/models/activity/cache.coffee
@@ -203,7 +203,7 @@
     cache = cursorArr.reduce (acc, doc)->
       if doc.type is lastDocType and /NewMemberBucket/.test lastDocType
         acc.last.createdAt[1] = doc.createdAt
-        if acc.last.count++ < 3
+        if acc.last.count++ < 4
           acc.last.ids.push doc._id
       else
         acc.push
@@ -227,8 +227,8 @@
           processedCache[i] = memberBucket
           bucketIndex       = i
         else
-          if processedCache[bucketIndex].ids.length < 3
-            newIds = item.ids.slice 0, 3 - processedCache[bucketIndex].ids.length
+          if processedCache[bucketIndex].ids.length < 4
+            newIds = item.ids.slice 0, 4 - processedCache[bucketIndex].ids.length
             processedCache[bucketIndex].ids = processedCache[bucketIndex].ids.concat newIds
           processedCache[bucketIndex].count        += item.count
           processedCache[bucketIndex].createdAt[1]  = item.createdAt.last
@@ -361,7 +361,7 @@
           key             = "overview.#{@newMemberBucketIndex}"

           newIds = newMemberBucket.ids.map((v,k)-> v)
-            .slice(-1 * (3 - freshNewMemberBucket.ids.length))
+            .slice(-1 * (4 - freshNewMemberBucket.ids.length))
             .concat(freshNewMemberBucket.ids)

           setModifier["#{key}.ids"]         = newIds


[social][graph] updated cypher query to fetch correct relationships for following feed

--- workers/social/lib/social/models/activity/index.coffee
+++ workers/social/lib/social/models/activity/index.coffee
@@ -347,7 +347,7 @@

       query = [
         "start koding=node:koding(id='#{userId}')"
-        'MATCH koding<-[:follower]-myfollowees-[:author]->items'
+        'MATCH koding<-[:follower]-myfollowees-[:author]-items'
         'where myfollowees.name="JAccount"'
         'AND items.group = "' + groupName + '"'
       ]


added group is 'koding' filter to cache worker when fetching activity

--- workers/social/lib/social/models/activity/index.coffee
+++ workers/social/lib/social/models/activity/index.coffee
@@ -98,7 +98,7 @@
     {to, from, lowQuality, types, limit, sort} = options

     selector =
-      # group        : 'koding'
+      group        : 'koding'
       createdAt    :
         $lt        : new Date to
         $gt        : new Date from


payment: minor bug fix

payment: minor bug fix

--- workers/social/lib/social/models/referral/index.coffee
+++ workers/social/lib/social/models/referral/index.coffee
@@ -178,7 +178,7 @@
   @spaceForReferring = 500

   @fetchEarnedSpace = (client, callback)->
-    @fetchReferredAccounts client, {}, {}, (err, accounts)->
+    @fetchReferredAccounts client, {}, {}, (err, accounts)=>
       return callback err  if err
       callback null, accounts.length * @spaceForReferring


grey lines all the time

grey lines all the time

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -344,6 +344,9 @@

   render : ->
     super()
+    unselected_color = "#ccc"
+    @plot_view.ctx.fillStyle = unselected_color
+    @plot_view.ctx.strokeStyle = unselected_color
     if @mget('orientation') in ['bottom', 'top']
       @render_x()
       @render_end()
@@ -391,7 +394,6 @@
       current_tick += interval

     can_ctx.stroke()
-    @plot_view.ctx.stroke()
     @render_end()

   DEFAULT_TEXT_HEIGHT : 8
@@ -417,7 +419,6 @@
     current_tick = first_tick
     y_ticks = []
     last_tick_end = 10000
-
     can_ctx.clearRect(0, 0,  @mget('width'), @mget('height'))
     while current_tick <= last_tick
       y_ticks.push(current_tick)
@@ -432,7 +433,6 @@
       current_tick += interval

     can_ctx.stroke()
-    @plot_view.ctx.stroke()
     @render_end()


Replace setTimeout by $timeout

Replace setTimeout by $timeout

The later is better, since it makes the code easier to test and avoids
global state.

--- src/coffee/controllers.coffee
+++ src/coffee/controllers.coffee
@@ -201,7 +201,7 @@
     return

-eob_controllers.controller 'eob_MapCtrl', ($scope, $http, $location,
+eob_controllers.controller 'eob_MapCtrl', ($scope, $http, $location, $timeout,
              eob_data, eob_geolocation, eob_imgCache) ->

     eob_imgCache.load MARKER_ICONS
@@ -283,7 +283,7 @@
         # The panel might be changing right now, in which case
         # 'offsetWidth' returns 0, so let's deffer this
         if $scope.seepanel or $scope.seemenu
-            setTimeout (->
+            $timeout (->
                 mapWidth = document.getElementById("map-canvas").offsetWidth
                 panelWidth = if not $scope.seepanel \
                              then 0


Moved remaining autocompletes from old method to new method

--- completions.cson
+++ completions.cson
@@ -123,7 +123,7 @@

 # General variables
 'Current Member':
-  'prefix': 'currentmember'
+  'prefix': 'member'
   'module': 'silverstripe/framework'
   'variations': [
     {
@@ -133,6 +133,20 @@
     {
       'scope': '.text.html.php'
       'body': 'Member::currentUser()'
+    }
+  ]
+
+'Current Member ID':
+  'prefix': 'memberid'
+  'module': 'silverstripe/framework'
+  'variations': [
+    {
+      'scope': '.text.html.ss'
+      'body': 'CurrentMember.ID'
+    }
+    {
+      'scope': '.text.html.php'
+      'body': 'Member::currentUserID()'
     }
   ]

@@ -265,7 +279,6 @@
       'body': 'Count()${0}'
     }
   ]
-

 # Scope traverse
 'Traverse Up Scope':
@@ -433,6 +446,23 @@
   \t{
   \t\tparent::init();
   \t}
+  }
+  """
+
+'Class DataObject Silverstripe':
+  'prefix': 'classdataobject'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * ${3:Description}
+   *
+   * @package silverstripe
+   * @subpackage ${4:mysite}
+   */
+  class ${1} extends ${2:DataObject}
+  {
+  \t${0}
   }
   """

@@ -478,6 +508,7 @@
   'prefix': 'menu_title'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Menu title for Left and Main CMS
@@ -490,6 +521,7 @@
   'prefix': 'menu_icon'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Menu icon for Left and Main CMS
@@ -502,6 +534,7 @@
   'prefix': 'singular_name'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Singular name for CMS
@@ -514,6 +547,7 @@
   'prefix': 'plural_name'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Plural name for CMS
@@ -526,6 +560,7 @@
   'prefix': 'db'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Database fields
@@ -540,6 +575,7 @@
   'prefix': 'has_one'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Has_one relationship
@@ -554,6 +590,7 @@
   'prefix': 'has_many'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Has_many relationship
@@ -568,6 +605,7 @@
   'prefix': 'many_many'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Many_many relationship
@@ -582,6 +620,7 @@
   'prefix': 'many_many_extrafields'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * \{@inheritdoc \}
@@ -598,6 +637,7 @@
   'prefix': 'belongs_to'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Belongs_to relationship
@@ -613,6 +653,7 @@
   'prefix': 'belongs_many_many'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Belongs_many_many relationship
@@ -627,6 +668,7 @@
   'prefix': 'casting'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Defines types to variables from custom functions
@@ -641,6 +683,7 @@
   'prefix': 'defaults'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Define the default values for all the $db fields
@@ -655,6 +698,7 @@
   'prefix': 'default_sort'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Default sort ordering
@@ -667,6 +711,7 @@
   'prefix': 'allowed_children'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Defines the allowed child page types
@@ -681,6 +726,7 @@
   'prefix': 'allowed_actions'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Defines methods that can be called directly
@@ -695,6 +741,7 @@
   'prefix': 'hide_ancestor'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   private static \\$hide_ancestor = \'\${1:PageType}\';${0}
   """
@@ -704,6 +751,7 @@
   'prefix': 'summary_fields'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Defines summary fields commonly used in table columns
@@ -719,6 +767,7 @@
   'prefix': 'searchable_fields'
   'module': 'silverstripe/framework'
   'minVersion': '3.0'
+  'scope': '.text.html.php'
   'body': """
   /**
    * Defines a default list of filters for the search context
@@ -737,6 +786,66 @@
   'scope': '.text.html.ss, .text.html.php'
   'body': '_t(${1}, ${2})${0}'

+# Events
+'onBeforeWrite':
+  'prefix': 'onbeforewrite'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * Event handler called before writing to the database.
+   */
+  public function onBeforeWrite()
+  {
+  \tparent::onBeforeWrite();
+  \t${0}
+  }
+  """
+
+'onAfterWrite':
+  'prefix': 'onafterwrite'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * Event handler called after writing to the database.
+   */
+  public function onAfterWrite()
+  {
+  \tparent::onAfterWrite();
+  \t${0}
+  }
+  """
+
+'onBeforeDelete':
+  'prefix': 'onbeforedelete'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * Event handler called before deleting from the database.
+   */
+  public function onBeforeDelete()
+  {
+  \tparent::onBeforeDelete();
+  \t${0}
+  }
+  """
+'onAfterDelete':
+  'prefix': 'onafterdelete'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * Event handler called after deleting from the database.
+   */
+  public function onAfterDelete()
+  {
+  \tparent::onAfterDelete();
+  \t${0}
+  }
+  """
+
 # Permissions
 'canView':
   'prefix': 'canview'
@@ -824,6 +933,549 @@
   \treturn array(
   \t\t'${1:PERMISSION}' => '${2:Title}'
   \t);
+  }
+  """
+
+'<?php …':
+  'prefix': 'php' # Overrides standard php to exclude ?>
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '\<?php\n${0}'
+
+# Debug Tools
+'Debug message':
+  'prefix': 'message'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'Debug::message(${1});${0}'
+
+'Debug show':
+  'prefix': 'show'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'Debug::show(${1});${0}'
+
+'Debug log':
+  'prefix': 'log'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'Debug::log(${1});${0}'
+
+'Debug dump':
+  'prefix': 'dump '
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'Debug::dump(${1});${0}'
+
+#  Fields
+'setAttribute':
+  'prefix': 'setattribute'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setAttribute(\'${1}\', \'${2}\')${0}'
+
+'setValue':
+  'prefix': 'setvalue'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setValue(${1})${0}'
+
+'setName':
+  'prefix': 'setname'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setName(\'${1}\')${0}'
+
+'setTitle':
+  'prefix': 'settitle'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setTitle(\'${1}\')${0}'
+
+'setRightTitle':
+  'prefix': 'setrighttitle'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setRightTitle(\'${1}\')${0}'
+
+'setLeftTitle':
+  'prefix': 'setlefttitle'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setLeftTitle(\'${1}\')${0}'
+
+'setDescription':
+  'prefix': 'setdescription'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setDescription(\'${1}\')${0}'
+
+'addExtraClass':
+  'prefix': 'addextraclass'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->addExtraClass(\'${1}\')${0}'
+
+'removeExtraClass':
+  'prefix': 'removeextraclass'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->removeExtraClass(\'${1}\')${0}'
+
+'Required':
+  'prefix': 'required'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->Required()${0}'
+
+'HTMLEditorField':
+  'prefix': 'htmlfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  HtmlEditorField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'UploadField':
+  'prefix': 'uploadfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  UploadField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'setFolderName':
+  'prefix': 'setfoldername'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setFolderName(\'${1}\')${0}'
+
+'setAllowedExtensions':
+  'prefix': 'setallowedextensions'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setAllowedExtensions(array(${1}))${0}'
+
+'setAllowedFileCategories':
+  'prefix': 'setallowedfilecategories'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setAllowedFileCategories(array(${1}))${0}'
+
+'setAllowedMaxFileNumber':
+  'prefix': 'setallowedmaxfilenumber'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setAllowedMaxFileNumber(${1:null})${0}'
+
+'setCanUpload':
+  'prefix': 'setcanupload'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setCanUpload(${1:true})${0}'
+
+'setCanAttachExisting':
+  'prefix': 'setcanattachexisting'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setCanAttachExisting(${1:true})${0}'
+
+'TextField':
+  'prefix': 'textfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  TextField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'EmailField':
+  'prefix': 'emailfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  EmailField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'ReadonlyField':
+  'prefix': 'readonlyfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  ReadonlyField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'NumericField':
+  'prefix': 'numericfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  NumericField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'NumericField_Readonly':
+  'prefix': 'numericfield_readonly'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  NumericField_Readonly::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'CheckboxField':
+  'prefix': 'checkboxfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  CheckboxField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'CheckboxField_Readonly':
+  'prefix': 'checkboxfield_readonly'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  CheckboxField_Readonly::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'NullableField':
+  'prefix': 'nullablefield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  NullableField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'TextareaField':
+  'prefix': 'textareafield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  TextareaField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'setRows':
+  'prefix': 'setrows'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setRows(${1})${0}'
+
+'setColumns':
+  'prefix': 'setcolumns'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->setColumns(${1})${0}'
+
+'TreeDropdownField':
+  'prefix': 'treedropdownfield'
+  'body': """
+  TreeDropdownField::create(
+  \t\'${1:name}ID\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:SiteTree}\'
+  )${0}
+  """
+
+'TreeDropdownField_Readonly':
+  'prefix': 'treedropdownfield_readonly'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  TreeDropdownField_Readonly::create(
+  \t\'${1:name}ID\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:SiteTree}\'
+  )${0}
+  """
+
+'DropdownField':
+  'prefix': 'dropdownfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  DropdownField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:datalist}\'
+  )${0}
+  """
+
+'LookupField':
+  'prefix': 'lookupfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  LookupField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:datalist}\'
+  )${0}
+  """
+
+'ListboxField':
+  'prefix': 'listboxfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  ListboxField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:datalist}\'
+  )${0}
+  """
+'GroupedDropdownField':
+  'prefix': 'groupeddropdownfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  GroupedDropdownField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:datalist}\'
+  )${0}
+  """
+
+'OptionsetField':
+  'prefix': 'optionsetfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  OptionsetField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:datalist}\'
+  )${0}
+  """
+
+'CheckboxSetField':
+  'prefix': 'checkboxsetfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  CheckboxSetField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\',
+  \t\'${3:datalist}\'
+  )${0}
+  """
+
+'HeaderField':
+  'prefix': 'headerfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  HeaderField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'HiddenField':
+  'prefix': 'hiddenfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  HiddenField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'LabelField':
+  'prefix': 'labelfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  LabelField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\'
+  )${0}
+  """
+
+'GridField':
+  'prefix': 'gridfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  GridField::create(
+  \t\'${1:name}\',
+  \t\'${2:${1:title}}\',
+  \t\\$this->${1}(),
+  \t${4:GridFieldConfig_Base::create()}
+  )${0}
+  """
+
+'GridFieldConfig_Base':
+  'prefix': 'gridfieldconfig_base'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'GridFieldConfig_Base::create(${1})${0}'
+
+'GridFieldConfig_RecordEditor':
+  'prefix': 'gridfieldconfig_recordeditor'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'GridFieldConfig_RecordEditor::create(${1})${0}'
+
+'GridFieldConfig_RelationEditor':
+  'prefix': 'gridfieldconfig_relationeditor'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'GridFieldConfig_RelationEditor::create(${1})${0}'
+
+'GridFieldConfig_RecordViewer':
+  'prefix': 'gridfieldconfig_recordviewer'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'GridFieldConfig_RecordViewer::create(${1})${0}'
+
+'addComponent':
+  'prefix': 'addcomponent'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->addComponent(${1})${0}'
+
+'removeComponentsByType':
+  'prefix': 'removecomponentsbytype'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': '->removeComponentsByType(${1})${0}'
+
+'GridFieldButtonRow':
+  'prefix': 'gridfieldbuttonrow'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'new GridFieldButtonRow(\'before\')'
+
+'GridFieldToolbarHeader':
+  'prefix': 'gridfieldtoolbarheader'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': 'new GridFieldToolbarHeader()'
+
+#  Tabs
+'addFieldToTab':
+  'prefix': 'addfield'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  addFieldToTab(
+  \t\'${1:Root.Main}\',
+  \t${0},
+  \t\'${3:insertBefore}\'
+  );
+  """
+
+'addFieldsToTab':
+  'prefix': 'addfields'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  addFieldsToTab(
+  \t\'${1:Root.Main}\',
+  \tarray(
+  \t\t${0}
+  \t)
+  );
+  """
+
+'getCMSFields':
+  'prefix': 'getcmsfields'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * CMS Fields
+   * @return FieldList
+   */
+  public function getCMSFields()
+  {
+  \t\\$fields = parent::getCMSFields();
+  \t\\$fields->${0}
+  \treturn \\$fields;
+  }
+  """
+
+'getSettingsFields':
+  'prefix': 'getsettingsfields'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * Settings Tab Fields
+   * @return FieldList
+   */
+  public function getSettingsFields()
+  {
+  \t\\$fields = parent::getSettingsFields();
+  \t\\$fields->${0}
+  \treturn \\$fields;
+  }
+  """
+
+'updateCMSFields':
+  'prefix': 'updatecmsfields'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * Update Fields
+   * @return FieldList
+   */
+  public function updateCMSFields(FieldList \\$fields)
+  {
+  \t\\$owner = $this->owner;
+  \t\\$fields->${0}
+  \treturn \\$fields;
+  }
+  """
+
+'getEditForm':
+  'prefix': 'geteditform'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.php'
+  'body': """
+  /**
+   * @param Int $id
+   * @param FieldList $fields
+   * @return Form
+   */
+  public function getEditForm($id = null, $fields = null)
+  {
+  \t\\$form = parent::getEditForm($id, $fields);
+  \t${0}
+  \treturn \\$form;
   }
   """


Fixed #330

Fixed #330

--- app/views/editor/level/thangs/LevelThangEditView.coffee
+++ app/views/editor/level/thangs/LevelThangEditView.coffee
@@ -24,7 +24,7 @@
     options ?= {}
     super options
     @world = options.world
-    @thangData = options.thangData ? {}
+    @thangData = $.extend true, {}, options.thangData ? {}
     @level = options.level
     @oldID = @thangData.id

@@ -59,7 +59,7 @@

   saveThang: (e) ->
     # Make sure it validates first?
-    Backbone.Mediator.publish 'editor:level-thang-edited', thangData: @thangData, thangID: @oldID
+    Backbone.Mediator.publish 'editor:level-thang-edited', thangData: $.extend(true, {}, @thangData), thangID: @oldID

   navigateToAllThangs: ->
     Backbone.Mediator.publish 'editor:level-thang-done-editing', {}


chore(Grunt): Add template watch task

chore(Grunt): Add template watch task

--- misc/grunt/contrib-watch.coffee
+++ misc/grunt/contrib-watch.coffee
@@ -25,5 +25,8 @@
       files: ["src/css/*.styl", "vendor/vendor.styl"]
       tasks: ["stylus", "concat:css", "clean"]
     jade:
-      files: ["src/**/*.jade", "docs/*.jade"]
+      files: ["docs/*.jade"]
       tasks: ["jade", "replace:docs"]
+    template:
+      files: ["src/template/*.html"]
+      tasks: ["copy:template"]


补全链接头和名称

补全链接头和名称

--- src/buttons/link.coffee
+++ src/buttons/link.coffee
@@ -139,8 +139,12 @@
         else
           @textEl.trigger('focus')
         return
+      val = @urlEl.val()
+      val = 'http://' + val unless /https?:\/\/|^\//ig.test(val) or !val
+
       @checkButtonStatus()
-      @target.attr 'href', @urlEl.val()
+      @target.attr 'href', val
+      @target.text @textEl.val()
       @editor.inputManager.throttledValueChanged()
       @active = true
       @hide()


fix getBy[RelationName]

fix getBy[RelationName]

--- lib/Model.coffee
+++ lib/Model.coffee
@@ -63,8 +63,6 @@
             models = wrapModel models, RelModel
             @[name] = models

-
-
           @::["add" + capSingName] = (model,  cb = ->) ->
             model = wrapModel model, RelModel
             @[name] ?= []
@@ -88,7 +86,7 @@
           RelModel.r().get(@[opts.foreignKey]).run db.conn, RelModel.wrapReply cb

         @["getBy" + capName] = (id, cb = ->) =>
-            query = new Query @, @r().getAll(id, {index:'questionId'})
+            query = new Query @, @r().getAll(id, {index:opts.foreignKey})
             query.collection = yes
             if cb?
               query.run cb


TeamMembersCommonView: Added search container.

--- client/admin/lib/views/members/teammemberscommonview.coffee
+++ client/admin/lib/views/members/teammemberscommonview.coffee
@@ -1,9 +1,12 @@
 kd                   = require 'kd'
 KDView               = kd.View
-MemberItemView       = require './memberitemview'
+KDSelectBox          = kd.SelectBox
 KDListItemView       = kd.ListItemView
 KDCustomHTMLView     = kd.CustomHTMLView
 KDListViewController = kd.ListViewController
+KDHitEnterInputView  = kd.HitEnterInputView
+
+MemberItemView = require './memberitemview'

 module.exports = class TeamMembersCommonView extends KDView
@@ -19,8 +22,35 @@

     @skip = 0

+    @createSearchView()
     @createListController()
     @fetchMembers()
+
+
+  createSearchView: ->
+
+    @addSubView @searchContainer = new KDCustomHTMLView
+      cssClass: 'search hidden'
+      partial : 'Sort by'
+
+    @searchContainer.addSubView new KDSelectBox
+      defaultValue  : 'nickname'
+      selectOptions : [
+        { title     : 'Screen name',  value : 'fullname'  }
+        { title     : 'Nickname',     value : 'nickname'  }
+      ]
+      callback      : (value) ->
+
+    @searchContainer.addSubView new KDHitEnterInputView
+      type                    : 'text'
+      placeholder             : 'Find by name/username'
+      validationNotifications : yes
+      validate                :
+        rules                 :
+          required            : yes
+        messages              :
+          required            : 'Please enter a name or username'
+      callback                : -> kd.log ',,,,,,,,,,,,,,,,,,'

   createListController: ->
@@ -73,3 +103,4 @@
       @listController.addItem member

     @listController.lazyLoader.hide()
+    @searchContainer.show()


Fixes minor bugs for set_silence interactions

--- src/centerdevice.coffee
+++ src/centerdevice.coffee
@@ -160,7 +160,7 @@
       logger.debug "#{module_name}: Ouuch, request for #{name} timed out ... sorry."

       brain.remove "centerdevice.#{name}.pending"
-      brain.remove "centerdevice.#{name}.timeout"
+      delete Timers["#{name}_timeout"]

       res.reply "Ouuch, request for #{name} timed out ... sorry."
     , config.timeout
@@ -168,8 +168,8 @@
 clear_timeout = (name, brain) ->
   logger.debug "#{module_name}: Clearing timeout for #{name}."
   brain.remove "centerdevice.#{name}.pending"
+  clearTimeout Timers["#{name}_timeout"]
   delete Timers["#{name}_timeout"]
-  clearTimeout brain.get "centerdevice.#{name}.timeout"
   brain.remove "centerdevice.#{name}.timeout"

 set_silence_checker = (context, robot) ->
@@ -177,7 +177,6 @@
   active_silence_id = robot.brain.get "centerdevice.bosun.set_silence.silence_id"
   if active_silence_id is context.silence_id
     logger.debug "#{module_name}: setting timeout for Bosun silence checker #{config.silence_check_interval} ms."
-    # robot.brain.set "centerdevice.set_silence.checker.timeout", setTimeout () ->
     Timers["set_silence_checker_timeout"] = setTimeout () ->
       logger.debug "#{module_name}: Emitting request to Bosun to check if silence with id #{context.silence_id} is still active."
       robot.emit 'bosun.check_silence', context
@@ -185,6 +184,7 @@

 clear_silence_checker = (brain) ->
   logger.debug "#{module_name}: Clearing silence checker."
+  clearTimeout Timers["set_silence_checker_timeout"]
   delete Timers["set_silence_checker_timeout"]
   robot.brain.remove "centerdevice.set_silence.checker.timeout"


Fixing tests

Fixing tests

--- test/strawpoll_test.coffee
+++ test/strawpoll_test.coffee
@@ -13,4 +13,4 @@
     require('../src/strawpoll')(@robot)

   it 'registers a respond listener', ->
-    expect(@robot.respond).to.have.been.calledWith(/\/strawpoll/).withArgs(/1/, /2/, /3/)
+    expect(@robot.respond).to.have.been.calledWith(/strawpoll ?(.*)/i)


Use the updateManager to get release notes URL

--- lib/about.coffee
+++ lib/about.coffee
@@ -27,7 +27,8 @@
         @deserializeAboutView(uri: AboutURI)

     @subscriptions.add atom.commands.add 'atom-workspace', 'about:view-release-notes', ->
-      require('shell').openExternal('https://atom.io/releases')
+      updateManager = getUpdateManager()
+      require('shell').openExternal(updateManager.getReleaseNotesURLForCurrentVersion())

     availableVersion = localStorage.getItem(AvailableUpdateVersion)
     localStorage.removeItem(AvailableUpdateVersion) if availableVersion is atom.getVersion()


Removing highlights from page when logging out

--- src/annotator.readmill.coffee
+++ src/annotator.readmill.coffee
@@ -63,6 +63,8 @@
   disconnect: =>
     @client.deauthorize()
     @store.remove "access-token"
+    @annotator.element.find(".annotator-hl").each ->
+      jQuery(this).replaceWith this.childNodes

   error: (message) ->
     Annotator.showNotification message, Annotator.Notification.ERROR


A hacky debounced function to fix select element widths in IE


Remove deprecated code.

Remove deprecated code.

--- tests/scripts/fuzzer.coffee
+++ tests/scripts/fuzzer.coffee
@@ -8,12 +8,4 @@
   window.reader = new Scribe.Editor($editors.get(1))
   writerToolbar = new Scribe.Toolbar('editor-toolbar-writer', writer)
   readerToolbar = new Scribe.Toolbar('editor-toolbar-reader', reader)
-  start = new Date()
-
-  writer.on(Scribe.Editor.events.TEXT_CHANGE, (delta) ->
-    reader.applyDelta(delta, false)
-  )
-
-  writer.getRandomOp = ->
-    Scribe.Debug.Test.getRandomOperation(this, ALPHABET, FORMATS)
 )


add defer to fuzzing finish, use constants style

--- tests/scripts/fuzzer.coffee
+++ tests/scripts/fuzzer.coffee
@@ -1,4 +1,5 @@
-alphabet = 'abcdefghijklmnopqrstuvwxyz'.split('').concat(_.map([0..15], -> return "\n"))
+ALPHABET = 'abcdefghijklmnopqrstuvwxyz'.split('').concat(_.map([0..15], -> return "\n"))
+NUM_OPERATIONS = 100

 getRandomLength = ->
   rand = Math.random()
@@ -28,7 +29,7 @@

 getRandomString = (length) ->
   return _.map([0..(length - 1)], ->
-    return alphabet[Math.floor(Math.random()*length)]
+    return ALPHABET[Math.floor(Math.random()*length)]
   ).join('')

@@ -42,19 +43,21 @@
     reader.applyDelta(delta)
   )

-  finishedFuzzing = _.after(100, ->
-    writerHtml = Tandem.Utils.cleanHtml(writer.doc.root.innerHTML)
-    readerHtml = Tandem.Utils.cleanHtml(reader.doc.root.innerHTML)
-    writerDelta = writer.getDelta()
-    readerDelta = reader.getDelta()
+  finishedFuzzing = _.after(NUM_OPERATIONS, ->
+    _.defer( ->
+      writerHtml = Tandem.Utils.cleanHtml(writer.doc.root.innerHTML)
+      readerHtml = Tandem.Utils.cleanHtml(reader.doc.root.innerHTML)
+      writerDelta = writer.getDelta()
+      readerDelta = reader.getDelta()

-    if writerHtml == readerHtml && _.isEqual(writerDelta, readerDelta)
-      console.log "Fuzzing passed"
-    else
-      console.error "Fuzzing failed", writer, reader
+      if writerHtml == readerHtml && _.isEqual(writerDelta, readerDelta)
+        console.log "Fuzzing passed"
+      else
+        console.error "Fuzzing failed", writer, reader
+    )
   )

-  _.each([1..100], ->
+  _.each([1..NUM_OPERATIONS], ->
     _.defer( ->
       operation = getRandomOperation(writer)
       if operation?


Tweak askParams of TentacleWhip skill

Tweak askParams of TentacleWhip skill

--- src/definitions/skills.coffee
+++ src/definitions/skills.coffee
@@ -1,4 +1,5 @@
 _ = require 'lodash'
+Promise = require 'bluebird'

 game = require '../game'
 log = require '../log'
@@ -18,12 +19,11 @@
 	askParams: (creature) ->
 		# only called for player, to populate params to pass to #use()

-		params = {}
+		Promise.all [
+			prompts.position 'Whip towards where?', default: creature
+		]

-		prompts.position 'Whip towards where?', default: creature
-		.then (pos) -> params.position = pos
-
-		.then -> params
+		.then ([position]) -> {position}

 	use: (creature, params) ->
 		console.log 'whip:', params


Numerical sort function because [100, 300, 5] is sorted else wise.

--- web/coffee/dependencies.coffee
+++ web/coffee/dependencies.coffee
@@ -32,6 +32,10 @@
   style = getStyle data
   $.notify data.message, style

+@numericalSort = (data) ->
+  data.sort (a, b) ->
+    return (b - a)
+
 $.fn.apiNotify = (data, configuration) ->
   configuration["className"] = getStyle data
   return $(this).notify(data.message, configuration)


bugfix _createRemote

bugfix _createRemote

--- modules/http/lib/Context.coffee
+++ modules/http/lib/Context.coffee
@@ -177,7 +177,7 @@
         ##
         _createRemote: (req, res, fn)->

-            @_createModel req, res, (err, model)=>
+            @_createModel req, res, 'remote', (err, model)=>
                 return fn(err) if err

                 @_createBoot req, res, model, (err, boot, model)=>
@@ -190,10 +190,7 @@
         ##
         ##
         _createModel: (req, res, type, fn)->
-            if typeof type is 'function'
-                fn = type
-                type = 'remote'
-
+
             next = (err, model)=>
                 return fn(err) if err


DoppioAPI no persists opening and closing the reference page to minimize untarring of mini-rt

--- web/scripts/referenceUI.coffee
+++ web/scripts/referenceUI.coffee
@@ -1,3 +1,5 @@
+root = exports ? referencePage = {}
+
 ###
 Dictionary takes a string json reference in the form of a directory path and a container DOM element to create the dictionary in.  Example .json format is in the
 config directory named dictionary.json.  The dictionary function appends a div to its container that serves the dynamic information content depending on the
@@ -157,7 +159,8 @@
         textOutput.text msg
         return
     log = console.log
-    sandBoxDoppio = new DoppioApi stdout, log, doppioWrapper
+    if not root.sandBoxDoppio
+        root.sandBoxDoppio = new DoppioApi stdout, log, doppioWrapper

     run = jQuery '<button>', {
         id: 'runCode',
@@ -165,7 +168,7 @@
         click: (e) ->
             textOutput.text ''
             msg = ''
-            sandBoxDoppio.run sandBoxEditor.getStudentCode()
+            root.sandBoxDoppio.run sandBoxEditor.getStudentCode()
             e.preventDefault()
             return
     }
@@ -173,7 +176,7 @@
         id: 'abortCode',
         text: 'Abort',
         click: (e) ->
-            sandBoxDoppio.abort()
+            root.sandBoxDoppio.abort()
             e.preventDefault()
             return
     }


chore: Opt out of new line length limit behavior. (#178)

--- grammars/language-asciidoc.cson
+++ grammars/language-asciidoc.cson
@@ -7,6 +7,7 @@
   "asciidoc"
   "adoc.txt"
 ]
+limitLineLength: false
 maxTokensPerLine: 100
 patterns: [
   {


reformat with lots more comments

reformat with lots more comments

--- lib/chain.coffee
+++ lib/chain.coffee
@@ -1,21 +1,24 @@
-
 Control = require './control'

+###
+  Chain holds an array of functions to execute in sequence managed by a Control.
+###
 module.exports = class Chain extends require('events').EventEmitter

   constructor: (options) ->

     # if module's builder function already validated the array then use it
+    # and avoid redoing that same work
     if options?.__validated
       array = options.array

-    # must find the array and validate it
+    # otherwise, let's be safe, must find the array and validate it
     else
       array =
-        if Array.isArray options then options
-        else if typeof options is 'function' then [ options ]
-        else if Array.isArray options?.array then options.array
-        else []
+        if Array.isArray options then options                    # new Chain [...]
+        else if typeof options is 'function' then [ options ]    # new Chain fn
+        else if Array.isArray options?.array then options.array  # new Chain array:[]
+        else []                                                  # new Chain()

       # validate array's contents: must be functions
       for element,index in array
@@ -23,8 +26,13 @@
           # can't return a value from constructor, so, throw Error
           throw new Error 'Elements must be functions. Invalid #'+index+':'+element

+    # an array of functions, so simple, until you wrap a Chain around it...
     @array = array

+  # add functions to the array:
+  #  chain.add fn
+  #  chain.add fn1, fn2, fn3, ...
+  #  chain.add [ fn1, fn2, ... ]
   add: (fns...) ->

     # unwrap array
@@ -48,25 +56,41 @@
     # it's all good
     return success:true

+  # remove function from the array
+  #  chain.remove fn1
   remove: (fn) ->
+
+    # return -1 unless we find it
     index = @array.indexOf fn
+
+    # by default, return an empty object because we didn't remove anything
     result = {}
+
+    # if we found it tho
     if index > -1
+      # then remove it from the array and assign it into the result
       result.removed = @array.splice index, 1
+
+      # let everyone know we removed it
       @emit 'remove', fn
+
+    # and return the result whether it's empty or not
     return result

+  # empty the chain of all functions and let everyone know
   clear: ->
     if @array.length > 0      # only operate when there's some content
       array = @array          # remember the array
       @array = []             # new empty array = clear
       @emit 'remove', array   # emit a remove event with the array

+  # main function which begins the execution of the chain
+  # options optionally configures the `context` and the `done`
   run: (options, done) ->
     context = options?.context ? {}                # get context or default to {}
     done = options?.done ? done                    # look for `done`
     control = new Control @, @array, context, done # create controller
-    @emit 'start', control:control, chain:this
+    @emit 'start', control:control, chain:this     # notify we're starting
     result = control._execute()                    # starts the chain
     if control.paused?                             # a paused chain isn't done
       results = paused:control.paused              # return only paused info


Draw turtle properly

Draw turtle properly


change timeout to 30000

change timeout to 30000

--- src/hubot-pull-request.coffee
+++ src/hubot-pull-request.coffee
@@ -2,7 +2,7 @@

 class HubotPullRequest
   constructor: ({ @timeout, @token } = {}) ->
-    @timeout ?= '3000'
+    @timeout ?= '30000'
     @waitList = []

   cancel: (res) ->


forgot to wipe out dom before appending children

--- src/coffee/bokeh_views.coffee
+++ src/coffee/bokeh_views.coffee
@@ -122,6 +122,9 @@

   render : ->
     super()
+    for view in _.values(@childviews)
+      view.$el.detach()
+    @$el.html('')
     row_heights =  @viewstate.layout_heights()
     col_widths =  @viewstate.layout_widths()
     y_coords = [0]


Update error message reference to user with member

--- src/collections/events.coffee
+++ src/collections/events.coffee
@@ -96,10 +96,10 @@
       `sendingMemberId`'
   unless Array.isArray(include)
     throw new TSArgsError 'teamsnap.sendAvailabilityReminders', "`include` must
-      be an array of user ids"
+      be an array of member ids"
   if !include? or include.length == 0
     throw new TSArgsError 'teamsnap.sendAvailabilityReminders', "`include` must
-      be an array of user ids"
+      be an array of member ids"

   options =
     id: eventId


resolved issue 29

resolved issue 29

--- lib/entry.coffee
+++ lib/entry.coffee
@@ -121,7 +121,7 @@

       # child is of the file, the child's name is not in the new list and it does not have any children itself
       # so we'll remove it.
-      if child.fileName == fileName and child.name not in existingChildNames and child.children.length == 0
+      if child.fileName == fileName and !(child.name in existingChildNames)
         @removeChild(child.name)
         continue
       i+= 1


Callback with error first

Callback with error first

--- lib/index.coffee
+++ lib/index.coffee
@@ -135,10 +135,9 @@
         # Get doc snapshot. We don't need it for transform, but we will
         # try to apply the operation locally before saving it.
         @fetch cName, docName, (err, snapshot) =>
-          opData.v = snapshot.v if !opData.v?
-
           return callback? err if err
           return callback? 'Invalid version' if snapshot.v < opData.v
+          opData.v = snapshot.v if !opData.v?

           trySubmit = =>
             # Eagarly try to submit to redis. If this fails, redis will return all the ops we need to


updateFunctionConfiguration関数のパラメータに「Runtime」を追加

--- lib/index.coffee
+++ lib/index.coffee
@@ -109,6 +109,7 @@
             Role:         config.role,
             MemorySize:   config.memorySize,
             Timeout:      config.timeout,
+            Runtime:      config.runtime,
             Handler:      config.handler,
           }
           updateFunctionCodeParam = {


fixed minor bug with station name generator

--- ng-modules/trader/trader.coffee
+++ ng-modules/trader/trader.coffee
@@ -54,7 +54,7 @@
             "The Enterprise",
             "The Cosmodonut"
         ]
-        @stationName = names[Math.floor(Math.random() * myArray.length)];
+        @stationName = names[Math.floor(Math.random() * names.length)];

     @selectOption = (option)=>
         option.action()


Account Settings: Specs: Don't show the upgrade button if current plan is professional

--- client/app/lib/providers/machinesettingsspecsview.coffee
+++ client/app/lib/providers/machinesettingsspecsview.coffee
@@ -21,7 +21,13 @@
     @createIcon 'DISK',  disk
     @createIcon 'CORES', cpu

-    @createMoreView()
+    kd.singletons.paymentController.subscriptions (err, subscription) =>
+
+      if err
+        @createMoreView()
+        return showError err
+
+      @createMoreView()  unless subscription.planTitle is 'professional'

   createIcon: (title, spec) ->


home/billing: dispatch action with a name

--- client/home/lib/billing/flux/cardformvalues/index.coffee
+++ client/home/lib/billing/flux/cardformvalues/index.coffee
@@ -9,7 +9,8 @@
   stores: [ FormValuesStore ]

   actions:
-    setValue: (type, value) -> dispatch { type, value }
+    setValue: (type, value) ->
+      dispatch actionTypes.SET_TEAM_BILLING_INPUT_VALUE, { type, value }

   getters:
     values: [FormValuesStore.getterPath]


tray notifications are updated for linking the group titles

--- client/landing/app/MainApp/notificationcontroller.coffee
+++ client/landing/app/MainApp/notificationcontroller.coffee
@@ -180,9 +180,9 @@
           when "groupInvited"
             "#{actorName} has invited you to #{subjectObj.title}."
           when "groupJoined"
-            "#{actorName} has joined #{subjectObj.title}."
+            "#{actorName} has joined #{subjectObj.title}."
           when "groupLeft"
-            "#{actorName} has left #{subjectObj.title}."
+            "#{actorName} has left #{subjectObj.title}."
           else
             if actorType is "follower"
               "#{actorName} started following you."


Cleanup Worker: added sending notification to user

--- client/landing/app/MainApp/notificationcontroller.coffee
+++ client/landing/app/MainApp/notificationcontroller.coffee
@@ -37,6 +37,8 @@

     @on 'UsernameChanged', ({username, oldUsername}) ->
       # FIXME: because of this (https://app.asana.com/0/search/6604719544802/6432131515387)
+    @on 'GuestTimePeriodHasEnded', ()->
+      #todo add a notification to user
       $.cookie 'clientId', erase: yes

       new KDModalView


SocialWorker: return access denied if allowedDomains is empty

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -646,9 +646,10 @@
       # yes weird, but we are creating user before creating group
       return callback null, { isEligible: yes } if not group

-      isAllowed = group.isInAllowedDomain email
-      return callback new KodingError 'Your email domain is not in allowed \
-        domains for this group'  unless isAllowed
+      unless group.isInAllowedDomain email
+        domainErr = 'Your email domain is not in allowed domains for this group'
+        return callback new KodingError domainErr  if group.allowedDomains?.length > 0
+        return callback new KodingError 'Access denied'

       return callback null, { isEligible: yes }


create a new session, not a new temporary user in a failing case.

--- workers/social/lib/social/models/user/index.coffee
+++ workers/social/lib/social/models/user/index.coffee
@@ -218,9 +218,9 @@
       if err
         callback createKodingError err
       else unless session?
-        JUser.createTemporaryUser (err, resp)->
-          return callback err  if err
-          callback null, resp.account
+        JSession.createSession (err, session, account)->
+          return callback err  if err?
+          callback null, account
       else
         {username} = session
         if username?


Add scrollable

Add scrollable

--- src/jquery.superslides.coffee
+++ src/jquery.superslides.coffee
@@ -40,11 +40,19 @@
     @start()
     this

-  setupContainers = ->
+  setupContainers = =>
     $control.css
       width: width * multiplier
       height: height
       left: -width
+
+    if @options.scrollable
+      $children.each ->
+        return if $('.scrollable', this).length
+
+        $(this).wrapInner('<div class="scrollable" />');
+        $(this).find('img:first-child')
+               .insertBefore($('.scrollable', this));

   setupChildren = =>
     $children.css


Increase informatics download timeout

Increase informatics download timeout

--- server/testSystems/Informatics.coffee
+++ server/testSystems/Informatics.coffee
@@ -86,6 +86,7 @@
             throw "Too many requests"
         _requests++
         await sleep(TIMEOUT)
+        options.timeout = options.timeout || 30 * 1000
         try
             result = await download(href, @jar, options)
         finally


image_cache: use url.parse to find out ext and path,   default to jpeg if not ext,   other minor format fixes;

--- servers/lib/server/image_cache.coffee
+++ servers/lib/server/image_cache.coffee
@@ -2,6 +2,7 @@
 http   = require "https"
 mime   = require "mime"
 crypto = require "crypto"
+parser = require "url"

 {
   embedly : {
@@ -16,17 +17,15 @@
 module.exports = (req, res) ->
   {endpoint, grow, width, height, url} = req.query

-  fullUrl  = "https://i.embed.ly/1/display/#{endpoint}?" +
+  fullUrl = "https://i.embed.ly/1/display/#{endpoint}?" +
              "width=#{width}&" +
              "height=#{height}&" +
              "key=#{apiKey}&" +
-             "url=#{url}"
+             "url=#{encodeURIComponent(url)}"

-  # split the ext, md5 just the url and write md5+ext to disk
-  # md5 since some urls are too big and cause 'ENAMETOOLONG' error
-  splitUrl = url.split(".")
-  ext      = splitUrl.pop()
-  noExt    = splitUrl.join(".")
+  url   = parser.parse url
+  ext   = url.pathname.split(".")[-1] or "jpeg"
+  noExt = "#{url.host}#{url.pathname}"

   # arbitary limit to prevent ENAMETOOLONG errors
   if ext.length > 20
@@ -35,7 +34,7 @@
   # deal with extensions like:
   #   'com/LbobbpWTGJSa45Mhrb6g_y3YjLn5OthdnugrHZJQqom1eduFCnFmqdmOOZmUttP8hLg=h310'
   if ext.indexOf("com") > -1
-    ext = "com"
+    ext = "jpeg"

   # replace nonalphanumeric characters
   ext = ext.replace(/\W+/g, "")
@@ -44,8 +43,7 @@
   for i in [noExt, width, height]
     md5.update(i)

-  digest = md5.digest("hex")
-
+  digest   = md5.digest("hex")
   filename = "#{imagePath}/#{digest}.#{ext}"

   serveFile = (filename, res)->
@@ -54,7 +52,7 @@

     mimeType = mime.lookup filename

-    res.writeHead(200, { 'Content-Type': mimeType });
+    res.writeHead(200, { 'Content-Type': mimeType })

   if fs.existsSync filename
     return serveFile filename, res


put /ip on web router

put /ip on web router

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -304,7 +304,7 @@
 				else
 					sendRes 500, "Compiler returned failure #{status}"

-		apiRouter.get "/ip", (req, res, next) ->
+		webRouter.get "/ip", (req, res, next) ->
 			res.send({
 				ip: req.ip
 				ips: req.ips


Remove unused socket.io endpoints

Remove unused socket.io endpoints

--- services/web/app/coffee/router.coffee
+++ services/web/app/coffee/router.coffee
@@ -254,13 +254,6 @@
 				metrics.inc ('socket-io.disconnect')
 				EditorController.leaveProject client, user

-			client.on 'reportError', (error, callback) ->
-				EditorController.reportError client, error, callback
-
-			client.on 'sendUpdate', (doc_id, windowName, change)->
-				AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
-					EditorUpdatesController.applyAceUpdate(client, project_id, doc_id, windowName, change)
-
 			client.on 'applyOtUpdate', (doc_id, update) ->
 				AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
 					EditorUpdatesController.applyOtUpdate(client, project_id, doc_id, update)
@@ -269,14 +262,6 @@
 				AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
 					EditorController.updateClientPosition(client, cursorData)

-			client.on 'addUserToProject', (email, newPrivalageLevel, callback)->
-				AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
-					EditorController.addUserToProject project_id, email, newPrivalageLevel, callback
-
-			client.on 'removeUserFromProject', (user_id, callback)->
-				AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
-					EditorController.removeUserFromProject(project_id, user_id, callback)
-
 			client.on 'leaveDoc', (doc_id, callback)->
 				AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
 					EditorController.leaveDoc(client, project_id, doc_id, callback)
@@ -285,19 +270,14 @@
 				AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
 					EditorController.joinDoc(client, project_id, args...)

-			# Deprecated and can be removed after deploying.
-			client.on 'pdfProject', (opts, callback)->
-				AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
-					CompileManager.compile project_id, user._id, opts, (error, status, outputFiles) ->
-						return callback error, status == "success", outputFiles
-
-			client.on 'getRootDocumentsList', (callback)->
-				AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
-					EditorController.getListOfDocPaths project_id, callback
-
-			client.on 'forceResyncOfDropbox', (callback)->
-				AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
-					EditorController.forceResyncOfDropbox project_id, callback
+			# The remaining can be done via HTTP
+			client.on 'addUserToProject', (email, newPrivalageLevel, callback)->
+				AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
+					EditorController.addUserToProject project_id, email, newPrivalageLevel, callback
+
+			client.on 'removeUserFromProject', (user_id, callback)->
+				AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
+					EditorController.removeUserFromProject(project_id, user_id, callback)

 			client.on 'getUserDropboxLinkStatus', (owner_id, callback)->
 				AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>


added countdown during loading of network

--- src/main/webapp/lib/util/GoogleMapRouteHandler.coffee
+++ src/main/webapp/lib/util/GoogleMapRouteHandler.coffee
@@ -20,7 +20,7 @@
         @setUpLink(link)
         @_directionsRequest(link)
         if(indexOfLink % 10 == 0)
-          $a.broker.trigger('app:show_message:info', 'Loading ...')
+          $a.broker.trigger('app:show_message:info', "Loading ... #{indexOfLink} more" )
       else
         $a.broker.trigger('map:draw_link', link)
         @_requestLink(indexOfLink - 1)


removed cache class

removed cache class

--- src/driver/driverCache.coffee
+++ src/driver/driverCache.coffee
@@ -3,60 +3,64 @@
 # -----------------------------------------------------
 driverUtil = require('./driverUtil')

-class Cache
-  constructor: ->
-    @hashmap = {}
-
-  _filterToHash: (filter) ->
-    return '' unless filter?
-    hash = []
-    if filter.filters?
-      for subFilter in filter.filters
-        hash.push @_filterToHash(subFilter)
-      return hash.sort().join(filter.type)
-    else
-      for k, v of filter
-        hash.push(k + ":" + v)
-    return hash.sort().join('|')
-
-  _splitToHash: (split) ->
-    hash = []
-    for k, v of split
+filterToHash = (filter) ->
+  return '' unless filter?
+  hash = []
+  if filter.filters?
+    for subFilter in filter.filters
+      hash.push filterToHash(subFilter)
+    return hash.sort().join(filter.type)
+  else
+    for k, v of filter
       hash.push(k + ":" + v)
-
-    return hash.sort().join('|')
-
-  _separateTimeFilter: (filter) ->
-    if filter.filters?
-      timeFilter = filter.filters.filter((({type}) -> type is 'within'), this)[0]
-      filtersWithoutTime = filter.filters.filter((({type}) -> type is 'within'), this)
-      if filtersWithoutTime.length is 1
-        return {
-          filter: filtersWithoutTime[0]
-          timeFilter
+  return hash.sort().join('|')
+
+splitToHash = (split) ->
+  hash = []
+  for k, v of split
+    continue if k is 'name'
+    hash.push(k + ":" + v)
+
+  return hash.sort().join('|')
+
+generateHash = (condensedQuery) ->
+  # Get Filter and Split
+  split = condensedQuery[1].split
+  filter = condensedQuery[0].filter
+  return filterToHash(filter) + '&' + splitToHash(split)
+
+separateTimeFilter = (filter) ->
+  if filter.filters?
+    timeFilter = filter.filters.filter((({type}) -> type is 'within'), this)[0]
+    filtersWithoutTime = filter.filters.filter((({type}) -> type is 'within'), this)
+    if filtersWithoutTime.length is 1
+      return {
+        filter: filtersWithoutTime[0]
+        timeFilter
+      }
+    else
+      filter.filters = filtersWithoutTime
+      return {
+        filter: {
+          operation: 'filter'
+          type: 'and'
+          filters: filtersWithoutTime
         }
-      else
-        # ToDo: make new And filter
-
-        filter.filters = filtersWithoutTime
-        return {
-          filter: {
-            operation: 'filter'
-            type: 'and'
-            filters: filtersWithoutTime
-          }
-          timeFilter
-        }
-    else # Only time filter exists
-      return {
-        filter: null
-        timeFilter: filter
-      }
-
-class FilterCache extends Cache
+        timeFilter
+      }
+  else # Only time filter exists
+    return {
+      filter: null
+      timeFilter: filter
+    }
+
+
+class FilterCache
   # { key: filter,
   #   value: { key: metric,
   #            value: value } }
+  constructor: ->
+    @hashmap = {}

   get: (condensedQuery, values) ->
     # Return format:
@@ -71,12 +75,11 @@
     #   }
     # ]
     filter = condensedQuery[0].filter
-    splitOp = condensedQuery[1].split
-    splitOpName = splitOp.name
+    splitOpName = condensedQuery[1].split.name
     ret = {}
     for value in values
       newFilter = @_addToFilter(condensedQuery, value)
-      ret[value] = @hashmap[@_filterToHash(newFilter)]
+      ret[value] = @hashmap[filterToHash(newFilter)]
       if ret[value]?
         ret[value][splitOpName] = value

@@ -84,13 +87,13 @@

   put: (condensedQuery, root) ->
     filter = condensedQuery[0].filter
-    hashValue = @hashmap[@_filterToHash(filter)] ?= {}
+    hashValue = @hashmap[filterToHash(filter)] ?= {}
     for k, v of root.prop
       hashValue[k] = v

     for split in root.splits
       newFilter = @_addToFilter(condensedQuery, split.prop[condensedQuery[1].split.name])
-      hashValue = @hashmap[@_filterToHash(newFilter)] ?= {}
+      hashValue = @hashmap[filterToHash(newFilter)] ?= {}
       for k, v of split.prop
         hashValue[k] = v

@@ -101,7 +104,7 @@
     splitOp = condensedQuery[1].split

     if splitOp.bucket in ['timePeriod', 'timeDuration']
-      { filter: oldFilter } = @_separateTimeFilter(oldFilter)
+      { filter: oldFilter } = separateTimeFilter(oldFilter)
       newFilterPiece = {
         attribute: splitOp.attribute
         type: 'within'
@@ -123,10 +126,12 @@

-class SplitCache extends Cache
+class SplitCache
   # { key: filter,
   #   value: { key: split,
   #            value: [list of dimension values] } }
+  constructor: ->
+    @hashmap = {}

   get: (condensedQuery) ->
     # Return format:
@@ -138,10 +143,10 @@
     if condensedQuery[1].split.bucket in ['timePeriod', 'timeDuration']
       return @_timeCalculate(condensedQuery)

-    return @hashmap[@_generateHash(condensedQuery)]
+    return @hashmap[generateHash(condensedQuery)]

   put: (condensedQuery, root) ->
-    hash = @_generateHash(condensedQuery)
+    hash = generateHash(condensedQuery)
     splitOpName = condensedQuery[1].split.name
     hashValue = []
     for split in root.splits
@@ -149,15 +154,9 @@
     @hashmap[hash] = hashValue
     return

-  _generateHash: (condensedQuery) ->
-    # Get Filter and Split
-    split = condensedQuery[1].split
-    filter = condensedQuery[0].filter
-    return @_filterToHash(filter) + '&' + @_splitToHash(split)
-
   _timeCalculate: (condensedQuery) ->
     split = condensedQuery[1].split
-    {timeFilter} = @_separateTimeFilter(condensedQuery[0].filter)
+    {timeFilter} = separateTimeFilter(condensedQuery[0].filter)
     timestamps = []
     timestamp = new Date(timeFilter.range[0])
     end = new Date(timeFilter.range[1])