fixed spelling of communication

fixed spelling of communication

--- app/scripts/controllers/main.coffee
+++ app/scripts/controllers/main.coffee
@@ -123,7 +123,7 @@
                 'IRC':
                     name: 'Communication Data'
                     src: 'views/examples/irc.html'
-                    desc: "Seven minutes of commication between members of the AngularJS, EmberJS, and KnockoutJS IRC channels."
+                    desc: "Seven minutes of communication between members of the AngularJS, EmberJS, and KnockoutJS IRC channels."

                 'Philosophy_Example':
                     name: 'Philosophers\' Relatedness'


Prevent regional dashboard data from modifying statusColor

- Fixes extra radio buttons showing up on the national dashboard

Conflicts:
	app/scripts/regdashboardctrl.coffee

--- app/scripts/regdashboardctrl.coffee
+++ app/scripts/regdashboardctrl.coffee
@@ -2,6 +2,16 @@

   .controller 'RegionalDashboardCtrl', ($scope, $http, $q, $filter, Map,
                                         gettext, modalSpinner, populationData) ->
+    # should http calls be cached
+    # FIXME: should be application level setting
+    cacheHttp = false
+
+    # FIXME: create our own statusColor version to prevent weird values
+    # from modifying the original one
+    stColor = d3.scale.ordinal()
+      .domain(statusColor.domain())
+      .range(statusColor.range());
+
     $scope.gridsterOpts =
       margins: [10, 10]
       columns: 12
@@ -386,7 +396,7 @@
         pieChart quantityChart, quantities, quantities.group(), all
         pieChart qualityChart, qualities, qualities.group(), all
         pieChart extractionChart, extractionTypes, extractionTypes.group(), all
-        pieChart statusChart, statuses, statusGroup, all, statusColor
+        pieChart statusChart, statuses, statusGroup, all, stColor
         pieChart paymentChart, paymentTypes, paymentGroup, all
         pieChart installerChart, installers, installersGroup, all
         pieChart funderChart, funders, fundersGroup, all
@@ -487,7 +497,7 @@
         .stack(group, "Not Functional", (d) -> d.value["not functional"])
         .x(d3.scale.ordinal())
         .xUnits(dc.units.ordinal)
-        .colors(statusColor)
+        .colors(stColor)
         .elasticY(true)
         .elasticX(true)
         .gap(gap || 10)
@@ -537,7 +547,7 @@
         .valueAccessor((p) -> p.value.functional)
         .stack(group, "Needs Repair", (d) -> d.value["needs repair"])
         .stack(group, "Not Functional", (d) -> d.value["not functional"])
-        .colors(statusColor)
+        .colors(stColor)
         .elasticY(true)
         .elasticX(true)
         .gap(1)


change characteristics

change characteristics

changes possible categories and values for gorongosa


sneaks artist page impression back in

sneaks artist page impression back in

--- apps/artist/client/index.coffee
+++ apps/artist/client/index.coffee
@@ -4,6 +4,7 @@
 Artist = require '../../../models/artist.coffee'
 CurrentUser = require '../../../models/current_user.coffee'
 ArtistRouter = require './router.coffee'
+{ track } = require '../../../lib/analytics.coffee'

 module.exports.init = ->
   artist = new Artist ARTIST
@@ -13,3 +14,5 @@

   router = new ArtistRouter model: artist, user: user
   Backbone.history.start pushState: true
+
+  track.impression 'Artist page', id: artist.id


Annotated flash msgs

Annotated flash msgs


Hide popup with 'leave shared VM' label, after opening popup with confirmation

--- client/app/lib/components/sidebarmachineslistitem/leavesharedmachinewidget.coffee
+++ client/app/lib/components/sidebarmachineslistitem/leavesharedmachinewidget.coffee
@@ -11,18 +11,32 @@
   @defaultProps =
     className : 'Approved'

+  constructor: (props) ->
+
+    super
+
+    @state = {
+      isModalActive : true
+    }

   onLeaveClicked: ->

+    console.log('@state ', @state)
+    @setState {isModalActive: false}
     modal = new ContentModal
       title   : 'Are you sure?'
       content : "
This will remove the shared VM from your sidebar. You won't be able to access it unless you are invited again.
"
       cssClass : 'content-modal'
+      cancel : =>
+        @setState {isModalActive: true}
+
       buttons :
         No         :
           title    : 'Cancel'
           cssClass : 'solid cancel medium'
-          callback : -> modal.destroy()
+          callback : =>
+            @setState {isModalActive: true}
+            modal.destroy()
         Yes        :
           title    : 'Yes'
           cssClass : 'solid medium'
@@ -30,9 +44,8 @@
           callback : =>
             actions.rejectInvitation @props.machine
             Tracker.track Tracker.VM_LEFT_SHARED
+            @setState {isModalActive: true}
             modal.destroy()
-
-

   render: ->

@@ -40,13 +53,15 @@
     then 'LEAVE SESSION'
     else 'LEAVE SHARED VM'

-    <SidebarWidget {...@props}>
+    if @state.isModalActive
+      <SidebarWidget {...@props}>

Shared with you by

-      <InvitationWidgetUserPart
-        owner={@props.machine.get 'owner'}
-       />
+        <InvitationWidgetUserPart
+          owner={@props.machine.get 'owner'}
+        />
       <button className='kdbutton GenericButton' onClick={@bound 'onLeaveClicked'}>
         {buttonText}
       </button>
-    </SidebarWidget>
-
+      </SidebarWidget>
+    else
+      null


hide follow / unfollow button from guests

--- client/landing/app/Applications/Topics.kdapplication/ContentDisplays/Topic.coffee
+++ client/landing/app/Applications/Topics.kdapplication/ContentDisplays/Topic.coffee
@@ -79,35 +79,38 @@

   constructor:(options, data)->

-    @followButton = new KDToggleButton
-      style           : if data.followee then "kdwhitebtn following-topic" else "kdwhitebtn"
-      title           : "Follow"
-      dataPath        : "followee"
-      defaultState    : if data.followee then "Following" else "Follow"
-      loader          :
-        color         : "#333333"
-        diameter      : 18
-        top           : 11
-      states          : [
-        title         : "Follow"
-        callback      : (callback)->
-          data.follow (err, response)=>
-            data.followee = yes
-            @hideLoader()
-            unless err
-              @setClass 'following-btn following-topic'
-              callback? null
-      ,
-        title         : "Following"
-        callback      : (callback)->
-          data.unfollow (err, response)=>
-            data.followee = no
-            @hideLoader()
-            unless err
-              @unsetClass 'following-btn following-topic'
-              callback? null
-      ]
-    , data
+    if KD.whoami().type is 'unregistered'
+      @followButton = new KDView
+    else
+      @followButton = new KDToggleButton
+        style           : if data.followee then "kdwhitebtn following-topic" else "kdwhitebtn"
+        title           : "Follow"
+        dataPath        : "followee"
+        defaultState    : if data.followee then "Following" else "Follow"
+        loader          :
+          color         : "#333333"
+          diameter      : 18
+          top           : 11
+        states          : [
+          title         : "Follow"
+          callback      : (callback)->
+            data.follow (err, response)=>
+              data.followee = yes
+              @hideLoader()
+              unless err
+                @setClass 'following-btn following-topic'
+                callback? null
+        ,
+          title         : "Following"
+          callback      : (callback)->
+            data.unfollow (err, response)=>
+              data.followee = no
+              @hideLoader()
+              unless err
+                @unsetClass 'following-btn following-topic'
+                callback? null
+        ]
+      , data

     super
     unless data.followee


simplify error handling for sass

simplify error handling for sass

--- app/templates/gulp/tasks/css.coffee
+++ app/templates/gulp/tasks/css.coffee
@@ -1,6 +1,5 @@
 gulp         = require "gulp"
 autoprefixer = require "gulp-autoprefixer"
-plumber      = require "gulp-plumber"
 sass         = require "gulp-sass"
 sourcemaps   = require "gulp-sourcemaps"

@@ -8,18 +7,12 @@

 conf = require '../gulpconfig'

-handleError = (err) ->
-  console.log err.toString()
-  if global.isWatching then @emit('end') else process.exit(1)
-
-

 gulp.task "css", () ->
   gulp.src(["#{conf.path.dev.scss}/**/*.{scss,sass}"])
-    .pipe plumber(conf.plumber)
-    .pipe sass({errLogToConsole: true}).on('error', handleError)
+    .pipe(sass().on('error', sass.logError))
     .pipe autoprefixer({browsers: ['last 2 versions']})
     .pipe gulp.dest(conf.path.dev.css)
     .pipe global.browserSync.stream match: '**/*.css'


Update Spellbook.equalHeights component

Update Spellbook.equalHeights component

--- components/equalHeights.coffee
+++ components/equalHeights.coffee
@@ -5,21 +5,49 @@
 #
 # *************************************

-@Spellbook.equalHeights = (options) ->
-  settings = $.extend(
-    element: $('.js-equalHeight')
-  , options)
+@Spellbook.equalHeights = do ->

-  heights = []
-  settings.element.each -> heights.push $(@).height()
+  # -------------------------------------
+  #   Private Variables
+  # -------------------------------------

-  height = Math.max.apply( Math, heights )
+  _settings = {}
+  _heights  = []

-  settings.element.css( 'height', height )
+  # -------------------------------------
+  #   Initialize
+  # -------------------------------------
+
+  init = (options) ->
+    _settings = $.extend(
+      element: $('.js-equalHeight')
+    , options)
+
+    _setHeight()
+    _setEventHandlers()
+
+
+  _setHeight = ->
+    _settings.element.css( 'height', 'auto' )
+
+    _settings.element.each -> _heights.push $(@).height()
+
+    height = Math.max.apply( Math, _heights )
+
+    _settings.element.css( 'height', height )
+
+  _setEventHandlers = ->
+    $(window).on 'resize', _setHeight
+
+  # -------------------------------------
+  #   Public Methods
+  # -------------------------------------
+
+  init: init

 # -------------------------------------
 #   Usage
 # -------------------------------------
 #
-# Spellbook.equalHeights()
+# Spellbook.equalHeights.init()
 #


Fix tests

Fix tests

--- assets/scripts/spec/job_spec.coffee
+++ assets/scripts/spec/job_spec.coffee
@@ -51,22 +51,20 @@
       responseTime: 0
       responseText: '1\n2\n3\n4\n5\n6\n7\n8\n9\n10'

-    Travis.OrderedLog.reopen
-      linesLimit: 5
+    Log.LIMIT = 5

     app 'travis-ci/travis-core/jobs/2'
     waitFor logRendered

   afterEach ->
-    Travis.OrderedLog.reopen
-      linesLimit: 5000
+    Log.LIMIT = 10000

   it 'is cut after given limit', ->
     displaysLog [
       '12345'
     ]

-    expect( $('#log .cut').text() ).toEqual 'Log was too long to display. Download the the raw version to get the full log.'
-    expect( $('#log .cut a').attr('href') ).toEqual '/jobs/2/log.txt?deansi=true'
+    expect( $('#log-container .warning').text() ).toMatch /This log is too long to be displayed/
+    expect( $('#log-container .warning a').attr('href') ).toEqual '/jobs/2/log.txt?deansi=true'


If not message selected dont try to remove it Disable logs for release After a successful delete, disable messageActionButtons

--- client/landing/app/Applications/Inbox.kdapplication/AppController.coffee
+++ client/landing/app/Applications/Inbox.kdapplication/AppController.coffee
@@ -3,7 +3,7 @@
   {race} = bongo

   constructor:(options, data)->
-    view = new (KD.getPageClass 'Inbox') cssClass : "inbox-application"
+    view = new (KD.getPageClass 'Inbox') cssClass : "inbox-application"
     options = $.extend {view},options
     super options,data
     @selection = {}
@@ -13,32 +13,32 @@
       options :
         name : 'Inbox'
       data : @mainView
-
+
   initAndBringToFront:(options,callback)->
     initApplication options, ->
-      @bringToFront()
+      @bringToFront()
       callback()
-
+
   initApplication:(options, callback)->
     callback()
-
+
   fetchMessages:(options, callback)->
     # log "FETCH MESSAGES INTERNAL"
     {currentDelegate} = KD.getSingleton('mainController').getVisitor()
     currentDelegate.fetchMail? options, callback
-
+
   fetchAutoCompleteForToField:(inputValue,blacklist,callback)->
     bongo.api.JAccount.byRelevance inputValue,{blacklist},(err,accounts)->
-      callback accounts
+      callback accounts

   loadView:(mainView)->
     mainView.createCommons()
     mainView.createTabs()
-
+
     {currentDelegate} = KD.getSingleton('mainController').getVisitor()

