JStackTemplate: getProvider as a separated util

--- workers/social/lib/social/models/computeproviders/stacktemplate.coffee
+++ workers/social/lib/social/models/computeproviders/stacktemplate.coffee
@@ -811,6 +811,17 @@
           Kloud.buildStack client, { stackId, provider, variables }, callback

+  getProvider: ->
+
+    # TODO: add multiple provider support here ~GG
+    provider = @getAt 'config.requiredProviders.0'
+
+    unless provider or not PROVIDERS[provider]
+      return [ new KodingError 'Provider is not supported' ]
+
+    return [ null, provider ]
+
+
   verify$: permit

     advanced: [
@@ -825,10 +836,9 @@
   verify: (client, callback) ->

     stackTemplateId = @getAt '_id'
-    provider = (@getAt 'config.requiredProviders')[0]
-
-    unless provider or not PROVIDERS[provider]
-      return callback new KodingError 'Provider is not supported'
+
+    [ err, provider ] = @getProvider()
+    return callback err  if err

     noMachines = ->
       callback new KodingError \


Change title of up.flow module

Change title of up.flow module

--- lib/assets/javascripts/unpoly/flow.js.coffee
+++ lib/assets/javascripts/unpoly/flow.js.coffee
@@ -1,6 +1,6 @@
 ###*
-Changing page fragments programmatically
-========================================
+Updating fragments from Javascript
+==================================

 This module contains Unpoly's core functions to [change](/up.replace) or
 [destroy](/up.destroy) page fragments via Javascript.


trackEvent method does not exist anymore

--- src/remote/services/track.coffee
+++ src/remote/services/track.coffee
@@ -30,8 +30,6 @@

     event = path

-    @trackEvent(event, params)
-
     EventBus.emit('remote.tracked',{event,params});

     @_request({


[#63730868] bug fix on pagination

[#63730868] bug fix on pagination

--- app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
+++ app/assets/javascripts/trade/controllers/annual_report_upload_controller.js.coffee
@@ -113,6 +113,7 @@
     # new for sandbox shipments updateSelection
     transitionToSandboxShipments: (errorSelector) ->
       params = @sanitizeQueryParams(errorSelector)
+      params.page = 1
       @transitionToRoute('sandbox_shipments', {
         queryParams: params
       })


Fixing some lint issues with tabs rather than spaces

--- src/scripts/elasticsearch.coffee
+++ src/scripts/elasticsearch.coffee
@@ -48,10 +48,10 @@
       msg.send("Getting the cat stats for the cluster: #{cluster_url}")
       msg.http("#{cluster_url}/_cat/nodes?h=host,heapPercent,load,segmentsMemory,fielddataMemory,filterCacheMemory,idCacheMemory,percolateMemory,u,heapMax,nodeRole,master")
         .get() (err, res, body) ->
-          lines  = body.split("\n")
-          header = lines.shift()
-          list   = [header].concat(lines.sort().reverse()).join("\n")
-          msg.send("/code #{list}")
+          lines = body.split("\n")
+          header = lines.shift()
+          list = [header].concat(lines.sort().reverse()).join("\n")
+          msg.send("/code #{list}")

   catIndexes = (msg, alias) ->
     cluster_url = _esAliases[alias]
@@ -62,10 +62,10 @@
       msg.send("Getting the cat indices for the cluster: #{cluster_url}")
       msg.http("#{cluster_url}/_cat/indices/logstash-*?h=idx,sm,fm,fcm,im,pm,ss,sc,dc&v")
         .get() (err, res, body) ->
-          lines  = body.split("\n")
-          header = lines.shift()
-          list   = [header].concat(lines.sort().reverse()).join("\n")
-          msg.send("/code #{list}")
+          lines = body.split("\n")
+          header = lines.shift()
+          list = [header].concat(lines.sort().reverse()).join("\n")
+          msg.send("/code #{list}")

   clearCache = (msg, alias) ->
     cluster_url = _esAliases[alias]


Only serialize properly formatted params

--- src/scripts/helpers/links.coffee
+++ src/scripts/helpers/links.coffee
@@ -23,7 +23,8 @@

       query.split('?').pop().split('&').forEach (prop) ->
         item = prop.split('=')
-        queryString[decodeURIComponent(item.shift())] = decodeURIComponent(item.shift())
+        if item.length is 2
+          queryString[decodeURIComponent(item.shift())] = decodeURIComponent(item.shift())

       return queryString


safety commit

safety commit

--- router/page/page.controller.coffee
+++ router/page/page.controller.coffee
@@ -1,6 +1,8 @@
 class @PageController extends RouteController
+
   # this controller uses the page layout
   layoutTemplate: "page_layout"
+
   # default values for all routes using this controller and controllers extending this
   defaults:
     # default templates to yield if none is specified
@@ -29,6 +31,7 @@
         to: 'breadcrumbs'
       'default_footer':
         to: 'footer'
+
     # default options and contexts to provide to the page
     options:
       navbar: {}
@@ -58,13 +61,9 @@
     # extend the defaults with the yieldTemplates and assign it to this
     @yieldTemplates = _.extend @defaults.yields, @yieldTemplates

-  data: ->
-    if @ready()
-      # merge the defaults in with the route options
-      _.defaults @route.options, @defaults.options
-      # merge the options into the page data context
-      _.defaults @data, @route.options
+  # merge the defaults in with the route options
+  data: -> return _.defaults @route.options, @defaults.options if @ready()

-  onAfterAction: ->
+  onAfterAction: -> return

   action: -> @render() if @ready()


Social: notification retrieval is added to messagesList

--- client/Main/CommonViews/messagesList.coffee
+++ client/Main/CommonViews/messagesList.coffee
@@ -46,27 +46,9 @@
       callback? err,messages

   fetchNotificationTeasers:(callback)->
-    KD.whoami().fetchActivityTeasers? {
-      targetName: $in: [
-        # 'CActivity'
-        'CReplieeBucketActivity'
-        'CFolloweeBucketActivity'
-        'CLikeeBucketActivity'
-        'CGroupJoineeBucketActivity'
-        'CNewMemberBucketActivity'
-        'CGroupLeaveeBucketActivity'
-      ]
-    }, {
-      limit: 8
-      sort:
-        timestamp: -1
-    }, (err, items)=>
-      if err
-        warn "There was a problem fetching notifications!",err
-      else
-        unglanced = items.filter (item)-> item.getFlagValue('glanced') isnt yes
-        @emit 'NotificationCountDidChange', unglanced.length
-        callback? items
-      @hideLazyLoader()
+    KD.remote.api.SocialNotification.fetch (err, notifications) =>
+      return callback err  if err

-
+      @emit 'NotificationCountDidChange', notifications.unreadCount
+      callback? null, notifications.notifications
+      @hideLazyLoader()


payment: preselect pack when creating a new vm

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -373,7 +373,12 @@
     productForm.on 'PackOfferingRequested', (subscription) ->
       KD.remote.api.JPaymentPack.one tags: 'vm', (err, pack) ->
         return KD.showError err  if err
-        productForm.setContents 'packs', [pack]
+        [subscription] = productForm.currentSubscriptions
+        return KD.showError "subscription not found"  unless subscription
+        subscription.checkUsage pack, (err) ->
+          if err
+          then productForm.showForm "upgrade"
+          else productForm.collectData {pack}

     workflow = new PaymentWorkflow
       productForm: productForm


vm: implement group context awareness to `VirtualizationController#createPaidVM` method

--- client/Main/VirtualizationController.coffee
+++ client/Main/VirtualizationController.coffee
@@ -355,10 +355,7 @@
             modal.destroy()

   createPaidVM: ->
-    @payment.fetchSubscriptionsWithPlans tags: ["vm"], (err, subscriptions) =>
-      return  if KD.showError err
-      [subscription] = subscriptions
-
+    callback = (subscription) =>
       KD.remote.api.JPaymentPack.one tags: "vm", (err, pack) =>
         return  if KD.showError err
         @provisionVm {subscription, productData: {pack}}, (err, nonce) =>
@@ -373,6 +370,16 @@
             overlay  : yes

           upgradeForm.on "Cancel", modal.bound "destroy"
+
+    if KD.getGroup().slug is "koding"
+      @payment.fetchSubscriptionsWithPlans tags: [tag], (err, subscriptions) ->
+        return  if KD.showError err
+        [subscription] = subscriptions
+        callback subscription
+    else
+      @payment.fetchGroupSubscription (err, subscription) ->
+        return  if KD.showError err
+        callback subscription

   provisionVm: ({ subscription, paymentMethod, productData }, callback) ->
     { JVM } = KD.remote.api


activity: pass payload to `SocialApiController::message.edit` function

--- client/Social/Activity/AppController.coffee
+++ client/Social/Activity/AppController.coffee
@@ -32,10 +32,10 @@

   edit: (options = {}, callback = noop) ->

-    {id, body} = options
+    {id, body, payload} = options
     {socialapi} = KD.singletons

-    socialapi.message.edit {id, body}, callback
+    socialapi.message.edit {id, body, payload}, callback

   reply: ({activity, body}, callback = noop) ->


Client: Activity: Feed: Added recent, popular and notificationSettings routes as a child route of '/Channels' route

--- client/activity/lib/reactivityroutes.coffee
+++ client/activity/lib/reactivityroutes.coffee
@@ -1,16 +1,22 @@
-React                     = require 'kd-react'
-ActivityAppComponent      = require 'activity/components/appcomponent'
-SingleChannelRoute        = require 'activity/routes/singlepublicchannel'
-SinglePrivateMessageRoute = require 'activity/routes/singleprivatechannel'
-NewPrivateChannelRoute    = require 'activity/routes/newprivatechannel'
-NewPublicChannelRoute     = require 'activity/routes/newpublicchannel'
-AllPublicChannelsRoute    = require 'activity/routes/allpublicchannels'
-AllPrivateChannelsRoute   = require 'activity/routes/allprivatechannels'
+React                                  = require 'kd-react'
+ActivityAppComponent                   = require 'activity/components/appcomponent'
+SingleChannelRoute                     = require 'activity/routes/singlepublicchannel'
+SinglePrivateMessageRoute              = require 'activity/routes/singleprivatechannel'
+NewPrivateChannelRoute                 = require 'activity/routes/newprivatechannel'
+NewPublicChannelRoute                  = require 'activity/routes/newpublicchannel'
+AllPublicChannelsRoute                 = require 'activity/routes/allpublicchannels'
+AllPrivateChannelsRoute                = require 'activity/routes/allprivatechannels'
+SinglePublicChannelRecentMessages      = require 'activity/routes/singlepublicchannelrecentmessages'
+SinglePublicChannelPopularMessages     = require 'activity/routes/singlepublicchannelpopularmessages'
+PublicChannelNotificationSettingsRoute = require 'activity/routes/publicchannelnotificationsettings'

 module.exports = [
   path: '/Channels'
   component: ActivityAppComponent
   childRoutes: [
+    new PublicChannelNotificationSettingsRoute
+    new SinglePublicChannelPopularMessages
+    new SinglePublicChannelRecentMessages
     new SingleChannelRoute
   ]
   onLeave: -> cleanSelectedThreads()


Teambuilder: Enforce Teambuilder models

Teambuilder: Enforce Teambuilder models

--- client/app/js/views/teambuilder_view.coffee
+++ client/app/js/views/teambuilder_view.coffee
@@ -56,6 +56,7 @@
     teamJSON = window.localStorage.getItem("team")
     if teamJSON
       teamJSON = JSON.parse(teamJSON)
+      pokemonJSON.teambuilder = true  for pokemonJSON in teamJSON
       @collection.reset(teamJSON)
     else
       @addNewPokemon()  for i in [1..6]


(Unfinished) project truncation

(Unfinished) project truncation

--- salvus/page/top_navbar.coffee
+++ salvus/page/top_navbar.coffee
@@ -24,7 +24,7 @@
         @buttons          = @navbar.find("ul.nav.pull-left.buttons")   # the list of buttons on the left
         @projects         = @navbar.find("ul.nav.pull-left.projects")
         @buttons_right    = @navbar.find("ul.nav.pull-right")  # the list of buttons on the right
-        @button_template  = $(".top_navbar-button-template")
+        @button_template  = $("#top_navbar-button-template")
         @divider_template = $("#top_navbar-divider-template")

     add_page: (opts) ->
@@ -283,5 +283,4 @@

 $(".salvus-connection-status-protocol").tooltip(delay:{ show: 500, hide: 100 })
-$(".salvus-connection-status-ping-time").tooltip(delay:{ show: 500, hide: 100 })
-
+$(".salvus-connection-status-ping-time").tooltip(delay:{ show: 500, hide: 100 })


quantity only required if unlimited quantity isn't checked

--- app/assets/javascripts/admin/product_form_validation_rules.js.coffee
+++ app/assets/javascripts/admin/product_form_validation_rules.js.coffee
@@ -9,8 +9,10 @@
                 required: true
                 number: true
             'product[quantity]':
-                required: true
                 digits: true
+                required: (element) ->
+                    !($ '#product_unlimited_quantity').prop('checked')
+
             # flatrate_shipping number true if !empty
             # quantity not required if unlimited is on
     $.extend(product_form_options, form_options)


Clear new message content on cancel.

Clear new message content on cancel.

--- app/assets/javascripts/controllers/message_task_controller.js.coffee
+++ app/assets/javascripts/controllers/message_task_controller.js.coffee
@@ -2,7 +2,9 @@
   newCommentBody: ""

   actions:
-    clearMessageContent: -> null
+    clearMessageContent: ->
+      @set('newCommentBody', "")
+
     postComment: ->
       userId = Tahi.currentUser.id.toString()
       commenter = @store.all('user').findBy('id', userId)


put a commercial on the landing page

put a commercial on the landing page

--- src/smc-webapp/landing_page.cjsx
+++ src/smc-webapp/landing_page.cjsx
@@ -314,26 +314,19 @@
         heading : 'Built-in LaTeX Editor'
         text : 'Write beautiful documents using LaTeX.'

+SMC_Commercial = () ->
+
+
+
 LandingPageContent = rclass
     displayName : 'LandingPageContent'

     mixins: [ImmutablePureRenderMixin]

     render : ->
-

+        <Well style={color:'#666'}>
             {<ContentItem icon={v.icon} heading={v.heading} key={k} text={v.text} /> for k, v of LANDING_PAGE_CONTENT}
-

-    ###
-    componentDidMount : ->
-        @update_mathjax()
-
-    componentDidUpdate : ->
-        @update_mathjax()
-
-    update_mathjax: ->
-        el = ReactDOM.findDOMNode(@)
-        MathJax.Hub.Queue(["Typeset",MathJax.Hub,el]);
-    ###
+        </Well>

 SagePreview = rclass
     displayName : "SagePreview"
@@ -492,8 +485,8 @@
                     </Col>
                 </Row>
                 <Row>
-                    <Col sm=7 className="hidden-xs">
-                        <LandingPageContent />
+                    <Col sm=7 className="hidden-xs" style=marginTop:'60px'>
+                        <SMC_Commercial />
                     </Col>
                     <Col sm=5>
                         <SignUp actions={@props.actions}
@@ -504,7 +497,11 @@
                                  has_account={@props.has_account} />
                     </Col>
                 </Row>
-

+                <Row>
+                    <Col sm=12 className='hidden-xs'>
+                        <LandingPageContent />
+                    </Col>
+                </Row>
                 <SagePreview />
                 <LandingPageFooter />


Fix missing export

Fix missing export

--- src/smc-webapp/r_misc/index.cjsx
+++ src/smc-webapp/r_misc/index.cjsx
@@ -46,6 +46,8 @@
 exports.Saving = Saving
 {Spinner} = require('./spinner')
 exports.Spinner = Spinner
+{ErrorDisplay} = require('./error-display')
+exports.ErrorDisplay = ErrorDisplay
 {SkinnyError} = require('./skinny-error')
 exports.SkinnyError = SkinnyError
 {SelectorInput} = require('./selector-input')


Dont test uncommited code

Dont test uncommited code

--- spec/highlight-selected-spec.coffee
+++ spec/highlight-selected-spec.coffee
@@ -216,19 +216,21 @@
             .querySelectorAll('.highlight-selected.light-theme .region')
             ).toHaveLength(4)

-    if hasMinimap
-      describe "minimap highlight selected still works", ->
-        beforeEach ->
-          editor = atom.workspace.getActiveTextEditor()
-          minimap = minimapModule.minimapForEditor(editor)
-
-          spyOn(minimap, 'decorateMarker').andCallThrough()
-          range = new Range(new Point(8, 2), new Point(8, 8))
-          editor.setSelectedBufferRange(range)
-          advanceClock(20000)
-
-        it 'adds a decoration for the selection in the minimap', ->
-          expect(minimap.decorateMarker).toHaveBeenCalled()
+    # Disabled until minimap-highlight-selected branch
+    # feature-highlight-selected-api is merged in
+    # if hasMinimap
+    #   describe "minimap highlight selected still works", ->
+    #     beforeEach ->
+    #       editor = atom.workspace.getActiveTextEditor()
+    #       minimap = minimapModule.minimapForEditor(editor)
+    #
+    #       spyOn(minimap, 'decorateMarker').andCallThrough()
+    #       range = new Range(new Point(8, 2), new Point(8, 8))
+    #       editor.setSelectedBufferRange(range)
+    #       advanceClock(20000)
+    #
+    #     it 'adds a decoration for the selection in the minimap', ->
+    #       expect(minimap.decorateMarker).toHaveBeenCalled()

   describe "when opening a php file", ->
     beforeEach ->


refactor(test): Remove unnecessary space

--- src/spec/csv-mapping.spec.coffee
+++ src/spec/csv-mapping.spec.coffee
@@ -8,7 +8,7 @@
 describe 'CsvMapping', ->
   it '#_mapOrder should remove redundant info', (done) ->
     mapping = new Mapping
-    mapping ._analyseTemplate template
+    mapping._analyseTemplate template
     .then ([header, mappings]) ->
       rows = mapping._mapOrder(testOrders[0], mappings)
       expect(rows.length).toBe 3
@@ -31,7 +31,7 @@

   it '#_mapOrder should fill all rows', (done) ->
     mapping = new Mapping fillAllRows: true
-    mapping ._analyseTemplate template
+    mapping._analyseTemplate template
     .then ([header, mappings]) ->
       rows = mapping._mapOrder(testOrders[0], mappings)
       expect(rows.length).toBe 3
@@ -54,7 +54,7 @@

   it '#_mapOrder should return order even when lineItems are missing', (done) ->
     mapping = new Mapping fillAllRows: true
-    mapping ._analyseTemplate template
+    mapping._analyseTemplate template
     .then ([header, mappings]) ->
       order = _.deepClone(testOrders[0])
       order.lineItems = []


video: view add an abstract handler to be extended

Subclasses of this instance needs to implement this method with the same signature. This feels somehow dangerous to me, but for now, it looks it solves the problem.

--- client/app/lib/videocollaboration/views/baseview.coffee
+++ client/app/lib/videocollaboration/views/baseview.coffee
@@ -45,6 +45,13 @@

   getContainer: -> @container

+  ###*
+   * This method needs to be overriden by subclasses.
+   *
+   * @abstract
+  ###
+  handleStateChanged: (type) -> (active) -> throw new Error 'needs to be implemented'
+

   show: ->


:bug: Fix test

:bug: Fix test

--- spec/paths-watcher-task-spec.coffee
+++ spec/paths-watcher-task-spec.coffee
@@ -18,5 +18,5 @@

   it 'emits only text files', ->
     expect(addedItemSpy).toHaveBeenCalled()
-    expect(addedItemSpy.calls[0].args[0].title).toEqual('empty.md')
-    expect(addedItemSpy.calls[1].args[0].title).toEqual('empty.txt')
+    expect(addedItemSpy.calls[0].args[0].path).toMatch('empty.md')
+    expect(addedItemSpy.calls[1].args[0].path).toMatch('empty.txt')


autocomplete controller tab keydown fixed, + minor copy fix

--- components/autocomplete/KDAutoCompleteController.coffee
+++ components/autocomplete/KDAutoCompleteController.coffee
@@ -54,7 +54,10 @@
   keyDownOnInputView:(autoCompleteView,event)=>
     switch event.which
       when 13, 9 #enter, tab
-        @submitAutoComplete autoCompleteView.getValue()
+        unless autoCompleteView.getValue() is ""
+          @submitAutoComplete autoCompleteView.getValue()
+        else
+          return yes
       when 27 #escape
         @hideDropdown()
       when 38 #uparrow


transcirbe tool: implement conditional return-to-mark callback with enter keybinding

--- app/assets/javascripts/components/transcribe/tools/text-tool/index.cjsx
+++ app/assets/javascripts/components/transcribe/tools/text-tool/index.cjsx
@@ -125,7 +125,10 @@

   handleKeyPress: (e) ->
     if [13].indexOf(e.keyCode) >= 0 # ENTER
-      @commitAnnotation()
+      if window.location.hash is '#/transcribe' || @props.task.next_task? # regular transcribe, i.e. no mark transition
+        @commitAnnotation()
+      else
+        @returnToMarking()
       e.preventDefault()

   handleBadMark: ()->


also change url on clicking the tab

also change url on clicking the tab

--- core/app/backbone/controllers/profile_controller.coffee
+++ core/app/backbone/controllers/profile_controller.coffee
@@ -32,16 +32,18 @@
         @main.tabsRegion.show(@getUserTabs(user, options.active_tab))
         @main.contentRegion.show(options.mainRegion(user))

-  switchToPage: (username, user, options)->
+  switchToPage: (username, user, path, options)->
     @main.setTitle options.title
     @main.tabsRegion.currentView.activate(options.active_tab)
     @main.contentRegion.show(options.mainRegion(user))
+    Backbone.history.navigate path, false

   getUserTabs: (user, active_tab) ->
     usertabs = new UserTabsView(model: user, active_tab: active_tab)
     username = user.get('username')
-    usertabs.on 'showProfile',       => @switchToPage(username, user, @profile_options(username))
-    usertabs.on 'showNotifications', => @switchToPage(username, user, @notification_options(username))
+    userjson = user.toJSON()
+    usertabs.on 'showProfile',       => @switchToPage(username, user, userjson.link , @profile_options(username))
+    usertabs.on 'showNotifications', => @switchToPage(username, user,userjson.notifications_settings_path, @notification_options(username))

   getUser: (username, options) ->
     user = new User(username: username)


Workspace: Resizing workspace when window resized.

--- client/app/CommonViews/workspace/workspace.coffee
+++ client/app/CommonViews/workspace/workspace.coffee
@@ -13,9 +13,8 @@
     @lastCreatedPanelIndex = 0
     @currentPanelIndex     = 0

-    @on "PanelCreated", (panel) =>
-      {header, container} = panel
-      container.setHeight panel.getHeight() - header.getHeight()  if header
+    @on "PanelCreated", =>
+      @doInternalResize()
       KD.getSingleton("windowController").notifyWindowResizeListeners()

     @init()
@@ -56,7 +55,13 @@

   _windowDidResize: ->
     return unless @activePanel
+    @doInternalResize()
     pane.emit "PaneResized" for pane in @activePanel.panes
+
+  doInternalResize: ->
+    panel               = @getActivePanel()
+    {header, container} = panel
+    container.setHeight panel.getHeight() - header.getHeight()  if header

   viewAppended: ->
     super


ComputeController: reinitGroupStack: Reinit's given stack or groups default stack

If stack given, it asks for re-init and first deletes and then calls createDefaultStack again.
If not given it tries to find default one and does the same thing, if it can't find the default one, asks to user what to do next.

We will need this for putting rebuild button into stack template editor #106403276

--- client/app/lib/providers/computecontroller.coffee
+++ client/app/lib/providers/computecontroller.coffee
@@ -1029,3 +1029,49 @@
         return stack

     return null
+
+
+  ###*
+   * Reinit's given stack or groups default stack
+   * If stack given, it asks for re-init and first deletes and then calls
+   * createDefaultStack again.
+   * If not given it tries to find default one and does the same thing, if it
+   * can't find the default one, asks to user what to do next.
+  ###
+  reinitGroupStack: (stack) ->
+
+    stack ?= @getGroupStack()
+
+    if not stack
+
+      if @stacks?.length
+        new kd.NotificationView
+          title   : "Couldn't find default stack"
+          content : 'Please re-init manually'
+
+        EnvironmentsModal = require 'app/environment/environmentsmodal'
+        new EnvironmentsModal
+
+      else
+        @createDefaultStack()
+
+      return
+
+    @ui.askFor 'reinitStack', {}, =>
+
+      stack.delete (err) =>
+        return showError err  if err
+
+        @reset()
+
+          .once 'RenderStacks', (stacks) ->
+
+            new kd.NotificationView
+              title : 'Stack reinitialized'
+
+            # We need to quit here to be able to re-load
+            # IDE with new machine stack, there might be better solution ~ GG
+            frontApp = kd.singletons.appManager.getFrontApp()
+            frontApp.quit()  if frontApp?.options.name is 'IDE'
+
+          .createDefaultStack()


handle bugs around empty member lists

handle bugs around empty member lists

--- models/models.coffee
+++ models/models.coffee
@@ -18,6 +18,7 @@

   beforeSave: (fn) ->
     threads: @members.length
+    return fn() unless threads
     for member in @members
       member.save (error, res) ->
         fn() if --threads is 0
@@ -29,9 +30,9 @@
       fn()

   setMembers: (emails, fn) ->
-    return fn() unless emails?.length
     @members: []
     threads: emails.length
+    return setTimeout fn, 0 unless threads
     for email in emails
       Person.firstOrCreate { email: email }, (error, member) =>
         @members.push member


Client: Activity: Updated renderChildren to list first 10 item or not by componentProp

--- client/app/lib/components/sidebarlist/index.coffee
+++ client/app/lib/components/sidebarlist/index.coffee
@@ -14,13 +14,15 @@

   renderChildren: ->

-    { itemComponent: Component, selectedId, threads, previewCount } = @props
+    { itemComponent: Component, selectedId, threads, previewCount, componentProp } = @props

-    threads = threads.slice 0, previewCount
+    if componentProp is 'SidebarMessagesListItem'
+      threads = threads.slice 0, previewCount

     threads.map (thread) ->
       id = thread.getIn ['channel', 'id']
       <Component key={id} active={id is selectedId} thread={thread} />
+

   render: ->
     <div className={classnames 'SidebarList', @props.className}>


Sort pins in GOST style connector symbol

--- src/symbol/gost/connector.coffee
+++ src/symbol/gost/connector.coffee
@@ -25,6 +25,7 @@
     numbers = numbers.concat symbol.bottom
   if numbers.length is 0 then numbers = Object.keys pins
   numbers = numbers.filter((v) -> v isnt '--')
+  numbers.sort((a, b) -> a - b)

   # Attributes
   symbol


Better defaults

Better defaults

--- src/text-editor-presenter.coffee
+++ src/text-editor-presenter.coffee
@@ -21,8 +21,8 @@
     @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
     @measuredVerticalScrollbarWidth = verticalScrollbarWidth
     @gutterWidth ?= 0
-    @tileOverdrawMargin ?= 0
-    @tileCount ?= 4
+    @tileOverdrawMargin ?= 1
+    @tileCount ?= 3
     @linesPresentersByTileIndex = {}

     @disposables = new CompositeDisposable


added hidden field

added hidden field

--- app/assets/javascripts/type_station/editables/entity_editor.js.coffee
+++ app/assets/javascripts/type_station/editables/entity_editor.js.coffee
@@ -38,23 +38,26 @@
   modelValues = if editor.data.action == 'edit' then editor.data.values else defaultValues

   for field in fields
-    label = "#{field.label}"
-    input = switch field.type
-      when "text" then "<input type='text' name='#{field.name}' id='#{field.name}' value='#{if modelValues[field.name] then modelValues[field.name] else ''}' />"
-      when "textarea" then "<textarea name='#{field.name}' id='#{field.name}' rows='5'>#{if modelValues[field.name] then modelValues[field.name] else ''}</textarea>"
-      when "select"
-        select = "<select name='#{field.name}' id='#{field.name}'>"
-        for o in field.options
-          select += "<option value='#{o[1]}' #{if modelValues[field.name] == o[1] then 'selected'}>#{o[0]}</option>"
-        select += "</select>"
-        select
-      when "multiple_select"
-        select = "<select name='#{field.name}' id='#{field.name}' class='multiple_select' multiple>"
-        for o in field.options
-          select += "<option value='#{o[1]}' #{if modelValues[field.name] && modelValues[field.name].indexOf(o[1]) > -1 then 'selected' else ''}>#{o[0]}</option>"
-        select += "</select>"
-        select
-    inputs += "
#{label}
#{input}

"
+    if field.label
+      label = "#{field.label}"
+      input = switch field.type
+        when "text" then "<input type='text' name='#{field.name}' id='#{field.name}' value='#{if modelValues[field.name] then modelValues[field.name] else ''}' />"
+        when "textarea" then "<textarea name='#{field.name}' id='#{field.name}' rows='5'>#{if modelValues[field.name] then modelValues[field.name] else ''}</textarea>"
+        when "select"
+          select = "<select name='#{field.name}' id='#{field.name}'>"
+          for o in field.options
+            select += "<option value='#{o[1]}' #{if modelValues[field.name] == o[1] then 'selected'}>#{o[0]}</option>"
+          select += "</select>"
+          select
+        when "multiple_select"
+          select = "<select name='#{field.name}' id='#{field.name}' class='multiple_select' multiple>"
+          for o in field.options
+            select += "<option value='#{o[1]}' #{if modelValues[field.name] && modelValues[field.name].indexOf(o[1]) > -1 then 'selected' else ''}>#{o[0]}</option>"
+          select += "</select>"
+          select
+      inputs += "
#{label}
#{input}

"
+    else
+      inputs += "<input type='hidden' name='#{field.name}' id='#{field.name}' value='#{if modelValues[field.name] then modelValues[field.name] else ''}' />"
   inputs

 handleDeleteEntity = (editor) ->


previous marks now showing

previous marks now showing

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -263,6 +263,7 @@
                 scale = @getScale()

                 console.log 'REFS: ', @refs
+                ToolComponent = markingTools[toolName]
                 console.log 'toolComponent: ', ToolComponent, toolName

                 <ToolComponent


add on child_subject_id to selected mark (for transcribe button)

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -264,8 +264,14 @@
       })
       .done (response) =>
         console.log "Success", response #, #response #, response._id.$oid
-        console.log 'RECEIVED SECONDARY SUBJECT ID: ', response.child_subject_id
+        console.log 'RECEIVED SECONDARY SUBJECT ID: ', response
         console.log 'SELECTED MARK: ', @state.selectedMark
+
+        selectedMark = @state.selectedMark
+        selectedMark.child_subject_id = response.classification.child_subject_id
+        @setState selectedMark: selectedMark, => console.log 'UPDATED MARK WITH CHILD SUBJECT ID: ', @state.selectedMark
+
+        # console.log 'TEST ANNOTATION: ', @props.annotation.value.child_subject_id = response.child_subject.id
         # @setTranscribeSubject(key, response._id.$oid)
         # @enableMarkButton(key)
         return


add padding to scroll position

add padding to scroll position

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -235,8 +235,8 @@
     scale = @getScale()

     if @props.workflow.name is 'transcribe'
-      yPos = @props.subject.data.y * @getScale().vertical - window.innerHeight/2
-      $('html, body').animate({scrollTop: yPos}, 300);
+      yPos = (@props.subject.data.y - @props.subject.data.height?) * @getScale().vertical - 100
+      $('html, body').animate({scrollTop: yPos}, 300);

     actionButton =
       if @state.loading


TinyMCE RTL in course bulk email

TinyMCE RTL in course bulk email

--- common/lib/xmodule/xmodule/js/src/html/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/html/edit.coffee
@@ -66,6 +66,8 @@
         schema: "html5",
         # Necessary to preserve relative URLs to our images.
         convert_urls : false,
+        # Sniff UI direction from `.wrapper-view` in studio or `.window-wrap` in LMS
+        directionality: $(".wrapper-view, .window-wrap").prop('dir'),
         content_css : tiny_mce_css_links.join(", "),
         formats : {
           # tinyMCE does block level for code by default
@@ -91,7 +93,7 @@
         height: '400px',
         menubar: false,
         statusbar: false,
-
+
         # Necessary to avoid stripping of style tags.
         valid_children : "+body[style]",

@@ -99,7 +101,7 @@
         valid_elements: "*[*]",
         extended_valid_elements: "*[*]",
         invalid_elements: "",
-
+
         setup: @setupTinyMCE,
         # Cannot get access to tinyMCE Editor instance (for focusing) until after it is rendered.
         # The tinyMCE callback passes in the editor as a parameter.


form: fix get calls

form: fix get calls

--- app/assets/javascripts/store/gestpay/iframe.js.coffee
+++ app/assets/javascripts/store/gestpay/iframe.js.coffee
@@ -1,13 +1,13 @@
 class form
   cardNumber: ->
-    get 'card_number'
+    @get 'card_number'

   cardExpiration: ->
-    month: get 'card_month'
-    year:  get 'card_year'
+    month: @get 'card_month'
+    year:  @get 'card_year'

   cardSecureCode: ->
-    get 'card_code'
+    @get 'card_code'

   get: (id) ->
     $("##{id}").val()


Omit theme from Indicator update as it is unnecessary, and would need some client side changes to make it work if it wasn't

--- server/routes/api/indicator.coffee
+++ server/routes/api/indicator.coffee
@@ -29,7 +29,7 @@
   )

 exports.update = (req, res) ->
-  params = _.omit(req.body, '_id')
+  params = _.omit(req.body, ['_id', 'theme'])

   Indicator.update(
     {_id: req.params.indicator},


Fixing/improving minor portuguese errors

--- app/assets/javascripts/uploadcare/locale/pt.js.coffee
+++ app/assets/javascripts/uploadcare/locale/pt.js.coffee
@@ -20,7 +20,7 @@
       image: 'Apenas imagens são permitidas'
       createGroup: 'Não foi possível criar o grupo de arquivos'
       deleted: 'O arquivo foi excluído'
-    draghere: 'Arraste um arquivo para cá'
+    draghere: 'Arraste um arquivo aqui'
     file:
       one: '%1 arquivo'
       other: '%1 arquivos'
@@ -32,7 +32,7 @@
           one: 'Escolha um arquivo'
           other: 'Escolha arquivos'
         images:
-          one: 'Escolha um imagem'
+          one: 'Escolha uma imagem'
           other: 'Escolha imagens'
     dialog:
       done: 'OK'
@@ -43,7 +43,7 @@
           file: 'Computador'
           url: 'Link da web'
         file:
-          drag: 'Arraste um arquivo para cá'
+          drag: 'Arraste um arquivo aqui'
           nodrop: 'Faça upload de arquivos do seu computador'
           or: 'ou'
           button: 'Escolha um arquivo do computador'
@@ -73,8 +73,8 @@
           back: 'Voltar'
           done: 'Adicionar'
           unknown:
-            title: 'Fazendo upload... Aguarde o preview.'
-            done: 'Pular preview e aceitar'
+            title: 'Fazendo upload... Aguarde a visualização.'
+            done: 'Pular visualização e aceitar'
           regular:
             title: 'Adicionar esse arquivo?'
             line1: 'Você está prestes a adicionar o arquivo acima.'


Fixed keyModel.readMacro() for demo purposes

--- app/coffee/modules/main/dashboard/views/layout.coffee
+++ app/coffee/modules/main/dashboard/views/layout.coffee
@@ -65,22 +65,25 @@
     @$el.addClass('active')

     # Reads macro from device
-    keyModel.readMacro().then =>
+    # NOTE - this happens asynchronously
+    # We _should_ not render the EditorWrapper view until if a device is not present,
+    # but we will in the meantime for demonstration purposes
+    keyModel.readMacro()

-      # Instantiates new EditorWrapper view
-      editorWrapper = new EditorWrapper({ model: keyModel, editor: editor })
+    # Instantiates new EditorWrapper view
+    editorWrapper = new EditorWrapper({ model: keyModel, editor: editor })

-      # Handles 'cancel' event
-      editorWrapper.on 'cancel', =>
-        @$el.removeClass('active')
+    # Handles 'cancel' event
+    editorWrapper.on 'cancel', =>
+      @$el.removeClass('active')

-      # Handles 'save' event
-      editorWrapper.on 'save', =>
-        # TODO - hit the KeyModel / DeviceModel to do the rest from here
-        @$el.removeClass('active')
+    # Handles 'save' event
+    editorWrapper.on 'save', =>
+      # TODO - hit the KeyModel / DeviceModel to do the rest from here
+      @$el.removeClass('active')

-      # Shows the EditorWrapper view in @editorRegion
-      @editorRegion.show(editorWrapper)
+    # Shows the EditorWrapper view in @editorRegion
+    @editorRegion.show(editorWrapper)

 # # # # #


Add setCurrentTab JS helper

Add setCurrentTab JS helper

--- app/assets/javascripts/easy_app_bootstrap.js.coffee
+++ app/assets/javascripts/easy_app_bootstrap.js.coffee
@@ -72,3 +72,18 @@

 root.setAlertDismiss = ->
   $('.alert:not(.dont-dismiss)').delay(3000).slideUp(200, -> $(this).alert('close'))
+
+
+root.setCurrentTab = ->
+  # show active tab on reload
+  if (location.hash != '')
+    current_tab = $('a[data-target="' + location.hash + '"]')
+    $.ajax({url: current_tab.attr('href'), dataType: 'script'}) if (current_tab.data('remote') == true)
+    current_tab.tab('show')
+
+  # remember the hash in the URL without jumping
+  $('a[data-toggle="tab"]').on 'shown.bs.tab', (e) ->
+    if(history.pushState)
+      history.pushState(null, null, '#' + $(e.target).data('target').substr(1))
+    else
+      location.hash = '#' + $(e.target).data('target').substr(1)


Add value on reference touch call

Add value on reference touch call

--- app/assets/javascripts/mixingpanel/source.js.coffee
+++ app/assets/javascripts/mixingpanel/source.js.coffee
@@ -60,7 +60,7 @@

   append: ->
     value = @getValue()
-    @writeReferenceTouch()
+    @writeReferenceTouch(value)
     if @registerSouce()
       @writeFirstTouch(value)
       @writeLastTouch(value)


OX-362 Simplify flycam.setSpaceDirection()

--- app/assets/javascripts/oxalis/model/flycam2d.coffee
+++ app/assets/javascripts/oxalis/model/flycam2d.coffee
@@ -119,8 +119,7 @@
     if @direction[ind[1]] <= 0 then @spaceDirection[ind[1]] = -1 else @spaceDirection[ind[1]] = 1

   setSpaceDirection : (direction) ->
-    indices = Dimensions.getIndices @activePlane
-    for index in indices
+    for index in [0..2]
       if direction[index] <= 0 then @spaceDirection[index] = -1 else @spaceDirection[index] = 1

   getSpaceDirection : ->


Don't show hidden iframe

Don't show hidden iframe

Well, it's supposed to be hidden :-)

--- core/app/backbone/modules/facebook_renewal.coffee
+++ core/app/backbone/modules/facebook_renewal.coffee
@@ -3,7 +3,7 @@
   class IframeView extends Backbone.Marionette.ItemView
     template:
       text: """
-
+
       """

   FacebookRenewal.addInitializer ->


Use empty className for ExtendedFact

Use empty className for ExtendedFact

By default inherits .fact-block from the FactView

Signed-off-by: tomdev <96835dd8bfa718bd6447ccc87af89ae1675daeca@north26.nl>

--- core/app/backbone/views/extended_fact_view.coffee
+++ core/app/backbone/views/extended_fact_view.coffee
@@ -2,6 +2,7 @@

 class window.ExtendedFactView extends FactView
   tagName: "section"
+  className: ""

   template: "facts/_extended_fact"


added acceptance test for redis compression

--- services/document-updater/test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee
+++ services/document-updater/test/acceptance/coffee/ApplyingUpdatesToADocTests.coffee
@@ -238,3 +238,153 @@
 				doc.lines.should.deep.equal @result
 				done()

+
+
+
+describe "Applying updates to a large doc (uses compression)", ->
+	MIN_SIZE = 500000
+	before ->
+		@lines = ["one", "two", "three"]
+		while @lines.join('').length < MIN_SIZE
+			@lines.push "this is a repeated long line which will create a large document which must be compressed #{@lines.length}"
+		@version = 42
+		@update =
+			doc: @doc_id
+			op: [{
+				i: "one and a half\n"
+				p: 4
+			}]
+			v: @version
+		@result = @lines.slice()
+		@result.splice 1, 0, "one and a half"
+
+	describe "when the document is not loaded", ->
+		before (done) ->
+			[@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]
+			sinon.spy MockWebApi, "getDocument"
+
+			MockWebApi.insertDoc @project_id, @doc_id, lines: @lines
+			db.docOps.insert {
+				doc_id: ObjectId(@doc_id)
+				version: @version
+			}, (error) =>
+				throw error if error?
+				DocUpdaterClient.sendUpdate @project_id, @doc_id, @update, (error) ->
+					throw error if error?
+					setTimeout done, 200
+
+		after ->
+			MockWebApi.getDocument.restore()
+
+		it "should load the document from the web API", ->
+			MockWebApi.getDocument
+				.calledWith(@project_id, @doc_id)
+				.should.equal true
+
+		it "should update the doc", (done) ->
+			DocUpdaterClient.getDoc @project_id, @doc_id, (error, res, doc) =>
+				doc.lines.should.deep.equal @result
+				done()
+
+		it "should push the applied updates to the track changes api", (done) ->
+			rclient.lrange "UncompressedHistoryOps:#{@doc_id}", 0, -1, (error, updates) =>
+				throw error if error?
+				JSON.parse(updates[0]).op.should.deep.equal @update.op
+				rclient.sismember "DocsWithHistoryOps:#{@project_id}", @doc_id, (error, result) =>
+					throw error if error?
+					result.should.equal 1
+					done()
+
+
+	describe "when the document is loaded", ->
+		before (done) ->
+			[@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]
+
+			MockWebApi.insertDoc @project_id, @doc_id, lines: @lines
+			db.docOps.insert doc_id: ObjectId(@doc_id), version: @version, (error) =>
+				throw error if error?
+				DocUpdaterClient.preloadDoc @project_id, @doc_id, (error) =>
+					throw error if error?
+					sinon.spy MockWebApi, "getDocument"
+					DocUpdaterClient.sendUpdate @project_id, @doc_id, @update, (error) ->
+						throw error if error?
+						setTimeout done, 200
+
+		after ->
+			MockWebApi.getDocument.restore()
+
+		it "should not need to call the web api", ->
+			MockWebApi.getDocument.called.should.equal false
+
+		it "should update the doc", (done) ->
+			DocUpdaterClient.getDoc @project_id, @doc_id, (error, res, doc) =>
+				doc.lines.should.deep.equal @result
+				done()
+
+		it "should push the applied updates to the track changes api", (done) ->
+			rclient.lrange "UncompressedHistoryOps:#{@doc_id}", 0, -1, (error, updates) =>
+				JSON.parse(updates[0]).op.should.deep.equal @update.op
+				rclient.sismember "DocsWithHistoryOps:#{@project_id}", @doc_id, (error, result) =>
+					result.should.equal 1
+					done()
+
+	describe "with a broken update", ->
+		before (done) ->
+			[@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]
+			MockWebApi.insertDoc @project_id, @doc_id, lines: @lines
+			db.docOps.insert doc_id: ObjectId(@doc_id), version: @version, (error) =>
+				throw error if error?
+				DocUpdaterClient.sendUpdate @project_id, @doc_id, @undefined, (error) ->
+					throw error if error?
+					setTimeout done, 200
+
+		it "should not update the doc", (done) ->
+			DocUpdaterClient.getDoc @project_id, @doc_id, (error, res, doc) =>
+				doc.lines.should.deep.equal @lines
+				done()
+
+	describe "with enough updates to flush to the track changes api", ->
+		before (done) ->
+			[@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]
+			updates = []
+			for v in [0..99] # Should flush after 50 ops
+				updates.push
+					doc_id: @doc_id,
+					op: [i: v.toString(), p: 0]
+					v: v
+
+			sinon.spy MockTrackChangesApi, "flushDoc"
+
+			MockWebApi.insertDoc @project_id, @doc_id, lines: @lines
+			db.docOps.insert doc_id: ObjectId(@doc_id), version: 0, (error) =>
+				throw error if error?
+				DocUpdaterClient.sendUpdates @project_id, @doc_id, updates, (error) =>
+					throw error if error?
+					setTimeout done, 200
+
+		after ->
+			MockTrackChangesApi.flushDoc.restore()
+
+		it "should flush the doc twice", ->
+			MockTrackChangesApi.flushDoc.calledTwice.should.equal true
+
+	describe "when there is no version in Mongo", ->
+		before (done) ->
+			[@project_id, @doc_id] = [DocUpdaterClient.randomId(), DocUpdaterClient.randomId()]
+			MockWebApi.insertDoc @project_id, @doc_id, {
+				lines: @lines
+			}
+
+			update =
+				doc: @doc_id
+				op: @update.op
+				v: 0
+			DocUpdaterClient.sendUpdate @project_id, @doc_id, update, (error) ->
+				throw error if error?
+				setTimeout done, 200
+
+		it "should update the doc (using version = 0)", (done) ->
+			DocUpdaterClient.getDoc @project_id, @doc_id, (error, res, doc) =>
+				doc.lines.should.deep.equal @result
+				done()
+


Fixed typo

Fixed typo

--- core/app/assets/javascripts/frontend/topbar.coffee
+++ core/app/assets/javascripts/frontend/topbar.coffee
@@ -1,4 +1,4 @@
 $('.bookmarklet').tooltip { placement: "left", offset: 5 }

 $('#factlink_search').bind 'focus', ->
-  mp_track "Search: Top bar search clicked"
+  mp_track "Search: Top bar search focussed"


ApplicationTabView: Increase margin value for use more free space on tab handler

--- client/app/lib/commonviews/applicationview/applicationtabview.coffee
+++ client/app/lib/commonviews/applicationview/applicationtabview.coffee
@@ -7,7 +7,7 @@
   constructor: (options = {}, data) ->

     options.resizeTabHandles            ?= yes
-    options.lastTabHandleMargin         ?= 80
+    options.lastTabHandleMargin         ?= 90
     options.closeAppWhenAllTabsClosed   ?= yes
     options.enableMoveTabHandle         ?= no
     options.detachPanes                 ?= no


sidebar: markup cleanup

sidebar: markup cleanup

--- client/app/lib/components/sidebarstacksection/sidebarnostacks.coffee
+++ client/app/lib/components/sidebarstacksection/sidebarnostacks.coffee
@@ -2,7 +2,6 @@
 React                   = require 'kd-react'
 Link                    = require 'app/components/common/link'
 ReactDOM                = require 'react-dom'
-classnames              = require 'classnames'
 ActivityFlux            = require 'activity/flux'
 SidebarSection          = require 'app/components/sidebarsection'
 isUserGroupAdmin        = require 'app/util/isusergroupadmin'
@@ -39,12 +38,9 @@
   renderContent: ->

     if @state.isGroupAdmin
-

-        No stacks
-        <Link href='/Stacks/Group-Stack-Templates'>
-          Create a stack
-        </Link>
-

+
+        <Link href='/Welcome'>No stacks</Link>
+
     else

@@ -52,9 +48,7 @@
           fully configured yet, please
           contact your team admin.

-        <Link href='/Messages/New' onClick={@bound 'handleOnClick'}>
-          Message admin
-        </Link>
+        <Link href='/Messages/New' onClick={@bound 'handleOnClick'}>Message admin</Link>

@@ -62,9 +56,11 @@

     return null  unless @state.isReady

-

-

+

+      <SidebarSection
+        className={@props.className}
+        title='Stacks' >
         {@renderContent()}
-

-

+      </SidebarSection>
+


remove check from datadog

remove check from datadog

--- plugins/datadog/index.coffee
+++ plugins/datadog/index.coffee
@@ -2,9 +2,6 @@

 class Datadog extends NotificationPlugin
     @receiveEvent = (config, event, callback) ->
-
-        if event?.trigger?.type == "linkExistingIssue"
-            return callback(null, null)

         # Refer to http://docs.datadoghq.com/api/#events
         payload = {


Remove dead code

Remove dead code

--- plugins/hipchat/index.coffee
+++ plugins/hipchat/index.coffee
@@ -1,16 +1,10 @@
 NotificationPlugin = require "../../notification-plugin"
 Handlebars = require 'handlebars'
-url = require "url"

 class Hipchat extends NotificationPlugin
   API_BASE_URL = "https://api.hipchat.com/"

   getApiUrl = (config, path) ->
-    if config.authToken.length == 40
-      apiVer = 2
-    else
-      apiVer = 1
-
     if config.host?
       host = if /\/$/.test(config.host) then config.host else config.host + "/"
     else


only show data upload button to admins; fixes #853

--- app/assets/javascripts/admin/views/dataset/dataset_switch_view.coffee
+++ app/assets/javascripts/admin/views/dataset/dataset_switch_view.coffee
@@ -13,9 +13,11 @@

   template : _.template("""

-
-        Upload Dataset
-
+      <% if(isAdmin()) { %>
+
+          Upload Dataset
+
+      <% } %>

         Show advanced view

@@ -40,6 +42,14 @@
   regions :
     "datasetPane" : ".dataset-region"
     "pagination" : ".pagination"
+
+
+  templateHelpers : ->
+
+    isAdmin : =>
+      userTeams = @model.get("teams")
+      return utils.isUserAdmin(userTeams)
+

   onShow : ->


Proceed to hide off-canvas when resized to >1024px

--- app/assets/javascripts/darkswarm/directives/off_canvas_wrap.js.coffee
+++ app/assets/javascripts/darkswarm/directives/off_canvas_wrap.js.coffee
@@ -4,6 +4,9 @@
 # In some browsers for mobile devices, the address bar is automatically hidden when scrolling down
 # the page. This is not workable if the height of the contents of the off-canvas exceeds the height
 # of the screen, because the latter portion of the contents stays hidden to the user.
+#
+# However, for screens over 1024px width for which the off-canvas is not styled to be visible, we
+# can proceed to hide this.
 #
 # https://github.com/openfoodfoundation/angular-foundation/blob/0.9.0-20180826174721/src/offcanvas/offcanvas.js
 angular.module('mm.foundation.offcanvas').directive 'offCanvasWrap', ($window) ->
@@ -19,4 +22,8 @@

       # Unbind hiding of the off-canvas upon window resize.
       win.unbind('resize.body', isolatedScope.hide)
+
+      # Bind hiding of the off-canvas that only happens when screen width is over 1024px.
+      win.bind 'resize.body', ->
+        isolatedScope.hide() if $(window).width() > 1024
   }


bleh disable for now

bleh disable for now

--- app/assets/javascripts/components/_scoreboard.js.cjsx
+++ app/assets/javascripts/components/_scoreboard.js.cjsx
@@ -10,10 +10,9 @@
     event.preventDefault() if event

     $.ajax
-      url: "http://104.236.170.77:3000/stat/urf?day=#{@props.day}",
+      url: "/scoreboard/top/#{@props.day}",
       type: "GET",
       dataType: "json",
-      crossDomain: true,
       success: (data) =>
         @setState(data)


fix tab-stops

fix tab-stops

--- snippets/core.BridleNSIS.cson
+++ snippets/core.BridleNSIS.cson
@@ -4,7 +4,7 @@
     'body': 'Abort("${1:user_message}")$2'
   'BringToFront()':
     'prefix': 'BringToFront()'
-    'body': 'BringToFront()$2'
+    'body': 'BringToFront()$1'
   'Call()':
     'prefix': 'Call()'
     'body': 'Call("${1:name}")$2'
@@ -13,34 +13,34 @@
     'body': 'CallInstDLL("${1:dll_file}", "${2:function_name}"'
   'ClearErrors()':
     'prefix': 'ClearErrors()'
-    'body': 'ClearErrors()$2'
+    'body': 'ClearErrors()$1'
   'Copy()':
     'prefix': 'Copy()'
-    'body': 'Copy("${1:file}", "${2:target}")$2'
+    'body': 'Copy("${1:file}", "${2:target}")$3'
   'CreateDirectory()':
     'prefix': 'CreateDirectory()'
-    'body': 'CreateDirectory("${1:path_to_create}")$2'
+    'body': 'CreateDirectory("${1:path_to_create}")$3'
   'CreateFont()':
     'prefix': 'CreateFont()'
-    'body': 'CreateFont("${1:face_name}",${2: "${3:height}"${4:, "${5:weight}"${6:, ${7:/ITALIC}${8:, ${9:/UNDERLINE}${10:, ${11:/STRIKE}}}}}})$2'
+    'body': 'CreateFont("${1:face_name}",${2: "${3:height}"${4:, "${5:weight}"${6:, ${7:/ITALIC}${8:, ${9:/UNDERLINE}${10:, ${11:/STRIKE}}}}}})$12'
   'CreateShortCut()':
     'prefix': 'CreateShortCut()'
-    'body': 'CreateShortCut("${1:link}.lnk", "${2:target_file}"${3:, "${4:parameters}"${5:, "${6:icon_file}"${7:, ${8:icon_index_number}${9:, ${10:start_options}${11:, ${12:keyboard_shortcut}${13:, "${14:description}"}}}}}}})$2'
+    'body': 'CreateShortCut("${1:link}.lnk", "${2:target_file}"${3:, "${4:parameters}"${5:, "${6:icon_file}"${7:, ${8:icon_index_number}${9:, ${10:start_options}${11:, ${12:keyboard_shortcut}${13:, "${14:description}"}}}}}}})$15'
   'Delete()':
     'prefix': 'Delete()'
-    'body': 'Delete("${1:source}"${2:, "${3:rebootok}"})$2'
+    'body': 'Delete("${1:source}"${2:, "${3:rebootok}"})$4'
   'DeleteINISec()':
     'prefix': 'DeleteINISec()'
-    'body': 'DeleteINISec("${1:ini_filename}", "${2:section_name}")$2'
+    'body': 'DeleteINISec("${1:ini_filename}", "${2:section_name}")$4'
   'DeleteINIStr()':
     'prefix': 'DeleteINIStr()'
-    'body': 'DeleteINIStr("${1:ini_filename}", "${2:section_name}", "${3:str_name}")$2'
+    'body': 'DeleteINIStr("${1:ini_filename}", "${2:section_name}", "${3:str_name}")$4'
   'DeleteRegKey()':
     'prefix': 'DeleteRegKey()'
-    'body': 'DeleteRegKey("${1:rootkey}", "${2:subkey}"}${3:, "${4:options}"})$2'
+    'body': 'DeleteRegKey("${1:rootkey}", "${2:subkey}"}${3:, "${4:options}"})$5'
   'DeleteRegValue()':
     'prefix': 'DeleteRegValue()'
-    'body': 'DeleteRegValue("${1:root_key}", "${2:subkey}", "${3:key_name}")$2'
+    'body': 'DeleteRegValue("${1:root_key}", "${2:subkey}", "${3:key_name}")$4'
   'DetailPrint()':
     'prefix': 'DetailPrint()'
     'body': 'DetailPrint("${1:user_message}")$2'
@@ -60,13 +60,13 @@
     """
   'EnableWindow()':
     'prefix': 'EnableWindow()'
-    'body': 'EnableWindow(${1:hwnd}, ${2:value})$2'
+    'body': 'EnableWindow(${1:hwnd}, ${2:value})$3'
   'EnumRegKey()':
     'prefix': 'EnumRegKey()'
-    'body': 'EnumRegKey("${1:root_key}", "${2:subkey}", "${3:index}")$2'
+    'body': 'EnumRegKey("${1:root_key}", "${2:subkey}", "${3:index}")$4'
   'EnumRegValue()':
     'prefix': 'EnumRegValue()'
-    'body': 'EnumRegValue("${1:root_key}", "${2:subkey}", "${3:index}")$2'
+    'body': 'EnumRegValue("${1:root_key}", "${2:subkey}", "${3:index}")$4'
   'Exch()':
     'prefix': 'Exch()'
     'body': 'Exch(${1:user_var})$2'
@@ -75,7 +75,7 @@
     'body': 'Exec("${1:command}")$2'
   'ExecShell()':
     'prefix': 'ExecShell()'
-    'body': 'ExecShell("${1:action}", "${2:command}"${3:, ${4:parameter}})$2'
+    'body': 'ExecShell("${1:action}", "${2:command}"${3:, ${4:parameter}})$5'
   'ExecWait()':
     'prefix': 'ExecWait()'
     'body': 'ExecWait("${1:command}")$2'
@@ -84,49 +84,49 @@
     'body': 'ExpandEnvStrings("${1:string}")$2'
   'File()':
     'prefix': 'File()'
-    'body': 'File("${1:file}"${2:, "${3:options}"${4:, "${5:outpath}"}})$2'
+    'body': 'File("${1:file}"${2:, "${3:options}"${4:, "${5:outpath}"}})$6'
   'FileClose()':
     'prefix': 'FileClose()'
     'body': 'FileClose(${1:handle})$2'
   'FileCopy()':
     'prefix': 'FileCopy()'
-    'body': 'FileCopy("${1:file}", "${2:target}")$2'
+    'body': 'FileCopy("${1:file}", "${2:target}")$3'
   'FileDelete()':
     'prefix': 'FileDelete()'
-    'body': 'FileDelete("${1:source}"${2:, "${3:rebootok}"})$2'
+    'body': 'FileDelete("${1:source}"${2:, "${3:rebootok}"})$4'
   'FileOpen()':
     'prefix': 'FileOpen()'
-    'body': 'FileOpen("${1:filename}", ${2:open_mode})$2'
+    'body': 'FileOpen("${1:filename}", ${2:open_mode})$3'
   'FileRead()':
     'prefix': 'FileRead()'
-    'body': 'FileRead(${1:handle}, ${2:maxlen})$2'
+    'body': 'FileRead(${1:handle}, ${2:maxlen})$3'
   'FileReadByte()':
     'prefix': 'FileReadByte()'
-    'body': 'FileReadByte(${1:handle}, ${2:maxlen})$2'
+    'body': 'FileReadByte(${1:handle}, ${2:maxlen})$3'
   'FileReadUTF16LE()':
     'prefix': 'FileReadUTF16LE()'
-    'body': 'FileReadUTF16LE(${1:handle}, ${2:maxlen})$2'
+    'body': 'FileReadUTF16LE(${1:handle}, ${2:maxlen})$3'
   'FileReadWord()':
     'prefix': 'FileReadWord()'
-    'body': 'FileReadWord(${1:handle}, ${2:maxlen})$2'
+    'body': 'FileReadWord(${1:handle}, ${2:maxlen})$3'
   'FileRename()':
     'prefix': 'FileRename()'
-    'body': 'FileRename("${1:source}", "${2:target}"${3:, "${4:options}"}})$2'
+    'body': 'FileRename("${1:source}", "${2:target}"${3:, "${4:options}"}})$5'
   'FileSeek()':
     'prefix': 'FileSeek()'
-    'body': 'FileSeek(${1:handle}, ${2:offset}${3:, ${4:mode}${5:, ${6:user_var}}})$2'
+    'body': 'FileSeek(${1:handle}, ${2:offset}${3:, ${4:mode}${5:, ${6:user_var}}})$7'
   'FileWrite()':
     'prefix': 'FileWrite()'
     'body': 'FileWrite(${1:handle})$2'
   'FileWriteByte()':
     'prefix': 'FileWriteByte()'
-    'body': 'FileWriteByte(${1:handle}, "${2:string}")$2'
+    'body': 'FileWriteByte(${1:handle}, "${2:string}")$3'
   'FileWriteUTF16LE()':
     'prefix': 'FileWriteUTF16LE()'
-    'body': 'FileWriteUTF16LE(${1:handle}, "${2:string}")$2'
+    'body': 'FileWriteUTF16LE(${1:handle}, "${2:string}")$3'
   'FileWriteWord()':
     'prefix': 'FileWriteWord()'
-    'body': 'FileWriteWord(${1:handle}, "${2:string}")$2'
+    'body': 'FileWriteWord(${1:handle}, "${2:string}")$3'
   'FindClose()':
     'prefix': 'FindClose()'
     'body': 'FindClose(${1:handle})$2'
@@ -135,7 +135,7 @@
     'body': 'FindNext(${1:handle})$2'
   'FindWindow()':
     'prefix': 'FindWindow()'
-    'body': 'FindWindow("${1:windowclass}", "${2:windowtitle}"${3:, ${4:windowparent}${5:, ${6:childafter}}})$2'
+    'body': 'FindWindow("${1:windowclass}", "${2:windowtitle}"${3:, ${4:windowparent}${5:, ${6:childafter}}})$7'
   'FlushINI()':
     'prefix': 'FlushINI()'
     'body': 'FlushINI("${1:ini_filename}")$2'
@@ -148,37 +148,37 @@
     """
   'GetCurInstType()':
     'prefix': 'GetCurInstType()'
-    'body': 'GetCurInstType()$2'
+    'body': 'GetCurInstType()$1'
   'GetCurrentAddress()':
     'prefix': 'GetCurrentAddress()'
-    'body': 'GetCurrentAddress()$2'
+    'body': 'GetCurrentAddress()$1'
   'GetDlgItem()':
     'prefix': 'GetDlgItem()'
-    'body': 'GetDlgItem(${1:dialog}, ${2:item_id})$2'
+    'body': 'GetDlgItem(${1:dialog}, ${2:item_id})$3'
   'GetErrorLevel()':
     'prefix': 'GetErrorLevel()'
-    'body': 'GetErrorLevel()$2'
+    'body': 'GetErrorLevel()$1'
   'GetFullPathName()':
     'prefix': 'GetFullPathName()'
-    'body': 'GetFullPathName("${1:path}"${2:, "${3:options}"})$2'
+    'body': 'GetFullPathName("${1:path}"${2:, "${3:options}"})$4'
   'GetFunctionAddress()':
     'prefix': 'GetFunctionAddress()'
     'body': 'GetCurrentAddress("${1:function_name}")$2'
   'GetInstDirError()':
     'prefix': 'GetInstDirError()'
-    'body': 'GetInstDirError()$2'
+    'body': 'GetInstDirError()$1'
   'GetLabelAddress()':
     'prefix': 'GetLabelAddress()'
     'body': 'GetLabelAddress("${1:label}")$2'
   'GetTempFileName()':
     'prefix': 'GetTempFileName()'
-    'body': 'GetTempFileName(${1:\$${2:base_dir}})$2'
+    'body': 'GetTempFileName(${1:\$${2:base_dir}})$3'
   'Goto()':
     'prefix': 'Goto()'
     'body': 'Goto(${1:name})$2'
   'HideWindow()':
     'prefix': 'HideWindow()'
-    'body': 'HideWindow()$2'
+    'body': 'HideWindow()$1'
   'If Not':
     'prefix': 'If Not'
     'body': """
@@ -231,28 +231,28 @@
     """
   'InitPluginsDir()':
     'prefix': 'InitPluginsDir()'
-    'body': 'InitPluginsDir()$2'
+    'body': 'InitPluginsDir()$1'
   'InstTypeGetText()':
     'prefix': 'InstTypeGetText()'
     'body': 'InstTypeGetText(${1:inst_type_idx})$2'
   'InstTypeSetText()':
     'prefix': 'InstTypeSetText()'
-    'body': 'InstTypeSetText(${1:inst_type_idx}, "${2:text}")$2'
+    'body': 'InstTypeSetText(${1:inst_type_idx}, "${2:text}")$3'
   'IntCmp()':
     'prefix': 'IntCmp()'
-    'body': 'IntCmp(${1:val1}, ${2:val2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}${6:, ${7:jump_if_val1_more}}})$2'
+    'body': 'IntCmp(${1:val1}, ${2:val2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}${6:, ${7:jump_if_val1_more}}})$8'
   'IntFmt()':
     'prefix': 'IntFmt()'
-    'body': 'IntFmt(${1:format}, ${2:numberstring})$2'
+    'body': 'IntFmt(${1:format}, ${2:numberstring})$3'
   'IntOp()':
     'prefix': 'IntOp()'
-    'body': 'IntOp(${1:value1}, ${2:op}, ${3:value2})$2'
+    'body': 'IntOp(${1:value1}, ${2:op}, ${3:value2})$4'
   'LangString()':
     'prefix': 'LangString()'
-    'body': 'LangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:string}")$2'
+    'body': 'LangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:string}")$4'
   'LicenseLangString()':
     'prefix': 'LicenseLangString()'
-    'body': 'LicenseLangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:license_path}")$2'
+    'body': 'LicenseLangString(${1:name}, \$\{LANG_${2:language_id}\}, "${3:license_path}")$4'
   'LoadLanguageFile()':
     'prefix': 'LoadLanguageFile()'
     'body': 'LoadLanguageFile("${1:language_file_nlf}")$2'
@@ -261,43 +261,43 @@
     'body': 'LockWindow(${1:value})$2'
   'LockWindow(off)':
     'prefix': 'LockWindow(off)'
-    'body': 'LockWindow(off)$2'
+    'body': 'LockWindow(off)$1'
   'LockWindow(on)':
     'prefix': 'LockWindow(on)'
-    'body': 'LockWindow(on)$2'
+    'body': 'LockWindow(on)$1'
   'LogSet()':
     'prefix': 'LogSet()'
     'body': 'LogSet(${1:value})$2'
   'LogSet(off)':
     'prefix': 'LogSet(off)'
-    'body': 'LogSet(off)$2'
+    'body': 'LogSet(off)$1'
   'LogSet(on)':
     'prefix': 'LogSet(on)'
-    'body': 'LogSet(on)$2'
+    'body': 'LogSet(on)$1'
   'LogText()':
     'prefix': 'LogText()'
     'body': 'LogText("${1:text}")$2'
   'MsgBox()':
     'prefix': 'MsgBox()'
-    'body': 'MsgBox("${1:buttons}", "${2:message}"${3:, ${4:options}${5:, ${6:sd}}})$2'
+    'body': 'MsgBox("${1:buttons}", "${2:message}"${3:, ${4:options}${5:, ${6:sd}}})$7'
   'MsgBox(ABORTRETRYIGNORE)':
     'prefix': 'MsgBox(ABORTRETRYIGNORE)'
-    'body': 'MsgBox("ABORTRETRYIGNORE", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$2'
+    'body': 'MsgBox("ABORTRETRYIGNORE", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
   'MsgBox(OK)':
     'prefix': 'MsgBox(OK)'
-    'body': 'MsgBox("OK", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$2'
+    'body': 'MsgBox("OK", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
   'MsgBox(OKCANCEL)':
     'prefix': 'MsgBox(OKCANCEL)'
-    'body': 'MsgBox("OKCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$2'
+    'body': 'MsgBox("OKCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
   'MsgBox(RETRYCANCEL)':
     'prefix': 'MsgBox(RETRYCANCEL)'
-    'body': 'MsgBox("RETRYCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$2'
+    'body': 'MsgBox("RETRYCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
   'MsgBox(YESNO)':
     'prefix': 'MsgBox(YESNO)'
-    'body': 'MsgBox("YESNO", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$2'
+    'body': 'MsgBox("YESNO", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
   'MsgBox(YESNOCANCEL)':
     'prefix': 'MsgBox(YESNOCANCEL)'
-    'body': 'MsgBox("YESNOCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$2'
+    'body': 'MsgBox("YESNOCANCEL", "${1:message}"${2:, ${3:options}${4:, ${5:sd}}})$6'
   'Pop()':
     'prefix': 'Pop()'
     'body': '${1:user_var} = Pop()$2'
@@ -306,34 +306,34 @@
     'body': 'Push(${1:string})$2'
   'Quit()':
     'prefix': 'Quit()'
-    'body': 'Quit()$2'
+    'body': 'Quit()$1'
   'ReadEnvStr()':
     'prefix': 'ReadEnvStr()'
     'body': 'ReadEnvStr(${1:name})$2'
   'ReadINIStr()':
     'prefix': 'ReadINIStr()'
-    'body': 'ReadINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}")$2'
+    'body': 'ReadINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}")$4'
   'ReadRegDWORD()':
     'prefix': 'ReadRegDWORD()'
-    'body': 'ReadRegDWORD("${1:root_key}", "${2:sub_key}", "${3:name}")$2'
+    'body': 'ReadRegDWORD("${1:root_key}", "${2:sub_key}", "${3:name}")$4'
   'ReadRegStr()':
     'prefix': 'ReadRegStr()'
-    'body': 'ReadRegStr("${1:root_key}", "${2:sub_key}", "${3:name}")$2'
+    'body': 'ReadRegStr("${1:root_key}", "${2:sub_key}", "${3:name}")$4'
   'Reboot()':
     'prefix': 'Reboot()'
-    'body': 'Reboot()$2'
+    'body': 'Reboot()$1'
   'RegDLL()':
     'prefix': 'RegDLL()'
-    'body': 'RegDLL("${1:file}"${2:, ${3:options}})$2'
+    'body': 'RegDLL("${1:file}"${2:, ${3:options}})$4'
   'Rename()':
     'prefix': 'Rename()'
-    'body': 'Rename("${1:source}", "${2:target}"${3:, "${4:options}"}})$2'
+    'body': 'Rename("${1:source}", "${2:target}"${3:, "${4:options}"}})$5'
   'ReserveFile()':
     'prefix': 'ReserveFile()'
-    'body': 'ReserveFile("${1:file}"${2:, "${3:options}"}})$2'
+    'body': 'ReserveFile("${1:file}"${2:, "${3:options}"}})$4'
   'RMDir()':
     'prefix': 'RMDir()'
-    'body': 'RMDir("${1:directory}"${2:, "${3:options}"}})$2'
+    'body': 'RMDir("${1:directory}"${2:, "${3:options}"}})$4'
   'SearchPath()':
     'prefix': 'SearchPath()'
     'body': 'SearchPath("${1:filename}")$2'
@@ -348,19 +348,19 @@
     'body': 'SectionGetSize(${1:section_index})$2'
   'SectionSetFlags()':
     'prefix': 'SectionSetFlags()'
-    'body': 'SectionSetFlags(${1:section_index}, ${2:inst_types})$2'
+    'body': 'SectionSetFlags(${1:section_index}, ${2:inst_types})$3'
   'SectionSetInstTypes()':
     'prefix': 'SectionSetInstTypes()'
-    'body': 'SectionSetInstTypes(${1:section_index}, ${2:section_flags})$2'
+    'body': 'SectionSetInstTypes(${1:section_index}, ${2:section_flags})$3'
   'SectionSetSize()':
     'prefix': 'SectionSetSize()'
-    'body': 'SectionSetSize(${1:handle}, ${2:maxlen})$2'
+    'body': 'SectionSetSize(${1:handle}, ${2:maxlen})$3'
   'SectionSetText()':
     'prefix': 'SectionSetText()'
-    'body': 'SectionSetText(${1:section_index}, "${2:section_text}")$2'
+    'body': 'SectionSetText(${1:section_index}, "${2:section_text}")$3'
   'SendMessage()':
     'prefix': 'SendMessage()'
-    'body': 'SendMessage(${1:hwnd}, ${2:msg}, ${3:wparam}, ${4:lparam}${5:, ${6:timeout_in_ms}})$2'
+    'body': 'SendMessage(${1:hwnd}, ${2:msg}, ${3:wparam}, ${4:lparam}${5:, ${6:timeout_in_ms}})$7'
   'SetAutoClose()':
     'prefix': 'SetAutoClose()'
     'body': 'SetAutoClose(${1:value})$2'
@@ -372,7 +372,7 @@
     'body': 'SetAutoClose(true)$2'
   'SetCtlColors()':
     'prefix': 'SetCtlColors()'
-    'body': 'SetCtlColors(${1:hwnd}${2:, ${3:branding}}, ${4:text_color}, ${5:bg_color})$2'
+    'body': 'SetCtlColors(${1:hwnd}${2:, ${3:branding}}, ${4:text_color}, ${5:bg_color})$6'
   'SetCurInstType()':
     'prefix': 'SetCurInstType()'
     'body': 'SetCurInstType(${1:inst_type_idx})$2'
@@ -381,46 +381,46 @@
     'body': 'SetDetailsPrint(${1:value})$2'
   'SetDetailsPrint(both)':
     'prefix': 'SetDetailsPrint(both)'
-    'body': 'SetDetailsPrint(both)$2'
+    'body': 'SetDetailsPrint(both)$1'
   'SetDetailsPrint(lastused)':
     'prefix': 'SetDetailsPrint(lastused)'
-    'body': 'SetDetailsPrint(lastused)$2'
+    'body': 'SetDetailsPrint(lastused)$1'
   'SetDetailsPrint(listonly)':
     'prefix': 'SetDetailsPrint(listonly)'
-    'body': 'SetDetailsPrint(listonly)$2'
+    'body': 'SetDetailsPrint(listonly)$1'
   'SetDetailsPrint(none)':
     'prefix': 'SetDetailsPrint(none)'
-    'body': 'SetDetailsPrint(none)$2'
+    'body': 'SetDetailsPrint(none)$1'
   'SetDetailsPrint(textonly)':
     'prefix': 'SetDetailsPrint(textonly)'
-    'body': 'SetDetailsPrint(textonly)$2'
+    'body': 'SetDetailsPrint(textonly)$1'
   'SetDetailsView()':
     'prefix': 'SetDetailsView()'
     'body': 'SetDetailsView(${1:value})$2'
   'SetDetailsView(hide)':
     'prefix': 'SetDetailsView(hide)'
-    'body': 'SetDetailsView(hide)$2'
+    'body': 'SetDetailsView(hide)$1'
   'SetDetailsView(show)':
     'prefix': 'SetDetailsView(show)'
-    'body': 'SetDetailsView(show)$2'
+    'body': 'SetDetailsView(show)$1'
   'SetErrorLevel()':
     'prefix': 'SetErrorLevel()'
     'body': 'SetErrorLevel(${1:error_level})$2'
   'SetErrorLevel(0)':
     'prefix': 'SetErrorLevel(0)'
-    'body': 'SetErrorLevel(0)$2'
+    'body': 'SetErrorLevel(0)$1'
   'SetErrorLevel(1)':
     'prefix': 'SetErrorLevel(1)'
-    'body': 'SetErrorLevel(1)$2'
+    'body': 'SetErrorLevel(1)$1'
   'SetErrorLevel(2)':
     'prefix': 'SetErrorLevel(2)'
-    'body': 'SetErrorLevel(2)$2'
+    'body': 'SetErrorLevel(2)$1'
   'SetErrors()':
     'prefix': 'SetErrors()'
-    'body': 'SetErrors()$2'
+    'body': 'SetErrors()$1'
   'SetFileAttributes()':
     'prefix': 'SetFileAttributes()'
-    'body': 'SetFileAttributes("${1:filename}", ${2:attributes})$2'
+    'body': 'SetFileAttributes("${1:filename}", ${2:attributes})$3'
   'SetOutPath()':
     'prefix': 'SetOutPath()'
     'body': 'SetOutPath("${1:path}")$2'
@@ -429,52 +429,52 @@
     'body': 'SetRebootFlag(${1:value})$2'
   'SetRebootFlag(false)':
     'prefix': 'SetRebootFlag(false)'
-    'body': 'SetRebootFlag(false)$2'
+    'body': 'SetRebootFlag(false)$1'
   'SetRebootFlag(true)':
     'prefix': 'SetRebootFlag(true)'
-    'body': 'SetRebootFlag(true)$2'
+    'body': 'SetRebootFlag(true)$1'
   'SetRegView()':
     'prefix': 'SetRegView()'
     'body': 'SetRegView(${1:value})$2'
   'SetRegView(32)':
     'prefix': 'SetRegView(32)'
-    'body': 'SetRegView(32)$2'
+    'body': 'SetRegView(32)$1'
   'SetRegView(64)':
     'prefix': 'SetRegView(64)'
-    'body': 'SetRegView(64)$2'
+    'body': 'SetRegView(64)$1'
   'SetRegView(lastused)':
     'prefix': 'SetRegView(lastused)'
-    'body': 'SetRegView(lastused)$2'
+    'body': 'SetRegView(lastused)$1'
   'SetShellVarContext()':
     'prefix': 'SetShellVarContext()'
     'body': 'SetShellVarContext(${1:value})$2'
   'SetShellVarContext(all)':
     'prefix': 'SetShellVarContext(all)'
-    'body': 'SetShellVarContext(all)$2'
+    'body': 'SetShellVarContext(all)$1'
   'SetShellVarContext(current)':
     'prefix': 'SetShellVarContext(current)'
-    'body': 'SetShellVarContext(current)$2'
+    'body': 'SetShellVarContext(current)$1'
   'SetSilent()':
     'prefix': 'SetSilent()'
     'body': 'SetSilent(${1:value})$2'
   'SetSilent(normal)':
     'prefix': 'SetSilent(normal)'
-    'body': 'SetSilent(normal)$2'
+    'body': 'SetSilent(normal)$1'
   'SetSilent(silent)':
     'prefix': 'SetSilent(silent)'
-    'body': 'SetSilent(silent)$2'
+    'body': 'SetSilent(silent)$1'
   'ShowWindow()':
     'prefix': 'ShowWindow()'
-    'body': 'ShowWindow(${1:hwnd}, ${2:value})$2'
+    'body': 'ShowWindow(${1:hwnd}, ${2:value})$3'
   'Sleep()':
     'prefix': 'Sleep()'
     'body': 'Sleep(${1:sleeptime_in_ms})$2'
   'StrCmp()':
     'prefix': 'StrCmp()'
-    'body': 'StrCmp(${1:str1}, ${2:str2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}})$2'
+    'body': 'StrCmp(${1:str1}, ${2:str2}, ${3:jump_if_equal}${4:, ${5:jump_if_val1_less}})$6'
   'StrCpy()':
     'prefix': 'StrCpy()'
-    'body': 'StrCpy(${1:str}${2:, ${3:maxlen}${4:, ${5:start_offset}}})$2'
+    'body': 'StrCpy(${1:str}${2:, ${3:maxlen}${4:, ${5:start_offset}}})$6'
   'StrLen()':
     'prefix': 'StrLen()'
     'body': 'StrLen(${1:str})$2'
@@ -483,34 +483,34 @@
     'body': 'UnRegDLL("${1:dllfile}")$2'
   'Var':
     'prefix': 'Var'
-    'body': 'Var ${1:user_vars}'
+    'body': 'Var ${1:user_vars}$2'
   'WordFind()':
     'prefix': 'WordFind()'
-    'body': 'WordFind("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"}${6:, "${7:center}"}})$2'
+    'body': 'WordFind("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"}${6:, "${7:center}"}})$8'
   'WordFindS()':
     'prefix': 'WordFindS()'
-    'body': 'WordFindS("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"}${6:, "${7:center}"}})$2'
+    'body': 'WordFindS("${1:string}", "${2:options}", "${3:delimiter1}"${4:, "${5:delimiter2}"}${6:, "${7:center}"}})$8'
   'WordReplace()':
     'prefix': 'WordReplace()'
-    'body': 'WordReplace("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$2'
+    'body': 'WordReplace("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$5'
   'WordReplaceS()':
     'prefix': 'WordReplaceS()'
-    'body': 'WordReplaceS("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$2'
+    'body': 'WordReplaceS("${1:string}", "${2:word1}", "${3:word2}", "${4:options}")$5'
   'WriteINIStr()':
     'prefix': 'WriteINIStr()'
-    'body': 'WriteINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}", "${4:value}")$2'
+    'body': 'WriteINIStr("${1:ini_filename}", "${2:section_name}", "${3:entry_name}", "${4:value}")$5'
   'WriteRegBin()':
     'prefix': 'WriteRegBin()'
-    'body': 'WriteRegBin("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value_data}")$2'
+    'body': 'WriteRegBin("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value_data}")$5'
   'WriteRegDWORD()':
     'prefix': 'WriteRegDWORD()'
-    'body': 'WriteRegDWORD("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$2'
+    'body': 'WriteRegDWORD("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
   'WriteRegExpandStr()':
     'prefix': 'WriteRegExpandStr()'
-    'body': 'WriteRegExpandStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$2'
+    'body': 'WriteRegExpandStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
   'WriteRegStr()':
     'prefix': 'WriteRegStr()'
-    'body': 'WriteRegStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$2'
+    'body': 'WriteRegStr("${1:root_key}", "${2:subkey}", "${3:key_name}", "${4:value}")$5'
   'WriteUninstaller()':
     'prefix': 'WriteUninstaller()'
-    'body': 'WriteUninstaller("${1:path}.exe")'
+    'body': 'WriteUninstaller("${1:path}.exe")$2'


Only set default path when opening files

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -496,7 +496,7 @@
       properties: properties.concat(['multiSelections', 'createDirectory'])
       title: 'Open'

-    if process.platform is 'linux'
+    if process.platform is 'linux' and type is 'file'
       if projectPath = @lastFocusedWindow?.projectPath
         openOptions.defaultPath = projectPath


handle mark deletion (still needs work)

handle mark deletion (still needs work)

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -155,10 +155,9 @@
     marks = @state.marks
     marks.splice(key,1) # delete marks[key]
     @setState
-      marks: marks, =>
-        @forceUpdate()
-      # selectedMark: null, =>
-        # @forceUpdate() # make sure keys are up-to-date before re-render
+      marks: marks
+      selectedMark: null, =>
+        @forceUpdate() # make sure keys are up-to-date before re-render

   handleMarkClick: (mark, e) ->
     {x,y} = @getEventOffset e
@@ -207,6 +206,7 @@
                 getEventOffset={@getEventOffset}
                 isSelected={mark is @state.selectedMark}
                 handleMarkClick={@handleMarkClick}
+                onClickDelete={@onClickDelete}
               />
             ), @
           }


added the tool_name and task_description to the annotation hash

--- app/assets/javascripts/components/subject-viewer.cjsx
+++ app/assets/javascripts/components/subject-viewer.cjsx
@@ -89,6 +89,8 @@
     console.log "sv PROPS", @props
     @props.annotation["subject_id"] = @props.subject.id
     @props.annotation["workflow_id"] = @props.workflow.id
+
+

     taskDescription = @props.workflow.tasks[@props.annotation.task]

@@ -278,25 +280,7 @@

                     mark._key ?= Math.random()
                     toolDescription = taskDescription.tools[mark.tool]
-
-                    # console.log 'MARK TOOL: ', mark.tool
-
-                    # toolEnv =
-                    #   scale: @getScale()
-                    #   disabled: isPriorAnnotation
-                    #   selected: mark is @state.selectedMark
-                    #   getEventOffset: @getEventOffset
-                    #   # ref: 'selectedTool' if mark is @state.selectedMark
-
-                    # toolProps =
-                    #   mark: mark
-                    #   color: toolDescription.color
-
-                    # toolMethods =
-                    #   onChange: @updateAnnotations
-                    #   # onSelect: @selectMark.bind this, annotation, mark
-                    #   # onDestroy: @destroyMark.bind this, annotation, mark
-
+                    @props.annotation["tool_task_description"] = @props.workflow.tasks[annotation.task].tools[mark.tool]
                     ToolComponent = markingTools[toolDescription.type]

                     <ToolComponent


fix attachment set url method

fix attachment set url method

--- app/modules/components/wysiwyg/wysiwyg.service.coffee
+++ app/modules/components/wysiwyg/wysiwyg.service.coffee
@@ -54,10 +54,10 @@

         return el.innerHTML

-    getAttachmentData: (el, tokens) ->
+    getAttachmentData: (el, tokens, attr) ->
         deferred = @q.defer()
         @attachmentsService.get(tokens[0], tokens[1]).then (response) =>
-            el.setAttribute('src', "#{response.data.url}#_taiga-refresh=#{tokens[0]}:#{tokens[1]}")
+            el.setAttribute(attr, "#{response.data.url}#_taiga-refresh=#{tokens[0]}:#{tokens[1]}")
             deferred.resolve(el)

         return deferred.promise
@@ -84,7 +84,7 @@
                     match = e.getAttribute(tag.attr).match(regex)
                     if match
                         tokens = match[1].split(":")
-                        promises.push(@.getAttachmentData(e, tokens))
+                        promises.push(@.getAttachmentData(e, tokens, tag.attr))

         @q.all(promises).then =>
             deferred.resolve(el.innerHTML)


change the path to refer

change the path to refer

--- server.coffee
+++ server.coffee
@@ -3,7 +3,7 @@
 path = require('path')
 request = require('request')

-app.get /^\/\?json_route=.*$/, (req, res) ->
+app.get /^\/wp-json.*$/, (req, res) ->
   console.log(req);
   request('http://wordpress.local' + req.originalUrl).pipe(res)
   console.log(res)


if no choices exist, don't export a choices sheet.

--- dkobo/koboform/static/js/xlform/XLF.model.survey.coffee
+++ dkobo/koboform/static/js/xlform/XLF.model.survey.coffee
@@ -41,7 +41,8 @@

     @finalize()

-    surveyCsvJson = do =>
+    out = {}
+    out.survey = do =>
       oCols = ["name", "type", "label"]
       oRows = []

@@ -71,13 +72,16 @@
         clName = choiceList.get("name")
         for option in choiceList.options.models
           rows.push _.extend {}, option.toJSON(), "list name": choiceList.get("name")
+      if rows.length > 0
+        columns: cols
+        rowObjects: rows
+      else
+        false

-      columns: cols
-      rowObjects: rows
+    out.choices = choicesCsvJson  if choicesCsvJson
+    out.settings = @settings.toCsvJson()

-    survey: surveyCsvJson
-    choices: choicesCsvJson
-    settings: @settings.toCsvJson()
+    out

   toCSV: ->
     sheeted = csv.sheeted()


Copy the frame info so we don't try to modify a native frame wrapper.

--- application/chrome/content/wesabe/util/inspect.coffee
+++ application/chrome/content/wesabe/util/inspect.coffee
@@ -101,7 +101,10 @@
   else if error.location
     frame = error.location
     while frame
-      trace.push(frame)
+      trace.push
+        name: frame.name
+        filename: frame.filename
+        lineNumber: frame.lineNumber
       frame = frame.caller

   for frame in trace


Client: Activity: Test: Deleted ChannelParticipantAvatars file. It is seperated as view and container components

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -1,354 +0,0 @@
-kd                          = require 'kd'
-whoami                      = require 'app/util/whoami'
-React                       = require 'kd-react'
-ReactDOM                    = require 'react-dom'
-Avatar                      = require 'app/components/profile/avatar'
-KDReactorMixin              = require 'app/flux/base/reactormixin'
-immutable                   = require 'immutable'
-classnames                  = require 'classnames'
-AppFlux                     = require 'app/flux'
-ActivityFlux                = require 'activity/flux'
-ProfileLinkContainer        = require 'app/components/profile/profilelinkcontainer'
-ChannelParticipantsDropdown = require 'activity/components/channelparticipantsdropdown'
-DropboxInputMixin           = require 'activity/components/dropbox/dropboxinputmixin'
-getGroup                    = require 'app/util/getGroup'
-isUserGroupAdmin            = require 'app/util/isusergroupadmin'
-validator                   = require 'validator'
-showErrorNotification       = require 'app/util/showErrorNotification'
-Tooltip                     = require 'app/components/tooltip'
-
-
-module.exports = class ChannelParticipantAvatars extends React.Component
-
-  @include [DropboxInputMixin]
-
-  PREVIEW_COUNT     = 0
-  MAX_PREVIEW_COUNT = 19
-
-  @defaultProps =
-    channelThread : null
-    participants  : null
-
-  constructor: (props) ->
-
-    super
-
-    @state =
-      value                 : ''
-      isGroupAdmin          : no
-      showAllParticipants   : no
-      addNewParticipantMode : no
-
-
-  componentWillReceiveProps: (nextProps) ->
-
-    channelId    = @props.channelThread.get 'channelId'
-    oldChannelId = nextProps.channelThread.get 'channelId'
-
-    @setState addNewParticipantMode: no  if channelId isnt oldChannelId
-
-
-  getDataBindings: ->
-
-    { getters } = ActivityFlux
-
-    return {
-      query              : getters.channelParticipantsSearchQuery
-      dropdownUsers      : getters.channelParticipantsInputUsers
-      selectedItem       : getters.channelParticipantsSelectedItem
-      selectedIndex      : getters.channelParticipantsSelectedIndex
-      dropdownVisibility : getters.channelParticipantsDropdownVisibility
-    }
-
-
-  componentDidMount: ->
-
-    isUserGroupAdmin (err, isAdmin) =>
-
-      return showErrorNotification err  if err
-
-      @setState isGroupAdmin: isAdmin
-
-    document.addEventListener 'mousedown', @bound 'handleOutsideMouseClick'
-
-
-  componentWillUnmount: ->
-
-    document.removeEventListener 'mousedown', @bound 'handleOutsideMouseClick'
-
-
-  handleOutsideMouseClick: (event) ->
-
-    return  unless @refs.AllParticipantsMenu
-
-    target             = event.target
-    moreButtonEl       = @refs.showMoreButton.getDOMNode()
-    participantsMenuEl = @refs.AllParticipantsMenu.getDOMNode()
-
-    if ((@isNodeInContainer target, moreButtonEl) or (@isNodeInContainer target, participantsMenuEl))
-      return
-
-    event.stopPropagation()
-    @setState showAllParticipants: no
-
-
-  isNodeInContainer: (el, container) ->
-    while el
-      return yes  if el is container
-      el = el.parentNode
-    no
-
-
-  getPreviewCount: ->
-
-    { participants } = @props
-
-    diff = participants.size is MAX_PREVIEW_COUNT
-
-    PREVIEW_COUNT = switch
-      when diff is 0 then MAX_PREVIEW_COUNT
-      when diff < 0 then participants.size
-      else MAX_PREVIEW_COUNT - 1
-
-
-  onNewParticipantButtonClick: ->
-
-    if @state.addNewParticipantMode is yes
-    then @setState addNewParticipantMode: no
-    else
-      @setState addNewParticipantMode: yes, ->
-        textInput = ReactDOM.findDOMNode @refs.textInput
-        textInput.focus()
-
-
-  onShowMoreParticipantButtonClick: (event) ->
-
-    event.stopPropagation()
-    if @state.showAllParticipants is yes
-    then @setState showAllParticipants: no
-    else @setState showAllParticipants: yes
-
-
-  renderPreviewAvatars: ->
-
-    return null  unless @props.participants
-
-    { participants } = @props
-
-    avatarOptions =
-      participants        : participants.slice 0, @getPreviewCount()
-      isNicknameVisible   : no
-      shouldTooltipRender : yes
-
-    @renderAvatars avatarOptions
-
-
-  renderNickname: (participant, isNicknameVisible)->
-
-    return  if isNicknameVisible is no
-
-    nickname = participant.getIn ['profile', 'nickname']
-    {nickname}
-
-
-  renderTooltip: (participant, shouldTooltipRender) ->
-
-    return  unless shouldTooltipRender
-
-    nickname = participant.getIn ['profile', 'nickname']
-
-    <Tooltip text={nickname} position='bottom' />
-
-
-  renderAvatars: (options={}) ->
-
-    { participants, isNicknameVisible, shouldTooltipRender } = options
-
-    participants.toList().map (participant) =>
-      <ProfileLinkContainer
-        key={participant.get '_id'}
-        account={participant.toJS()}
-        className='ChannelParticipantAvatars-singleBox'>
-

-          <Avatar
-            className='ChannelParticipantAvatars-avatar'
-            width={30}
-            account={participant.toJS()}
-            height={30} />
-          {@renderNickname participant, isNicknameVisible }
-          {@renderTooltip participant, shouldTooltipRender}
-

-      </ProfileLinkContainer>
-
-
-  renderMoreCount: ->
-
-    return null  unless @props.participants
-
-    moreCount = @props.participants.size - PREVIEW_COUNT
-
-    return null  unless moreCount > 0
-
-    moreCount = Math.min moreCount, 99
-
-

-

-        +{moreCount}
-

-

-
-
-  renderAllParticipantsMenu: ->
-
-    return null  unless @state.showAllParticipants
-    return null  unless @props.participants
-
-    { participants }  = @props
-
-    avatarOptions =
-      participants        : participants.slice @getPreviewCount()
-      isNicknameVisible   : yes
-      shouldTooltipRender : no
-
-

-
Other participants

-

-        {@renderAvatars avatarOptions }
-

-

-
-
-  getAddNewParticipantButtonClassNames: ->
-
-    isParticipant = @props.channelThread.getIn ['channel', 'isParticipant']
-
-    return classnames
-      'ChannelParticipantAvatars-newParticipantBox': yes
-      'cross': @state.addNewParticipantMode
-      'hidden': not isParticipant
-
-
-  renderNewParticipantButton: ->
-
-

-

-

-
-
-  getNewParticipantInputClassNames: -> classnames
-    'ChannelParticipantInput': yes
-    'slide-down': @state.addNewParticipantMode
-
-
-  onChange: (event) ->
-
-    { value } = event.target
-    @setState { value }
-
-    matchResult = value.match /^@(.*)/
-
-    query = value
-    query = matchResult[1]  if matchResult
-
-    { channel, user } = ActivityFlux.actions
-
-    user.setChannelParticipantsInputQuery query
-    channel.setChannelParticipantsDropdownVisibility yes
-
-
-  isGroupChannel: ->
-
-    channelId = @props.channelThread.getIn ['channel','id']
-
-    return channelId is getGroup().socialApiDefaultChannelId
-
-
-  onEnter: (event) ->
-
-    DropboxInputMixin.onEnter.call this, event
-
-    if @state.isGroupAdmin and @isGroupChannel()
-
-      value        = event.target.value.trim()
-      isValidEmail = validator.isEmail value
-
-      if isValidEmail
-
-        { channel, user } = ActivityFlux.actions
-
-        channel.inviteMember([{email: value}]).then ->
-          user.unsetChannelParticipantsInputQuery()
-
-
-  getPlaceHolder: ->
-
-    placeholder  = 'type a @username and hit enter'
-
-    if @state.isGroupAdmin and @isGroupChannel()
-      placeholder = 'type a @username or email'
-
-    return placeholder
-
-
-  renderAddNewParticipantInput: ->
-
-

-      <input ref='ChannelParticipantsInput'
-        onKeyDown   = { @bound 'onKeyDown' }
-        onChange    = { @bound 'onChange' }
-        placeholder = { @getPlaceHolder() }
-        value       = { @state.value }
-        ref         = 'textInput'
-      />
-

-
-
-  onDropdownItemConfirmed: (item) ->
-
-    channelId   = @props.channelThread.get 'channelId'
-    participant = @state.selectedItem
-
-    userIds     = [ participant.get '_id' ]
-    accountIds  = [ participant.get 'socialApiId' ]
-
-    { channel } = ActivityFlux.actions
-
-    channel.addParticipants channelId, accountIds, userIds
-
-    @setState value: ''
-
-
-  renderAddNewChannelParticipantsDropdown: ->
-
-    <ChannelParticipantsDropdown
-      ref             = 'dropdown'
-      query           = { @state.query }
-      value           = { @state.value }
-      visible         = { @state.dropdownVisibility }
-      items           = { @state.dropdownUsers }
-      selectedItem    = { @state.selectedItem }
-      selectedIndex   = { @state.selectedIndex }
-      onItemConfirmed = { @bound 'onDropdownItemConfirmed' }
-    />
-
-
-  render: ->
-

-

-        {@renderPreviewAvatars()}
-        {@renderMoreCount()}
-        {@renderNewParticipantButton()}
-

-      {@renderAddNewParticipantInput()}
-      {@renderAllParticipantsMenu()}
-      {@renderAddNewChannelParticipantsDropdown()}
-

-
-
-React.Component.include.call ChannelParticipantAvatars, [KDReactorMixin]


avoid flashing the abort button

avoid flashing the abort button

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -170,7 +170,7 @@
         max_size_stack: 100 # If the stack of the query (including function, string, object etc. is greater than @max_size_stack, we stop parsing the query
         max_size_query: 1000 # If the query is more than 1000 char, we don't show suggestion (codemirror doesn't highlight/parse if the query is more than 1000 characdd_ters too

-        delay_show_abort: 70 # If a query didn't return during this period (ms) we let people abort the query
+        delay_toggle_abort: 70 # If a query didn't return during this period (ms) we let people abort the query

         events:
             'mouseup .CodeMirror': 'handle_click'
@@ -2569,23 +2569,29 @@

         toggle_executing: (executing) =>
             if executing == @executing
+                if executing and @state.query_result?.is_feed
+                    @$('.loading_query_img').hide()
                 return
             if @disable_toggle_executing
                 return
-            if executing is true
-                @executing = true
-                @timeout_show_abort = setTimeout =>
+            @executing = executing
+            if @timeout_toggle_abort?
+                clearTimeout @timeout_toggle_abort
+            if executing
+                @timeout_toggle_abort = setTimeout =>
+                    @timeout_toggle_abort = null
                     if not @state.query_result?.is_feed
                         @$('.loading_query_img').show()
                     @$('.execute_query').hide()
                     @$('.abort_query').show()
-                , @delay_show_abort
-            else if executing is false
-                clearTimeout @timeout_show_abort
-                @executing = false
-                @$('.loading_query_img').hide()
-                @$('.execute_query').show()
-                @$('.abort_query').hide()
+                , @delay_toggle_abort
+            else
+                @timeout_toggle_abort = setTimeout =>
+                    @timeout_toggle_abort = null
+                    @$('.loading_query_img').hide()
+                    @$('.execute_query').show()
+                    @$('.abort_query').hide()
+                , @delay_toggle_abort

         # A portion is one query of the whole input.
         execute_portion: =>


added enforce_aspect_ratio funtion with clearer private vars for _last_width and _last_height

--- bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
+++ bokehjs/src/coffee/renderer/tile/tile_renderer.coffee
@@ -21,6 +21,8 @@
     @y_range = @map_plot.get('y_range')
     @y_mapper = this.map_frame.get('y_mappers')['default']
     @extent = @get_extent()
+    @_last_height = undefined
+    @_last_width = undefined

   _map_data: () ->
     @initial_extent = @get_extent()
@@ -76,28 +78,32 @@
     @mget('tile_source').tiles[tile.tile_data.cache_key] = tile.tile_data
     tile.src = @mget('tile_source').get_image_url(x, y, z)
     return tile
-
-  render: (ctx, indices, args) ->
-
-    if not @map_initialized?
-      @_set_data()
-      @_map_data()
-      @map_initialized = true
-
+
+  _enforce_aspect_ratio: () ->
     # brute force way of handling resize or responsive event -------------------------------------------------------------
-    if @height != @map_frame.get('height') or @width != @map_frame.get('width')
+    if @_last_height != @map_frame.get('height') or @_last_width != @map_frame.get('width')
       extent = @get_extent()
       zoom_level = @mget('tile_source').get_level_by_extent(extent, @map_frame.get('height'), @map_frame.get('width'))
       new_extent = @mget('tile_source').snap_to_zoom(extent, @map_frame.get('height'), @map_frame.get('width'), zoom_level)
       @x_range.set({start:new_extent[0], end: new_extent[2]})
       @y_range.set({start:new_extent[1], end: new_extent[3]})
       @extent = new_extent
-      @height = @map_frame.get('height')
-      @width = @map_frame.get('width')
+      @_last_height = @map_frame.get('height')
+      @_last_width = @map_frame.get('width')
+      return true
+    return false
+
+  render: (ctx, indices, args) ->
+
+    if not @map_initialized?
+      @_set_data()
+      @_map_data()
+      @map_initialized = true
+
+    if @_enforce_aspect_ratio()
       return

     @_update()
-
     if @prefetch_timer?
       clearTimeout(@prefetch_timer)


Improve a comment

Improve a comment

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -883,7 +883,12 @@
                                 @codemirror.setCursor cursor
                                 return false
                 else if event.which is 9  or (event.type is 'keydown' and ((event.ctrlKey is true or event.metaKey is true) and event.which is 32) and (@$('.suggestion_description').css('display') is 'none' and @.$('.suggestion_name_list').css('display') is 'none'))
-                    # If the user hit tab  or (Ctrl/Cmd+space with suggestion/description being hidden) we switch the highlighted suggestion
+                    # If the user just hit tab, we are going to show the suggestions if they are hidden
+                    # or if they suggestions are already shown, we are going to cycle through them.
+                    #
+                    # If the user just hit Ctrl/Cmd+space with suggestion/description being hidden we show the suggestions
+                    # Note that the user cannot cycle through suggestions because we make sure in the IF condition that suggestion/description are hidden
+                    # If the suggestions/description are visible, the event will be caught earlier with ESC
                     event.preventDefault()
                     if event.type isnt 'keydown'
                         return false


delegateEvents now works on the data explorer

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -206,6 +206,7 @@
         render: (query, result) =>
             if query? and result?
                 @.$el.html @result_view.render(query, result).el
+                @result_view.delegateEvents()
             else
                 @.$el.html @default_view.render().el

@@ -243,7 +244,6 @@
             'click .jt_arrow': 'toggle_collapse'
             'keypress .jt_editable': 'handle_keypress'
             'blur .jt_editable': 'send_update'
-
             # For Table view
             'mousedown td': 'handle_mousedown'
             'click .jta_arrow_v': 'expand_tree_in_table'
@@ -432,6 +432,7 @@

         expand_tree_in_table: (event) =>
+            console.log 'expand'
             dom_element = @.$(event.target).parent()
             data = dom_element.data('json_data')
             result = @json_to_tree data
@@ -581,7 +582,6 @@
                     type = "json"
                 else
                     type = "table"
-
             switch type
                 when  'json'
                      @.$('.results').html @json_to_tree @current_result
@@ -590,7 +590,9 @@
                 when  'raw'
                      @.$('.results').html @json_to_tree @current_result

+            debugger
             @delegateEvents()
+
             return @

         toggle_collapse: (event) =>


creep_actions: adding more complex warrior and healer

Healer: don't heal yourself
Warrior: run from battle when hurt

--- src/creep_actions.coffee
+++ src/creep_actions.coffee
@@ -1,4 +1,11 @@
 #include util.coffee
+
+
+set_job = (creep, job) ->
+  if creep.memory.job != job
+    LOG(" job: #{creep.name} '#{job}' at #{creep.pos}")
+  creep.memory.job = job
+

 closest_spawn = (creep) -> creep.pos.findNearest(Game.MY_SPAWNS)

@@ -15,39 +22,64 @@

 attack_hostile = (creep, hostile, hostile_type) ->
   if hostile != null
-    creep.memory.attacking = "#{hostile_type} at #{hostile.pos} of " +
-      "#{hostile.owner.username}"
+    set_job(creep, "attack #{hostile_type} of #{hostile.owner.username} at #{hostile.pos}")
     creep.moveTo(hostile)
     creep.attack(hostile)
     return true

-set_job = (creep, job) ->
-  if creep.memory.job != job
-    LOG("#{creep.name} '#{job}' at #{creep.pos}")
-  creep.memory.job = job
+run_from_battle = (creep) ->
+  # Run from battle when more than 75% hurt
+  if creep.hits > creep.hitsMax * .75
+    return false
+  set_job(creep, 'run from battle')
+  # TODO
+  return_to_base(creep)
+  return true
+
+
+attack_enemy = (creep) ->
+  hostile = creep.pos.findNearest(Game.HOSTILE_CREEPS)
+  return attack_hostile(creep, hostile, "creep")
+
+
+attack_buildings = (creep) ->
+  hostile = creep.pos.findNearest(Game.HOSTILE_SPAWNS)
+  return attack_hostile(creep, hostile, "spawn")
+
+
+return_to_base =  (creep) ->
+  return_to_closest_spawn(creep)
+
+
+do_warrior_actions = (creep) ->
+  if run_from_battle(creep)
+    delete creep.memory.attacking
+    return
+  if attack_enemy(creep)
+    return
+  if attack_buildings(creep)
+    return
+  delete creep.memory.attacking
+  if chill_at_base(creep)
+    return

 run_creep_actions = () ->
   for _, creep of Game.creeps
     if creep.getActiveBodyparts(Game.HEAL) > 0
-      hurt_creeps = (c for _, c of Game.creeps when c.hits < c.hitsMax)
+      hurt_creeps = (c for _, c of Game.creeps when c != creep and c.hits < c.hitsMax)
       if hurt_creeps.length == 0
         creep.moveTo(closest_spawn(creep))
         continue
       creep_to_heal = hurt_creeps[0]
-      LOG("#{creep.name} healing #{creep_to_heal.name}")
+      set_job(creep, "healing #{creep_to_heal.name}")
       creep.moveTo(creep_to_heal)
       creep.heal(creep_to_heal)
       continue
-    if creep.getActiveBodyparts(Game.ATTACK) > 0
-      hostile = creep.pos.findNearest(Game.HOSTILE_CREEPS)
-      if attack_hostile(creep, hostile, "creep")
-        continue
-      hostile = creep.pos.findNearest(Game.HOSTILE_SPAWNS)
-      if attack_hostile(creep, hostile, "spawn")
-        continue
-      delete creep.memory.attacking
+    if creep.memory.role == 'warrior'
+      do_warrior_actions(creep)
+      continue
     if creep.memory.role == 'harvester'
       if creep.energy >= creep.energyCapacity
         return_to_closest_spawn(creep)