Add button to clear cache and reload

Add button to clear cache and reload

--- lib/incompatible-packages-view.coffee
+++ lib/incompatible-packages-view.coffee
@@ -7,6 +7,12 @@
     @div class: 'tool-panel panel-bottom padded incompatible-packages native-key-bindings', tabindex: -1, =>
       @div class: 'padded', =>
         @div outlet: 'description'
+        @div outlet: 'reloadArea', =>
+          @p """
+            If you think a package is listed here and should no longer be, click
+            the button below to reload Atom and recheck all packages.
+          """
+          @button outlet: 'reloadButton', class: 'btn', 'Reload Atom And Recheck Packages'

   initialize: ({@uri}) ->
     if atom.packages.getActivePackages().length > 0
@@ -14,6 +20,11 @@
     else
       # Render on next tick so packages have been activated
       setImmediate => @populateViews()
+
+    @reloadArea.hide()
+    @reloadButton.on 'click', =>
+      atom.workspaceView.trigger 'incompatible-packages:clear-cache'
+      atom.workspaceView.trigger 'window:reload'

   populateViews: ->
     incompatiblePackageCount = 0
@@ -26,6 +37,7 @@

   addDescription: (incompatiblePackageCount) ->
     if incompatiblePackageCount > 0
+      @reloadArea.show()
       @description.append $$ ->
         @p """
           The following packages could not be loaded because they contain native


:fix: replace operator can be canceled without effect

I’ve tried to test for this but failed, please advise.

--- lib/operators/replace-operator.coffee
+++ lib/operators/replace-operator.coffee
@@ -10,6 +10,7 @@
     @viewModel = new ViewModel(@, class: 'replace', hidden: true, singleChar: true, defaultText: '\n')

   execute: (count=1) ->
+    return if @input.characters is "" # replace canceled

     @editor.transact =>
       if @motion?


Add compatibility to old view in room

Add compatibility to old view in room

--- src/desktop/components/view_in_room/view.coffee
+++ src/desktop/components/view_in_room/view.coffee
@@ -20,10 +20,11 @@
   events:
     'click .js-view-in-room-close': 'remove'

-  initialize: ({ @imgUrl, @imgSelector, @positionStyles, @dimensions }) ->
+  # Should provide dimensions AND (img OR imgUrl, imgSelector, positionStyles)
+  initialize: ({ @$img, @imgUrl, @imgSelector, @positionStyles, @dimensions }) ->
     $(window).on 'resize.view-in-room', _.throttle(@scale, 100)
     @scrollbar = new Scrollbar
-    @$img = $(@imgSelector)
+    @$sourceImage = $(@imgSelector)

   __render__: ->
     @$el.html template()
@@ -52,13 +53,20 @@
       .attr 'src', @imgUrl

     # Position exactly where original image was
-    @$artwork.css @positionStyles
+    if @positionStyles
+      @$artwork.css @positionStyles
+    else
+      @$artwork.css @getRect(@$img)
+
   getRect: ($el) ->
     _.pick $el[0].getBoundingClientRect(), 'height', 'width', 'top', 'left'

   transitionIn: ->
     @$el.attr 'data-state', 'in'
-    @$img.css visibility: 'hidden'
+    if @$img
+      @$img.css visibility: 'hidden'
+    if @$sourceImage
+      @$sourceImage.css visibility: 'hidden'

     artworkTransformCSS = @getRect @$placeholder

@@ -79,7 +87,7 @@
     @$el.attr 'data-state', 'out'
     @$artwork
       .removeClass 'is-notransition'
-      .css @positionStyles
+      .css if @positionStyles then @positionStyles else @getReact(@$img)

   scalePlaceholder: ->
     [significantDimension] = @getArtworkDimensions()
@@ -131,7 +139,10 @@
     $(window).off 'resize.view-in-room'
     @transitionOut()
       .one $.support.transition.end, =>
-        @$img.css visibility: 'visible'
+        if @$img
+          @$img.css visibility: 'visible'
+        if @$sourceImage
+          @$sourceImage.css visibility: 'visible'
         @scrollbar.reenable()
         ViewInRoom.__super__.remove.apply this, arguments
         @trigger 'removed'


Fixed worst bug ever

Fixed worst bug ever

When catching the scrollview beyond constraints, we need to correct the layer begin position with the overdrag scale, or it will scale from an already scaled point creating a nice positioning bug.

--- framer/LayerDraggable.coffee
+++ framer/LayerDraggable.coffee
@@ -142,6 +142,8 @@
 			x: @layer.x
 			y: @layer.y

+		# If we are beyond bounds, we need to correct for the scaled clamping from the last drag,
+		# hence the 1 / overdragScale
 		if @constraints and @bounce
 			@_layerStartPoint = @_constrainPosition(@_layerStartPoint, @constraints, 1 / @overdragScale)

@@ -180,8 +182,8 @@
 			t: Date.now() # We don't use timeStamp because it's different on Chrome/Safari

 		delta =
-			x: touchEvent.clientX - @_cursorStartPoint.x
-			y: touchEvent.clientY - @_cursorStartPoint.y
+			x: touchEvent.clientX - @_layerStartPoint.x
+			y: touchEvent.clientY - @_layerStartPoint.y

 		# Correct for current drag speed and scale
 		correctedDelta =
@@ -193,8 +195,8 @@
 			x: @layer.x
 			y: @layer.y

-		point.x = @_cursorStartPoint.x + correctedDelta.x - @_layerCursorOffset.x if @horizontal
-		point.y = @_cursorStartPoint.y + correctedDelta.y - @_layerCursorOffset.y if @vertical
+		point.x = @_layerStartPoint.x + correctedDelta.x - @_layerCursorOffset.x if @horizontal
+		point.y = @_layerStartPoint.y + correctedDelta.y - @_layerCursorOffset.y if @vertical

 		# Direction lock
 		if @lockDirection
@@ -205,9 +207,8 @@
 				point.x = @_layerStartPoint.x if @_lockDirectionEnabledX
 				point.y = @_layerStartPoint.y if @_lockDirectionEnabledY

-		# Constraints
-		if @_constraints
-			point = @_constrainPosition(point, @_constraints)
+		# Constraints and overdrag
+		point = @_constrainPosition(point, @_constraints, @overdragScale) if @_constraints

 		# Pixel align all moves
 		if @pixelAlign
@@ -286,8 +287,8 @@

 		if @overdrag
 			point =
-				x: @_clampAndScale(proposedPoint.x, minX, maxX, @overdragScale)
-				y: @_clampAndScale(proposedPoint.y, minY, maxY, @overdragScale)
+				x: @_clampAndScale(proposedPoint.x, minX, maxX, scale)
+				y: @_clampAndScale(proposedPoint.y, minY, maxY, scale)
 		else
 			point =
 				x: Utils.clamp(proposedPoint.x, minX, maxX)


centerOrigin

centerOrigin

--- framer/LayerPinchable.coffee
+++ framer/LayerPinchable.coffee
@@ -18,7 +18,7 @@

 	@define "enabled", @simpleProperty("enabled", true)
 	@define "threshold", @simpleProperty("threshold", 0)
-	@define "setOrigin", @simpleProperty("setOrigin", true)
+	@define "centerOrigin", @simpleProperty("centerOrigin", true)

 	@define "scale", @simpleProperty("scale", true)
 	@define "scaleIncrements", @simpleProperty("scaleIncrements", 0)
@@ -53,8 +53,7 @@
 		@emit(Events.ScaleStart, event) if @scale
 		@emit(Events.RotateStart, event) if @rotate

-		if @setOrigin
-
+		if @centerOrigin
 			topInSuperBefore = Utils.convertPoint({}, @layer, @layer.superLayer)
 			pinchLocation = Utils.convertPointFromContext(event.center, @layer, true, true)
 			@layer.originX = pinchLocation.x / @layer.width


feat: checklist snippets (#117)

feat: checklist snippets (#117)

--- snippets/language-asciidoc.cson
+++ snippets/language-asciidoc.cson
@@ -281,3 +281,13 @@
     descriptionMoreURL: 'http://asciidoctor.org/docs/user-manual/#callouts'
     prefix: 'co'
     body: '<$1> $0'
+  'Unchecked':
+    description: 'Unchecked checklist item'
+    descriptionMoreURL: 'http://asciidoctor.org/docs/user-manual/#checklist'
+    prefix: '-o'
+    body: '- [ ] $0'
+  'Checked':
+    description: 'Checked checklist item'
+    descriptionMoreURL: 'http://asciidoctor.org/docs/user-manual/#checklist'
+    prefix: '-x'
+    body: '- [x] $0'


use js-big-decimal to calculate unit_value_with_description


jQuery event should be click touch for tablets.

--- server/src/www/js/main.coffee
+++ server/src/www/js/main.coffee
@@ -16,17 +16,13 @@
     init: =>
         $(document).clickHandler '.logo', =>
             $('.site-options').show()
-            false

-        $(document).bindNew 'click', '.cover', =>
+        $(document).bindNew 'click touch', '.cover', =>
             $('.site-options').hide()

-        $(document).bindNew 'click', '.container', =>
+        $(document).bindNew 'click touch', '.container', =>
             $('.site-options').hide()

-        $(document).clickHandler '.account-options .logout', =>
-            @logout()
-

     getUser: =>
         {
@@ -122,8 +118,8 @@
     _fn = ->
         fn.apply this, arguments
         false
-    $(this).off 'click', selector
-    $(this).on 'click', selector, _fn
+    $(this).off 'click touch', selector
+    $(this).on 'click touch', selector, _fn

 $.fn._hide = () ->
     $(this).removeClass 'visible'


small fix

small fix

--- lib/assets/javascripts/caroufredsel.js.coffee
+++ lib/assets/javascripts/caroufredsel.js.coffee
@@ -4,7 +4,7 @@
 #= require caroufredsel/jquery.carouFredSel-6.0.3

 class @Carousel
-  constructor: (@carousel, options) ->
+  constructor: (@carousel, options = {}) ->
     if options.onChange
       @onChange = options.onChange
       delete options.onChange


Finish docs for up.navigation

Finish docs for up.navigation

--- lib/assets/javascripts/unpoly/feedback.coffee
+++ lib/assets/javascripts/unpoly/feedback.coffee
@@ -3,43 +3,53 @@
 ===================

 The `up.feedback` module adds useful CSS classes to links while they are loading,
-or when they point to the current URL. By styling these classes you may
-provide instant feedback to user interactions. This improves the perceived speed of your interface.
+or when they point to the current URL.
+
+By styling these classes you may provide instant feedback to user interactions,
+improving the perceived speed of your interface.

 \#\#\# Example

 Let's say we have an `<nav>` element with two links, pointing to `/foo` and `/bar` respectively:

-
-      Foo
-      Bar
-
+```html
+<nav>
+  <a href="/foo" up-follow>Foo</a>
+  <a href="/bar" up-follow>Bar</a>
+</nav>
+```

 By giving the navigation bar the `[up-nav]` attribute, links pointing to the current browser address are highlighted
 as we navigate through the site.

 If the current URL is `/foo`, the first link is automatically marked with an [`.up-current`](/a.up-current) class:

-
-      Foo
-      Bar
-
+```html
+<nav up-nav>
+  <a href="/foo" up-follow class="up-current">Foo</a>
+  <a href="/bar" up-follow>Bar</a>
+</nav>
+```

 When the user clicks on the `/bar` link, the link will receive the [`up-active`](/a.up-active) class while it is waiting
 for the server to respond:

-
-      Foo
-      Bar
-

+```
+<nav up-nav>
+  <a href="/foo" up-follow class="up-current">Foo</a>
+  <a href="/bar" up-follow class="up-active">Bar</a>
+</div>
+```

 Once the response is received the URL will change to `/bar` and the `up-active` class is removed:

-
-      Foo
-      Bar
-
+```html
+<nav up-nav>
+  <a href="/foo" up-follow>Foo</a>
+  <a href="/bar" up-follow class="up-current">Bar</a>
+</nav>
+```

 @see [up-nav]
 @see a.up-current


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- source/number_extensions.coffee
+++ source/number_extensions.coffee
@@ -181,7 +181,7 @@
 @returns {Boolean} true if this number is an odd integer, false otherwise.
 ###
 Number::odd = ->
-  not @even()
+  @mod(2) is 1

 ###*
 Calls iterator the specified number of times, passing in the number of the


adds build_num to VM.selected

adds build_num to VM.selected

--- js/app.coffee
+++ js/app.coffee
@@ -412,6 +412,11 @@

   @get '^/gh/:username/:project/:build_num',
     (cx) ->
+      VM.selected
+        username: cx.params.username
+        project: cx.params.project
+        project_name: "#{cx.params.username}/#{cx.params.project}"
+        build_num: cx.params.build_num
       VM.loadBuild cx, cx.params.username, cx.params.project, cx.params.build_num

   @get('^/gh/:username/:project',


fixed iOS localstorage issue

fixed iOS localstorage issue

--- js/app.coffee
+++ js/app.coffee
@@ -11,14 +11,22 @@
 DEFAULT_TIER = 4

 pref =
+  activeTier: DEFAULT_TIER
   validate: (tier) ->
     Math.min(Math.max(tier, 0), tiers.length - 1)
   get: ->
-    @validate localStorage?.getItem?('tier') or DEFAULT_TIER
+    try
+      @activeTier = @validate localStorage?.getItem?('tier') or @activeTier
+    catch ex
+      console.log ex.stack
+    @activeTier
   set: (tier) ->
-    tier = @validate tier
-    localStorage?.setItem 'tier', tier
-    tier
+    @activeTier = @validate tier
+    try
+      localStorage?.setItem 'tier', @activeTier
+    catch ex
+      console.log ex.stack
+    @activeTier
   minus: ->
     @set @get() - 1
   plus: ->


don't show error notify if already loaded

--- client/app/MainApp/filetree/controllers/findercontroller.coffee
+++ client/app/MainApp/filetree/controllers/findercontroller.coffee
@@ -100,20 +100,22 @@
       @utils.killWait kiteFailureTimer if kiteFailureTimer

       kiteFailureTimer = @utils.wait 5000, =>
-        msg = "Couldn't fetch files! Click to retry"
-        @treeController.notify msg, 'clickable', "Sorry, a problem occured while communicating with servers, please try again later.", yes
-        log msg+" fired from initial kiteFailureTimer"
-        @mount.emit "fs.fetchContents.finished"
+        unless @treeLoaded
+          msg = "Couldn't fetch files! Click to retry"
+          @treeController.notify msg, 'clickable', "Sorry, a problem occured while communicating with servers, please try again later.", yes
+          log msg+" fired from initial kiteFailureTimer"
+          @mount.emit "fs.fetchContents.finished"

-        @treeController.once 'fs.retry.scheduled', =>
-          @defaultStructureLoaded = no
-          @loadDefaultStructure()
+          @treeController.once 'fs.retry.scheduled', =>
+            @defaultStructureLoaded = no
+            @loadDefaultStructure()

       if recentFolders.length is 0
         return log "recentFolders", recentFolders.length

       @multipleLs recentFolders, (err, response)=>
         if response
+          @treeLoaded = true
           @utils.killWait kiteFailureTimer
           @treeController.hideNotification()
           files = FSHelper.parseLsOutput recentFolders, response


form workflow: implement buggy history

form workflow: implement buggy history

this is proving to be more difficult than i originally thought.  I'm going to commit it as is (read "buggy") and loop back around for a non-buggy implementation after I do payment nonces

--- client/landing/app/CommonViews/formworkflow/formworkflow.coffee
+++ client/landing/app/CommonViews/formworkflow/formworkflow.coffee
@@ -10,7 +10,29 @@
     @forms      = {}
     @providers  = {}

+    @active = null
+
+    @history = new FormWorkflow.History
+
+  isWorkflow: yes
+
   enter: -> @ready @bound 'nextForm'
+
+  go: (direction) ->
+
+    provider = do @history[direction]
+
+    if provider.isWorkflow
+      if (provider is @active and direction is 'back') or
+         (direction is 'next')
+
+        provider.go direction
+
+    @showForm provider, no
+
+  next: -> @go 'next'
+
+  back: -> @go 'back'

   requireData: (fields, mode = 'all') ->
     gate =
@@ -67,6 +89,7 @@
     form.hide()
     @forwardEvent form, 'Cancel'
     @provideData formName, provides
+
     return this

   removeForm: (form) ->
@@ -87,11 +110,15 @@

     return this

-  showForm: (form) ->
+  showForm: (form, shouldPushState = yes) ->
     @hideForms()
     form = @getForm form
     form.activate? this
     form.show()
+
+    @active = form
+
+    @history.push form  if shouldPushState

     return this


MainController: Use LocalStorage instead using window.localStorage directly

--- client/landing/app/MainApp/maincontroller/maincontroller.coffee
+++ client/landing/app/MainApp/maincontroller/maincontroller.coffee
@@ -96,11 +96,12 @@

   doLogout:->
     KD.logout()
+    storage = new LocalStorage 'Koding'
     KD.remote.api.JUser.logout (err, account, replacementToken)=>
       @_logoutAnimation()
       KD.utils.wait 1000, ->
         $.cookie 'clientId', replacementToken  if replacementToken
-        localStorage.loggingOut = '1'
+        storage.setValue 'loggingOut', '1'
         location.reload()

   _logoutAnimation:->
@@ -152,8 +153,6 @@

           window.location.pathname = firstRoute or "/"

-
-
   setVisitor:(visitor)-> @visitor = visitor
   getVisitor: -> @visitor
   getAccount: -> KD.whoami()
@@ -161,14 +160,15 @@
   isUserLoggedIn: -> KD.isLoggedIn()

   isLoggingIn: (isLoggingIn) ->
-    if localStorage.loggingOut is '1'
-      delete localStorage.loggingOut
+
+    storage = new LocalStorage 'Koding'
+    if storage.getValue('loggingOut') is '1'
+      storage.unsetKey 'loggingOut'
       return yes
     if isLoggingIn?
       @_isLoggingIn = isLoggingIn
     else
       @_isLoggingIn ? no
-

   showInstructionsBook:->
     if $.cookie 'newRegister'


increase POST body parse size limit

increase POST body parse size limit

--- src/smc-hub/hub_http_server.coffee
+++ src/smc-hub/hub_http_server.coffee
@@ -79,8 +79,10 @@
     app    = express()
     app.use(cookieParser())

-    router.use(body_parser.json())
-    router.use(body_parser.urlencoded({ extended: true }))
+    # Very large limit, since can be used to send, e.g., large single patches, and
+    # the default is only 100kb!  https://github.com/expressjs/body-parser#limit-2
+    router.use(body_parser.json({limit: '3mb'}))
+    router.use(body_parser.urlencoded({extended: true, limit: '3mb'}))

     # initialize metrics
     response_time_histogram = MetricsRecorder.new_histogram('http_histogram', 'http server'


fix typos

fix typos

--- spec/bookmarks-view-spec.coffee
+++ spec/bookmarks-view-spec.coffee
@@ -281,7 +281,7 @@
             editor2 = e
             editorElement2 = atom.views.getView(editor2)

-      it "sets the cursor to the location of the bookmark and activated the right editor", ->
+      it "sets the cursor to the location of the bookmark and activates the right editor", ->
         editor.setCursorBufferPosition([8])
         atom.commands.dispatch editorElement, 'bookmarks:toggle-bookmark'
         editor.setCursorBufferPosition([0])
@@ -299,7 +299,7 @@
         runs ->
           expect(atom.workspace.getActiveTextEditor()).toEqual editor

-      it "searches the bookmark among all panes and editors", ->
+      it "searches for the bookmark among all panes and editors", ->
         editor.setCursorBufferPosition([8])
         atom.commands.dispatch editorElement, 'bookmarks:toggle-bookmark'
         editor.setCursorBufferPosition([0])


new feature: load rule

new feature: load rule

--- flight-app.coffee
+++ flight-app.coffee
@@ -57,6 +57,7 @@
     comment: /^\s*--\s*(.*)$/
     empty: /^\s*$/
     size: /^\s*size\s+(\d+)\s+(\d+)\s*$/
+    rule: /^\s*rule\s+(.+)\s*$/

   pos = [0,0]
   pattern = []
@@ -65,6 +66,7 @@
   curColors = defaultPalette
   size = null
   descriptions = []
+  rule = null
   for line in fdlText.split "\n"
    for instruction in line.split ";"
     instruction = instruction.trim()
@@ -86,9 +88,17 @@
       null
     else if m = instruction.match(FLD.comment)
       descriptions.push m[1]
+    else if m = instruction.match(FLD.rule)
+      rule = makeRule m[1].split('|').join(';')
     else
       throw new Error "Unexpected instruction: #{instruction}"
-  return [pattern, colors, size, descriptions.join("\n")]
+  return {
+    pattern: pattern
+    colors: colors
+    size: size
+    name: descriptions.join("\n")
+    rule: rule
+  }

 #taken from http://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
 getCanvasCursorPosition = (e, canvas) ->
@@ -178,13 +188,14 @@
     cx = (@isim.simulator.width/2) & ~1
     cy = (@isim.simulator.height/2) & ~1
     try
-      [pp, cc] = parseFieldDescriptionlLanguage fdl, @colorPalette
+      parsed = parseFieldDescriptionlLanguage fdl, @colorPalette
       document.getElementById("fld-text").value = fdl
     catch e
       alert "Failed to parse: #{e}"
     @isim.clear()
-    @isim.put pp, cx, cy
-    @colors = cc
+    @isim.put parsed.pattern, cx, cy
+    @colors = parsed.colors
+    @isim.simulator.rule = parsed.rule if parsed.rule?
     @_updateClearBtnIcon()
     @_clearBackground()
     window.scrollTo 0, 0
@@ -346,7 +357,7 @@
     @iconSize = [64, 56]

   addFdl: (fdl, palette)->
-    [pattern, colors, sz, description] = parseFieldDescriptionlLanguage fdl, palette
+    {pattern, colors, name} = parseFieldDescriptionlLanguage fdl, palette
     liItem = document.createElement "li"
     liItem.setAttribute "data-fdl", fdl
     liAnchor = document.createElement "a"
@@ -362,7 +373,7 @@
     canvasContainer.appendChild itemImage
     liAnchor.appendChild canvasContainer

-    descText = document.createTextNode description
+    descText = document.createTextNode name
     liAnchor.appendChild descText
     @drawPatternOn pattern, colors, itemImage


Remove space

Remove space

--- SingularityUI/app/views/requests.coffee
+++ SingularityUI/app/views/requests.coffee
@@ -105,7 +105,7 @@

         $unpauseLinks = @$el.find('[data-action="unpause"]')

-        $unpauseLinks .unbind('click').on 'click', (e) =>
+        $unpauseLinks.unbind('click').on 'click', (e) =>
             row = $(e.target).parents('tr')
             requestModel = @collection.get($(e.target).data('request-id'))


setting content type to JSON for http PATCH

Updating OperationView.coffee to add JSON content type for PATCH
similar to POST and PUT so that the newly added PATCH works fine

--- src/main/coffeescript/view/OperationView.coffee
+++ src/main/coffeescript/view/OperationView.coffee
@@ -78,7 +78,7 @@
         complete: (data) =>
           @showCompleteStatus(data)

-      obj.contentType = "application/json" if (obj.type.toLowerCase() == "post" or obj.type.toLowerCase() == "put")
+      obj.contentType = "application/json" if (obj.type.toLowerCase() == "post" or obj.type.toLowerCase() == "put" or obj.type.toLowerCase() == "patch")

       jQuery.ajax(obj)
       false


Don't fire key events with ctrl / alt combos pressed (for now)

--- lineman/app/services/key_event_service.coffee
+++ lineman/app/services/key_event_service.coffee
@@ -12,7 +12,7 @@
       40:  'pressedDownArrow'

     broadcast: (event) ->
-      if key = @keyboardShortcuts[event.which]
+      if !event.ctrlKey and !event.altKey and key = @keyboardShortcuts[event.which]
         $rootScope.$broadcast key, event, angular.element(document.activeElement)[0]

     registerKeyEvent: (scope, eventCode, execute, shouldExecute) ->


fixed default subtab in invitations tab

fixed default subtab in invitations tab

--- client/app/Applications/Groups.kdapplication/Views/groupsinvitationtabview.coffee
+++ client/app/Applications/Groups.kdapplication/Views/groupsinvitationtabview.coffee
@@ -44,7 +44,7 @@
   createTabs:->
     for tab, i in @getTabs()
       tab.view = new tab.viewOptions.viewClass {delegate: this}, @getData()
-      @addPane new KDTabPaneView(tab), i is 2
+      @addPane new KDTabPaneView(tab), i is 0

   addHeaderButtons:->
     bulkSubject = if @approvalEnabled then 'Approve' else 'Invite'


Clean up build_errors().

Clean up build_errors().

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -9,11 +9,10 @@
   throw new Error(errors) unless errors == ""

 build_errors = ->
-  # TODO: use a mapping function?
-  errors = ""
-  for expectation in all_expectations when expectation.called == false
-    errors += "'#{expectation.method_name}(#{expectation._args})' was never called\n"
-  errors
+  (build_not_called_error(expectation) for expectation in all_expectations when not expectation.called).join("")
+
+build_not_called_error = (expectation) ->
+  "'#{expectation.method_name}(#{expectation._args})' was never called\n"

 class Expectation


Doc ::removeKeyBindings

Doc ::removeKeyBindings

--- src/keymap.coffee
+++ src/keymap.coffee
@@ -77,6 +77,11 @@
   # For testing purposes
   getOtherPlatforms: -> OtherPlatforms

+  # Public: Remove the key bindings added with {::addKeyBindings} or
+  # {::loadKeyBindings}.
+  #
+  # source - A {String} representing the `source` in a previous call to
+  #   {::addKeyBindings} or the path in {::loadKeyBindings}.
   removeKeyBindings: (source) ->
     @keyBindings = @keyBindings.filter (keyBinding) -> keyBinding.source isnt source


add_points: remove some debug code

add_points: remove some debug code

--- src/add_points.coffee
+++ src/add_points.coffee
@@ -105,8 +105,6 @@
                     scaledPoints[i].fx = scaledPoints[i].x unless scaledPoints[i].xnull
                     scaledPoints[i].fy = scaledPoints[i].y unless scaledPoints[i].ynull)

-                console.log("before: #{scaledPoints[0].x}, #{scaledPoints[0].y}")
-
                 isolate = (force, filter) ->
                     initialize = force.initialize
                     force.initialize = (nodes) ->


Show review pane immediately after a problem has been solved.

--- picoCTF-web/web/coffee/problems.coffee
+++ picoCTF-web/web/coffee/problems.coffee
@@ -13,9 +13,12 @@
   input = $(e.target).find("input")
   apiCall "POST", "/api/problems/submit", {pid: input.data("pid"), key: input.val()}
   .done (data) ->
-    apiNotify(data)
     if data['status'] is 1
       loadProblems()
+      apiNotify(data)
+      setTimeout( ->
+        $("div[data-target='#" + input.data("pid") + "']").click()
+      , 100)

 addProblemReview = (e) ->
   e.preventDefault()


Test for ref before running window.objectFitPolyfill

--- concerns/fits-assets.coffee
+++ concerns/fits-assets.coffee
@@ -57,7 +57,10 @@
 			return unless window.objectFitPolyfill
 			@$refs[assetType].dataset.objectFit = @objectFit
 			@$refs[assetType].dataset.objectPosition = @objectPosition
-			setTimeout (=> window.objectFitPolyfill @$refs[assetType].$el), 0
-
+			setTimeout =>
+				return unless el = @$refs[assetType]?.$el
+				window.objectFitPolyfill el
+			, 0
+
 		# Support plain numbers for px units
 		autoUnit: (val) -> if String(val).match /^\d+$/ then "#{val}px" else val


Use fs.resolve instead of fs.resolveExtension

--- src/app/config.coffee
+++ src/app/config.coffee
@@ -26,7 +26,7 @@
       core: _.clone(require('root-view').configDefaults)
       editor: _.clone(require('editor').configDefaults)
     @settings = {}
-    @configFilePath = fs.resolveExtension(fs.join(configDirPath, 'config'), ['json', 'cson'])
+    @configFilePath = fs.resolve(configDirPath, 'config', ['json', 'cson'])
     @configFilePath ?= fs.join(configDirPath, 'config.cson')

   load: ->


Lipstick

Lipstick

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -654,33 +654,6 @@
   removeCursorView: (cursorView) ->
     _.remove(@cursorViews, cursorView)

-  updateCursorViews: ->
-    if @newCursors.length > 0
-      @addCursorView(cursor) for cursor in @newCursors
-      @syncCursorAnimations()
-      @newCursors = []
-
-    for cursorView in @getCursorViews()
-      if cursorView.needsRemoval
-        cursorView.remove()
-      else if cursorView.needsUpdate
-        cursorView.updateDisplay()
-
-  updateSelectionViews: ->
-    if @newSelections.length > 0
-      @addSelectionView(selection) for selection in @newSelections
-      @newSelections = []
-
-    for selectionView in @getSelectionViews()
-      if selectionView.destroyed
-        selectionView.remove()
-      else
-        selectionView.updateDisplay()
-
-  syncCursorAnimations: ->
-    for cursorView in @getCursorViews()
-      do (cursorView) -> cursorView.resetCursorAnimation()
-
   getSelectionView: (index) ->
     index ?= @selectionViews.length - 1
     @selectionViews[index]
@@ -734,8 +707,6 @@
       @renderedLines.css('min-width', minWidth)
       @underlayer.css('min-width', minWidth)
       @overlayer.css('min-width', minWidth)
-    throw new Error("Re-entry into updateDisplay") if @updatingDisplay
-    @updatingDisplay = true
       @layerMinWidth = minWidth

   clearRenderedLines: ->
@@ -743,7 +714,6 @@
     @firstRenderedScreenRow = null
     @lastRenderedScreenRow = null

-    @updatingDisplay = false
   resetDisplay: ->
     return unless @attached

@@ -764,11 +734,41 @@
     @updateDisplay(suppressAutoScroll: true)

   updateDisplay: (options={}) ->
+    throw new Error("Re-entry into updateDisplay") if @updatingDisplay
+    @updatingDisplay = true
     return unless @attached
     @updateCursorViews()
     @updateSelectionViews()
     @autoscroll(options)
     @updateRenderedLines()
+    @updatingDisplay = false
+
+  updateCursorViews: ->
+    if @newCursors.length > 0
+      @addCursorView(cursor) for cursor in @newCursors
+      @syncCursorAnimations()
+      @newCursors = []
+
+    for cursorView in @getCursorViews()
+      if cursorView.needsRemoval
+        cursorView.remove()
+      else if cursorView.needsUpdate
+        cursorView.updateDisplay()
+
+  updateSelectionViews: ->
+    if @newSelections.length > 0
+      @addSelectionView(selection) for selection in @newSelections
+      @newSelections = []
+
+    for selectionView in @getSelectionViews()
+      if selectionView.destroyed
+        selectionView.remove()
+      else
+        selectionView.updateDisplay()
+
+  syncCursorAnimations: ->
+    for cursorView in @getCursorViews()
+      do (cursorView) -> cursorView.resetCursorAnimation()

   autoscroll: (options={}) ->
     for cursorView in @getCursorViews() when cursorView.needsAutoscroll


Add possibility to show N content with the same data tab

--- simpletabs.coffee
+++ simpletabs.coffee
@@ -17,7 +17,10 @@

     updateTabContainer: ->
       dataTab = this._element.data('tab')
-      $("[data-content='" + dataTab + "']").addClass('is-active').show().siblings().removeClass('is-active').hide()
+      contentTab = $("[data-content='" + dataTab + "']")
+      $('[data-content]').not(contentTab).removeClass('is-active')
+      contentTab.each ->
+        $(this).addClass('is-active')

     updateURLHash: ->
       window.location.hash = this._element.data('tab')
@@ -44,4 +47,4 @@
       tabs = new hashTabs()
       tabs.element $(this)
       tabs.updateTab()
-      e.preventDefault()
+      e.preventDefault()


Implement Composition #getRange and #setRange

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -197,13 +197,9 @@
   setBlockAttribute: (attributeName, value) ->
     return unless locationRange = @getLocationRange()
     @notifyDelegateOfIntentionToSetLocationRange()
-    [startPosition, endPosition] = @document.rangeFromLocationRange(locationRange)
-
+    range = @document.rangeFromLocationRange(locationRange)
     @document.applyBlockAttributeAtLocationRange(attributeName, value, locationRange)
-
-    {start} = @document.locationRangeFromPosition(startPosition)
-    {end} = @document.locationRangeFromPosition(endPosition)
-    @setLocationRange(start, end)
+    @setRange(range)

   removeCurrentAttribute: (attributeName) ->
     if Trix.blockAttributes[attributeName]
@@ -253,9 +249,16 @@

   # Location range and selection

+  getRange: ->
+    locationRange = @getLocationRange()
+    @document.rangeFromLocationRange(locationRange)
+
+  setRange: (range) ->
+    locationRange = @document.locationRangeFromRange(range)
+    @setLocationRange(locationRange)
+
   getPosition: ->
-    locationRange = @getLocationRange()
-    @document.rangeFromLocationRange(locationRange)[0]
+    @getRange()[0]

   setPosition: (position) ->
     locationRange = @document.locationRangeFromPosition(position)
@@ -266,10 +269,9 @@
     @setPosition(@getPosition() + distance)

   expandLocationRangeInDirection: (direction) ->
-    locationRange = @getLocationRange()
-    [start, end] = @document.rangeFromLocationRange(locationRange)
-    if direction is "backward" then start-- else end++
-    @setLocationRange(@document.locationRangeFromRange([start, end]))
+    range = @getRange()
+    if direction is "backward" then range[0]-- else range[1]++
+    @setRange(range)

   expandSelectionInDirection: (direction) ->
     if @shouldExpandInDirectionUsingLocationRange(direction)


Reverse history items to show newest at top

--- resources/assets/coffee/react/beatmap-discussions/events.coffee
+++ resources/assets/coffee/react/beatmap-discussions/events.coffee
@@ -29,7 +29,7 @@

     div className: 'osu-page osu-page--small osu-page--generic osu-page--full',
       div className: 'beatmapset-events',
-        for event in @props.events
+        for event in _.reverse(@props.events)
           createdAt = moment(event.created_at)
           createdAtString = createdAt.format 'LL'


made F1 case-insensitive

made F1 case-insensitive

--- keymap.coffee
+++ keymap.coffee
@@ -4,7 +4,7 @@
     "'`'": (cm) -> cm.setOption 'keyMap', 'dyalogBackquote'
     F1: (cm) ->
       c = cm.getCursor(); s = cm.getLine c.line
-      x = s[s[...c.ch].replace(/.[áa-z]*$/i, '').length..].replace /^(\)[a-z]+|⎕[áa-z]*|.).*$/i, '$1'
+      x = s[s[...c.ch].replace(/.[áa-z]*$/i, '').length..].replace(/^(\)[a-z]+|⎕[áa-z]*|.).*$/i, '$1').toLowerCase()
       if u = helpURLs[x]
         w = screen.width; h = screen.height
         open "help/#{u}.html", 'help',


Organize methods

Organize methods

--- SingularityUI/app/components/aggregateTail/Contents.cjsx
+++ SingularityUI/app/components/aggregateTail/Contents.cjsx
@@ -2,6 +2,10 @@
 Loader = require './Loader'

 Contents = React.createClass
+
+  # ============================================================================
+  # Lifecycle Methods                                                          |
+  # ============================================================================

   getInitialState: ->
     @state =
@@ -15,8 +19,19 @@
   componentDidMount: ->
     @scrollNode = @refs.scrollContainer.getDOMNode()

+  componentDidUpdate: (prevProps, prevState) ->
+    # If loading without an offset, start tailing immediately
+    if !@props.offset and @props.logLines.length > 0 and prevProps.logLines.length is 0
+      @scrollToBottom()
+    else if @tailingPoll
+      @scrollToBottom()
+
   componentWillUnmount: ->
     $(window).off 'resize orientationChange', @handleResize
+
+  # ============================================================================
+  # Event Handlers                                                             |
+  # ============================================================================

   handleResize: ->
     @setState
@@ -35,6 +50,7 @@
   startTailingPoll: ->
     # Make sure there isn't one already running
     @stopTailingPoll()
+
     @setState
       isLoading: true
       loadingText: 'Tailing...'
@@ -47,6 +63,10 @@
       loadingText: ''
     clearInterval @tailingPoll
     @tailingPoll = null
+
+  # ============================================================================
+  # Rendering                                                                  |
+  # ============================================================================

   renderError: ->
     if @props.ajaxError.get("present")
@@ -78,12 +98,9 @@
       <Loader isVisable={@state.isLoading} text={@state.loadingText} />
     </div>

-  componentDidUpdate: (prevProps, prevState) ->
-    # If loading without an offset, start tailing immediately
-    if !@props.offset and @props.logLines.length > 0 and prevProps.logLines.length is 0
-      @scrollToBottom()
-    else if @tailingPoll
-      @scrollToBottom()
+  # ============================================================================
+  # Utility Methods                                                            |
+  # ============================================================================

   setScrollHeight: (height) ->
     $(@scrollNode).scrollTop(height);


Fix spec for RenderOptions

Fix spec for RenderOptions

--- spec_app/spec/javascripts/up/classes/render_options_spec.coffee
+++ spec_app/spec/javascripts/up/classes/render_options_spec.coffee
@@ -23,7 +23,6 @@
         expect(options.peel).toBe(true)
         expect(options.cache).toBe(true)
         expect(options.scroll).toBe('auto')
-        expect(options.transition).toBe('navigate')

       it 'allows to configure defaults using up.fragment.config.navigateOptions', ->
         up.fragment.config.navigateOptions.feedback = false
@@ -95,7 +94,6 @@
         expect(options.history).toBe('auto')
         expect(options.peel).toBe(true)
         expect(options.scroll).toBe('auto')
-        expect(options.transition).toBe('navigate')
         expect(options.cache).toBe(true)

     describe 'with { navigate: false }', ->


Allow display addition on number-range filters


Fallback to default avatar if avatar fails to load

--- lib/assets/javascripts/views/profile_component/avatar.js.coffee
+++ lib/assets/javascripts/views/profile_component/avatar.js.coffee
@@ -5,5 +5,16 @@
   constructor: ->
     super

+    @on 'ready', @checkImageMortality
+
     @fetch()

+  checkImageMortality: =>
+    img = Marbles.DOM.querySelector('img', @el)
+    unless img.complete
+      return setTimeout @checkImageMortality, 10
+
+    # Fallback to default avatar if image fails to load
+    if img.naturalHeight == 0 && TentStatus.config.DEFAULT_AVATAR_URL
+      img.src = TentStatus.config.DEFAULT_AVATAR_URL
+


adding pandas plot source

adding pandas plot source

--- static/coffee/pandas/pandas.coffee
+++ static/coffee/pandas/pandas.coffee
@@ -1,4 +1,5 @@
 base = require("../base")
+datasource = require("../common/datasource")
 ContinuumView = require("../common/continuum_view").ContinuumView
 HasParent = base.HasParent
 HasProperties = base.HasProperties
@@ -9,6 +10,15 @@
 coll = Collection.extend({model : IPythonRemoteData})
 exports.ipythonremotedatas = new coll()
 ENTER = 13
+
+class PandasPlotSource extends datasource.ColumnDataSource
+  type : 'PandasPlotSource'
+
+coll = Collection.extend({model : PandasPlotSource})
+exports.pandasplotsources = new coll()
+
+class PandasPlotSources extends Backbone.Collection
+  model : PandasPlotSource

 class PandasPivotView extends ContinuumView
   template : require("./pandaspivot")


Document exclusive property and use it in equality test

--- src/marker.coffee
+++ src/marker.coffee
@@ -118,7 +118,12 @@
   # * `range` A {Range} or range-compatible {Array}. The range will be clipped
   #   before it is assigned.
   # * `params` (optional) An {Object} with the following keys:
-  #   * `reversed`  {Boolean} If true, the marker will to be in a reversed orientation.
+  #   * `reversed`  {Boolean} indicating the marker will to be in a reversed
+  #      orientation.
+  #   * `exclusive` {Boolean} indicating that changes occurring at either end of
+  #     the marker will be considered *outside* the marker rather than inside.
+  #     This defaults to `false` unless the marker's invalidation strategy is
+  #     `inside` or the marker has no tail, in which case it defaults to `true`.
   setRange: (range, params) ->
     params ?= {}
     @update(@getRange(), {reversed: params.reversed, tailed: true, range: Range.fromObject(range, true), exclusive: params.exclusive})
@@ -250,6 +255,7 @@
     @invalidate is other.invalidate and
       @tailed is other.tailed and
       @reversed is other.reversed and
+      @exclusive is other.exclusive and
       isEqual(@properties, other.properties) and
       @getRange().isEqual(other.getRange())


Make empty pie chart appear on proposal page

--- lineman/app/js/controllers/proposal_pie_chart_controller.coffee
+++ lineman/app/js/controllers/proposal_pie_chart_controller.coffee
@@ -4,7 +4,8 @@
     { value : 0, color : "#90D490" },
     { value : 0, color : "#F0BB67" },
     { value : 0, color : "#D49090" },
-    { value : 0, color : "#dd0000"}
+    { value : 0, color : "#dd0000" },
+    { value : 0, color : "#cccccc" }
   ]

   $scope.pieChartOptions =
@@ -16,11 +17,12 @@
   refreshPieChartData = ->
     return unless $scope.proposal
     counts = $scope.proposal.voteCounts
-    # yeah - this is done to preseve the view binding on the pieChartData
+    hasAnyVotes = counts.yes + counts.abstain + counts.no + counts.block > 0
     $scope.pieChartData[0].value = counts.yes
     $scope.pieChartData[1].value = counts.abstain
     $scope.pieChartData[2].value = counts.no
     $scope.pieChartData[3].value = counts.block
+    $scope.pieChartData[4].value = if hasAnyVotes then 0 else 1

   $scope.$watch 'proposal.voteCounts', ->
     refreshPieChartData()


fix null and point changefeeds in the dataexplorer

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -54,12 +54,12 @@
                 else
                     @profile = null
                     value = result
-                if typeof value._next is 'function' and value not instanceof Array # if it's a cursor
+                if value? and typeof value._next is 'function' and value not instanceof Array # if it's a cursor
                     @type = 'cursor'
                     @results = []
                     @results_offset = 0
                     @cursor = value
-                    @is_feed = @cursor.toString() == '[object Feed]'
+                    @is_feed = @cursor.toString() in ['[object Feed]', '[object AtomFeed]']
                     @missing = 0
                     @ended = false
                 else


Fix following Michael comments

Fix following Michael comments

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -690,7 +690,7 @@
                                         suggestion_to_write: @current_suggestions[@current_highlighted_suggestion] # Auto complete with the highlighted suggestion
                                     @ignore_tab_keyup = true # If we are switching suggestion, we don't want to do anything else related to tab
                                     return true
-                        else if @extra_suggestions? and @extra_suggestions.length > 0 and @start_body is @start_body
+                        else if @extra_suggestions? and @extra_suggestions.length > 0 and @extra_suggestion.start_body is @extra_suggestion.start_body
                             # Trim suggestion
                             if @extra_suggestion?.body?[0]?.type is 'string'
                                 if @extra_suggestion.body[0].complete is true
@@ -1717,7 +1717,7 @@
                     data =
                         no_database: true
                 else
-                    databases_available = _.map(databases.models, (database) -> return database.get('name'))
+                    databases_available = databases.models.map (database) -> return database.get('name')
                     data =
                         no_database: false
                         databases_available: databases_available


Add comment, review 132, issue #191

Add comment, review 132, issue #191

--- admin/static/coffee/dataexplorer.coffee
+++ admin/static/coffee/dataexplorer.coffee
@@ -1506,7 +1506,9 @@
             @mouse_down = false
             @.$('.json_table').toggleClass('resizing', false)
             @set_scrollbar()
-        default_size_column: 310
+
+        default_size_column: 310 # max-width value of a cell of a table (as defined in the css file)
+
         render_result: (query, result) =>
             if query?
                 @query = query


Credential: don't shadow sensitiveKeys for owners but for everyone else

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -488,6 +488,7 @@
   # Poor man's shadow function ~ GG
   shadowed = (c) ->
     return ''  unless c
+    c = c[0...(Math.min  c.length, 30)]
     r = (c) -> Math.ceil c.length / 1.5
     return "*#{Array(r c).join '*'}#{c[(r c)..]}"

@@ -536,6 +537,9 @@
         shadowSecretData    : yes
         shadowSensitiveData : yes

+      if client._allowedPermissionIndex is 0
+        options.shadowSensitiveData = no
+
       @fetchData client, options, callback


bug fix

bug fix

--- src/module.coffee
+++ src/module.coffee
@@ -12,7 +12,7 @@
       @::[key] = val
     obj.included?.call(@)

-  @connect: (name, cls) ->
+  @connect: (cls) ->
     return unless typeof cls is 'function'

     unless cls.pluginName


* minor change

* minor change

--- src/normal.coffee
+++ src/normal.coffee
@@ -82,4 +82,3 @@
       value = vAttr.value if value is undefined and vAttr.value != undefined
       dest[name] = value if vCanAssign
     return
-


Fix lasso dragging bug.

Fix lasso dragging bug.

--- src/controller.coffee
+++ src/controller.coffee
@@ -1134,18 +1134,12 @@

             @addMicroUndoOperation new ReparseOperation parent, newBlock

-      @redrawMain()
-
       # Move the cursor to the position we just
       # dropped the block
       @moveCursorTo @draggingBlock.end, true

       # Now that we've done that, we can annul stuff.
-      @draggingBlock = null
-      @draggingOffset = null
-      @lastHighlight = null
-
-      @clearDrag()
+      @endDrag()

   # FLOATING BLOCK SUPPORT
   # ================================
@@ -1235,12 +1229,7 @@
         if @draggingBlock is @lassoSegment
           @lassoSegment = null

-        @draggingBlock = null
-        @draggingOffset = null
-        @lastHighlight = null
-
-        @clearDrag()
-        @redrawMain()
+        @endDrag()
         return

       else if renderPoint.x - @scrollOffsets.main.x < 0
@@ -2217,7 +2206,7 @@
       @addMicroUndoOperation new CreateSegmentOperation @lassoSegment

       # Move the cursor to the segment we just created
-      @moveCursorTo @lassoSegment.end.next, true
+      @moveCursorTo @lassoSegment.end.nextVisibleToken(), true

       @redrawMain()

@@ -3437,6 +3426,22 @@

     @dragCover.style.display = 'none'

+  # FAILSAFE END DRAG HACK
+  # ================================
+
+  hook 'mousedown', 10, ->
+    if @draggingBlock?
+      @endDrag()
+
+  Editor::endDrag = ->
+    @draggingBlock = null
+    @draggingOffset = null
+    @lastHighlight = null
+
+    @clearDrag()
+    @redrawMain()
+    return
+
   # TOUCHSCREEN SUPPORT
   # =================================

@@ -3657,13 +3662,7 @@

       if @inTree(@draggingBlock) and @mainViewOrChildrenContains @draggingBlock, renderPoint
         @draggingBlock.ephemeral = false
-
-        @draggingBlock = null
-        @draggingOffset = null
-        @lastHighlight = null
-
-        @clearDrag()
-        @redrawMain()
+        @endDrag()

   # LINE NUMBER GUTTER CODE
   # ================================


Fix a bug, document mark(), unmark()

Fix a bug, document mark(), unmark()

--- src/controller.coffee
+++ src/controller.coffee
@@ -502,7 +502,7 @@
         path = @getHighlightPath info.model, info.style
         path.draw @highlightCtx
       else
-        delete @markedLines[line]
+        delete @markedLines[id]

     for id, info of @extraMarks
       if @inTree info.model
@@ -3395,6 +3395,8 @@
     if head.type is 'newline'
       return false

+    # `key` is a unique identifier for this
+    # mark, to be used later for removal
     key = @markedBlockKey++

     @markedBlocks[key] = {
@@ -3404,6 +3406,8 @@

     @redrawHighlights()

+    # Return `key`, so that the caller can
+    # remove the line mark later with unmark(key)
     return key

   Editor::unmark = (key) ->


Remove debugging

Remove debugging

--- lib/db.coffee
+++ lib/db.coffee
@@ -55,7 +55,7 @@
   add: (project, callback) ->
     projects = CSON.readFileSync(@file()) || {}
     id = project.title.replace(/\s+/g, '').toLowerCase()
-    console.log id
+
     projects[id] = project
     successMessage = "#{project.title} has been added"
     errorMessage = "#{project.title} could not be saved"


Highlights are now sent up to Readmill

Highlights are now sent up to Readmill

--- src/annotator.readmill.coffee
+++ src/annotator.readmill.coffee
@@ -67,6 +67,21 @@
   error: (message) ->
     Annotator.showNotification message, Annotator.Notification.ERROR

+  _highlightFromAnnotation: (annotation) ->
+    # See: https://github.com/Readmill/API/wiki/Readings
+    {
+      pre: annotation.ranges
+      content: annotation.quote
+      highlighted_at: undefined
+    }
+
+  _commentFromAnnotation: (annotation) ->
+    # Documentation seems to indicate this should be wrapped in an object
+    # with a "content" property but that does not seem to work so we just
+    # return the text string here.
+    # See: https://github.com/Readmill/API/wiki/Readings
+    annotation.text
+
   _onConnectSuccess: (params) =>
     @connected params.access_token, params

@@ -104,9 +119,17 @@
   _onGetReadingError: (reading) =>
     @error "Unable to create reading for this book"

+  _onCreateHighlight: (annotation, data) ->
+    annotation.highlightUrl = data.location
+
   _onAnnotationCreated: (annotation) =>
     if @client.isAuthorized() and @book.id
-      @client.createHighlight()
+      url = @book.reading.highlights
+      comment = @_commentFromAnnotation annotation
+      highlight = @_highlightFromAnnotation annotation
+      request = @client.createHighlight url, highlight, comment
+      request.then jQuery.proxy(this, "_onCreateHighlight", annotation), =>
+        @error "Unable to send annotation to Readmill"
     else
       @unsaved.push annotation
       @connect() unless @client.isAuthorized()
@@ -229,8 +252,8 @@
   getHighlights: (url) ->
     @request url: url, type: "GET"

-  createHighlight: ->
-    @request type: "POST"
+  createHighlight: (url, highlight, comment) ->
+    @request type: "POST", url: url, data: {highlight: highlight, comment: comment}

   updateHighlight: ->


fix download start in common.js

fix download start in common.js

--- app/assets/javascripts/common.js.coffee
+++ app/assets/javascripts/common.js.coffee
@@ -40,11 +40,12 @@
             'project': DownloadsProject
             'search': DownloadsSearch

-          downloader = new DOWNLOADERS[list.data('download-type')]
-          downloader.start(
+          downloader = new DOWNLOADERS[list.data('download-type')](
             button.data('type'),
             {itemId: list.data('item-id')}
           )
+
+          downloader.start()
         )
       )
   )


Fix javascript.

Fix javascript.

--- app/assets/javascripts/groups.js.coffee
+++ app/assets/javascripts/groups.js.coffee
@@ -26,7 +26,7 @@

 #*** ajax for discussions on group page ***
 $ ->
-  if $("body.groups.show").length > 0
+  if $("body.groups.show").length > 0 && $('#group-discussions').html() != null
     idStr = new Array
     idStr = $('#group-discussions').children().attr('class').split('_')
     params = Application.getPageParam()
@@ -39,7 +39,7 @@
     $("#all-discussions-loading").addClass('hidden')

 $ ->
-  if $("body.groups.show").length > 0 && $('#group-discussions').html() != null
+  if $("body.groups.show").length > 0
     $(document).on('click', '#group-discussions .pagination a', (e)->
       unless $(this).parent().hasClass("gap")
         if Application.html5.supported


Don't set uri when pane item doesn't serialize itself

--- src/app/pane-container.coffee
+++ src/app/pane-container.coffee
@@ -77,9 +77,9 @@
         newPane.focus()

   itemDestroyed: (item) ->
-    state = item.serialize?()
-    state.uri ?= item.getUri?()
-    @destroyedItemStates.push(state) if state?
+    if state = item.serialize?()
+      state.uri ?= item.getUri?()
+      @destroyedItemStates.push(state)

   itemAdded: (item) ->
     itemUri = item.getUri?()


Data is converted

Data is converted

--- app/assets/javascripts/places.js.coffee
+++ app/assets/javascripts/places.js.coffee
@@ -34,4 +34,4 @@
   $.ajax
     url: '/places.json'
   .done (data) ->
-    googleMap content+    convert data


Simplify the regex a little bit

Simplify the regex a little bit

--- app/assets/javascripts/task_list.coffee
+++ app/assets/javascripts/task_list.coffee
@@ -126,16 +126,12 @@
     #{escapePattern(incomplete)}
   )
   \s+                     # is followed by whitespace
-  (                       # followed by anything unmistakable for a link,
-    ([^\[\(]|$)           # or end of line
-  |
-    (?!
-      \(.*?\)             # or, not immediately followed by a link URL
-    )
-    (                     # and not part of a link reference
-      (\[.*?\]\s*(\[.*?\]|\(.*?\))\s*)+
-      ([^\[]|$)
-    )
+  (?!
+    \(.*?\)               # is not part of a [foo](url) link
+  )
+  (?=                     # and is followed by zero or more links
+    (?:\[.*?\]\s*(?:\[.*?\]|\(.*?\))\s*)*
+    (?:[^\[]|$)           # and either a non-link or the end of the string
   )
 ///


On moving task error, revert to previous status

--- app/coffee/controllers/taskboard.coffee
+++ app/coffee/controllers/taskboard.coffee
@@ -121,9 +121,14 @@
                     task.status = parseInt(statusId, 10)
                     if task.isModified()
                         task.moving = true
-                        task.save().then (task) ->
+                        task.save().then((task) ->
                             task.moving = false
                             calculateStats()
+                        , (error) ->
+                            task.revert()
+                            task.moving = false
+                            loadTasks()
+                        )

         for statusId, tasks of $scope.unassignedTasks
             for task in tasks
@@ -131,9 +136,14 @@
                 task.status = parseInt(statusId, 10)
                 if task.isModified()
                     task.moving = true
-                    task.save().then (task) ->
+                    task.save().then((task) ->
                         task.moving = false
                         calculateStats()
+                    , (error) ->
+                        task.revert()
+                        task.moving = false
+                        loadTasks()
+                    )

     return


[DOC] Add documentation for src/core/token.coffee

--- src/core/token.coffee
+++ src/core/token.coffee
@@ -18,9 +18,11 @@
 Request     = require 'request'
 Config      = require '../common/config'

+# Generate a unique token.
 class _Token
         constructor : () ->

+        # Create the unique token and add it into the dataBase.
         createToken : (clientId, roomId, callback) =>
             Request.post {
                 json    : {
@@ -34,6 +36,7 @@
                 else
                     callback body.id

+        # Delete a token from database.
         deleteToken : (token) =>
             Request.del "#{Config.Restapi.Url}/token/#{token}", (e, r, data) =>
                 if e? or r.statusCode > 200


FIX: Fix ng-model attribute update for task def selector

--- src/app/tasks/task-definition-selector/task-definition-selector.coffee
+++ src/app/tasks/task-definition-selector/task-definition-selector.coffee
@@ -30,8 +30,7 @@

     $scope.setSelectedDefinition = (taskDef) ->
       $scope.selectedDefinition = taskDef
+      $scope.ngModel = taskDef
       if $scope.onSelectDefinition? && _.isFunction($scope.onSelectDefinition)
         $scope.onSelectDefinition(taskDef)
-      if $scope.ngModel? && _.isObject($scope.ngModel)
-        $scope.ngModel = taskDef
 )


heroku deletes public/assets without telling you!

--- packages/tower-application/server/assets.coffee
+++ packages/tower-application/server/assets.coffee
@@ -38,7 +38,7 @@
         # queue.push name: name, paths: paths, extension: extension, type: type, compressor: compressor
         _console.debug "Bundling public/#{type}/#{name}#{extension}"
         content = ""
-i
+
         for path in paths
           content += File.read("public/#{type}#{path}#{extension}") + "\n\n"


remove old icon logic

remove old icon logic

--- app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee
+++ app/assets/javascripts/app/controllers/agent_ticket_create.js.coffee
@@ -66,9 +66,6 @@
     # set active tab
     selectedTab = @el.find(".type-tabs .tab[data-type='#{type}']")
     selectedTab.addClass('active')
-    selectedTabIcon = @el.find(".type-tabs .tab[data-type='#{type}'] .icon")
-    selectedTabIcon.removeClass('gray')
-    selectedTabIcon.addClass('white')

     # set form type attributes
     articleSenderTypeMap =


alphabetise deps

alphabetise deps

--- app/templates/gulp/tasks/scripts.coffee
+++ app/templates/gulp/tasks/scripts.coffee
@@ -1,11 +1,11 @@
 gulp       = require "gulp"
+gutil      = require "gulp-util"
 babelify   = require "babelify"
 browserify = require "browserify"
 es         = require "event-stream"
 rename     = require "gulp-rename"
 source     = require "vinyl-source-stream"
 transform  = require "vinyl-transform"
-gutil      = require "gulp-util"

 bundleScripts = (files) ->
   tasks = files.map (entry) ->


Added all/post/put/delete to Router

Added all/post/put/delete to Router

--- src/router.coffee
+++ src/router.coffee
@@ -5,7 +5,7 @@
 	constructor: (@app) ->

 	group: (action, callback) ->
-		@_scopedAction = Object.assign {}, action
+		@_scopedAction = Object.assign { }, action

 		if @_scopedAction.prefix
 			@_scopedPrefix = @_scopedAction.prefix
@@ -18,9 +18,20 @@
 		@_scopedAction = null
 		@_scopedPrefix = ''

+	all: (path, action) ->
+		return @app.all @_scopedPrefix + path, @_makeAction(action)
+
 	get: (path, action) ->
-		action = @_makeAction action
-		return @app.get @_scopedPrefix + path, action
+		return @app.get @_scopedPrefix + path, @_makeAction(action)
+
+	post: (path, action) ->
+		return @app.post @_scopedPrefix + path, @_makeAction(action)
+
+	put: (path, action) ->
+		return @app.put @_scopedPrefix + path, @_makeAction(action)
+
+	delete: (path, action) ->
+		return @app.delete @_scopedPrefix + path, @_makeAction(action)

 	_makeAction: (action) ->
 		if typeof action is 'function'
@@ -30,7 +41,7 @@
 				action =
 					method: action

-			action = Object.assign {}, @_scopedAction, action
+			action = Object.assign { }, @_scopedAction, action
 			method = action.controller[action.method]
 			controller = action.controller
 			return ->


Sorted the friend sessions a bit.

Sorted the friend sessions a bit.

--- app/views/play/ladder/ladder_tab.coffee
+++ app/views/play/ladder/ladder_tab.coffee
@@ -155,7 +155,10 @@

   consolidateFriends: ->
     allFriendSessions = (@facebookFriendSessions or []).concat(@gplusFriendSessions or [])
-    _.uniq allFriendSessions, false, (session) -> session._id
+    sessions = _.uniq allFriendSessions, false, (session) -> session._id
+    sessions = _.sortBy sessions, 'totalScore'
+    sessions.reverse()
+    sessions

 class LeaderboardData
   constructor: (@level, @team, @session) ->


runner: Check for component:getsource capability before trying to fetch tests

--- src/runner.coffee
+++ src/runner.coffee
@@ -139,6 +139,8 @@

 exports.getComponentSuites = (runner, callback) ->
+  return callback null, [] if not runner.client.canDo 'component:getsource'
+
   protocol.getComponentTests runner.client, (err, tests) ->
     return callback err if err
     suites = loadComponentSuites tests


Small improvement to the static file serving code.

--- src/server.coffee
+++ src/server.coffee
@@ -77,7 +77,7 @@
 # send that one, or otherwise (since static file loading only happens
 # after we've checked all our custom filetype handlers and we are thus
 # at the end of the line) return a 404.
-app.get /^.+[^\/]$/, (req, res) ->
+filehandler = (req, res) ->
     if req.file
         res.sendfile req.file.path
     else
@@ -91,6 +91,10 @@
         destination = url.parse relay_server
     else
         destination = null
+        # if there's a relay server, like an Apache instance, that one will handle
+        # static file serving too, but if not, we let our app handle static file
+        # serving by adding in a new controller that does exactly that
+        app.get /^.+[^\/]$/, filehandler

     # proxy server
     proxy_server = http.createServer (req, res) ->


members browserified

members browserified

--- client/members/lib/contentdisplays/profilecontenteditableview.coffee
+++ client/members/lib/contentdisplays/profilecontenteditableview.coffee
@@ -1,3 +1,8 @@
-class ProfileContentEditableView extends KDContentEditableView
+kd = require 'kd'
+KDContentEditableView = kd.ContentEditableView
+JView = require 'app/jview'
+
+
+module.exports = class ProfileContentEditableView extends KDContentEditableView
   JView.mixin @prototype
- No newline at end of file
+


another constructor.name usage down

another constructor.name usage down

--- src/exceptions.coffee
+++ src/exceptions.coffee
@@ -28,9 +28,8 @@
   method_catch_handler: (rs, method, top_of_stack) ->
     cf = rs.curr_frame()
     if not top_of_stack and method.has_bytecode
-     cf.pc -= 3  # rewind the invoke opcode
-     # XXX: this will break on IE (due to constructor.name being undefined)
-     cf.pc -= 1 until method.code.opcodes[cf.pc]?.constructor.name.match /Invoke/ or cf.pc <= 0
+      cf.pc -= 3  # rewind the invoke opcode
+      --cf.pc until cf.pc <= 0 or method.code.opcodes[cf.pc]?.name.match /^invoke/
     exception_handlers = method.code?.exception_handlers
     etype = @exception.type
     handler = _.find exception_handlers, (eh) ->


Fix test to match implementation

Fix test to match implementation

--- services/web/test/unit_frontend/coffee/ide/editor/directives/cmEditorTests.coffee
+++ services/web/test/unit_frontend/coffee/ide/editor/directives/cmEditorTests.coffee
@@ -4,9 +4,11 @@

     beforeEach () ->
       @richTextInit = sinon.stub()
+      @richTextOpenDoc = sinon.stub()
       window.Frontend = {
         richText: {
-          init: @richTextInit
+          init: @richTextInit,
+          openDoc: @richTextOpenDoc
         }
       }

@@ -17,8 +19,6 @@

     it 'attaches to CM', () ->
       inject ($compile, $rootScope) ->
-        setValue = sinon.stub()
-        @richTextInit.returns({ setValue: setValue })
         getSnapshot = sinon.stub()
         detachFromCM = sinon.stub()
         attachToCM = sinon.stub()
@@ -30,10 +30,11 @@

         $compile('<div cm-editor sharejs-doc="sharejsDoc"></div>')($rootScope)
         $rootScope.$digest()
+        $rootScope.$digest()

+        expect(detachFromCM).to.have.been.called
         expect(getSnapshot).to.have.been.called
-        expect(setValue).to.have.been.called
-        expect(detachFromCM).to.have.been.called
+        expect(@richTextOpenDoc).to.have.been.called
         expect(attachToCM).to.have.been.called

     it 'detaches from CM when destroyed', () ->


simplified syntax

simplified syntax

--- core/app/backbone/views/conversations/conversation_reply_view.coffee
+++ core/app/backbone/views/conversations/conversation_reply_view.coffee
@@ -6,9 +6,9 @@

   submit: ->
     @message = new Message
-    @message.set('content', @$('.text').val())
-    @message.set('sender', currentUser)
-    @model.messages().push(@message)
+      content: @$('.text').val()
+      sender: currentUser
+    @model.messages().add @message

     @error('hide')
     @disableSubmit()


update hololens product

update hololens product

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -497,7 +497,7 @@
       description_cart: "Binocular camera mounts for Epson Moverio BT-200 with USBC clip. Use with the 120hz eye camera upgrades or existing 120hz eye cameras from a Pupil headset."
       img: [vr_ar_img_path,'addon_epson_bt300b.jpg'].join('/')
     epsonbt300_w120_e200b:
-      cost: 1400
+      cost: 1750
       title_product: 'Epson Moverio BT-300 Add-on'
       title_store: 'Epson Moverio BT-300 Binocular Mount Add-on'
       description_store: "Binocular camera mounts for Epson Moverio BT-300 and world camera mount. Ships with USBC clip, 2 x 200hz eye cameras, and 1 x high speed world camera."


remove w3d from configurator

remove w3d from configurator

--- assets/coffeescript/product_list.coffee
+++ assets/coffeescript/product_list.coffee
@@ -35,20 +35,20 @@
       weight: 5
       title_video: "Download sample videos"
       link_video: "https://drive.google.com/file/d/0B4Q4b_aBOfU-TmJvSFhFeWp1NEk/view?usp=sharing"
-    w3d:
-      id: 'w3d'
-      title_store: '3d'
-      description_store: "Intel RealSense R200 RGBD camera. Provides depth image and monocular RGB color image. (Note: This sensor is not compatible with Pupil Mobile)."
-      tech_specs:
-        sensor: "See Intel R200 data sheet for details"
-        fov:"See Intel R200 data sheet for details"
-        latency: "See Intel R200 data sheet for details"
-        audio: "Record audio via host device microphone"
-      title_cart: "World Camera: 3d"
-      description_cart: "Intel RealSense R200. Provides stereo RGB images and depth image."
-      weight: 17
-      title_video: "Download sample video"
-      link_video: "https://drive.google.com/file/d/0Byap58sXjMVfRi13ZUo2MkJTWUE/view?usp=sharing"
+    # w3d:
+    #   id: 'w3d'
+    #   title_store: '3d'
+    #   description_store: "Intel RealSense R200 RGBD camera. Provides depth image and monocular RGB color image. (Note: This sensor is not compatible with Pupil Mobile)."
+    #   tech_specs:
+    #     sensor: "See Intel R200 data sheet for details"
+    #     fov:"See Intel R200 data sheet for details"
+    #     latency: "See Intel R200 data sheet for details"
+    #     audio: "Record audio via host device microphone"
+    #   title_cart: "World Camera: 3d"
+    #   description_cart: "Intel RealSense R200. Provides stereo RGB images and depth image."
+    #   weight: 17
+    #   title_video: "Download sample video"
+    #   link_video: "https://drive.google.com/file/d/0Byap58sXjMVfRi13ZUo2MkJTWUE/view?usp=sharing"


Reduce space bar shortcut timeout

Reduce space bar shortcut timeout

--- assets/javascripts/app/shortcuts.coffee
+++ assets/javascripts/app/shortcuts.coffee
@@ -47,7 +47,7 @@
       when 27
         @trigger 'escape'
       when 32
-        if not @lastKeypress or @lastKeypress < Date.now() - 1000
+        if not @lastKeypress or @lastKeypress < Date.now() - 500
           @trigger 'pageDown'
           false
       when 33


limit NSIS to blocks

limit NSIS to blocks

--- grammars/nsL.cson
+++ grammars/nsL.cson
@@ -3,6 +3,22 @@
 ]
 name: "nsL Assembler"
 patterns: [
+  {
+    begin: "^\\s*(#nsis)\\b"
+    beginCaptures:
+      "1":
+        name: "keyword.other.nsl"
+    end: "^\\s*(#nsisend)\\b"
+    endCaptures:
+      "1":
+        name: "keyword.other.nsl"
+    name: "source.nsis.embedded.nsl"
+    patterns: [
+      {
+        include: "source.nsis"
+      }
+    ]
+  }
   {
     match: "\\b(Abort|AddBrandingImage|AddIncludeDir|AddPluginDir|AddSize|AllowRootDirInstall|AllowSkipFiles|AutoCloseWindow|BGFont|BGGradient|BrandingText|BringToFront|Call|Caption|ChangeUI|CheckBitmap|ClearErrors|CompletedText|ComponentText|CopyFiles|CRCCheck|CreateDirectory|CreateFont|CreateShortCut|Delete|DeleteINISec|DeleteINIStr|DeleteRegKey|DeleteRegValue|DetailPrint|DetailsButtonText|DirText|DirVar|DirVerify|EnableWindow|EnumRegKey|EnumRegValue|Exch|Exec|ExecShell|ExecWait|ExpandEnvStrings|File|FileBufSize|FileClose|FileErrorText|FileOpen|FileRead|FileReadByte|FileRecursive|FileSeek|FileWrite|FileWriteByte|FindClose|FindFirst|FindNext|FindWindow|FlushINI|GetCurInstType|GetCurrentAddress|GetDlgItem|GetDLLVersion|GetDLLVersionLocal|GetErrorLevel|GetFileTime|GetFileTimeLocal|GetFunctionAddress|GetInstDirError|GetLabelAddress|GetTempFileName|HideWindow|Icon|IfAbort|IfErrors|IfFileExists|IfRebootFlag|IfSilent|Include|InitPluginsDir|InstallButtonText|InstallColors|InstallDir|InstallDirRegKey|InstProgressFlags|InstType|InstTypeGetText|InstTypeSetText|IntFmt|IsWindow|LangString|LicenseBkColor|LicenseData|LicenseForceSelection|LicenseLangString|LicenseText|LoadLanguageFile|LockWindow|LogSet|LogText|MessageBox|MiscButtonText|Name|OutFile|Pop|Push|Quit|ReadEnvStr|ReadINIStr|ReadRegDWORD|ReadRegStr|Reboot|RegDLL|Rename|RequestExecutionLevel|ReserveFile|ReserveFileRecursive|RMDir|RMDirRecursive|SearchPath|SectionGetFlags|SectionGetInstTypes|SectionGetSize|SectionGetText|SectionIn|SectionSetFlags|SectionSetInstTypes|SectionSetSize|SectionSetText|SendMessage|SetAutoClose|SetBrandingImage|SetCompress|SetCompressor|SetCompressorDictSize|SetCtlColors|SetCurInstType|SetDatablockOptimize|SetDateSave|SetDetailsPrint|SetDetailsView|SetErrorLevel|SetErrors|SetFileAttributes|SetFont|SetOutPath|SetOverwrite|SetRebootFlag|SetRegView|SetShellVarContext|SetSilent|ShowInstDetails|ShowUninstDetails|ShowWindow|SilentInstall|SilentUninstall|Sleep|SpaceTexts|StrCmp|StrCmpS|StrCpy|StrLen|SubCaption|UninstallButtonText|UninstallCaption|UninstallIcon|UninstallSubCaption|UninstallText|UnRegDLL|VIAddVersionKey|VIProductVersion|WindowIcon|WriteINIStr|WriteRegBin|WriteRegDWORD|WriteRegExpandStr|WriteRegStr|WriteUninstaller|XPStyle)\\b"
     name: "keyword.nsl"
@@ -177,8 +193,5 @@
     end: "\\*/"
     name: "comment.block.nsl"
   }
-  {
-    include: "source.nsis"
-  }
 ]
 scopeName: "source.nsl"


more log params, and add preventDefault check:

--- src/sirius.coffee
+++ src/sirius.coffee
@@ -267,7 +267,6 @@
         current = window.location.hash
         tgt = window.location.origin
         history.replaceState({href: current}, "#{current}", "#{tgt}/#{current}")
-        #window.location.href="#{tgt}/#{current}"
       else
         # plain
         route_array = plain_routes
@@ -275,7 +274,10 @@
         history.pushState({href: href}, "#{href}", href)
         pathname = window.location.pathname
         pathname = "/" if pathname == ""
-        e.preventDefault()
+        if (e.preventDefault)
+          e.preventDefault()
+        else
+          e.returnValue = false
         current = pathname

       Sirius.Application.logger("Url change to: #{current}")
@@ -350,7 +352,7 @@
   ###
     a root url for application
   ###
-  start : false
+  start : false

   ###
     when, false, then hash will be add into last for url, for true, no
@@ -390,16 +392,24 @@
     @route   = options["route"]   || @route
     @logger  = options["logger"]  || @logger
     @start   = options["start"]   || @start
+    @hash_always_on_top = options["hash_always_on_top"] || @hash_always_on_top
+    @use_hash_routing_for_old_browsers = options["use_hash_routing_for_old_browsers"] || @use_hash_routing_for_old_browsers
     @logger("Logger enabled? #{@log}")

     n = @adapter.constructor.name
     @logger("Adapter: #{n}")
+    @logger("Hash always on top: #{@hash_always_on_top}")
+    @logger("Use hash routing for old browsers: #{@use_hash_routing_for_old_browsers}")
+    @logger("Current browser: #{navigator.userAgent}")
+
+

     # start
     Sirius.RouteSystem.create(@route, () =>
       @adapter.fire(document, "application:run", new Date());
     );

+    @logger()
     if @start
       Sirius.redirect(@start)


Add out of office feature

Add out of office feature

--- src/status.coffee
+++ src/status.coffee
@@ -3,7 +3,7 @@
 window.Status or= {}

 class window.Status.Widget
-  _version = "3.7.1"
+  _version = "3.8.0"

   constructor: (@options = {}) ->
     requiredOptions = ["hostname", "selector"]
@@ -18,6 +18,7 @@
       "ssl": true,
       "css": true,
       "debug": false,
+      "outOfOffice": false,
       "linkTarget": "_blank",
       "display": {
         "hideOnError": true,
@@ -29,6 +30,9 @@
         "statistic": {
           "uptimeDecimals": 4,
           "minIncidentFreeStreak": 86400
+        },
+        "outOfOffice": {
+          "resetStatusLed": false
         }
       }
       "i18n": {
@@ -50,6 +54,9 @@
               but we have detected outages on at least one component."
           }
         },
+        "state": {
+          "outOfOffice": "Out of Office"
+        },
         "linkBack": "View Status Page",
         "time": {
           "distanceInWords": {
@@ -108,6 +115,7 @@
     @hostname = @options["hostname"]
     @display = @options["display"]
     @i18n = @options["i18n"]
+    @outOfOffice = @display["outOfOffice"]

     if !/^https?:\/\//i.test(@hostname)
       protocol = (if @options["ssl"] then "https" else "http")
@@ -149,6 +157,8 @@
     unless @display["ledOnly"]
       @elements.state = @createEl "span", @elements.widget, "state"
       setElText @elements.state, @i18n["loading"]
+
+    @officeHoursTimeout()

     if @display["ledPosition"] != "left"
       @elements.widget.appendChild @elements.led
@@ -323,28 +333,40 @@

   parseBasicEventData: (e) ->
     data = @parse e
-    humanData = {
+    @humanData = {
       state: @humanState(data.state),
       percentUptime: @humanPercentUptime(data.percent_uptime),
       incidentFreeStreak: @humanIncidentFreeStreak(data.incident_free_streak)
     }

     @updateState data.state
-    @updatePercentUptime data.percent_uptime, humanData.percentUptime
+    @updatePercentUptime data.percent_uptime, @humanData.percentUptime
     @updateIncidentFreeStreak data.incident_free_streak,
-      humanData.incidentFreeStreak
-    @updateToggle humanData
+      @humanData.incidentFreeStreak
+    @updateToggle @humanData
     data

   updateState: (state) ->
     state = "pending" if !state?
-    setElDataAttr @elements.led, "state", state
+
+    if @isOutOfOffice()
+      setElDataAttr @elements.led, "state", "pending"
+    else
+      setElDataAttr @elements.led, "state", state
+
     @state = state
     @updateIssuePaneText()

   updateToggle: (humanData) ->
     return unless @elements.state?
     text = template @i18n["toggle"], humanData
+    @stateText = text
+
+    if @isOutOfOffice()
+      fakeHumanData = Object.assign {}, humanData
+      fakeHumanData.state = @humanState "outOfOffice"
+      text = template @i18n["toggle"], fakeHumanData
+
     setElText @elements.state, text
     @alignPane()

@@ -506,6 +528,49 @@
       offset -= 20

     @elements.pane.style.left = offset + "px"
+
+  resetState: ->
+    return unless @state? && @humanData?
+    @updateState @state
+    @updateToggle @humanData
+
+  officeHoursTimeout: ->
+    now = utcDate().getTime()
+    officeOpen = @officeOpenTimestamp() - now
+    officeClose = @officeCloseTimestamp() - now
+    nextChange = if officeOpen < officeClose then officeOpen else officeClose
+
+    setTimeout =>
+      @resetState()
+      @officeHoursTimeout()
+    , nextChange
+
+  isOutOfOffice: ->
+    return false if !@options["outOfOffice"]
+    now = utcDate().getTime()
+    !(@officeOpenTimestamp() <= now && now <= @officeCloseTimestamp())
+
+  officeOpenTimestamp: ->
+    @officeHourToTimestamp "officeOpenHour"
+
+  officeCloseTimestamp: ->
+    @officeHourToTimestamp "officeCloseHour"
+
+  officeHourToTimestamp: (hourOptionKey) ->
+    hour = @outOfOffice[hourOptionKey]
+    advanceDay = hourOptionKey == "officeCloseHour" && @officeHoursOverlapDays()
+
+    if window.moment
+      day = moment().tz(@outOfOffice["timezone"]).startOf("day").add hour, "h"
+      day.add(1, "d") if advanceDay
+      day.valueOf()
+    else
+      day = utcDate()
+      day.setDate(day.getDate() + 1) if advanceDay
+      day.setHours hour, 0, 0, 0
+
+  officeHoursOverlapDays: ->
+    @outOfOffice["officeOpenHour"] < @outOfOffice["officeCloseHour"]

   debounce: (func, threshold, execAsap) ->
     timeout = null
@@ -588,6 +653,9 @@
   setElHTML = (el, html) ->
     el.innerHTML = html

+  getElDataAttr = (el, attr) ->
+    el.getAttribute "data-#{attr}"
+
   setElDataAttr = (el, attr, val) ->
     el.setAttribute "data-#{attr}", val

@@ -607,6 +675,10 @@

   clamp = (n, min, max) ->
     if n <= min then min else if n >= max then max else n
+
+  utcDate = ->
+    now = new Date()
+    new Date now.getTime() + now.getTimezoneOffset() * 60000

   deepMerge = (target, source) ->
     destination = {}


Add deprecations to the syntax scoped property methods

--- src/syntax.coffee
+++ src/syntax.coffee
@@ -40,16 +40,21 @@
     @propertyStore.propertySets

   addProperties: (args...) ->
+    deprecate 'Consider using atom.config.set() instead. A direct (but private) replacement is available at atom.config.addScopedDefaults().'
     atom.config.addScopedDefaults(args...)

   removeProperties: (name) ->
+    deprecate 'A direct (but private) replacement is available at atom.config.removeScopedSettingsForName().'
     atom.config.removeScopedSettingsForName(name)

   clearProperties: ->
+    deprecate 'A direct (but private) replacement is available at atom.config.clearScopedSettings().'
     atom.config.clearScopedSettings()

   getProperty: (scope, keyPath) ->
+    deprecate 'A direct (but private) replacement is available at atom.config.getRawScopedValue().'
     atom.config.getRawScopedValue(scope, keyPath)

   propertiesForScope: (scope, keyPath) ->
+    deprecate 'A direct (but private) replacement is available at atom.config.settingsForScopeDescriptor().'
     atom.config.settingsForScopeDescriptor(scope, keyPath)


Tack model ID back in for server JSON

Tack model ID back in for server JSON

--- vendor/assets/javascripts/brainstem/brainstem-collection.coffee
+++ vendor/assets/javascripts/brainstem/brainstem-collection.coffee
@@ -189,7 +189,7 @@
     @_getCacheObject()?.valid = false

   toServerJSON: (method) ->
-    @map (model) -> model.toServerJSON(method)
+    @map (model) -> _.extend(model.toServerJSON(method), id: model.id)

   #


chmod src/tester

chmod src/tester


Fixing typo.

Fixing typo.

--- src/timber.coffee
+++ src/timber.coffee
@@ -453,7 +453,7 @@
         # Stop, if so desired.
         return if not @continue

-        # Determine how much time hsa elapsed since the last loop.
+        # Determine how much time has elapsed since the last loop.
         now = new Date()
         elapsed = now - @timestamp


replace " with " while submitting an activity. this is only suboptimal. we are a coding community but you cannot post HTML

--- client/landing/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
+++ client/landing/app/Applications/Activity.kdapplication/widgets/widgetcontroller.coffee
@@ -112,6 +112,9 @@

   widgetSubmit:(data,constructorName,callback)->
+    for own key, field of data when _.isString(field)
+      data[key] = field.replace(/"/g, '"')
+
     # if troll clear the tag input
     data.meta?.tags = [] if KD.checkFlag 'exempt'
     if data.activity
@@ -196,4 +199,4 @@
           content     : activity.code
           syntax      : activity.syntax
         ]
-    fakePost+    fakePost


postpone mail sending to keep right variable context; hopefully fixes #169

--- both/methods/shift/shiftSchedule.coffee
+++ both/methods/shift/shiftSchedule.coffee
@@ -10,41 +10,42 @@
 				check message, String

 			for team in shift.teams when team._id == teamId
-				Meteor.call 'sendCancelTeam', shiftId, teamId, message, ->
-					if message == 'missingParticipant'
-						for pendingUser in team.pending
-							pendingUser.checked = false
-							pendingUser.informed = false
-
-							Shifts.update _id: shiftId, 'teams._id': teamId,
-								$pull: 'teams.$.pending': _id: pendingUser._id
-								$addToSet: 'teams.$.declined': pendingUser
-
-						for participant in team.participants
-							participant.checked = true
-							participant.informed = false
-
-							Shifts.update _id: shiftId, 'teams._id': teamId,
-								$pull: 'teams.$.participants': _id: participant._id
-								$addToSet: 'teams.$.pending': participant
-
-						Meteor.call 'openTeam', shiftId, teamId
-
-					else
-						for participant in team.participants.concat(team.pending)
-							participant.thisTeamleader = false
-
-							Shifts.update _id: shiftId, 'teams._id': teamId,
-								$pull: 'teams.$.declined': _id: participant._id
-
-							Shifts.update _id: shiftId, 'teams._id': teamId,
-								$pull: 'teams.$.pending': _id: participant._id
-
-							Shifts.update _id: shiftId, 'teams._id': teamId,
-								$pull: 'teams.$.participants': _id: participant._id
-								$addToSet: 'teams.$.declined': participant
-
-						Meteor.call 'openTeam', shiftId, teamId
+				if message == 'missingParticipant'
+					for pendingUser in team.pending
+						pendingUser.checked = false
+						pendingUser.informed = false
+
+						Shifts.update _id: shiftId, 'teams._id': teamId,
+							$pull: 'teams.$.pending': _id: pendingUser._id
+							$addToSet: 'teams.$.declined': pendingUser
+
+					for participant in team.participants
+						participant.checked = true
+						participant.informed = false
+
+						Shifts.update _id: shiftId, 'teams._id': teamId,
+							$pull: 'teams.$.participants': _id: participant._id
+							$addToSet: 'teams.$.pending': participant
+
+					Meteor.call 'openTeam', shiftId, teamId
+
+				else
+					for participant in team.participants.concat(team.pending)
+						participant.thisTeamleader = false
+
+						Shifts.update _id: shiftId, 'teams._id': teamId,
+							$pull: 'teams.$.declined': _id: participant._id
+
+						Shifts.update _id: shiftId, 'teams._id': teamId,
+							$pull: 'teams.$.pending': _id: participant._id
+
+						Shifts.update _id: shiftId, 'teams._id': teamId,
+							$pull: 'teams.$.participants': _id: participant._id
+							$addToSet: 'teams.$.declined': participant
+
+					Meteor.call 'openTeam', shiftId, teamId
+
+				Meteor.call 'sendCancelTeam', shiftId, teamId, message

 	approveRequest: (shiftId, teamId, userId) ->
 		shift = Shifts.findOne shiftId, fields: teams: 1, tagId: 1, projectId: 1


fix(kill): Don’t attempt to re-open windows on killed

--- src/browser/nylas-window.coffee
+++ src/browser/nylas-window.coffee
@@ -194,7 +194,13 @@
         detail: 'Would you like to force close it or keep waiting?'
       @browserWindow.destroy() if chosen is 0

-    @browserWindow.webContents.on 'crashed', =>
+    @browserWindow.webContents.on 'crashed', (event, killed) =>
+      if killed
+        # Killed means that the app is exiting and the browser window is being
+        # forceably cleaned up. Carry on, do not try to reload the window.
+        @browserWindow.destroy()
+        return
+
       app.exit(100) if @exitWhenDone

       if @neverClose


ComputeProvider: fix Amazon provider default image name (source_ami)

--- workers/social/lib/social/models/computeproviders/amazon.coffee
+++ workers/social/lib/social/models/computeproviders/amazon.coffee
@@ -15,7 +15,7 @@
     meta =
       type          : "amazon"
       region        : "us-east-1"
-      source_ami    : "ami-6926dce"
+      source_ami    : "ami-a6926dce"
       instance_type : instanceType

     callback null, { meta, credential }


Fix init

Fix init

--- helper.coffee
+++ helper.coffee
@@ -1,10 +1,10 @@
 load = ->
-  el = document.querySelector(INSTALL_OPTIONS.location.selector)
+  el = document.querySelector(INSTALL_OPTIONS.location)
   return unless el

   el.innerHTML = Typist.renderVariants el.textContent, INSTALL_OPTIONS.variants

-  new Typist el,
+  new Typist el.querySelector('.typist-element'),
     letterInterval: 60
     textInterval:   3000


Form

Form

- Improved "destroy".

--- src/elements/Form/Form.coffee
+++ src/elements/Form/Form.coffee
@@ -854,6 +854,7 @@

 	destroy: ->
 		if @table
+			@unregisterTableListeners()
 			DOM.remove(@table)
 			@table = null
 		super()


Word movement uses `end-of-word` and `beginning-of-word`

Closes #1253

--- keymaps/base.cson
+++ keymaps/base.cson
@@ -1,9 +1,9 @@
 '.editor':
   # Platform Bindings
-  'alt-left': 'editor:move-to-previous-word-boundary'
-  'alt-right': 'editor:move-to-next-word-boundary'
-  'alt-shift-left': 'editor:select-to-previous-word-boundary'
-  'alt-shift-right': 'editor:select-to-next-word-boundary'
+  'alt-left': 'editor:move-to-beginning-of-word'
+  'alt-right': 'editor:move-to-end-of-word'
+  'alt-shift-left': 'editor:select-to-beginning-of-word'
+  'alt-shift-right': 'editor:select-to-end-of-word'
   'home': 'editor:move-to-first-character-of-line'
   'end': 'editor:move-to-end-of-line'
   'shift-home': 'editor:select-to-first-character-of-line'


new status update: add signature annotations

--- workers/social/lib/social/models/messages/newstatusupdate/index.coffee
+++ workers/social/lib/social/models/messages/newstatusupdate/index.coffee
@@ -103,6 +103,7 @@
         fetchLikedByes:[
           (signature Function)
           (signature Object, Function)
+          (signature Object, Object, Function)
         ]

     schema            : schema


Also check absence of "days left" message

On narrow screens, "Free trial:" doesn't show anyway, so we need
to check for something else.

--- test/integration/free_trial.coffee
+++ test/integration/free_trial.coffee
@@ -112,3 +112,8 @@
         (/Free trial/i.test text).should.be.false
         done()

+    it 'should not tell me "days left"', (done) ->
+      wd40.getText 'body', (err, text) ->
+        (/days left/i.test text).should.be.false
+        done()
+


Spelling correction.

Spelling correction.

--- src/viewer.coffee
+++ src/viewer.coffee
@@ -199,7 +199,7 @@
             faces #{f1} and #{f2}. Using first found in the faceOrder list."
           if m.fs[f1].ord["f#{f2}"] != o
             console.log "Error: duplicate ordering [#{f1},#{f2},#{o}] \
-              is inconsistant with a previous entry."
+              is inconsistent with a previous entry."
         else
           m.fs[f1].ord["f#{f2}"] = o
           m.fs[f2].ord["f#{f1}"] = -o * normRel


Deprecated the steroids.view.rotateTo in favour of the newer steroids.screen.rotate() API. steroids.view.rotateTo is internally calling the native API setOrientation which is used in the newer steroids.screen.rotate() function.

--- src/models/views/WebView.coffee
+++ src/models/views/WebView.coffee
@@ -100,16 +100,29 @@

     callbacks.onSuccess?.call()

+  mapDegreesToOrientations: (degrees) ->
+    if degrees == 0 or degrees == "0"
+      "portrait"
+    else if degrees == 180 or degrees == "180"
+      "portraitupsidedown"
+    else if degrees == -90 or degrees == "-90"
+      "landscapeleft"
+    else if degrees == 90 or degrees == "90"
+      "landscaperight"
+
+  # Deprecated. should use steroids.screen.rotate() instead.
   rotateTo: (options={}, callbacks={}) ->
-    degrees = if options.constructor.name == "String"
+    degrees = if options.constructor.name == "String" or options.constructor.name == "Number"
       options
     else
       options.degrees

+    orientation = @mapDegreesToOrientations degrees
+
     steroids.nativeBridge.nativeCall
-      method: "rotateTo"
+      method: "setOrientation"
       parameters:
-        orientation: degrees
+        orientation: orientation
       successCallbacks: [callbacks.onSuccess]
       failureCallbacks: [callbacks.onFailure]