-    currentDelegate.fetchPrivateChannel (err, channel)->
-      console.log channel
+    # currentDelegate.fetchPrivateChannel (err, channel)->
+    #   console.log channel

     # mainView.inboxMessagesList.on 'ReceivedClickElsewhere', =>
     #   @deselectMessages()
@@ -62,7 +62,7 @@
     #     {reply, message} = options
     #     message.reply reply, (err)->
     #       console.log err
-
+
     mainView.registerListener
       KDEventTypes      : 'NotificationIsSelected'
       listener          : @
@@ -91,38 +91,42 @@
             hiddenHandle: yes
           mainView.inboxMessagesContainer.addPane paneView
           detail = new InboxMessageDetail cssClass : "message-detail", data
-
+
           detail.registerListener
             KDEventTypes: 'viewAppended'
             listener: @
             callback: =>
               data.restComments 0, (err, comments)-> # log arguments, data
-
+
           paneView.addSubView detail
           paneView.detail = detail
           item.paneView = paneView

         @selectMessage data, item, paneView
-
+
     {newMessageBar} = mainView

     newMessageBar.registerListener
       KDEventTypes  : "AutoCompleteNeedsMemberData"
       listener      : @
-      callback      : (pubInst,event)=>
+      callback      : (pubInst,event)=>
         {callback,inputValue,blacklist} = event
         @fetchAutoCompleteForToField inputValue,blacklist,callback

     newMessageBar.registerListener
       KDEventTypes  : 'MessageShouldBeSent'
       listener      : @
-      callback      : (pubInst,{formOutput,callback})->
+      callback      : (pubInst,{formOutput,callback})->
         @prepareMessage formOutput,callback

     newMessageBar.registerListener
       KDEventTypes: 'MessageShouldBeDisowned'
       listener    : @
       callback    : do=>
+        if not @selection
+          newMessageBar.disableMessageActionButtons()
+          modal.destroy()
+          return
         disownAll = (items, callback)->
           disownItem = race (i, item, fin)->
             item.data.disown (err)->
@@ -133,29 +137,31 @@
           , callback
           disownItem item for own id, item of items
         (pubInst, modal) =>
-          log modal
+          # log modal
           disownAll @selection, =>
-            log "2", modal
+            # log "2", modal
             for own id, {item, paneView} of @selection
               item.destroy()
               paneView.destroy()
               @deselectMessages()
             modal.destroy()
-
+            newMessageBar.disableMessageActionButtons()
+
     newMessageBar.registerListener
       KDEventTypes: 'MessageShouldBeMarkedAsUnread'
       listener    : @
       callback    : =>
         for own id, {item, data} of @selection
-          log 'marking unread'
-          data.unmark 'read', (err)->
+          # log 'marking unread'
+          data.unmark 'read', (err)=>
             log err if err
             item.setClass 'unread' unless err
-
+            newMessageBar.disableMessageActionButtons()
+
   goToNotifications:(notification)->
     @getView().showTab "notifications"
     @mainView.propagateEvent KDEventType : 'NotificationIsSelected', {item: notification, event} if notification?
-
+
   goToMessages:(message)->
     @getView().showTab "messages"
     @mainView.propagateEvent KDEventType : "MessageIsSelected", {item: message, event} if message?
@@ -169,16 +175,16 @@

   deselectMessages:->
     @selection = {}
-
+
   sendMessage:(messageDetails, callback)->
-    log "I just send a new message: ", messageDetails
+    # log "I just send a new message: ", messageDetails
     bongo.api.JPrivateMessage.create messageDetails, callback
-
+
   prepareMessage:(formOutput, callback)=>
     {body, subject, recipients} = formOutput
-
+
     to = recipients.join ' '
-
+
     @sendMessage {to, body, subject}, (err, message)->
       new KDNotificationView
         title     : if err then "There was an error sending your message - try again" else "Message Sent!"


Disable tests because of problems with phantomjs

--- common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.coffee
+++ common/lib/xmodule/xmodule/js/spec/videoalpha/display/html5_video.coffee
@@ -52,7 +52,7 @@
     it 'check if click event is handled on the player', ->
         expect(@videoEl).toHandle 'click'

-  describe 'events:', ->
+  xdescribe 'events:', ->

     beforeEach ->
       spyOn(@player, 'callStateChangeCallback').andCallThrough()
@@ -201,7 +201,7 @@
           jasmine.fireEvent @videoEl, "timeupdate"
           expect(@player.end).toBe @videoEl.duration

-  describe 'methods:', ->
+  xdescribe 'methods:', ->

     beforeEach ->
       waitsFor ( ->


Improve todo of filter spec

Improve todo of filter spec

--- spec/filter-spec.coffee
+++ spec/filter-spec.coffee
@@ -1,5 +1,9 @@
-# TODO
+# TODO
+
 ###
+expect = require('chai').expect
+Phone = require('./helpers/require-all')
+
 describe "front.phone filter", ->

 	beforeEach module("vtex.phoneFilter")


Add specs for resolving multiple matching bindings for an element

--- spec/keymap-spec.coffee
+++ spec/keymap-spec.coffee
@@ -50,6 +50,47 @@
         expect(events[0].type).toBe 'x-command'
         expect(events[0].target).toBe elementB

+  describe "when the keystroke matches multiple bindings on the same element", ->
+    [elementA, elementB, events] = []
+
+    beforeEach ->
+      elementA = appendContent $$ ->
+        @div class: 'a', ->
+          @div class: 'b c d'
+      elementB = elementA.querySelector('.b')
+
+      events = []
+      elementA.addEventListener 'command-1', ((e) -> events.push(e)), false
+      elementA.addEventListener 'command-2', ((e) -> events.push(e)), false
+
+    describe "when the bindings have selectors with different specificity", ->
+      beforeEach ->
+        keymap.addKeyBindings "test",
+          ".b.c":
+            "ctrl-x": "command-1"
+          ".b":
+            "ctrl-x": "command-2"
+
+      it "dispatches the command associated with the most specific binding", ->
+        keymap.handleKeyboardEvent(keydownEvent('x', ctrl: true, target: elementB))
+        expect(events.length).toBe 1
+        expect(events[0].type).toBe 'command-1'
+        expect(events[0].target).toBe elementB
+
+    describe "when the bindings have selectors with the same specificity", ->
+      beforeEach ->
+        keymap.addKeyBindings "test",
+          ".b.c":
+            "ctrl-x": "command-1"
+          ".c.d":
+            "ctrl-x": "command-2"
+
+      it "dispatches the command associated with the most recently added binding", ->
+        keymap.handleKeyboardEvent(keydownEvent('x', ctrl: true, target: elementB))
+        expect(events.length).toBe 1
+        expect(events[0].type).toBe 'command-2'
+        expect(events[0].target).toBe elementB
+
   describe "::addKeyBindings(source, bindings)", ->
     it "normalizes keystrokes containing capitalized alphabetic characters", ->
       keymap.addKeyBindings 'test', '*': 'ctrl-shift-l': 'a'


Fix test: return error when the current branch check out to a commit.

--- src/ref.coffee
+++ src/ref.coffee
@@ -40,7 +40,12 @@
   @current: (repo, callback) ->
     fs.readFile "#{repo.dot_git}/HEAD", (err, data) ->
       return callback err if err
-      [m, branch] = /ref: refs\/heads\/([^\s]+)/.exec data
+
+      ref = /ref: refs\/heads\/([^\s]+)/.exec data
+      # When the current branch check out to a commit, instaed of a branch name.
+      return callback new Error "Current branch is not a valid branch." if !ref
+
+      [m, branch] = ref
       fs.readFile "#{repo.dot_git}/refs/heads/#{branch}", (err, id) ->
         Commit.find repo, id, (err, commit) ->
           return callback err if err


Go to dashboardRoute, closes #25

Go to dashboardRoute, closes #25

--- sign-in/signIn.coffee
+++ sign-in/signIn.coffee
@@ -26,5 +26,5 @@
       if error
         Session.set('entryError', error.reason)
       else
-        Router.go('/')
+        Router.go Session.get('entrySettings').dashboardRoute
     )


Fix translation in different pages context

--- assets/js/src/i18n/index.coffee
+++ assets/js/src/i18n/index.coffee
@@ -1,4 +1,5 @@
 $ ->
+
   selectors = [
     # newsletter modal
     '.subscribe-banner'
@@ -16,20 +17,23 @@
     '.prefix-date'
   ]

-  $(selector).html(i18n[selector]) for selector in selectors when i18n[selector]
-
-  # inline css
-  selector = '.read-next-story .post:before'
-  property = "<style>.read-next-story .post:before{ content: '#{i18n[selector]}' }</style>"
-  $('head').append(property)
-
-  # pagination
-  selector = '.page-number'
-  pageNumber = $(selector).html().split(' ')
-  pageNumberi18n = i18n[selector].split(' ')
-  pageNumber[n] = pageNumberi18n[n] for n in [0, 2]
-  $(selector).html(pageNumber.join(' '))
+  $(selector).html(i18n[selector]) for selector in selectors

   # forms
   window.fieldEmail.placeholder = i18n.fieldEmail
   window.newsletter_subscribe.value = i18n.newsletter_subscribe
+
+  if Bloggy.is 'page', 'post'
+    # read-next inline css
+    selector = '.read-next-story .post:before'
+    property = "<style>.read-next-story .post:before{ content: '#{i18n[selector]}' }</style>"
+    $('head').append(property)
+  else
+    # pagination
+    selector = '.page-number'
+    pageNumber = $(selector).html().split(' ')
+    pageNumberi18n = window.i18n[selector].split(' ')
+    pageNumber[n] = pageNumberi18n[n] for n in [0, 2]
+    $(selector).html pageNumber.join(' ')
+
+


run: dont retry unhanded errors

run: dont retry unhanded errors

--- src/run.coffee
+++ src/run.coffee
@@ -93,7 +93,9 @@
                   merge action, action.callback.call ctx, ctx, (err, statusOrMsg) =>
                     # actionRun.end() if statusOrMsg is ctx.STOP
                     done err, statusOrMsg
-              catch e then done e
+              catch e
+                retry = false # Dont retry unhandled errors
+                done e
             # Timeout, default to 100s
             action.timeout ?= 100000
             if action.timeout > 0


add Firefox keyCodes, dynamically inc panable area

--- app/components/frame-viewer.cjsx
+++ app/components/frame-viewer.cjsx
@@ -33,15 +33,15 @@

   componentDidMount: ->
     if @props.project? && 'pan and zoom' in @props.project?.experimental_tools
-      # these events enable a user to navigate an image using arrows, +, and - keys,
+      # these events enable a user to navigate an image using arrows, +, and - keys,
       # while the user is in pan and zoom mode.
       addEventListener "keydown", @frameKeyPan
-      addEventListener "mousewheel", @frameKeyPan
+      addEventListener "wheel", @frameKeyPan

   componentWillUnmount: ->
     if @props.project? && 'pan and zoom' in @props.project?.experimental_tools
       removeEventListener "keydown", @frameKeyPan
-      removeEventListener "mousewheel", @frameKeyPan
+      removeEventListener "wheel", @frameKeyPan

   render: () ->
     subject = @props.subject
@@ -82,30 +82,30 @@

-              <button
-                title={ "zoom out" }
-                className={"zoom-out fa fa-minus" + if @cannotZoomOut() then " disabled" else "" }
-                onMouseDown={ @continuousZoom.bind(this, 1.1 ) }
-                onMouseUp={@stopZoom}
-                onKeyDown={@keyDownZoomButton.bind(this,1.1)}
+              <button
+                title={ "zoom out" }
+                className={"zoom-out fa fa-minus" + if @cannotZoomOut() then " disabled" else "" }
+                onMouseDown={ @continuousZoom.bind(this, 1.1 ) }
+                onMouseUp={@stopZoom}
+                onKeyDown={@keyDownZoomButton.bind(this,1.1)}
                 onKeyUp={@stopZoom}
               />

-              <button
-                title={ "zoom in" }
-                className={ "zoom-in fa fa-plus" }
-                onMouseDown={@continuousZoom.bind(this, .9)}
-                onMouseUp={@stopZoom}
+              <button
+                title={ "zoom in" }
+                className={ "zoom-in fa fa-plus" }
+                onMouseDown={@continuousZoom.bind(this, .9)}
+                onMouseUp={@stopZoom}
                 onKeyDown={@keyDownZoomButton.bind(this,.9)}
-                onKeyUp={@stopZoom}
+                onKeyUp={@stopZoom}
               />

               <button title={"reset zoom levels"} className={"reset fa fa-refresh" + if @cannotZoomOut() then " disabled" else ""} onClick={ this.zoomReset } ></button>

}
-
+

@@ -130,7 +130,7 @@
     @props.onLoad? e, @props.frame

   cannotZoomOut: ->
-    return @state.frameDimensions.width == @state.viewBoxDimensions.width && @state.frameDimensions.height == @state.viewBoxDimensions.height
+    return @state.frameDimensions.width == @state.viewBoxDimensions.width && @state.frameDimensions.height == @state.viewBoxDimensions.height

   continuousZoom: (change) ->
     @clearZoomingTimeout()
@@ -143,11 +143,11 @@
         @zoom(change)
         @clearZoomingTimeout()
         @setState zoomingTimeoutId: setTimeout(zoomNow, 200)
-
+
       zoomNow()

   clearZoomingTimeout: ->
-    if @state.zoomingTimeoutId
+    if @state.zoomingTimeoutId
       clearTimeout @state.zoomingTimeoutId

   zoom: (change) ->
@@ -155,7 +155,7 @@
     return if !@state.zooming
     newNaturalWidth = @state.viewBoxDimensions.width * change
     newNaturalHeight = @state.viewBoxDimensions.height * change
-
+
     newNaturalX = @state.viewBoxDimensions.x - (newNaturalWidth - @state.viewBoxDimensions.width) / 2
     newNaturalY = @state.viewBoxDimensions.y - (newNaturalHeight - @state.viewBoxDimensions.height) / 2

@@ -169,7 +169,7 @@
           height: newNaturalHeight,
           x: newNaturalX,
           y: newNaturalY
-
+
   keyDownZoomButton: (change, e) ->
     # only zoom if a user presses enter on the zoom button.
     if e.which == 13
@@ -184,7 +184,7 @@
   zoomReset: ->
     @setState
       viewBoxDimensions:
-        width: @state.frameDimensions.width,
+        width: @state.frameDimensions.width,
         height: @state.frameDimensions.height,
         x: 0,
         y: 0
@@ -193,10 +193,10 @@
     unless @state.panEnabled
       @setState panEnabled: true, =>
         this.refs.subjectImage.focus()
-
+
   togglePanOff: ->
     @setState panEnabled: false
-
+
   toggleKeyPanZoom: ->
     @setState keyPanZoomEnabled: !@state.keyPanZoomEnabled, =>
       if @state.panEnabled then this.refs.subjectImage.focus()
@@ -204,12 +204,12 @@
   panByDrag: (e, d) ->
     return unless @state.panEnabled

-    maximumX = (@state.frameDimensions.width - @state.viewBoxDimensions.width) + 20
-    minumumX = -20
+    maximumX = (@state.frameDimensions.width - @state.viewBoxDimensions.width) + (@state.frameDimensions.width * 0.6)
+    minumumX = -(@state.frameDimensions.width * 0.6)
     changedX = @state.viewBoxDimensions.x -= d.x

-    maximumY = (@state.frameDimensions.height - @state.viewBoxDimensions.height) + 20
-    minimumY = -20
+    maximumY = (@state.frameDimensions.height - @state.viewBoxDimensions.height) + (@state.frameDimensions.height * 0.6)
+    minimumY = -(@state.frameDimensions.height * 0.6)
     changedY = @state.viewBoxDimensions.y -= d.y

     @setState
@@ -228,35 +228,36 @@
         e.preventDefault()
         @panHorizontal(-20)
       # up
-      when 38
-        e.preventDefault()
-        @panVertical(-20)
+      when 38
+        e.preventDefault()
+        @panVertical(-20)
       # right
-      when 39
-        e.preventDefault()
-        @panHorizontal(20)
+      when 39
+        e.preventDefault()
+        @panHorizontal(20)
       # down
-      when 40
-        e.preventDefault()
-        @panVertical(20)
-      # zoom out
-      when 187
+      when 40
+        e.preventDefault()
+        @panVertical(20)
+      # zoom out - Chrome(187), Firefox(61)
+      when 187, 61
         e.preventDefault()
         @setState zooming: true
-        @zoom(.9)
-      # zoom in
-      when 189
+        @zoom(.9)
+      # zoom in - Chrome(189), Firefox(173)
+      when 189, 173
         e.preventDefault()
         @setState zooming: true
-        @zoom(1.1)
-      # zooming by mousewheel
+        @zoom(1.1)
+      # zooming by wheel
       when 1
         e.preventDefault()
+        @setState zooming: true
         if e.deltaY > 0 then @zoom(.9) else @zoom(1.1)

   panHorizontal:(direction) ->
-    maximumX = (@state.frameDimensions.width - @state.viewBoxDimensions.width) + 20
-    minumumX = -20
+    maximumX = (@state.frameDimensions.width - @state.viewBoxDimensions.width) + (@state.frameDimensions.width * 0.6)
+    minumumX = -(@state.frameDimensions.width * 0.6)
     changedX = @state.viewBoxDimensions.x + direction
     @setState
       viewBoxDimensions:
@@ -266,12 +267,12 @@
         height: @state.viewBoxDimensions.height

   panVertical:(direction)->
-    maximumY = (@state.frameDimensions.height - @state.viewBoxDimensions.height) + 20
-    minimumY = -20
+    maximumY = (@state.frameDimensions.height - @state.viewBoxDimensions.height) + (@state.frameDimensions.height * 0.6)
+    minimumY = -(@state.frameDimensions.height * 0.6)
     changedY = @state.viewBoxDimensions.y + direction
     @setState
       viewBoxDimensions:
-        x: @state.viewBoxDimensions.x
+        x: @state.viewBoxDimensions.x
         y: Math.max(minimumY, Math.min(changedY, maximumY))
         width: @state.viewBoxDimensions.width
         height: @state.viewBoxDimensions.height


enhance: start dynamic scan after yes or no

--- app/components/vnc-viewer.coffee
+++ app/components/vnc-viewer.coffee
@@ -110,6 +110,7 @@
         isApiScanEnabled: isApiScanEnabled
       @get("ajax").post apiScanOptions, data: data
       .then (data)->
+        that.send "dynamicScan"
         that.get("notify").success "Starting the scan"
       .catch (error) ->
         for error in error.errors
@@ -128,7 +129,6 @@
           that.get("notify").error error.detail?.message

     openAPIScanModal: ->
-      @send "dynamicScan"
       @set "showAPIScanModal", true

     closeModal: ->
@@ -153,6 +153,7 @@
       @get("ajax").post apiScanOptions, data: data
       .then (data)->
         that.send "closeModal"
+        that.send "dynamicScan"
         that.get("notify").success "Successfully added the url filter & Starting the scan"
       .catch (error) ->
         for error in error.errors


Work around for lodash -> index

Work around for lodash -> index

--- config/amd/gulp-options.coffee
+++ config/amd/gulp-options.coffee
@@ -6,4 +6,5 @@
     'backbone-orm': {exports: 'BackboneORM', deps: ['backbone', 'stream']}
     'parameters': {exports: '__test__parameters__', deps: ['backbone-orm']}
   post_load: 'window._ = window.Backbone = null; window.BackboneORM = backbone_orm;'
-  aliases: {'lodash': 'underscore'}
+
+  aliases: {'index': 'underscore'} # aliases: {'lodash': 'underscore'} # WARNING: not scalable if multiple files with index name


cosmetic

cosmetic

--- lib/provider-panel.coffee
+++ lib/provider-panel.coffee
@@ -38,16 +38,18 @@
       """
     @container.appendChild(element)

+    # loading
     loadingElement = element.getElementsByClassName('loading')[0]
-    searchTermElement = element.getElementsByClassName('search-term')[0]
-    [@ignoreCaseButton, @wholeWordButton] = element.getElementsByTagName('button')
-
     @ui.onWillRefresh -> loadingElement.classList.remove('hide')
     @ui.onDidRefresh -> loadingElement.classList.add('hide')

+    # searchTerm
+    searchTermElement = element.getElementsByClassName('search-term')[0]
     @ui.grammar.onDidChangeSearchTerm (regexp) ->
       searchTermElement.textContent = regexp?.toString() ? ''

+    # searchOptions
+    [@ignoreCaseButton, @wholeWordButton] = element.getElementsByTagName('button')
     @ignoreCaseButton.addEventListener('click', @toggleSearchIgnoreCase)
     @wholeWordButton.addEventListener('click', @toggleSearchWholeWord)


scan.registrations is an associative object, not an array.

--- app/javascripts/intensity.coffee
+++ app/javascripts/intensity.coffee
@@ -123,7 +123,7 @@

       # Flag indicating whether the T1 scan has more than one
       # realignment.
-      hasMultipleRegs = scan.registrations.length > 1
+      hasMultipleRegs = Object.keys(scan.registration).length > 1

       # Makes the data series {key, color} format object for the
       # given registration.
@@ -153,9 +153,8 @@
         values: coordinates(registration.intensity.intensities)

       # Sort the scan registrations by key.
-      regs = scan.registrations
-      regKeys = _.keys(regs).sort()
-      regsSorted = (regs[key] for key in regKeys)
+      regKeys = Object.keys(scan.registration).sort()
+      regsSorted = (scan.registration[key] for key in regKeys)
       # Collect the data series specifications.
       regData = (registrationDataSeries(reg, i) for reg, i in regsSorted)


Added different in_screen_time for seperate messages

--- app/js/views/notification.coffee
+++ app/js/views/notification.coffee
@@ -53,13 +53,16 @@
   show
     message: 'Factlink posted!'
     type_classes: 'fl-message-success fl-message-icon-logo'
+    in_screen_time: 1000

 Factlink.showShouldSelectTextNotification = ->
   show
     message: 'To create a Factlink, select a statement and click the Factlink button.'
     type_classes: 'fl-message-icon-add'
+    in_screen_time: 3000

 Factlink.showLoadedNotification = ->
   show
     message: 'Factlink is loaded!'
     type_classes: 'fl-message-success fl-message-icon-time'
+    in_screen_time: 3000


Fix GoalManager to return failure in checkOverallStatus

--- app/lib/world/GoalManager.coffee
+++ app/lib/world/GoalManager.coffee
@@ -134,6 +134,7 @@
       overallStatus = if _.all(levelStatuses, isSuccess) and _.any(conceptStatuses, isSuccess) then 'success' else 'failure'
     else
       overallStatus = 'success' if statuses.length > 0 and _.every(statuses, isSuccess)
+      overallStatus = 'failure' if statuses.length > 0 and 'failure' in statuses
     overallStatus

   # WORLD GOAL TRACKING


#105 post review stuff

#105 post review stuff

--- buildengine/minify.coffee
+++ buildengine/minify.coffee
@@ -64,17 +64,16 @@
 				lines = data.split(/\s+/)

 				if lines.length > 1
-					pattern = '{'+lines.join(',')+'}'
+					pathsToInclude = '{'+lines.join(',')+'}'
 				else
-					pattern = lines[0]
+					pathsToInclude = lines[0]

-				# REVIEW: It's not very clear what this line is supposed to do.
-				#         Partly since the `pattern` variable name is confusing.
-				if /^(\{[\s,]*\}|\s*)$/.test pattern
+				# If there are no paths to include
+				if /^(\{[\s,]*\}|\s*)$/.test pathsToInclude
 					return console.log 'No files to include'

 				glob = require('glob')
-				glob pattern, {root: absroot}, (err, files) ->
+				glob pathsToInclude, {root: absroot}, (err, files) ->
 					if(files.length == 0)
 						console.log 'No files found'
 						process.exit


Redo ext list

Redo ext list

--- plugins/webpack/webpack.coffee
+++ plugins/webpack/webpack.coffee
@@ -4,6 +4,9 @@

 values = (map)->
   v for k, v of map
+
+brk = (s)->
+  s.split ' '

 module.exports = (BasePlugin) ->
   BasePlugin.extend
@@ -21,7 +24,7 @@
             test: /[.](litcoffee|coffee[.]md)$/
             loader: "coffee-loader?literate"
       resolve:
-        extensions: ["", ".js", ".coffee", ".litcoffee", ".coffee.md"]
+        extensions: brk " .js .coffee .litcoffee .coffee.md"
       plugins: values
         minimize: new webpack.optimize.UglifyJsPlugin
         reorder: new webpack.optimize.OccurenceOrderPlugin


JVerificationToken: provide different email content for different actions

--- workers/social/lib/social/models/verificationtoken.coffee
+++ workers/social/lib/social/models/verificationtoken.coffee
@@ -58,7 +58,7 @@

           if createdAt < 20 # min
             if resendIfExists
-              confirmation.sendEmail {subject, firstName}, callback
+              confirmation.sendEmail {subject, firstName, action}, callback
             else
               callback if err then err else new PINExistsError \
                 "PIN exists and not expired,
@@ -68,21 +68,22 @@

         # Remove all waiting pins for given action and email
         @remove {username, action}, (err, count)->
-          if err then console.warn err
-          else
-            if count > 0 then console.log "#{count} expired PIN removed."
-            else console.log "No such waiting PIN found."

-            # Create a random pin
-            pin = hat 16
+          return console.warn err  if err

-            # Create and send new pin
-            confirmation = new JVerificationToken {username, action, email, pin}
-            confirmation.save (err)->
-              if err
-                callback err
-              else
-                confirmation.sendEmail {subject, firstName}, callback
+          if count > 0 then console.log "#{count} expired PIN removed."
+          else console.log "No such waiting PIN found."
+
+          # Create a random pin
+          pin = hat 16
+
+          # Create and send new pin
+          confirmation = new JVerificationToken {username, action, email, pin}
+          confirmation.save (err)->
+            if err
+              callback err
+            else
+              confirmation.sendEmail {subject, firstName, action}, callback

   @confirmByPin = (options, callback)->
@@ -100,7 +101,52 @@
         callback null, false

-  sendEmail: ({subject, firstName}, callback)->
+  getTextBody = ({firstName, pin, action})->
+
+    templates =
+
+      'verify-account': """
+        Hi #{firstName},
+
+        Welcome to Koding!
+
+        You can use the following code to verify your koding.com account:
+
+          #{pin}
+
+        Have a nice day!
+        --
+        Koding Team
+      """
+
+      'update-email'  : """
+        Hi #{firstName},
+
+        To verify your new e-mail address you can use the following code:
+
+          #{pin}
+
+        Have a nice day!
+        --
+        Koding Team
+      """
+
+      default         : """
+        Hi #{firstName},
+
+        Here’s your koding.com verification code:
+
+          #{pin}
+
+        Have a nice day!
+        --
+        Koding Team
+      """
+
+    return templates[action] or templates.default
+
+
+  sendEmail: ({subject, firstName, action}, callback)->

     JMail = require './email'

@@ -108,28 +154,12 @@
       from    : 'hello@koding.com'
       email   : @email
       subject : subject
-      content : @getTextBody firstName, @pin
+      content : getTextBody {firstName, @pin, action}
       force   : yes

     console.log "Pin (#{@pin}) sent to #{@email} for #{@action} action."

     email.save callback
-
-
-  getTextBody: (firstName, plainPin)->
-
-    """
-    Hi #{firstName},
-
-    Here’s your koding.com verification code:
-
-      #{plainPin}
-
-    Have a nice day!
-    --
-    Koding Team
-
-    """

   @invalidatePin = (options, callback)->


corrects fetch_cart urls Fixes #5325

corrects fetch_cart urls
Fixes #5325

--- frontend/app/assets/javascripts/spree/frontend/cart.js.coffee
+++ frontend/app/assets/javascripts/spree/frontend/cart.js.coffee
@@ -10,6 +10,6 @@

 Spree.fetch_cart = ->
   $.ajax
-    url: Spree.pathFor("/cart_link"),
+    url: Spree.pathFor("cart_link"),
     success: (data) ->
       $('#link-to-cart').html data


fix when going from homepage to proposal, read long description no longer works

Former-commit-id: 6c9de4a7838638e7842fba54ef4898a3643f6b0b [formerly 6744a3037371f44fc108dde1732b4240b6a821ff] [formerly d9bcc42e588ac3b8b9c920f59d511ca9eb6cf201 [formerly d9bcc42e588ac3b8b9c920f59d511ca9eb6cf201 [formerly 32399235d6b03a44a35d286a40342ae26405d8e6]]]
Former-commit-id: 40076d5e85eea8b2044f07aa22c30a28c1576bbe [formerly 045f06b3b5a33b0f13822b904cd123ad2156c7c7]
Former-commit-id: 68e8e6f30d725aa892289fc8cfb8e41a8b69325a
Former-commit-id: 58c2c30b7342ea038beec18b25625aa341a02b94

--- app/assets/javascripts/backbone/apps/franklin/proposal/description/description_controller.coffee
+++ app/assets/javascripts/backbone/apps/franklin/proposal/description/description_controller.coffee
@@ -27,7 +27,6 @@

     transition : (region, view) ->
       if @state != Proposal.DescriptionState.collapsed && @prior_state == Proposal.DescriptionState.collapsed
-        region.$el.empty().append view.el
         view.ui.details.hide()
         region.$el.empty().append view.el
         view.ui.details.slideDown 1400


Avoid selecting deleted beatmap as default

--- resources/assets/coffee/_classes/beatmap-helper.coffee
+++ resources/assets/coffee/_classes/beatmap-helper.coffee
@@ -18,7 +18,8 @@

 class @BeatmapHelper
   @default: ({group, items, mode}) =>
-    return _.last(items) if items?
+    if items?
+      return _.findLast items, (i) -> !i.deleted_at?

     return unless group?


State magic

State magic

--- resources/assets/coffee/react/profile-page/main.coffee
+++ resources/assets/coffee/react/profile-page/main.coffee
@@ -23,6 +23,10 @@
   mixins: [StickyTabsMixin, ScrollingPageMixin]

   getInitialState: ->
+    savedStateString = document.body.dataset.profilePageState
+    if savedStateString?
+      return JSON.parse(savedStateString)
+
     optionsHash = ProfilePageHash.parse location.hash
     @initialPage = optionsHash.page

@@ -74,6 +78,8 @@
     for sortable in ['pages', 'tabs']
       $(@refs[sortable]).sortable 'destroy'

+    document.body.dataset.profilePageState = JSON.stringify(@state)
+

   render: ->
     rankHistories = @props.allRankHistories[@state.currentMode]


revert

revert

--- app/scripts/app.coffee
+++ app/scripts/app.coffee
@@ -30,8 +30,8 @@
   'directives.horiz_graph'
   'directives.manage_users'
 ])
-.constant("API_PREFIX", "http://api.cobudget.enspiral.info/cobudget")
-#.constant("API_PREFIX", "http://localhost:9292/cobudget")
+#.constant("API_PREFIX", "http://api.cobudget.enspiral.info/cobudget")
+.constant("API_PREFIX", "http://localhost:9292/cobudget")
 .config(["$httpProvider", '$urlRouterProvider', '$sceDelegateProvider', 'RestangularProvider', 'API_PREFIX', ($httpProvider, $urlRouterProvider, $sceDelegateProvider, RestangularProvider, API_PREFIX)->
   $urlRouterProvider.otherwise('/')
   RestangularProvider.setBaseUrl(API_PREFIX)


Fixed a typo

Fixed a typo

--- src/core/actions.coffee
+++ src/core/actions.coffee
@@ -66,7 +66,7 @@
       newShapes = []
       for shape in @lc.shapes
         newShapes.push(shape) if shape.id != @shape.id
-      lc.shapes = newShapes
+      @lc.shapes = newShapes
     @lc.repaintLayer('main')


check if user exists on recurly callback

--- services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee
+++ services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee
@@ -70,6 +70,8 @@
 			return callback(error) if error?
 			User.findById recurlySubscription.account.account_code, (error, user) ->
 				return callback(error) if error?
+				if !user?
+					return callback("no user found")
 				SubscriptionUpdater.syncSubscription recurlySubscription, user?._id, callback


Stub V1 project getter

Stub V1 project getter

--- services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee
+++ services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee
@@ -389,6 +389,7 @@
 			@TagsHandler.getAllTags.callsArgWith(1, null, @tags, {})
 			@NotificationsHandler.getUserNotifications = sinon.stub().callsArgWith(1, null, @notifications, {})
 			@ProjectGetter.findAllUsersProjects.callsArgWith(2, null, @allProjects)
+			@V1ProjectGetter.findAllUsersProjects.callsArg(1) # Without integration module cb returns without args

 		it "should render the project/list page", (done)->
 			@res.render = (pageName, opts)=>


Include token based

Include token based

--- services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee
+++ services/web/test/UnitTests/coffee/Project/ProjectControllerTests.coffee
@@ -282,7 +282,7 @@

 		it "should send the projects", (done)->
 			@res.render = (pageName, opts)=>
-				opts.projects.length.should.equal (@projects.length + @collabertions.length + @readOnly.length)
+				opts.projects.length.should.equal (@projects.length + @collabertions.length + @readOnly.length + @tokenReadAndWrite.length + @tokenReadOnly.length)
 				done()
 			@ProjectController.projectListPage @req, @res

@@ -313,7 +313,14 @@

 			it 'should include V1 projects', (done) ->
 				@res.render = (pageName, opts) =>
-					opts.projects.length.should.equal (@projects.length + @collabertions.length + @readOnly.length + @V1Response.projects.length)
+					opts.projects.length.should.equal (
+						@projects.length +
+						@collabertions.length +
+						@readOnly.length +
+						@tokenReadAndWrite.length +
+						@tokenReadOnly.length +
+						@V1Response.projects.length
+					)
 					opts.projects.forEach (p) ->
 						# Check properties correctly mapped from V1
 						expect(p).to.have.property 'id'


Log info for parse errors from generated CCSS.

--- src/gss-compiler.coffee
+++ src/gss-compiler.coffee
@@ -77,6 +77,12 @@
               try
                 subParsed = ccss.parse(ccssRule)
               catch error
+                info = []
+                info.push 'Generated by VFL statement:'
+                info.push ''
+                info.push "@#{chunk.name} #{chunk.terms}"
+                console.info info.join '\n'
+
                 error.name = 'VFL generated CCSS parse error'
                 throw error
               parsed.selectors = parsed.selectors.concat(subParsed.selectors)


Simplify spec object

Simplify spec object

--- spec/cson-spec.coffee
+++ spec/cson-spec.coffee
@@ -79,12 +79,12 @@
   describe "when converting back to an object", ->
     it "produces the original object", ->
       object =
-        showInvisibles: true
-        fontSize: 20
-        core:
-          themes: ['a', 'b']
-        whitespace:
-          ensureSingleTrailingNewline: true
+        a: true
+        b: 20
+        c:
+          d: ['a', 'b']
+        e:
+          f: true

       cson = CSON.stringify(object)
       CoffeeScript = require 'coffee-script'


Minor

Minor

--- client/Pricing/AppController.coffee
+++ client/Pricing/AppController.coffee
@@ -47,7 +47,7 @@
     @getView().addWorkflow workflow = KD.singleton("paymentController").createUpgradeWorkflow
       productForm: productForm

-    # # TODO : remove after test
+    # TODO : remove after test
     # workflow.emit "Finished",
     #     productData          :
     #       planOptions        :


terminal: set key focus of terminal on non copy-paste keystrokes

--- client/Terminal/src/Terminal.coffee
+++ client/Terminal/src/Terminal.coffee
@@ -61,14 +61,20 @@
     @outputbox.append @measurebox.getDomElement()
     @container.append @outputbox

-    outputboxElement.addEventListener "keydown", (event) =>
-      range = KD.utils.getSelectionRange()
-      if range.startOffset isnt range.endOffset
+    outputboxElement.addEventListener "keydown", do =>
+      controlMeta = no
+      (event) =>
+        range = KD.utils.getSelectionRange()
+        return  if range.startOffset is range.endOffset
         if event.ctrlKey or event.metaKey
-          @setKeyFocus()  if String.fromCharCode(event.which) is "X"
+          return  controlMeta = yes  if event.keyIdentifier in ["Control", "Meta"]
+          char = String.fromCharCode event.which
+          if char is "X" then return @setKeyFocus()
+          else if char in ["C", "V"] then return
+          else if controlMeta then @setKeyFocus()
+          controlMeta = no
         else
           @setKeyFocus()
-    , yes

     outputboxElement.addEventListener "keypress", (event) =>
       KD.utils.stopDOMEvent event  if event.target isnt @keyInput.getElement()


tweak display text

tweak display text

--- src/join-request.coffee
+++ src/join-request.coffee
@@ -83,8 +83,8 @@
           reply_to: env.HUBOT_SLACK_ADMIN_CHANNEL or 'invites'
         content:
           fallback: "New invite request from #{user.displayName}"
-          pretext: "<#{user.url}|#{user.displayName}> would like to join Slack!"
-          text: if req.files.screenshot.name then "#{imageUrl}" else '<no screenshot>'
+          pretext: "#{user.displayName} would like to join Slack!"
+          text: (if req.files.screenshot.name then "#{imageUrl}" else '<no screenshot>') + " | #{user.url}"
           fields: [
             {title: 'Full Name'
             value: user.displayName


Stop using Project::open

Stop using Project::open

--- spec/tabs-spec.coffee
+++ spec/tabs-spec.coffee
@@ -183,7 +183,7 @@
       editor2 = null

       waitsForPromise ->
-        atom.project.open('sample.txt').then (o) -> editor2 = o
+        atom.workspace.open('sample.txt', activateItem: false).then (o) -> editor2 = o

       runs ->
         editor2.insertText('x')
@@ -841,7 +841,7 @@
       it "adds tab with class 'temp'", ->
         editor1 = null
         waitsForPromise ->
-          atom.project.open('sample.txt').then (o) -> editor1 = o
+          atom.workspace.open('sample.txt').then (o) -> editor1 = o

         runs ->
           pane.activateItem(editor1)
@@ -852,7 +852,7 @@
       it "removes the 'temp' class", ->
         editor1 = null
         waitsForPromise ->
-          atom.project.open('sample.txt').then (o) -> editor1 = o
+          atom.workspace.open('sample.txt').then (o) -> editor1 = o

         runs ->
           pane.activateItem(editor1)
@@ -866,10 +866,10 @@
         editor2 = null

         waitsForPromise ->
-          atom.project.open('sample.txt').then (o) ->
+          atom.workspace.open('sample.txt').then (o) ->
             editor1 = o
             pane.activateItem(editor1)
-            atom.project.open('sample2.txt').then (o) ->
+            atom.workspace.open('sample2.txt').then (o) ->
               editor2 = o
               pane.activateItem(editor2)

@@ -883,7 +883,7 @@
         editor2 = null

         waitsForPromise ->
-          atom.project.open('sample.txt').then (o) -> editor2 = o
+          atom.workspace.open('sample.txt').then (o) -> editor2 = o

         runs ->
           pane.activateItem(editor2)
@@ -898,7 +898,7 @@

       beforeEach ->
         waitsForPromise ->
-          atom.project.open('sample.txt').then (o) ->
+          atom.workspace.open('sample.txt').then (o) ->
             editor2 = o
             pane.activateItem(editor2)

@@ -970,7 +970,7 @@
       it "makes the tab permanent in the new pane", ->
         editor1 = null
         waitsForPromise ->
-          atom.project.open('sample.txt').then (o) -> editor1 = o
+          atom.workspace.open('sample.txt').then (o) -> editor1 = o

         runs ->
           pane.activateItem(editor1)
@@ -983,7 +983,7 @@
       it "makes the tab permanent in the other pane", ->
         editor1 = null
         waitsForPromise ->
-          atom.project.open('sample.txt').then (o) -> editor1 = o
+          atom.workspace.open('sample.txt').then (o) -> editor1 = o

         runs ->
           pane.activateItem(editor1)
@@ -1022,7 +1022,7 @@
           workspaceElement.querySelector('.tree-view')

         waitsForPromise ->
-          atom.project.open('sample.js').then (o) -> editor1 = o
+          atom.workspace.open('sample.js').then (o) -> editor1 = o

         runs ->
           pane.activateItem(editor1)


do it more functionally

do it more functionally

--- modules/plot/main/coffee/Utils.coffee
+++ modules/plot/main/coffee/Utils.coffee
@@ -2,11 +2,15 @@
   reductor = (oldDistance, node, i) ->
     previousNodes = nodes.slice 0, (i - oldDistance + 1)
       .map (n, j) -> [n, j]
-    possibleNewDistance = hx.find previousNodes, ([previousNode, j]) ->
+    tuple = possibleNewDistance = hx.find previousNodes, ([previousNode, j]) ->
       prevBox = previousNode.getBoundingClientRect()
       currBox = node.getBoundingClientRect()
-      i - j if currBox.left < prevBox.right
-    possibleNewDistance or oldDistance
+      currBox.left < prevBox.right
+    if tuple
+      [_, j] = tuple
+      i - j
+    else
+      oldDistance
   distance = nodes.reduce reductor, 1
   nodes.forEach (node, i) ->
     if i % (distance + 1)


Fix editor for very old submits

Fix editor for very old submits

--- client/components/SubmitList.coffee
+++ client/components/SubmitList.coffee
@@ -104,7 +104,7 @@
                 startLanguage={@props.startLanguage || (@props.data?.length && @props.data[@props.data.length-1].language) }
                 editorOn={@props.editorOn}
                 editorDidMount={@props.editorDidMount}
-                editorValue={@props.data?.length && toUtf8(@props.data[@props.data.length-1].sourceRaw) || @props.defaultSource}/>
+                editorValue={@props.data?.length && toUtf8(@props.data[@props.data.length-1].sourceRaw || "") || @props.defaultSource}/>
             {
             if @state.openSubmit?._id
                 <OpenSubmit submit={@state.openSubmit} close={@closeSubmit}/>


client: welcome: done/undone step getters improved

--- client/home/lib/flux/actions.coffee
+++ client/home/lib/flux/actions.coffee
@@ -2,16 +2,19 @@
 globals = require 'globals'
 actionTypes = require './actiontypes'
 HomeGetters = require './getters'
+remote = require('app/remote').getInstance()
+
 { queryKites } = require 'app/providers/managed/helpers'
-
 { getters: EnvironmentGetters } = require 'app/flux/environment'

 markAsDone = (step) ->
   # set app state
-  kd.singletons.reactor.dispatch actionTypes.MARK_WELCOME_STEP_AS_DONE, { step }
+  { reactor, mainController, appStorageController } = kd.singletons
+  reactor.dispatch actionTypes.MARK_WELCOME_STEP_AS_DONE, { step }
+  if reactor.evaluate HomeGetters.areStepsFinished
+    mainController.emit 'AllWelcomeStepsDone'

   # persist state
-  { appStorageController } = kd.singletons
   appStorage = appStorageController.storage "WelcomeSteps-#{globals.currentGroup.slug}"
   appStorage.fetchValue 'finishedSteps', (finishedSteps = {}) ->
     finishedSteps[step] = yes
@@ -28,7 +31,9 @@

 checkFinishedSteps = ->

-  { appStorageController, groupsController, reactor } = kd.singletons
+  { appStorageController, groupsController
+    reactor, mainController, computeController } = kd.singletons
+
   appStorage = appStorageController.storage "WelcomeSteps-#{globals.currentGroup.slug}"

   welcomeSteps = reactor.evaluate(HomeGetters.welcomeSteps).toJS()
@@ -37,14 +42,37 @@
     Object.keys(welcomeSteps).forEach (step) ->
       markAsDone step  if finishedSteps[step]

+    if Object.keys(welcomeSteps).length > Object.keys(finishedSteps).length
+      mainController.emit 'AllWelcomeStepsNotDoneYet'
+
+
   if groupsController.getCurrentGroup().counts?.members > 1
     markAsDone 'inviteTeam'

-  return  if reactor.evaluate(HomeGetters.welcomeSteps).getIn [ 'common', 'installKd', 'isDone' ]
+  if reactor.evaluate(EnvironmentGetters.stacks).size
+    markAsDone 'stackCreation'
+
+  remote.api.JCredential.some {}, { limit: 1 }, (err, res) ->
+    return  if err
+    markAsDone 'enterCredentials'  if res?.length
+
+  if reactor.evaluate(HomeGetters.welcomeSteps).getIn [ 'common', 'installKd', 'isDone' ]
+    if reactor.evaluate HomeGetters.areStepsFinished
+    then mainController.emit 'AllWelcomeStepsDone'
+    else mainController.emit 'AllWelcomeStepsNotDoneYet'
+
+    return

   queryKites().then (result) ->

-    return markAsDone 'installKd'  if result?.length
+    if result?.length
+
+      markAsDone 'installKd'
+
+      unless reactor.evaluate HomeGetters.areStepsFinished
+        mainController.emit 'AllWelcomeStepsNotDoneYet'
+
+      return

     kiteTimer = kd.utils.repeat 30000, -> queryKites().then (result) ->


Login: login with github button initial styling

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -49,7 +49,8 @@

     @github       = new KDButtonView
       title       : 'Sign in with GitHub'
-      style       : 'solid'
+      style       : 'solid github'
+      icon        : yes
       callback    : -> KD.singletons.oauthController.openPopup "github"

     # @loginOptions = new LoginOptions


Register: minor fix

Register: minor fix

--- client/landing/Login/AppView.coffee
+++ client/landing/Login/AppView.coffee
@@ -331,7 +331,7 @@

   showPasswordModal: (formData, form) ->

-    unless form.email.input.valid and form.username.input.valid
+    if no in [form.email.input.valid, form.username.input.valid]
       return form.button.hideLoader()

     {mainView} = KD.singletons


Fixed keymap

Fixed keymap

The keymap now uses 'todo-show:find-in-project' instead of the not-bound 'todo-show:find', allowing ctrl-shift-T to work as expected.

--- keymaps/show-todo.cson
+++ keymaps/show-todo.cson
@@ -8,4 +8,4 @@
 # For more detailed documentation see
 # https://atom.io/docs/latest/advanced/keymaps
 '.workspace':
-  'ctrl-shift-t': 'show-todo:find'
+  'ctrl-shift-t': 'todo-show:find-in-project'


stupid server schema design

stupid server schema design

--- src/scripts/services/webService.coffee
+++ src/scripts/services/webService.coffee
@@ -1,5 +1,15 @@
+_ = require 'underscore'
+
 module.exports = (m) ->
 	m.factory 'webService', ($http) ->
+
+		fixLeasterSchema = (hand) ->
+			return hand unless hand.handType is 'leaster'
+			hand = _.extend {}, hand
+			hand.score = _.extend {}, hand.score
+			hand.score.secondaryPlayerIndex ?= hand.score.primaryPlayerIndex
+			return hand
+
 		getNames: (value) ->
 			$http.get remote('sheepshead.cgi'), params: { action: 'name-lookup', query: value }
 				.then (result) -> result.data.names
@@ -8,5 +18,5 @@
 				action: 'submit-hands'
 			data =
 				playerNames: players.map (p) -> p.name
-				hands: hands
+				hands: (fixLeasterSchema hand for hand in hands) #stupid server schema design
 			$http.post remote('sheepshead.cgi'), data, { params }


No more Bootstrap 3 tooltips

No more Bootstrap 3 tooltips

--- client/lib/tooltip.coffee
+++ client/lib/tooltip.coffee
@@ -1,29 +1,6 @@
 import React, {useEffect, useRef} from 'react'
 import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
 import Tooltip from 'react-bootstrap/Tooltip'
-
-## Helper to call from `onRendered` methods to initialize tooltips.
-@tooltipInit = (template = Template.instance()) ->
-  template.$('[data-toggle="tooltip"]')
-  .tooltip()
-
-## Helper to call to close all tooltips in current template.
-@tooltipHide = (template = Template.instance()) ->
-  template.$('[data-toggle="tooltip"]')
-  .tooltip 'hide'
-
-## Helper to call from template helpers that updates all tooltips in the
-## current template, after the helper completes.  Call from helpers that
-## define tooltip `title`s.
-@tooltipUpdate = (template = Template.instance()) ->
-  template.debounced ?= _.debounce ->
-    try
-      template.$('[data-toggle="tooltip"]')
-      .tooltip 'fixTitle'
-    catch e
-      console.warn e  # e.g. DomRange removed
-  , 50
-  Meteor.defer template.debounced

 export TextTooltip = React.memo ({title, placement, children}) ->
   <OverlayTrigger placement={placement} flip


change cpu/memory threshholds just in case...

--- src/smc-project/jupyter/jupyter.coffee
+++ src/smc-project/jupyter/jupyter.coffee
@@ -230,8 +230,8 @@
             clearInterval(@_usage_interval)
         locals =
             last_usage : {cpu:0, memory:0}
-            thresh     : 0.1   # report any change of at least thresh percent (we always report cpu dropping to 0)
-            interval   : 3500  # frequently (~3.5s) should be OK, since it just reads /proc filesystem
+            thresh     : 0.2  # report any change of at least thresh percent (we always report cpu dropping to 0)
+            interval   : 8000  # frequently (~8s) should be OK, since it just reads /proc filesystem
         @emit('usage', locals.last_usage)
         dbg = @dbg("usage_monitor")
         update_usage = =>


Remove superfluous .

Remove superfluous .

--- client/test/src/section_view.coffee
+++ client/test/src/section_view.coffee
@@ -72,7 +72,7 @@
   assert.isTrue view.section.get('narrative').get('editing')
   sinon.assert.calledOnce(spy, "resize")

-  Backbone.Views.NarrativeView::.resize.restore()
+  Backbone.Views.NarrativeView::resize.restore()
 )

 test("Can see the section visualisation", ->


fix(build): fix codesign step

fix(build): fix codesign step

--- build/Gruntfile.coffee
+++ build/Gruntfile.coffee
@@ -389,6 +389,9 @@
   ciTasks.push('dump-symbols') if process.platform isnt 'win32'
   ciTasks.push('set-version', 'lint', 'generate-asar')

+  if process.platform is "darwin"
+    ciTasks.push('test', 'codesign')
+
   if process.platform is "linux"
     ciTasks.push('mkdeb')
     ciTasks.push('mkrpm')


Clean up values.

Clean up values.

--- src/spec/utils/order-utils.spec.coffee
+++ src/spec/utils/order-utils.spec.coffee
@@ -47,6 +47,10 @@
   beforeEach ->
     @utils = new OrderUtils
     @order = order
+
+  afterEach ->
+    @utils = null
+    @order = null

   it "should return required actions for syncing status", ->


add FlyCircuitIdids to DriverLines collection schema

--- model.coffee
+++ model.coffee
@@ -168,7 +168,7 @@

 # Schemas.DriverLines -------------------
 Schemas.DriverLines = new SimpleSchema(
-  compose(NamedWithTagsImagesHistoryComments, LinksNeuronTypes, LinksNeuropils))
+  compose(NamedWithTagsImagesHistoryComments, LinksNeuronTypes, HasFlyCircuitIdids, LinksNeuropils))
 DriverLines.attachSchema( Schemas.DriverLines )

 # Schemas.NeuronTypes ------------------


Allow numbers between -1 and 1 to have extra precision

--- app/assets/javascripts/lib/models/quantity.coffee
+++ app/assets/javascripts/lib/models/quantity.coffee
@@ -172,7 +172,7 @@
       opts.strip_insignificant_zeros = true

     if opts.precision is 'auto' or not opts.precision?
-      maxPrecision = opts.maxPrecision || 2
+      maxPrecision = opts.maxPrecision || (if Math.abs(@value) < 1 then 3 else 2)

       # Automatically determine how many significant decimal places are
       # present in the number.


fix for zenodo author names

fix for zenodo author names

--- noddy/service/oabutton/receive.coffee
+++ noddy/service/oabutton/receive.coffee
@@ -20,17 +20,16 @@
         up.content = files[0].data
         up.name = files[0].filename
       up.publish = API.settings.service.openaccessbutton?.zenodo?.publish
-      creators = [{name:''}]
+      creators = [{name:(if lastname or firstname then '' else 'Unknown')}]
       creators[0].name = lastname if lastname
-      creators[0].name += ', ' + firstname if firstname
-      creators[0].name = r.name if creators[0].name is '' and r.name
-      if creators[0].name is '' and r.author
+      creators[0].name += (if lastname then ', ' else '') + firstname if firstname
+      creators[0].name = r.name if creators[0].name is 'Unknown' and r.name
+      if creators[0].name is 'Unknown' and r.author
         try
           for a in r.author
-            if a.family and ( creators[0].name = '' or r.email.toLowerCase().indexOf(a.family.toLowerCase()) isnt -1 )
+            if a.family and ( creators[0].name is 'Unknown' or r.email.toLowerCase().indexOf(a.family.toLowerCase()) isnt -1 )
               creators[0].name = a.family
-              creators[0].name += ', ' + a.given if a.given
-      creators[0].name ?= 'Unknown'
+              creators[0].name += (if a.family then ', ' else '') + a.given if a.given
       # http://developers.zenodo.org/#representation
       # journal_volume and journal_issue are acceptable too but we don't routinely collect those
       # access_right can be open embargoed restricted closed


Lint.

Lint.

--- grunt-custom-tasks/jscoverage_report.coffee
+++ grunt-custom-tasks/jscoverage_report.coffee
@@ -117,13 +117,13 @@
 	for i of _$jscoverage
 		file = i
 		tmp = _$jscoverage[i]
-		continue  if typeof tmp is "function" or tmp.length is `undefined`
+		continue  if typeof tmp is "function" or tmp.length is undefined
 		total = touched = 0
 		n = 0
 		len = tmp.length

 		while n < len
-			if tmp[n] isnt `undefined`
+			if tmp[n] isnt undefined
 				total++
 				touched++  if tmp[n] > 0
 			n++
@@ -142,7 +142,7 @@
 	for i of _$jscoverage
 		file = i
 		tmp = _$jscoverage[i]
-		continue  if typeof tmp is "function" or tmp.length is `undefined`
+		continue  if typeof tmp is "function" or tmp.length is undefined
 		source = tmp.source
 		allcovered = true
 		console.log "[UNCOVERED CODE]", file


Docs on "layer" module

Docs on "layer" module


Fixed an IE bug with a workaround added in Icon class for SVG icons. For more information see #45741


Working on denormalization propagation (untested)

--- denorm.coffee
+++ denorm.coffee
@@ -69,132 +69,162 @@
         value

-    resetWatchers: (modelName, instanceId, oldValues, newValues) ->
-        ###
-            Returns watcherModelName: reactiveName: [instanceId]
-        ###
-
+    resetWatchers: (modelName, instanceId, oldValues, newValues, timestamp = new Date()) ->
         modelClass = J.models[modelName]
         if modelClass.immutable
             return {}

         console.log 'resetWatchers', modelName, JSON.stringify(instanceId)

-        # Makes sure every fieldSpec in he watching-selector is consistent
-        # with either oldValues or newValues
-        subFieldSelectorMatcher = [
-            selector: $type: 3 # object
-        ]
-
-        subFieldSelectorMatcher.push
-            $or: [
-                'selector._id': $in: [null, instanceId]
+        makeWatcherMatcher = (instanceId, oldValues, newValues) ->
+            # Makes sure every fieldSpec in the watching-selector is consistent
+            # with either oldValues or newValues
+            subFieldSelectorMatcher = [
+                selector: $type: 3 # object
+            ]
+
+            subFieldSelectorMatcher.push
+                $or: [
+                    'selector._id': $in: [null, instanceId]
+                ,
+                    'selector._id.*DOLLAR*in': $in: [instanceId]
+                ]
+
+            addClauses = (selectorPrefix, oldSubValues, newSubValues) =>
+                # TODO: Handle more selectors besides just default (equality) and $in
+                # e.g. $gt, $gte, $lt, $lte
+
+                subFieldNameSet = {}
+                for subFieldName of oldSubValues
+                    subFieldNameSet[subFieldName] = true
+                for subFieldName of newSubValues
+                    subFieldNameSet[subFieldName] = true
+
+                for subFieldName of subFieldNameSet
+                    if selectorPrefix is 'selector'
+                        selectorPath = "selector.#{J.Model.escapeDot subFieldName}"
+                    else
+                        selectorPath = "#{selectorPrefix}*DOT*#{J.Model.escapeDot subFieldName}"
+
+                    oldValue = oldSubValues[subFieldName]
+                    newValue = newSubValues[subFieldName]
+
+                    term = $or: [{}, {}]
+                    term.$or[0][selectorPath] =
+                        $in: [null]
+                    term.$or[1]["#{selectorPath}.*DOLLAR*in"] =
+                        $elemMatch: $in: [null]
+
+                    if oldValue?
+                        term.$or[0][selectorPath].$in.push oldValue
+                        term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push oldValue
+                        if _.isArray oldValue then for elem in oldValue
+                            term.$or[0][selectorPath].$in.push elem
+                            term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push elem
+                    if newValue? and not EJSON.equals oldValue, newValue
+                        term.$or[0][selectorPath].$in.push newValue
+                        term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push newValue
+                        if _.isArray newValue then for elem in newValue
+                            term.$or[0][selectorPath].$in.push elem
+                            term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push elem
+
+                    subFieldSelectorMatcher.push term
+
+                    if J.util.isPlainObject(oldValue) or J.util.isPlainObject(newValue)
+                        addClauses(
+                            selectorPath
+                            if J.util.isPlainObject(oldValue) then oldValue else {}
+                            if J.util.isPlainObject(newValue) then newValue else {}
+                        )
+
+            mutableOldValues = {}
+            mutableNewValues = {}
+            for fieldName, fieldSpec of modelClass.fieldSpecs
+                continue if fieldSpec.immutable
+                if fieldName of oldValues
+                    mutableOldValues[fieldName] = oldValues[fieldName]
+                if fieldName of newValues
+                    mutableNewValues[fieldName] = newValues[fieldName]
+
+            addClauses 'selector', mutableOldValues, mutableNewValues
+
+            watcherMatcher =
+                modelName: modelName
+                $or: [
+                    selector: $in: [null, instanceId]
+                ,
+                    $and: subFieldSelectorMatcher
+                ]
+
+            # TODO: Add this to watcherMatcher:
+            #   fields: // smart logic that rules out watchers that
+            #              don't care about any of the fields that
+            #              were changed from oldValues to newValues
+
+            watcherMatcher
+
+
+        resetCountByModelReactive = {} # "#{watcherModelName}.#{reactiveName}": resetCount
+        resetOneWatcherDoc = (watcherModelName, watcherReactiveName) ->
+            watcherModelClass = J.models[watcherModelName]
+            console.log "resetOneWatcherDoc: <#{watcherModelName}>.#{reactiveName} watching <#{modelName}
+                #{JSON.stringify instanceId}>.#{reactiveName}"
+
+            selector = {}
+            selector["_reactives.#{reactiveName}.watching"] =
+                ts: $lt: timestamp
+                $elemMatch:
+                makeWatcherMatcher instanceId, oldValues, newValues
+
+            setter = {}
+            setter["_reactives.#{reactiveName}.ts"] = timestamp
+            unsetter = {}
+            unsetter["_reactives.#{reactiveName}.val"] = 1
+            unsetter["_reactives.#{reactiveName}.watching"] = 1
+
+            resetCountByModelReactive["#{watcherModelClass.name}.#{watcherReactiveName}"] ?= 0
+            watcherModelClass.collection.rawCollection().findAndModify(
+                selector
             ,
-                'selector._id.*DOLLAR*in': $in: [instanceId]
-            ]
-
-        addClauses = (selectorPrefix, oldSubValues, newSubValues) =>
-            # TODO: Handle more selectors besides just default (equality) and $in
-            # e.g. $gt, $gte, $lt, $lte
-
-            subFieldNameSet = {}
-            for subFieldName of oldSubValues
-                subFieldNameSet[subFieldName] = true
-            for subFieldName of newSubValues
-                subFieldNameSet[subFieldName] = true
-
-            for subFieldName of subFieldNameSet
-                if selectorPrefix is 'selector'
-                    selectorPath = "selector.#{J.Model.escapeDot subFieldName}"
-                else
-                    selectorPath = "#{selectorPrefix}*DOT*#{J.Model.escapeDot subFieldName}"
-
-                oldValue = oldSubValues[subFieldName]
-                newValue = newSubValues[subFieldName]
-
-                term = $or: [{}, {}]
-                term.$or[0][selectorPath] =
-                    $in: [null]
-                term.$or[1]["#{selectorPath}.*DOLLAR*in"] =
-                    $elemMatch: $in: [null]
-
-                if oldValue?
-                    term.$or[0][selectorPath].$in.push oldValue
-                    term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push oldValue
-                    if _.isArray oldValue then for elem in oldValue
-                        term.$or[0][selectorPath].$in.push elem
-                        term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push elem
-                if newValue? and not EJSON.equals oldValue, newValue
-                    term.$or[0][selectorPath].$in.push newValue
-                    term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push newValue
-                    if _.isArray newValue then for elem in newValue
-                        term.$or[0][selectorPath].$in.push elem
-                        term.$or[1]["#{selectorPath}.*DOLLAR*in"].$elemMatch.$in.push elem
-
-                subFieldSelectorMatcher.push term
-
-                if J.util.isPlainObject(oldValue) or J.util.isPlainObject(newValue)
-                    addClauses(
-                        selectorPath
-                        if J.util.isPlainObject(oldValue) then oldValue else {}
-                        if J.util.isPlainObject(newValue) then newValue else {}
-                    )
-
-        mutableOldValues = {}
-        mutableNewValues = {}
-        for fieldName, fieldSpec of modelClass.fieldSpecs
-            continue if fieldSpec.immutable
-            if fieldName of oldValues
-                mutableOldValues[fieldName] = oldValues[fieldName]
-            if fieldName of newValues
-                mutableNewValues[fieldName] = newValues[fieldName]
-
-        addClauses 'selector', mutableOldValues, mutableNewValues
-
-        # projectionSpecKeys makes sure one of the fieldspecs
-        # that have a diffed value is present in the
-        # watching-projection.
-        relevantProjectionSpecKeys = []
-        # TODO
-
-        watchersByModelReactive = {} # watcherModelName: reactiveName: [instanceId]
-        for watcherModelName, watcherModelClass of J.models
-            for reactiveName, reactiveSpec of watcherModelClass.reactiveSpecs
-                if reactiveSpec.denorm
-                    # TODO: add relevantProjectionSpecKeys logic here too
-                    selector = {}
-                    selector["_reactives.#{reactiveName}.watching"] =
-                        $elemMatch:
-                            modelName: modelName
-                            $or: [
-                                selector: $in: [null, instanceId]
-                            ,
-                                $and: subFieldSelectorMatcher
-                            ]
-
-                    setter = {}
-                    setter["_reactives.#{reactiveName}.ts"] = new Date()
-                    unsetter = {}
-                    unsetter["_reactives.#{reactiveName}.val"] = 1
-                    unsetter["_reactives.#{reactiveName}.watching"] = 1
-
-                    watchersByModelReactive[watcherModelName] ?= {}
-                    numWatchersReset = watchersByModelReactive[watcherModelName][reactiveName] = watcherModelClass.update(
-                        selector
-                    ,
-                        $set: setter
-                        $unset: unsetter
-                    ,
-                        multi: true
-                    )
-                    if numWatchersReset
-                        console.log "    <#{watcherModelName}>.#{reactiveName}:
+                []
+            ,
+                $set: setter
+                $unset: unsetter
+            ,
+                (err, doc) ->
+                    if err
+                        console.error "Error while resetting #{watcherModelName}.#{watcherReactiveName}
+                                    watchers of #{modelName}.#{JSON.stringify instanceId}:"
+                        console.error err
+                        return
+
+                    if doc?
+                        resetCountByModelReactive["#{watcherModelClass.name}.#{watcherReactiveName}"] += 1
+
+                        # Continue to reset one doc at a time until there are no more docs to reset
+                        resetOneWatcherDoc watcherModelClass, watcherReactiveName
+
+                        # Also branch into resetting all docs watching this watching-reactive in this doc
+                        watcherOldValues = {}
+                        watcherOldValues[watcherReactiveName] = doc._reactives?[watcherReactiveName]?.val
+                        watcherNewValues = {}
+                        watcherNewValues[watcherReactiveName] = undefined
+                        J.denorm.resetWatchers watcherModelName, doc._id, watcherOldValues, watcherNewValues, timestamp
+
+                    else
+                        numWatchersReset = resetCountByModelReactive["#{watcherModelClass.name}.#{watcherReactiveName}"]
+                        console.log "    <#{watcherModelName}>.#{watcherReactiveName}:
                             #{numWatchersReset} watchers reset"
                         # console.log "selector: #{JSON.stringify selector, null, 4}"
-
-        watchersByModelReactive
-
-
+            )
+
+
+        for watcherModelName, watcherModelClass of J.models
+            for watcherReactiveName, watcherReactiveSpec of watcherModelClass.reactiveSpecs
+                if watcherReactiveSpec.denorm
+                    resetOneWatcherDoc watcherModelName, watcherReactiveName
+
+        null


Track ladderAchievementDifficulty in multiplayer human level sessions

--- server/queues/scoring/scoringUtils.coffee
+++ server/queues/scoring/scoringUtils.coffee
@@ -52,12 +52,13 @@

 retrieveOldSessionData = (sessionID, callback) ->
   formatOldScoreObject = (session) =>
-    oldScoreObject =
+    oldScoreObject =
       standardDeviation: session.standardDeviation ? 25/3
       meanStrength: session.meanStrength ? 25
       totalScore: session.totalScore ? (25 - 1.8*(25/3))
       id: sessionID
       submittedCodeLanguage: session.submittedCodeLanguage
+      ladderAchievementDifficulty: session.ladderAchievementDifficulty
     if session.leagues?.length
       _.find(@clientResponseObject.sessions, sessionID: sessionID).leagues = session.leagues
       oldScoreObject.leagues = []
@@ -74,7 +75,7 @@
   return formatOldScoreObject @levelSession if sessionID is @levelSession?._id  # No need to fetch again

   query = _id: sessionID
-  selection = 'standardDeviation meanStrength totalScore submittedCodeLanguage leagues'
+  selection = 'standardDeviation meanStrength totalScore submittedCodeLanguage leagues ladderAchievementDifficulty'
   LevelSession.findOne(query).select(selection).lean().exec (err, session) ->
     return callback err, {'error': 'There was an error retrieving the session.'} if err?
     callback err, formatOldScoreObject session
@@ -150,6 +151,13 @@
   async.each sessionIDs, updateMatchesInSession.bind(@, matchObject), (err) ->
     callback err

+ladderBenchmarkAIs =
+  '564ba6cea33967be1312ae59': 0
+  '564ba830a33967be1312ae61': 1
+  '564ba91aa33967be1312ae65': 2
+  '564ba95ca33967be1312ae69': 3
+  '564ba9b7a33967be1312ae6d': 4
+
 updateMatchesInSession = (matchObject, sessionID, callback) ->
   currentMatchObject = {}
   currentMatchObject.date = matchObject.date
@@ -163,6 +171,11 @@
   #currentMatchObject.randomSeed = parseInt(@clientResponseObject.randomSeed or 0, 10)  # Uncomment when actively debugging simulation mismatches
   sessionUpdateObject = @levelSessionUpdates[sessionID]
   sessionUpdateObject.$push.matches = {$each: [currentMatchObject], $slice: -200}
+  if currentMatchObject.metrics.rank is 0 and defeatedAI = ladderBenchmarkAIs[currentMatchObject.opponents[0].userID]
+    mySession = _.find @clientResponseObject.sessions, sessionID: sessionID
+    newLadderAchievementDifficulty = Math.max defeatedAI, mySession.ladderAchievementDifficulty || 0
+    if newLadderAchievementDifficulty isnt mySession.ladderAchievementDifficulty
+      sessionUpdateObject.ladderAchievementDifficulty = newLadderAchievementDifficulty

   myScoreObject = @newScoresObject[sessionID]
   opponentSession = _.find @clientResponseObject.sessions, (session) -> session.sessionID isnt sessionID


- prevent submit when selecting value with enter key - prevent tabbing in next element if select options are shown

--- app/assets/javascripts/app/lib/app_post/searchable_select.js.coffee
+++ app/assets/javascripts/app/lib/app_post/searchable_select.js.coffee
@@ -52,8 +52,9 @@
     switch event.keyCode
       when 40 then @nudge event, 1 # down
       when 38 then @nudge event, -1 # up
-      when 13 then @selectHighlightedItem() # enter
+      when 13 then @onEnter event
       when 27 then @onEscape()
+      when 9 then @onTab event

   onEscape: ->
     @toggle() if @isOpen
@@ -78,9 +79,18 @@
     @input.val event.currentTarget.textContent.trim()
     @shadowInput.val event.currentTarget.getAttribute('data-value')

-  selectHighlightedItem: ->
+  onTab: (event) ->
+    return if not @isOpen
+    event.preventDefault()
+
+  onEnter: (event) ->
     if not @isOpen
-      return @toggle()
+      if @shadowInput.val() is ''
+        event.preventDefault()
+        @toggle()
+      return
+
+    event.preventDefault()

     @input.val @values.filter('.is-active').text().trim()
     @shadowInput.val @values.filter('.is-active').attr('data-value')
@@ -88,7 +98,7 @@

   filterList: (event) =>
     @toggle() if not @isOpen
-
+
     query = @input.val()
     @filterByQuery query


Cleanup

Cleanup

--- coffee/simulate-ui.coffee
+++ coffee/simulate-ui.coffee
@@ -1,80 +1,58 @@
-((Offline, document) ->
+throw new Error("Offline simulate UI brought in without Offline.js") unless Offline?

-  throw new Error("Offline simulate UI brought in without Offline.js")  unless Offline
+console.info "The offline.simulate.ui.js module is a development-only resource. Make sure to remove offline.simulate.ui.js in production."

-  console.info "The offline.simulate.ui.js module is a development-only resource. Make sure to remove offline.simulate.ui.js in production."
-
-  Offline.options.reconnect = false
+Offline.options.reconnect = false

-  scripts = document.getElementsByTagName("script")
-  forceFail = "data-force-fail"
-  i = 0
+if document.querySelector('script[data-force-fail="true"]')
+  Offline.options.isSimulating = true
+
+if Offline.options.isSimulating
+  Offline.options.checks.active = 'down'

-  while i < scripts.length
-    Offline.options.isSimulating = true  if scripts[i].getAttribute(forceFail) and scripts[i].getAttribute(forceFail) is "true"
-    i++
+document.addEventListener "DOMContentLoaded", ->
+  STYLE = """
+          <style>
+            .offline-simulate-ui {
+              position: fixed;
+              z-index: 100000;
+              left: -4px;
+              top: 45%;
+              border: solid 1px rgba(0, 0, 0, 0.15);
+              -webkit-border-radius: 4px 4px 4px 4px;
+              -moz-border-radius: 4px 4px 4px 4px;
+              -ms-border-radius: 4px 4px 4px 4px;
+              -o-border-radius: 4px 4px 4px 4px;
+              border-radius: 4px 4px 4px 4px;
+              font-family: \"Lucida Grande\", sans-serif;
+              font-size: 12px;
+              padding: 2px;
+              padding-left: 6px;
+              width: 25px;
+              background: #f6f6f6;
+              color: #888888;
+            }
+          </style>
+  """

-  Offline.forceChecksToFail = ->
-    Offline.options.checks =
-      image:
-        url: "simulate-offline-state.png"
+  styleElement = document.createElement("div")
+  styleElement.innerHTML = STYLE
+  document.body.appendChild styleElement

-      active: "image"
+  TEMPLATE = "<input type=\"checkbox\" id=\"offline-simulate-check\" title=\"Simulate online/offline states\">"

-    return
+  container = document.createElement("div")
+  container.className = "offline-simulate-ui"
+  container.innerHTML = TEMPLATE
+  document.body.appendChild container

-  Offline.forceChecksToFail()  if Offline.options.isSimulating
+  document.getElementById("offline-simulate-check").addEventListener "click", ->
+    if @checked
+      Offline.options.checks.active = 'down'
+      Offline.markDown()
+    else
+      Offline.options.checks.active = 'up'
+      Offline.markUp()

-  document.addEventListener "DOMContentLoaded", (->
-    STYLE = "<style>" +
-            ".offline-simulate-ui {" +
-            "    position: fixed;" +
-            "    z-index: 100000;" +
-            "    left: -4px;" +
-            "    top: 45%;" +
-            "    border: solid 1px rgba(0, 0, 0, 0.15);" +
-            "    -webkit-border-radius: 4px 4px 4px 4px;" +
-            "    -moz-border-radius: 4px 4px 4px 4px;" +
-            "    -ms-border-radius: 4px 4px 4px 4px;" +
-            "    -o-border-radius: 4px 4px 4px 4px;" +
-            "    border-radius: 4px 4px 4px 4px;" +
-            "    font-family: \"Lucida Grande\", sans-serif;" +
-            "    font-size: 12px;" +
-            "    padding: 2px;" +
-            "    padding-left: 6px;" +
-            "    width: 25px;" +
-            "    background: #f6f6f6;" +
-            "    color: #888888;" +
-            "}" +
-            "</style>"
-
-    styleElement = document.createElement("div")
-    styleElement.innerHTML = STYLE
-
-    body = document.getElementsByTagName("body")[0]
-
-    body.appendChild styleElement
-
-    TEMPLATE = "<input type=\"checkbox\" id=\"offline-simulate-check\" title=\"Simulate online/offline states\">"
-
-    container = document.createElement("div")
-    container.className = "offline-simulate-ui"
-    container.innerHTML = TEMPLATE
-    body.appendChild container
-
-    document.getElementById("offline-simulate-check").addEventListener "click", ((e) ->
-      if @checked
-        Offline.markDown()
-      else
-        Offline.markUp()
-      return
-    ), false
-
-    Offline.on "confirmed-up", ->
-      document.getElementById("offline-simulate-check").checked = false
-      return
-
-    return
-  ), false
-  return
-) window.Offline, window.document+  Offline.on "confirmed-up", ->
+    document.getElementById("offline-simulate-check").checked = false


fix character lengths problem for subdomains

--- client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
+++ client/landing/app/Applications/Environments.kdapplication/views/domains/domaincreationform.coffee
@@ -211,7 +211,7 @@
           @showSuccess domain

     else # create a subdomain
-      subDomainPattern = /^[a-zA-Z0-9][a-zA-Z0-9.-]+[a-zA-Z0-9]$/
+      subDomainPattern = /^[a-zA-Z0-9][a-zA-Z0-9.-]*$/
       unless subDomainPattern.test domainName
         createButton.hideLoader()
         return notifyUser "#{domainName} is an invalid subdomain."


added null check for child events in processAppointmentData

fixes #CNVS-3387. The CommonEventFactory was returning null
for some child events because it couldn't find the proper
context for them. This might be a legitimate filter, although
the api probably shouldn't be returning them. Either way, it
makes sense to put a null check on the results of the factory
since we do this elsewhere as well.

Test Plan:
 - Check for scheduler regressions

Change-Id: Idca8942ac5e138107248cda9c4dd04452823239e
Reviewed-on: https://gerrit.instructure.com/17290
Tested-by: Jenkins <d95b56ce41a2e1ac4cecdd398defd7414407cc08@instructure.com>
Reviewed-by: Zach Pendleton <514dbb4d54d4f29e454990df7d075678f43f92aa@instructure.com>
QA-Review: Marc LeGendre <d33c80bc45d65303e33ca83108a9952b745af9ef@instructure.com>

--- app/coffeescripts/calendar/EventDataSource.coffee
+++ app/coffeescripts/calendar/EventDataSource.coffee
@@ -184,7 +184,8 @@
               for childEventData in eventData.child_events
                 childEvent = commonEventFactory(childEventData, @contexts)
                 @addEventToCache event
-                event.childEvents.push childEvent
+                if childEvent
+                  event.childEvents.push childEvent

     getEventsForAppointmentGroup: (group, cb) =>
       if @inFlightRequest


Remove leftover stuff

Remove leftover stuff

--- resources/assets/coffee/react/beatmapset-page/contents.coffee
+++ resources/assets/coffee/react/beatmapset-page/contents.coffee
@@ -19,9 +19,6 @@
 el = React.createElement

 class BeatmapsetPage.Contents extends React.Component
-  @modeSwitch: (e) =>
-    e.preventDefault()
-
   render: ->
     beatmap = @props.beatmaps[@props.currentMode]


Solve jitter bug by making deep copy of x and y array

--- bokehjs/src/coffee/models/annotations/label.coffee
+++ bokehjs/src/coffee/models/annotations/label.coffee
@@ -54,14 +54,17 @@
     if @mget('x_units') == "data"
       vx = @xmapper.v_map_to_target(@x)
     else
-      vx = @x
-    @sx = @canvas.v_vx_to_sx(vx)
+      vx = @x.slice(0) # make deep copy to not mutate
+    sx = @canvas.v_vx_to_sx(vx)

     if @mget('y_units') == "data"
       vy = @ymapper.v_map_to_target(@y)
     else
-      vy = @y
-    @sy = @canvas.v_vy_to_sy(vy)
+      vy = @y.slice(0) # make deep copy to not mutate
+
+    sy = @canvas.v_vy_to_sy(vy)
+
+    return [sx, sy]

   _calculate_offset: (ctx, height, width) ->
     if ctx.textAlign == 'left'
@@ -85,8 +88,7 @@
     return [x_shift, y_shift]

   render: () ->
-    @_map_data()
-
+    [@sx, @sy] = @_map_data()
     if @mget('render_mode') == 'canvas'
       @_canvas_text()
     else
@@ -137,12 +139,12 @@
           'left': "#{@sx[i] + @x_offset[i] + @x_shift[i]}px"
           'color': "#{@text_color[i]}"
           'opacity': "#{@text_alpha[i]}"
+          'font-size': "#{@text_font_size[i]}"
+          'font-family': "#{@mget('text_font')}"
           'background-color': "#{@visuals.background_fill.color_value()}"
           'border-style': "#{@line_dash}"
           'border-width': "#{@border_line_width[i]}"
           'border-color': "#{@visuals.border_line.color_value()}"
-          'font-size': "#{@text_font_size[i]}"
-          'font-family': "#{@mget('text_font')}"
           })
         .show()


got conditional logic working almost all the way, only remaining issues are nested sections inside repeaters when replicating don't trigger their default state correct and one repeater can actually cause a show of a nested section in other repeaters, all checkmarks scenario is still broken, all other scenarios working, sample conditional logic 1 template seed conditional logic with test section outside of spill doesn't work but it will work if the test section is nested inside of the spill

--- app/assets/javascripts/hanuman/conditional-logic.js.coffee
+++ app/assets/javascripts/hanuman/conditional-logic.js.coffee
@@ -21,9 +21,12 @@
         #  all_questions = $container
         #  all_questions << $($container).find(".form-container-repeater[data-question-id=" + ancestor_id + "],[data-question-id=" + ancestor_id + "],[data-ancestor=" + ancestor_id + "]")

-        $triggerContainer = $ruleElement.closest('.form-container-survey').find("[data-question-id=" + questionId + "]")
+        $triggerContainer = $ruleElement.closest('.form-container-survey').find("[data-question-id=" + questionId + "]");
         # getting element on edit and new page
         $triggerElement = $triggerContainer.find(".form-control")
+
+
+        #$triggerContainer = $ruleElement.closest('.form-container-repeater');
         # getting element on show page
         if $triggerElement.length < 1
           $triggerElement = $triggerContainer.find(".form-control-static")
@@ -44,11 +47,16 @@
             for element in $triggerElement
               do (element) ->
                 self.bindConditions(rule.question_id, $(element), condition.operator, condition.answer)
+        # determine if we are in a repeater
+        inRepeater = false
+        $repeater = $($triggerElement).closest(".form-container-repeater")
+        if $repeater.length > 0
+          inRepeater = true
         # hide show questions
         if rule.conditions.length > 1
-          self.checkConditionsAndHideShow(rule.conditions, ancestorId, $ruleElement, $container, matchType)
+          self.checkConditionsAndHideShow(rule.conditions, ancestorId, $ruleElement, $container, inRepeater, matchType)
         else
-          self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement, $container)
+          self.hideShowQuestions(hideQuestions, ancestorId, $ruleElement, $container, inRepeater)
     return

   #bind conditions to question
@@ -56,48 +64,83 @@
     $triggerElement.on "change", ->
       # pop out of condition into rules to handle all conditions defined in the rule
       # TODO it seems this is looping through ALL data-rule in the DOM instead of the data-rule associated with the element that triggered the onchange event-kdh
-      $($triggerElement).closest(".form-container-repeater").find("[data-rule]").each ->
-        $ruleElement = $(this)
-        $container = $(this).closest(".form-container-repeater")
-        rule = $.parseJSON($ruleElement.attr("data-rule")).rule_hash
-        matchType = rule.match_type
-        questionId = $triggerElement.closest('.form-container-entry-item').attr('data-question-id')
-        conditions = rule.conditions
-        ancestorId = rule.question_id
-        matchingCondition = _.where(conditions, {question_id: Number(questionId)})
-        hideQuestions = self.evaluateCondition(operator, answer, self.getValue($triggerElement))
-        if matchingCondition.length > 0
-          if conditions.length > 1
-            self.checkConditionsAndHideShow(conditions, ancestorId, $ruleElement, $container, matchType)
-          else
-            self.hideShowQuestions(hideQuestions, ancestor_id, $ruleElement, $container)
+      $repeater = $($triggerElement).closest(".form-container-repeater")
+      # check first to see if this bind is in a repeater
+      if $repeater.length > 0
+        $repeater.find("[data-rule]").each ->
+          inRepeater = true
+          $ruleElement = $(this)
+          $container = $(this).closest(".form-container-repeater")
+          rule = $.parseJSON($ruleElement.attr("data-rule")).rule_hash
+          matchType = rule.match_type
+          questionId = $triggerElement.closest('.form-container-entry-item').attr('data-question-id')
+          conditions = rule.conditions
+          ancestorId = rule.question_id
+          matchingCondition = _.where(conditions, {question_id: Number(questionId)})
+          hideQuestions = self.evaluateCondition(operator, answer, self.getValue($triggerElement))
+          if matchingCondition.length > 0
+            if conditions.length > 1
+              self.checkConditionsAndHideShow(conditions, ancestorId, $ruleElement, $container, inRepeater, matchType)
+            else
+              self.hideShowQuestions(hideQuestions, ancestor_id, $ruleElement, $container, inRepeater)
+      # if not then lets assume its at the top most level outside of a repeater
+      else
+        $($triggerElement).closest(".form-container-survey").find("[data-rule]").each ->
+          inRepeater = false
+          $ruleElement = $(this)
+          $container = $(this).closest(".form-container-survey")
+          rule = $.parseJSON($ruleElement.attr("data-rule")).rule_hash
+          matchType = rule.match_type
+          questionId = $triggerElement.closest('.form-container-entry-item').attr('data-question-id')
+          conditions = rule.conditions
+          ancestorId = rule.question_id
+          matchingCondition = _.where(conditions, {question_id: Number(questionId)})
+          hideQuestions = self.evaluateCondition(operator, answer, self.getValue($triggerElement))
+          if matchingCondition.length > 0
+            if conditions.length > 1
+              self.checkConditionsAndHideShow(conditions, ancestorId, $ruleElement, $container, inRepeater, matchType)
+            else
+              self.hideShowQuestions(hideQuestions, ancestor_id, $ruleElement, $container, inRepeater)
     return

-  checkConditionsAndHideShow: (conditions, ancestorId, $ruleElement, $container, matchType) ->
+  checkConditionsAndHideShow: (conditions, ancestorId, $ruleElement, $container, inRepeater, matchType) ->
     conditionMetTracker = []
     _.each conditions, (condition) ->
       $conditionElement = $("[data-question-id=" + condition.question_id + "]").find('.form-control')
       hideQuestions = self.evaluateCondition(condition.operator, condition.answer, self.getValue($conditionElement))
+      console.log(string(condition.operator));
+      console.log(string(condition.answer));
+      console.log(string(self.getValue($conditionElement)));
+      console.log(string(hideQuestions));
       conditionMet = !hideQuestions
       conditionMetTracker.push conditionMet
     # match type any (or)
     if matchType == "any"
       if _.indexOf(conditionMetTracker, true) > 0
-        self.hideShowQuestions(false, ancestorId, $ruleElement, $container)
-      else
-        self.hideShowQuestions(true, ancestorId, $ruleElement, $container)
+        self.hideShowQuestions(false, ancestorId, $ruleElement, $container, inRepeater)
+      else
+        self.hideShowQuestions(true, ancestorId, $ruleElement, $container, inRepeater)
     # match type all
     if matchType == "all"
       if _.indexOf(conditionMetTracker, false) == -1
-        self.hideShowQuestions(false, ancestorId, $ruleElement, $container)
-      else
-        self.hideShowQuestions(true, ancestorId, $ruleElement, $container)
+        self.hideShowQuestions(false, ancestorId, $ruleElement, $container, inRepeater)
+      else
+        self.hideShowQuestions(true, ancestorId, $ruleElement, $container, inRepeater)

   #hide or show questions
-  hideShowQuestions: (hide_questions, ancestor_id, $ruleElement, $container) ->
-    all_questions = $container
-    all_questions << $($container).find(".form-container-repeater[data-question-id=" + ancestor_id + "],[data-question-id=" + ancestor_id + "],[data-ancestor=" + ancestor_id + "]")
+  hideShowQuestions: (hide_questions, ancestor_id, $ruleElement, $container, inRepeater) ->
+    all_questions = $($container).find(".form-container-repeater[data-question-id=" + ancestor_id + "],[data-question-id=" + ancestor_id + "],[data-ancestor=" + ancestor_id + "]")
+
+    # deal with container
+    unless $container.hasClass("form-container-survey")
+      if hide_questions
+        $container.addClass("conditional-logic-hidden")
+        self.clearQuestions($container)
+      else
+        $container.removeClass("conditional-logic-hidden")
+
+    # deal with questions
     if hide_questions
       all_questions.addClass("conditional-logic-hidden")
       self.clearQuestions(all_questions)


Feature [#42416889]

Feature [#42416889]

--- app/assets/javascripts/views/projects/work_weeks.js.coffee
+++ app/assets/javascripts/views/projects/work_weeks.js.coffee
@@ -48,6 +48,14 @@
   updateOrCreateWorkWeek: (event, cid, attributes) ->
     event.currentTarget.timeout = null
     workWeek = @collection.get cid
+    if workWeek? and ((parseInt(attributes["estimated_hours"], 10) > 0) or (parseInt(attributes["actual_hours"], 10) > 0))
+      assignment = workWeek.collection.parent
+
+      assignment.save {archived: false},
+        success: (model, response, options) ->
+          console.log response
+      , error: (model, xhr, options) ->
+        alert "Failed to unarchive the assignment. Try again?"

     element = $(event.currentTarget)