Don't allow opening results box if input is disabled.

--- jquery.immybox.coffee
+++ jquery.immybox.coffee
@@ -159,15 +159,16 @@
     toggleResults: (e) =>
       e.stopPropagation() # stop the event from bubbling up and the body click event catching it
       @revertOtherInstances() # because the event isn't bubbling, other instances won't revert
-      if @queryResultArea.is(':visible')
-        @hideResults()
-      else
-        if @selectedChoice?
-          @insertFilteredChoiceElements @oldQuery
+      if @element.is ':enabled'
+        if @queryResultArea.is(':visible')
+          @hideResults()
         else
-          @insertFilteredChoiceElements ''
-
-      @element.focus()
+          if @selectedChoice?
+            @insertFilteredChoiceElements @oldQuery
+          else
+            @insertFilteredChoiceElements ''
+
+        @element.focus()

     ###################
     # private methods #


Add "Cache-Control: no-cache" header to /xhr POST requests.

This should not be required by the standard, but iOS 6 appears to need it.  See
http://stackoverflow.com/questions/12506897/is-safari-on-ios-6-caching-ajax-results

--- src/sockjs.coffee
+++ src/sockjs.coffee
@@ -85,7 +85,7 @@
             ['GET',     p('/websocket'),   ['raw_websocket']],
             ['GET',     t('/jsonp'), ['h_sid', 'h_no_cache', 'jsonp']],
             ['POST',    t('/jsonp_send'), ['h_sid', 'expect_form', 'jsonp_send']],
-            ['POST',    t('/xhr'), ['h_sid', 'xhr_cors', 'xhr_poll']],
+            ['POST',    t('/xhr'), ['h_sid', 'h_no_cache', 'xhr_cors', 'xhr_poll']],
             ['OPTIONS', t('/xhr'), opts_filters()],
             ['POST',    t('/xhr_send'), ['h_sid', 'xhr_cors', 'expect_xhr', 'xhr_send']],
             ['OPTIONS', t('/xhr_send'), opts_filters()],


Send collaboration activation message after realtime document is created

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -626,7 +626,6 @@
   createCollaborationSession: (callbacks) ->

     fileName = @getRealtimeFileName()
-    socialHelpers.sendActivationMessage @socialChannel, kd.noop

     realtimeHelpers.createCollaborationFile @rtm, fileName, (err, file) =>
       return callbacks.error err  if err
@@ -635,6 +634,9 @@
         return callbacks.error err  if err

         @rtmFileId = file.id
+
+        socialHelpers.sendActivationMessage @socialChannel, kd.noop
+
         @setMachineSharingStatus on, (err) =>
           return callbacks.error err  if err
           callbacks.success doc


Revert "Prepare collaboration for owned machines only"

This reverts commit f536109308359877c5a5a06bbe6588cf46d89788.

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -864,8 +864,6 @@

   prepareCollaboration: ->

-    return  unless @mountedMachine.isMine() or @workspaceData.channelId
-
     @rtm = new RealtimeManager
     @showShareButton()
     @rtm.ready => @initCollaborationStateMachine()


Remove functionality.

Remove functionality.

--- client/templates/items_dial/items_dial.coffee
+++ client/templates/items_dial/items_dial.coffee
@@ -18,11 +18,20 @@
       projectId = center_item_id
       project = Projects.findOne projectId
       userId = $(this).attr("data-id")
-      projectProperties =
-        members: (member for member in project.members when member != userId)
-      Projects.update projectId, {$set: projectProperties}, (error) ->
-        if error
-          throwError error.reason
+      members = (member for member in project.members when member != userId)
+      if members.length > 0
+        projectProperties =
+          members: members
+        Projects.update projectId, {$set: projectProperties}, (error) ->
+          if error
+            throwError error.reason
+        if Meteor.userId not in members
+          Router.go 'memberPage', {_id: Meteor.userId}
+      else
+        Projects.remove projectId, (error) ->
+          if error
+            throwError error.reason
+        Router.go 'memberPage', {_id: userId}
     else
       console.log("Delete project from member not implemented yet")
   else


Remove unused pluralize function

Remove unused pluralize function

--- src/status.coffee
+++ src/status.coffee
@@ -320,8 +320,6 @@
     cssClass += "__#{className}" if className?
     createEl type, cssClass, parent

-  pluralize = (x) -> if x > 1 then "s" else ""
-
   addClass = (el, className) ->
     if el.classList
       el.classList.add className


Fixed double clicking sometimes firing when you're just dragging units around in the level editor.

--- app/views/editor/level/thangs/ThangsTabView.coffee
+++ app/views/editor/level/thangs/ThangsTabView.coffee
@@ -38,6 +38,7 @@
     'editor:view-switched': 'onViewSwitched'
     'sprite:dragged': 'onSpriteDragged'
     'sprite:mouse-up': 'onSpriteMouseUp'
+    'sprite:mouse-down': 'onSpriteMouseDown'
     'sprite:double-clicked': 'onSpriteDoubleClicked'
     'surface:stage-mouse-up': 'onStageMouseUp'
     'editor:random-terrain-generated': 'onRandomTerrainGenerated'
@@ -192,6 +193,7 @@
     @surface?.spriteBoss?.selectSprite null, null

   onSpriteMouseDown: (e) ->
+    @dragged = false
     # Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked.
     # clearTimeout @backgroundAddClickTimeout
     # if e.originalEvent.nativeEvent.button == 2
@@ -206,6 +208,7 @@

   onSpriteDragged: (e) ->
     return unless @selectedExtantThang and e.thang?.id is @selectedExtantThang?.id
+    @dragged = true
     @surface.camera.dragDisabled = true
     {stageX, stageY} = e.originalEvent
     wop = @surface.camera.screenToWorld x: stageX, y: stageY
@@ -230,7 +233,7 @@
     @thangsTreema.set path + '/config/pos', x: pos.x, y: pos.y, z: pos.z

   onSpriteDoubleClicked: (e) ->
-    return unless e.thang
+    return unless e.thang and not @dragged
     @editThang thangID: e.thang.id

   onRandomTerrainGenerated: (e) ->


Don't use the full new footer

Don't use the full new footer

--- js/outer/_page.coffee
+++ js/outer/_page.coffee
@@ -28,7 +28,7 @@

   render: (cx) =>
     template = @name
-    footer_name = "footer"
+    footer_name = "footer_nav" # dont show the full nav yet
     klass = "outer"

     unless VM.ab().new_outer_old_copy()


Fixing comment typo.

Fixing comment typo.

--- src/timber.coffee
+++ src/timber.coffee
@@ -357,7 +357,7 @@
         context.stroke()

     getAxisAlignedBoundingBox : () ->
-        # FIXME: need non-alis aligned collisions for this
+        # FIXME: need non-axis aligned collisions for this

         yt = @position.y - @radius
         yb = @position.y + @radius


Update

Update

--- ng-qtip.coffee
+++ ng-qtip.coffee
@@ -22,20 +22,6 @@
   link: (scope, el, attrs) ->
     string_to_bool = (str) -> !(String(str).toLowerCase() in ['false', '0', 'null'])

-    my = scope.qtipMy || 'bottom center'
-    at = scope.qtipAt || 'top center'
-    qtipClass = attrs.qtipClass || 'qtip'
-    adjustX = parseInt(scope.qtipAdjustX) || 0
-    adjustY = parseInt(scope.qtipAdjustY) || 0
-    content = scope.qtipContent || scope.qtip
-    event = scope.qtipEvent || 'mouseover'
-    eventOut = scope.qtipEventOut || 'mouseout'
-    fixed = if scope.qtipFixed isnt null then string_to_bool scope.qtipFixed else yes
-    delay = scope.qtipDelay || 100
-    style =
-      classes: qtipClass
-      tip: scope.qtipStyle ? {}
-
     if scope.qtipEvent is 'false'
       event = false
     if scope.qtipEventOut is 'false'
@@ -48,21 +34,23 @@

     generateQtip = (content) ->
       options =
-        content: content
+        content: scope.qtipContent ? scope.qtip
         position:
-          my: my
-          at: at
+          my: scope.qtipMy ? 'bottom center'
+          at: scope.qtipAt ? 'top center'
           target: el
           adjust:
-            x: adjustX
-            y: adjustY
+            x: parseInt(scope.qtipAdjustX) ? 0
+            y: parseInt(scope.qtipAdjustY) ? 0
         show:
-          event: event
+          event: scope.qtipEvent ? 'mouseover'
         hide:
-          fixed: fixed
-          delay: delay
-          event: eventOut
-        style: style
+          fixed: if scope.qtipFixed isnt null then string_to_bool scope.qtipFixed else yes
+          delay: scope.qtipDelay ? 100
+          event: scope.qtipEventOut ? 'mouseout'
+        style:
+          classes: attrs.qtipClass ? 'qtip'
+          tip: scope.qtipStyle ? {}

       $(el).qtip options


don't fetch data

don't fetch data

--- src/trello.coffee
+++ src/trello.coffee
@@ -105,14 +105,8 @@
       msg.reply "Yep, ok, I moved that card to #{list_name}." unless err

 module.exports = (robot) ->
-  # fetch our board data when the script is loaded
   ensureConfig console.log
-  trello.get "/1/boards/#{process.env.HUBOT_TRELLO_BOARD}", (err, data) ->
-    board = data
-    trello.get "/1/boards/#{process.env.HUBOT_TRELLO_BOARD}/lists", (err, data) ->
-      for list in data
-        lists[list.name.toLowerCase()] = list
-
+
   robot.respond /trello new ["'](.+)["']\s(.*)/i, (msg) ->
     ensureConfig msg.send
     card_name = msg.match[2]


Add small documentation

Add small documentation

--- src/tumblr.coffee
+++ src/tumblr.coffee
@@ -1,21 +1,33 @@
-##
-# Tumblr API
-##
+###
+  node-tumblr 0.0.2
+  (c) 2011 Alexey Simonenko, Serenity LLC.
+  For all details and documentation:
+  http://go.simonenko.su/node-tumblr
+###

 xhr = require 'request'
 qs  = require 'querystring'

+# Tumblr
+# ------
+
+# Constructor
 Tumblr = exports.Tumblr = (host, key) ->
   @host = host
   @key  = key

 (->

+  # Retrieve blog info.
+  # This method returns general information about the blog,
+  # such as the title, number of posts, and other high-level data.
   @info = (fn) ->
     url = urlFor 'info', @

     request url, fn

+  # Retrieve published posts.
+  # Posts are returned as an array attached to the posts field.
   @posts = (options, fn) ->
     [fn, options] = [options, null] if typeof options is 'function'

@@ -23,14 +35,17 @@

     request url, fn

+  # Create alias for each type of posts and forward this call to @posts method
   alias = (self, type) ->
     self[type] = (options, fn) ->
       options = {} if not options
       options.type = type if not options.type
       @posts options, fn

+  # Alias text, quote, link, answer, video, audio and photo posts
   alias @, type for type in ['text', 'quote', 'link', 'answer', 'video', 'audio', 'photo']

+  # Prepare url for API call
   urlFor = (action, self, options = null) ->
     params = [
       'http://api.tumblr.com/v2/blog/'         # Tumblr API URL
@@ -46,6 +61,7 @@

     params.join ''

+  # Request API and call callback function with response
   request = (url, fn = ->) ->
     xhr {url}, (error, request, body) ->
       body = JSON.parse body


partially reverts feeabb in order to hide menu bar win/linux

--- src/ui/app.coffee
+++ src/ui/app.coffee
@@ -51,12 +51,19 @@
 # called when window is ready to show
 #  note: could not use event here, as it must be defined
 #  before
-ipc.on 'ready-to-show', () ->
+ipc.on 'ready-to-show', (aca) ->
+    # get window object
+    mainWindow = remote.getCurrentWindow()
+    # hide menu bar in all platforms but darwin
+    unless process.platform is 'darwin'
+        mainWindow.setAutoHideMenuBar(true)
+        mainWindow.setMenuBarVisibility(false)
+    # handle the visibility of the window
     if viewstate.startminimizedtotray
-        remote.getCurrentWindow().hide()
+        mainWindow.hide()
     else if !remote.getGlobal('windowHideWhileCred')? ||
              remote.getGlobal('windowHideWhileCred') != true
-        remote.getCurrentWindow().show()
+        mainWindow.show()

 # wire up stuff from server
 ipc.on 'init', (ev, data) -> dispatcher.init data


Redo the scaling logic

Redo the scaling logic

--- web/source/javascripts/angular-app.js.coffee
+++ web/source/javascripts/angular-app.js.coffee
@@ -64,13 +64,18 @@
     height = 800
     diag = Math.sqrt(width**2+height**2)
     minSide = Math.min(width, height)
+    border = 100
+    xScale = d3.scale.linear().range([border, width-border])
+    yScale = d3.scale.linear().range([border, height-border])
+    xScaleD = (doc) -> xScale(doc.x)
+    yScaleD = (doc) -> yScale(doc.y)

     svg = d3.select(elem[0]).append('svg')
       .attr('width', width)
       .attr('height', height)
     force = d3.layout.force()
-      .gravity(.05)
-      .charge(-100)
+      # .gravity(.1)
+      # .charge(-30)
       .size([width, height])
     # preparing the pies
     pie = d3.layout.pie()
@@ -98,29 +103,30 @@
         .outerRadius(outerRadius)
       force
         .linkDistance((l) -> l.distance)
-        .charge((n) -> if n.active then -150 else -30) # fush nodes away from selected node
-        # TODO try friction based on mouse distance
         .nodes(nodes)
         .links(links)
         .start()

+      xScale.domain(d3.extent(nodes, (n) -> n.x))
+      yScale.domain(d3.extent(nodes, (n) -> n.y))
+
       # voronoi
-      voronoi = d3.geom.voronoi().x((n) -> n.x).y((n) -> n.y)
+      voronoi = d3.geom.voronoi().x(xScaleD).y(yScaleD)
       calculateVoronoi = ->
         _.zipWith(nodes, voronoi(nodes), (node, patch) -> node.voronoiArea = patch)
       calculateVoronoi()

       # hulls
       hulls = svg.selectAll('.hull').data(_.pairs(byParty)).enter().append('path').attr('class', (d) -> [party, articles] = d; "hull #{party}")
-      hull = d3.geom.hull().x((node) -> node.x).y((node) -> node.y)
-      hullArea = d3.svg.line().x((n) -> n.x).y((n) -> n.y).interpolate('linear-closed')
+      hull = d3.geom.hull().x(xScaleD).y(yScaleD)
+      hullArea = d3.svg.line().x(xScaleD).y(yScaleD).interpolate('linear-closed')
       updateActive = ->
-        node.sort((d, o) -> +d.active * 2 - 1) # map true, false to 1 and -1
+        # node.sort((d, o) -> +d.active * 2 - 1) # map true, false to 1 and -1
         voronoiPatches.classed('active', (d) -> d.active)
         _.delay -> node.classed('active', (d) -> d.active) # delay to ensure css animations still work

       # link = svg.selectAll('.link').data(links).enter().append('line').attr('class', 'link')
-      node = svg.selectAll('.node').data(nodes).enter().append('g').attr('class', (d) -> 'node ' + d.predictedLabel).call(force.drag)
+      node = svg.selectAll('.node').data(nodes, (a) -> a.url).enter().append('g').attr('class', (d) -> 'node ' + d.predictedLabel).call(force.drag)
       node.append('circle').attr('class', 'selectionIndicator').attr('cx', 0).attr('cy', 0).attr('r', circleSize * 4)
       titles = node.append('g').attr('class', 'title')
       titles
@@ -154,29 +160,81 @@
       voronoiPatches
         .on('mouseover', (d) ->
           d.active = true
-          d.fixed = true
-          force.start()
+
+          scaleD = (other) ->
+            selectedPos = [xScale(d.x), yScale(d.y)]
+            pos = [xScale(other.x), yScale(other.y)]
+            diff = vSub(pos, selectedPos)
+            dist = vNorm(diff)
+            theShift = outerRadius*shift(outerRadius/2)(dist/outerRadius)
+            _.object ['x','y'], vAdd(pos, vScale(theShift/dist, diff))
+
+          xScaleD = (d) -> scaleD(d).x
+          yScaleD = (d) -> scaleD(d).y
+
+          duration = 500
+          n = nodes.length
+
+          angle = (doc, other) ->
+            diff = vSub([doc.x, doc.y], [other.x, other.y])
+            unitDiff = vScale(1/vNorm(diff), diff)
+            alpha = Math.acos(vDot([0, 1], unitDiff)) / Math.PI * 180
+            alpha = if diff[0] <= 0 then alpha else 360 - alpha
+
+          node
+            .sort((doc, other) ->
+              # sort by distance to selected
+              d3.descending(euclideanDistance([d.x, d.y], [doc.x, doc.y]), euclideanDistance([d.x, d.y], [other.x, other.y])))
+            .transition()
+              .duration((doc, i) ->
+                duration)
+              .delay((doc, i) -> # calculate angle to basis vector
+                return 0 if doc == d
+                dist = euclideanDistance([xScale(d.x), yScale(d.y)], [xScale(doc.x), yScale(doc.y)])
+                # distDelay = d3.scale.linear().domain([0, minSide]).range([0, duration])(dist)
+                console.log dist, gaussian(outerRadius*3, 2)(dist)*1000
+                gaussian(outerRadius*3, 2)(dist)*1000 # works pretty well... FIXME to many magic numbers
+              )
+              .attr 'transform', (d) ->
+                'translate(' + xScaleD(d) + ',' + yScaleD(d) + ')'
+
+          # force.start()
           updateActive()
         )
         .on('mouseout', (d) ->
           d.active = false
-          d.fixed = false
+          # d.fixed = false
           updateActive()
         )

+      # x + sign(x)*2(1-sigmoid(|x|))
+      shift = (offset) -> (x) ->
+        if x < 1e-10
+          0
+        else
+          Math.sign(x)*2*(1-sigmoid(Math.abs(x)-offset))
+
       calculateVoronoiThrottled = _.throttle(calculateVoronoi, 200)

       force.on 'tick', ->
+        console.log 'tick'
+        # smooth the layout change with inertia
+        rate = 0.8
+        smooth = (previous, next) -> (1 - rate) * previous + rate * next
+        xScale.domain(_.zipWith(xScale.domain(), d3.extent(nodes, (n) -> n.x), smooth))
+        yScale.domain(_.zipWith(yScale.domain(), d3.extent(nodes, (n) -> n.y), smooth))
+
         # link.attr('x1', (d) ->
-        #   d.source.x
+        #   xScale d.source.x
         # ).attr('y1', (d) ->
-        #   d.source.y
+        #   yScale d.source.y
         # ).attr('x2', (d) ->
-        #   d.target.x
+        #   xScale d.target.x
         # ).attr 'y2', (d) ->
-        #   d.target.y
+        #   yScale d.target.y
+
         node.attr 'transform', (d) ->
-          'translate(' + d.x + ',' + d.y + ')'
+          'translate(' + xScaleD(d) + ',' + yScaleD(d) + ')'

         hulls.attr "d", (d) ->
           [party, articles] = d


CLI/docs making it generate the main.coffee as well

--- cli/commands/docs.coffee
+++ cli/commands/docs.coffee
@@ -19,7 +19,7 @@
 # @param {String} filename
 #
 generate_docs_for = (filename)->
-  doc_file = "#{cwd}/#{filename.replace(/^src\//, docs)}.md"
+  doc_file = "#{cwd}/#{docs}#{filename.replace(/^src\//, '')}.md"
   doc_dir  = doc_file.replace(/\/[^\/]+$/, '')

   index.push(doc_file)
@@ -65,6 +65,8 @@

     print " • ".grey + "Generating documentation from sources"

+    generate_docs_for('main.coffee') if path.existsSync('main.coffee')
+    generate_docs_for('main.js')     if path.existsSync('main.js')
     loop_directory_recursively('src')

     print "\nDONE".green


Use defaults instead of extend

Use defaults instead of extend

--- command-register.coffee
+++ command-register.coffee
@@ -75,9 +75,9 @@
     deviceParams =
       type: @config.type

-    _.extend deviceParams, lockedDownParams unless @isOpen
-    _.extend deviceParams, openParams if @isOpen
-    _.extend deviceParams, @data if @data?
+    _.defaults deviceParams, lockedDownParams unless @isOpen
+    _.defaults deviceParams, openParams if @isOpen
+    _.defaults deviceParams, @data if @data?

     @conn.register deviceParams, (credentials) =>
       @config.uuid = credentials.uuid


Less bouncy overlayCenter modal transition

--- framer/Components/NavComponent.coffee
+++ framer/Components/NavComponent.coffee
@@ -47,7 +47,7 @@
 			visible: false

 		@overlay.onTap(@_handleOverlayTap)
-
+
 		if layer
 			@showNext(layer)
 		else
@@ -391,8 +391,8 @@
 Transitions.overlayCenter = (nav, layerA, layerB, overlay) ->
 	transition =
 		layerB:
-			show: {options: {curve: "spring(800, 30, 0)"}, x: Align.center, y: Align.center, scale: 1.0, opacity: 1}
-			hide: {options: {curve: "spring(800, 30, 0)"}, x: Align.center, y: Align.center, scale: 0.5, opacity: 0}
+			show: {options: {curve: "spring(500, 35, 0)"}, x: Align.center, y: Align.center, scale: 1.0, opacity: 1}
+			hide: {options: {curve: "spring(500, 35, 0)"}, x: Align.center, y: Align.center, scale: 0.5, opacity: 0}
 		overlay:
 			show: {options: {time: 0.1}, opacity: .5, x: 0, y: 0, size: nav.size}
 			hide: {options: {time: 0.1}, opacity: 0, x: 0, y: 0, size: nav.size}


landing: display only invite tab option for ReferrerModal

--- client/app/Applications/Account.kdapplication/views/referrermodal.coffee
+++ client/app/Applications/Account.kdapplication/views/referrermodal.coffee
@@ -5,6 +5,7 @@
     options.overlay       ?= yes
     options.title          = "Get free disk space!"
     options.url          or= "#{location.origin}/R/#{KD.nick()}"
+    options.onlyInviteTab ?= no
     options.tabs           =
       navigable            : no
       goToNextFormOnSubmit : no
@@ -17,6 +18,8 @@
             you'll get 250 MB free disk space for your VM, up to 16 GB total."
         invite             :
           customView       : KDCustomHTMLView
+
+    options.cssClass = KD.utils.curry "hidden", options.cssClass if options.onlyInviteTab

     super options, data

@@ -137,9 +140,11 @@
       else if contacts.length is 0
         new KDNotificationView
           title: "Your all contacts are already invited. Thanks!"
-        @modalTabs.showPaneByName "share"
+        if @getOptions().onlyInviteTab then @destroy()
+        else @modalTabs.showPaneByName "share"
       else
         @setTitle "Invite your friends from Gmail"
+        @show()
         @modalTabs.showPaneByName "invite"
         listController.instantiateListItems contacts


No need to stack them on a Array, we can push them as soon as possible

--- client/landing/app/CommonViews/comments/commentlistviewcontroller.coffee
+++ client/landing/app/CommonViews/comments/commentlistviewcontroller.coffee
@@ -43,10 +43,10 @@
       @utils.wait 5000, -> listView.emit "BackgroundActivityFinished"

       {meta} = listView.getData()
-      @commentStack = []

       listView.emit "BackgroundActivityStarted"
       @_hasBackgrounActivity = yes
+      @_removedBefore = no
       @fetchRelativeComments 10, meta.createdAt

     listView.registerListener
@@ -84,15 +84,17 @@
     message = @getListView().getData()
     message.fetchRelativeComments limit:_limit, after:_after, (err, comments)=>

-      Array::push.apply @commentStack, comments[_limit-10...]
+      if not @_removedBefore
+        @removeAllItems()
+        @_removedBefore = yes
+
+      @instantiateListItems comments[_limit-10...], yes

       if comments.length is _limit
-        startTime = @commentStack[@commentStack.length-1].meta.createdAt
+        startTime = comments[comments.length-1].meta.createdAt
         @fetchRelativeComments 11, startTime
       else
         listView = @getListView()
-        @removeAllItems()
-        @instantiateListItems @commentStack, yes
         listView.emit "BackgroundActivityFinished"
         listView.emit "AllCommentsWereAdded"
         @_hasBackgrounActivity = no


add loader

add loader

--- MediaAdminBundle/Resources/public/coffee/LoadBaseConfigurationRoute.coffee
+++ MediaAdminBundle/Resources/public/coffee/LoadBaseConfigurationRoute.coffee
@@ -10,8 +10,8 @@
     return

   router.route 'folder/:folderId/list', 'listFolder', (folderId) ->
-    @initDisplayRouteChanges()
-    GalleryLoad folderId
+    if @initDisplayRouteChanges()
+      GalleryLoad folderId
     return

   router.route 'folder/:folderId/add/media', 'addMedia', (folderId) ->


payment: WIP on plans

payment: WIP on plans

--- workers/social/lib/social/models/payment/plan.coffee
+++ workers/social/lib/social/models/payment/plan.coffee
@@ -38,10 +38,11 @@
       lastUpdate   : Number

   @fetchAccountDetails = secure ({connection:{delegate}}, callback) ->
-    throw Error 'needs to be reimplemented'
+    console.error 'needs to be reimplemented'
 #    recurly.fetchAccountDetailsByPaymentMethodId (JPayment.userCodeOf delegate), callback

   @fetchPlans = secure (client, options, callback) ->
+    console.log 'FETCH PLANS'

     selector = (Object.keys options)
       .reduce( (acc, key) ->
@@ -56,44 +57,49 @@
   getToken: secure (client, data, callback) ->
     JPaymentToken.createToken client, planCode: @code, callback

-  doSubscribe = (code, data, callback) ->
+  subscribe: (paymentMethodId, data, callback) ->
     data.multiple ?= no

     JPaymentSubscription.fetchAllSubscriptions {
-      userCode
+      paymentMethodId
       planCode  : @code
       $or       : [
         {status : 'active'}
         {status : 'canceled'}
       ]
-    }, (err, [sub])=>
+    }, (err, [sub]) =>
       return callback err  if err

       if sub
         return callback 'Already subscribed.'  unless data.multiple

-        sub.quantity ?= 1
-        recurly.updateSubscription userCode,
-          quantity : ++subs.quantity
+        quantity = (sub.quantity ? 1) + 1
+
+        recurly.updateSubscription paymentMethodId,
+          quantity : quantity
           plan     : @code
-          uuid     : subs.uuid
-        , (err)=>
+          uuid     : sub.uuid
+        , (err) =>
           return callback err  if err
-          sub.save (err)-> callback err, sub
+          sub.update $set: { quantity }, (err)->
+            if err
+            then callback err
+            else callback null, sub
       else
-        recurly.createSubscription userCode, plan: @code, (err, result)->
+        recurly.createSubscription paymentMethodId, plan: @code, (err, result) ->
           return callback err  if err
-          {planCode: plan, uuid, quantity, status, datetime, expires, renew, amount} = result
-          sub = new JPaymentSubscription {
-            userCode, planCode, uuid, quantity, status, datetime, expires, renew, amount
-          }
-          sub.save (err)-> callback err, sub
+          console.log { err, result }
+          # { planCode, uuid, quantity, status, datetime, expires, renew, amount } = result
+          # sub = new JPaymentSubscription {
+          #   userCode, planCode, uuid, quantity, status, datetime, expires, renew, amount
+          # }
+          # sub.save (err)-> callback err, sub

-  subscribe: secure ({connection:{delegate}}, data, callback)->
-    doSubscribe "user_#{delegate._id}", data, callback
+  subscribe$: secure ({connection:{delegate}}, paymentMethodId, data, callback) ->
+    @subscribe paymentMethodId, data, callback

-  subscribeGroup: (group, data, callback)->
-    doSubscribe "group_#{group._id}", data, callback
+  # subscribeGroup: (group, data, callback)->
+  #   doSubscribe "group_#{group._id}", data, callback

   fetchSubscription: secure ({ connection:{ delegate }}, callback) ->
     selector    =


fixed onProgress issue

fixed onProgress issue

--- client/crypto.coffee
+++ client/crypto.coffee
@@ -115,8 +115,7 @@
         params.size = params.data.byteLength
       if not params.transfer
         params.transfer = false
-      if not params.onDone
-        params.onDone = ->
+      params.onDone = callback
       @worker.queue params

     finalize: (callback) ->
@@ -133,6 +132,7 @@
       params =
         message: 'finalize'
         onDone: callback
+        size: 0
       @worker.queue params

     destroy: ->
@@ -151,7 +151,7 @@

     @handler =
       progress: ->
-        progress = @totalSizeProcessed / (@totalSize or @totalSizeQueued)
+        progress = self.totalSizeProcessed / (self.totalSize or self.totalSizeQueued)
         self.onProgress? progress
         self.busy = false
         self.flush()


throw a better exception when missing a controller action in the client router

--- client/router.coffee
+++ client/router.coffee
@@ -49,6 +49,7 @@
       else
         params = @getParamsHash(pattern, paramsArray)
         handler = @getController(route.controller)[route.action]
+        throw new Error("Missing action \"#{route.action}\" for controller \"#{route.controller}\"") unless handler
         handler = @authenticationFilter(handler, route)
         handler.call(@, params, @render)


Added language highlighter

Added language highlighter

--- SwiftNSURLSessionCodeGenerator.coffee
+++ SwiftNSURLSessionCodeGenerator.coffee
@@ -115,5 +115,6 @@
 SwiftNSURLSessionCodeGenerator.title =
     "Swift (NSURLSession)"
 SwiftNSURLSessionCodeGenerator.fileExtension = "swift"
+SwiftNSURLSessionCodeGenerator.languageHighlighter = "swift"

 registerCodeGenerator SwiftNSURLSessionCodeGenerator


Update signup.coffee

Update signup.coffee

--- ui/tests/views/signup.coffee
+++ ui/tests/views/signup.coffee
@@ -1,6 +1,6 @@

-describe('signup view', ->
+describe.skip('SignupView', ->
     before((done) ->
         # ensure user removed
     )


window title = last path segment

window title = last path segment

--- src/window.coffee
+++ src/window.coffee
@@ -6,6 +6,7 @@
 Storage = require 'storage'

 fs = require 'fs'
+_ = require 'underscore'

 # This a weirdo file. We don't create a Window class, we just add stuff to
 # the DOM window.
@@ -20,7 +21,7 @@
     KeyBinder.register "window", window

     @path = atomController.path
-    @setTitle @path
+    @setTitle _.last @path.split '/'

     @editor = if fs.isFile @path
       new Editor @path


Removed testing data from the pie chart class.

--- coffee/charts/pie.coffee
+++ coffee/charts/pie.coffee
@@ -3,7 +3,6 @@
   defaults =
     margin: 10
     inner: 0
-    data: PIE_DATA

   constructor: (@options={}) ->
     super(@options = Epoch.Util.defaults(@options, defaults))


config: bugfix: update config.css uri

config: bugfix: update config.css uri

--- coffee/src/config.coffee
+++ coffee/src/config.coffee
@@ -28,7 +28,7 @@

 acorn.config.css = [
-  '/css/acorn-player.css',
+  '/build/css/acorn-player.css',
   '/lib/fontawesome/css/font-awesome.css',
   '/lib/jquery/jquery-ui.custom.min.css',
 ]


update author block

update author block

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -1,9 +1,11 @@
 ###
 * oriDomi
 * fold up the DOM
+* http://oridomi.com
 *
 * Dan Motzenbecker
-* (c) 2012
+* http://oxism.com
+* Copyright 2012
 ###


added noOp function

added noOp function

--- oriDomi.coffee
+++ oriDomi.coffee
@@ -133,6 +133,11 @@

 # Defaults
 # ========
+
+# Empty function to be used as placeholder for callback defaults
+# (instead of creating separate empty functions).
+noOp = ->
+

 # Map of oriDomi instance defaults.
 defaults =


annotation updates

annotation updates

--- oridomi.coffee
+++ oridomi.coffee
@@ -1199,7 +1199,7 @@
   unfold: prep (callback) -> @_unfold arguments...

-  # For custom folding behavior, you can pass a function to map that will
+  # For custom folding behavior, you can pass a function to `map()` that will
   # determine the folding angle applied to each panel. The passed function
   # is supplied with the input angle, the panel index, and the number of
   # panels in the active anchor. Calling map returns a new function bound to
@@ -1264,7 +1264,7 @@
   # Set a version flag for easy external retrieval.
   @VERSION = '1.0.4'

-  # Externally check if OriDomi is supported by the browser.
+  # Externally reveal if OriDomi is supported by the browser.
   @isSupported = isSupported


remove shading on twist and fracture

remove shading on twist and fracture

--- oridomi.coffee
+++ oridomi.coffee
@@ -1178,8 +1178,11 @@
       # Set the CSS transformation.
       @_transformPanel panel, deg, anchor, options.fracture

-      if @_shading and !(i is 0 and options.sticky) and Math.abs(deg) isnt 180
-        @_setShader i, anchor, deg
+      if @_shading
+        if options.twist or options.fracture or (i is 0 and options.sticky)
+          @_setShader i, anchor, 0
+        else if Math.abs(deg) isnt 180
+          @_setShader i, anchor, deg

   # This effect appears to bend rather than fold the paper. Its curves can


set last angle to zero after unfold

set last angle to zero after unfold

--- oridomi.coffee
+++ oridomi.coffee
@@ -866,7 +866,8 @@
             if i is len - 1
               @_inTrans = @isFoldedUp = false
               callback?()
-              @_lastOp.fn = @accordion
+              @_lastOp.fn    = @accordion
+              @_lastOp.angle = 0
             defer => panel.style[css.transitionDuration] = @_settings.speed
           , delay + @_settings.speed * .25


Add defensive check

Add defensive check

--- client/stacks/lib/index.coffee
+++ client/stacks/lib/index.coffee
@@ -76,4 +76,4 @@
     { view } = @getOptions()

     view.tabs.panes.forEach (pane) ->
-      pane.mainView?.initialView.reload()  unless pane.name is 'My Stacks'
+      pane.mainView?.initialView?.reload()  unless pane.name is 'My Stacks'


Do not prettify HTML with images because html-beautify will choke on long data URIs

--- client/views/admin/edit.coffee
+++ client/views/admin/edit.coffee
@@ -163,7 +163,8 @@
     if $html.is(':visible')
       return

-    $html.val BlogEditor.make(tpl).pretty()
+    if $editable.find('.medium-insert-images').length is 0
+      $html.val BlogEditor.make(tpl).pretty()
     setEditMode tpl, 'html'
     $html.height($editable.height())


Fix for https://github.com/cozy-labs/konnectors/issues/68

--- server/konnectors/free.coffee
+++ server/konnectors/free.coffee
@@ -97,7 +97,7 @@

     request options, (err, res, body) ->
         console.log body
-        if err or not res.headers.location? or res.statusCode isnt 302
+        if err or not res.headers.location? or res.statusCode isnt 302 or res.headers.location.indexOf "error" isnt -1
             log.error "Authentification error"
             next 'bad credentials'
         else


Removed pulished property, since we have _timestamp

--- server/models/activity.coffee
+++ server/models/activity.coffee
@@ -112,8 +112,6 @@
         type: "string"
         index: "no"
         include_in_all: false
-      published:
-        type: "date"
     _timestamp:
       enabled: true
       path:    "published"


Render: Fix texture binding with texture reuse for once and for all.

--- engine/render.coffee
+++ engine/render.coffee
@@ -80,8 +80,6 @@
         @background_alpha = if ba? then ba else 1
         @compiled_shaders_this_frame = 0
         @use_frustum_culling = true
-        # TODO: workaround until we properly manage reused textures
-        @unbind_textures_on_draw_mesh = false
         @unbind_textures_on_draw_viewport = true
         @probes = []

@@ -342,8 +340,7 @@
     # TODO: Check if a shader needs more textures than available
     bind_texture: (texture, avoid_material) ->
         {gl, bound_textures, active_texture, next_texture, max_textures} = @
-        old_tex = bound_textures[texture.bound_unit]
-        if texture.bound_unit == -1 or old_tex != texture
+        if texture.bound_unit == -1
             while avoid_material? and (nt = bound_textures[next_texture])? and\
                     nt.last_used_material == avoid_material
                 next_texture = (next_texture+1) % max_textures
@@ -351,6 +348,7 @@
             if active_texture != bound_unit
                 @active_texture = bound_unit
                 gl.activeTexture gl.TEXTURE0 + bound_unit
+            old_tex = bound_textures[bound_unit]
             if old_tex?
                 old_tex.bound_unit = -1
                 if old_tex.gl_target != texture.gl_target
@@ -367,17 +365,17 @@
             if active_texture != bound_unit
                 @active_texture = bound_unit
                 gl.activeTexture gl.TEXTURE0 + bound_unit
-
-        # TODO: debug option to check if texture is actually bound
         return bound_unit

     # Unbinds a texture if it was bound
     unbind_texture: (texture) ->
         {gl, bound_textures} = @
-        old_tex = bound_textures[texture.bound_unit]
+        {bound_unit} = texture
+        old_tex = bound_textures[bound_unit]
         if old_tex == texture
-            bound_textures[texture.bound_unit] = null
-            gl.activeTexture gl.TEXTURE0 + texture.bound_unit
+            bound_textures[bound_unit] = null
+            @active_texture = bound_unit
+            gl.activeTexture gl.TEXTURE0 + bound_unit
             gl.bindTexture texture.gl_target, null
             texture.bound_unit = -1
             texture.last_used_material = null
@@ -495,19 +493,6 @@
         inv_radius_y = 2/inv_radius_y
         inv_radius_z = 2/inv_radius_z

-        # TODO: This was added to workaround a problem with meshes
-        # cloned from another scene; it shouldn't be necessary
-        if @unbind_textures_on_draw_mesh
-            # unbind all textures
-            for tex, i in @bound_textures when tex?
-                tex.bound_unit = -1
-                tex.last_used_material = null
-                gl.activeTexture gl.TEXTURE0 + i
-                gl.bindTexture tex.gl_target, null
-                @bound_textures[i] = null
-            gl.activeTexture gl.TEXTURE0
-            @active_texture = @next_texture = 0
-
         # Main routine for each submesh
         for mat,submesh_idx in amesh.materials
             if not (pass_ == -1 or mesh.passes[submesh_idx] == pass_)
@@ -543,6 +528,7 @@
                     # this means it's the probe cube texture
                     tex = mesh.probe?.cubemap or @blank_cube_texture
                     tex_input.value = tex
+                    tex.last_used_material = mat
                 else
                     tex = tex_input.value
                 if tex.bound_unit == -1
@@ -708,7 +694,8 @@
                 gl.bindTexture tex.gl_target, null
                 @bound_textures[i] = null
             gl.activeTexture gl.TEXTURE0
-            @active_texture = @next_texture = 0
+            @active_texture = -1
+            @next_texture = 0

         {mesh_lod_min_length_px} = @context

@@ -1132,6 +1119,19 @@
         return

     # @nodoc
+    debug_break_on_any_gl_error: ->
+        gl = @gl
+        for k,v of gl when typeof v == 'function' and k != 'getError'
+            gl['_'+k] = gl[k]
+            gl[k] = do (k, gl) => (args...)->
+                r = gl["_"+k](args...)
+                if error = gl.getError()
+                    debugger
+                return r
+        return
+
+
+    # @nodoc
     # This method allows to compare performance between few objects
     # and many object with similar total poly count.
     polycount_debug: (ratio=1)->


make startup message consistent

make startup message consistent

--- services/real-time/app.coffee
+++ services/real-time/app.coffee
@@ -63,7 +63,7 @@

 server.listen port, host, (error) ->
 	throw error if error?
-	logger.log "real-time-sharelatex listening on #{host}:#{port}"
+	logger.info "realtime starting up, listening on #{host}:#{port}"

 # Stop huge stack traces in logs from all the socket.io parsing steps.
 Error.stackTraceLimit = 10


added getByTitle to retrieve a channel from a collection

--- core/app/backbone/collections/channels.coffee
+++ core/app/backbone/collections/channels.coffee
@@ -59,6 +59,10 @@
           error: _.bind(args.callee, self)
       ), 7000

+  getByTitle: (title)->
+    results = @filter (ch)-> ch.get('title').toLowerCase() == title.toLowerCase()
+    if results.length == 1 then results[0] else `undefined`
+
   orderedByAuthority: ->
     topchannels = new ChannelList()
     _.each @models, (channel) ->


Client: Print stack on errors.

Client: Print stack on errors.

--- client/app/js/collections/battle_collection.coffee
+++ client/app/js/collections/battle_collection.coffee
@@ -210,6 +210,7 @@
           done()
     catch e
       console.error(e)
+      console.error(e.stack)
       done()
       throw e  if document.domain == 'localhost'
     if wasAtBottom && !view.chatView.isAtBottom()


Alias ctrl-y to paste

Alias ctrl-y to paste

--- etc/atom/keymap.cson
+++ etc/atom/keymap.cson
@@ -22,4 +22,5 @@
   'alt-v': 'core:page-up'
   'ctrl-e': 'editor:move-to-end-of-screen-line'
   'ctrl-a': 'editor:move-to-beginning-of-screen-line'
+  'ctrl-y': 'core:paste'
   'ctrl-\\': 'bracket-matcher:go-to-matching-bracket'


Associate concise/html with tags

Associate concise/html with tags

--- grammars/marko.cson
+++ grammars/marko.cson
@@ -494,7 +494,7 @@
     begin: "^\\s*([a-zA-Z0-9_.#-]+)(?=(\\s|$|\\())"
     beginCaptures:
       "1":
-        name: "entity.name.tag"
+        name: "entity.name.tag.concise"
     comment: "A concise tag name"
     end: "$"
     patterns: [
@@ -506,10 +506,10 @@
       }
     ]
   "tag-name-custom-concise":
-    begin: "^\\s*(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|[#.][a-zA-Z0-9_#.-]+|[a-zA-Z0-9_#.]+([:-])[a-zA-Z0-9_:-]+)(?=(\\s|$|[(]))"
-    beginCaptures:
-      "1":
-        name: "support.function.marko-tag"
+    begin: "^\\s*(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|[#.][a-zA-Z0-9_#.-]+|[a-zA-Z0-9_#.]+([:-])[a-zA-Z0-9_:-]*)(?=(\\s|$|[(]))"
+    beginCaptures:
+      "1":
+        name: "support.function.marko-tag.concise"
     comment: "A concise custom tag name"
     end: "$"
     patterns: [
@@ -521,15 +521,15 @@
       }
     ]
   "tag-name-custom-html":
-    begin: "<(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|[a-zA-Z0-9_]+[-:][a-zA-Z0-9\\-_:]+)(?=(>|/>|\\s|\\())"
-    beginCaptures:
-      "1":
-        name: "support.function.marko-tag"
+    begin: "<(for|if|unless|else-if|else|var|assign|macro|invoke|include|app|[a-zA-Z0-9_]+[-:][a-zA-Z0-9\\-_:]*)(?=(>|/>|\\s|\\())"
+    beginCaptures:
+      "1":
+        name: "support.function.marko-tag.html"
     comment: "The beginning of a custom/special HTML tag"
     end: "</(\\1)>|</>|/>"
     endCaptures:
       "1":
-        name: "support.function.marko-tag"
+        name: "support.function.marko-tag.html"
     patterns: [
       {
         include: "#attr-stuff"
@@ -542,12 +542,12 @@
     begin: "<([a-zA-Z0-9]+)(?=(>|/>|\\s|\\())"
     beginCaptures:
       "1":
-        name: "entity.name.tag"
+        name: "entity.name.tag.html"
     comment: "The beginning of a regular HTML tag in non-concise mode"
     end: "</(\\1)>|</>|/>"
     endCaptures:
       "1":
-        name: "entity.name.tag"
+        name: "entity.name.tag.html"
     patterns: [
       {
         include: "#attr-stuff"
@@ -560,12 +560,12 @@
     begin: "<(base|br|col|hr|embed|img|input|keygen|link|meta|param|source|track|wbr|lasso-img)(?=(>|/>|\\s|\\())"
     beginCaptures:
       "1":
-        name: "entity.name.tag"
+        name: "entity.name.tag.html"
     comment: "HTML tags that are open tag only"
     end: ">|/>"
     endCaptures:
       "1":
-        name: "entity.name.tag"
+        name: "entity.name.tag.html"
     patterns: [
       {
         include: "#attr-stuff"
@@ -575,12 +575,12 @@
     begin: "<(script)(?=(>|/>|\\s|\\())"
     beginCaptures:
       "1":
-        name: "entity.name.tag.script"
+        name: "entity.name.tag.script.html"
     comment: "HTML style tag"
     end: "</(script)>|</>|/>"
     endCaptures:
       "1":
-        name: "entity.name.tag.script"
+        name: "entity.name.tag.script.html"
     patterns: [
       {
         include: "#attr-stuff"
@@ -593,14 +593,14 @@
     begin: "<(([a-zA-Z0-9_-]+)[#.][a-zA-Z0-9_#.:-]+)(?=(>|/>|\\s|\\())"
     beginCaptures:
       "1":
-        name: "support.function.marko-tag"
-    comment: "HTML style tag"
+        name: "support.function.marko-tag.html"
+    comment: "HTML tag with shorthand ID/class parts"
     end: "</(\\1)>|</(\\2)>|</>|/>"
     endCaptures:
       "1":
-        name: "support.function.marko-tag"
+        name: "support.function.marko-tag.html"
       "2":
-        name: "support.function.marko-tag"
+        name: "support.function.marko-tag.html"
     patterns: [
       {
         include: "#attr-stuff"
@@ -613,14 +613,14 @@
     begin: "<([#.][a-zA-Z0-9_#.:-]+)(?=(>|/>|\\s|\\())"
     beginCaptures:
       "1":
-        name: "support.function.marko-tag"
+        name: "support.function.marko-tag.html"
     comment: "HTML style tag"
     end: "</(\\1)>|</>|</(div)>|/>"
     endCaptures:
       "1":
-        name: "support.function.marko-tag"
+        name: "support.function.marko-tag.html"
       "2":
-        name: "support.function.marko-tag"
+        name: "support.function.marko-tag.html"
     patterns: [
       {
         include: "#attr-stuff"
@@ -633,12 +633,12 @@
     begin: "<(style)(?=(>|/>|\\s|\\())"
     beginCaptures:
       "1":
-        name: "entity.name.tag.style"
+        name: "entity.name.tag.style.html"
     comment: "HTML style tag"
     end: "</(style)>|</>|/>"
     endCaptures:
       "1":
-        name: "entity.name.tag.style"
+        name: "entity.name.tag.style.html"
     patterns: [
       {
         include: "#attr-stuff"
@@ -669,7 +669,7 @@
     begin: "^(\\s*)(script)(?=(\\s|$|\\())"
     beginCaptures:
       "2":
-        name: "entity.name.tag.script.marko"
+        name: "entity.name.tag.script.marko.concise"
     comment: "HTML script tag with nested JavaScript code"
     end: "^(?!(\\1\\s)|\\s*$)"
     name: "meta.tag.other"
@@ -706,7 +706,7 @@
     begin: "^(\\s*)(style)(?=(\\s|$|\\())"
     beginCaptures:
       "2":
-        name: "entity.name.tag.style.marko"
+        name: "entity.name.tag.style.marko.concise"
     comment: "style tag with CSS code."
     end: "^(?!(\\1\\s)|\\s*$)"
     name: "meta.tag.other"


remove negative lookahead for "in" or "of"

--- grammars/stata.cson
+++ grammars/stata.cson
@@ -71,7 +71,7 @@
         ]
   }
   {
-    match: '\\b(foreach)\\s+((?!in |of ).+)\\s+(in|of var(list|lis|li|l)?|of new(list|lis|li|l)?|of num(list|lis|li|l)?)\\b'
+    match: '\\b(foreach)\\s+(.+)\\s+(in|of var(list|lis|li|l)?|of new(list|lis|li|l)?|of num(list|lis|li|l)?)\\b'
     captures:
       1: name: 'keyword.control.flow.stata'
       2:
@@ -83,7 +83,7 @@
       3: name: 'keyword.control.flow.stata'
   }
   {
-    begin: '\\b(foreach)\\s+((?!in |of ).+)\\s+(of loc(al|a)?|of glo(bal|ba|b)?)\\b\\s*'
+    begin: '\\b(foreach)\\s+(.+)\\s+(of loc(al|a)?|of glo(bal|ba|b)?)\\b\\s*'
     beginCaptures:
       1: name: 'keyword.control.flow.stata'
       2:


Editor: fix preview problem on opened tabs

--- client/Ace/aceappview.coffee
+++ client/Ace/aceappview.coffee
@@ -120,9 +120,10 @@
     return items

   preview: ->
-    {path, vmName} = @getActiveAceView().getData()
+    file = @getActiveAceView().getData()
+    {path, vmName} = file
     return  if /^localfile/.test path
-    path = KD.getPublicURLOfPath path
+    path = KD.getPublicURLOfPath FSHelper.getFullPath file

     notify = =>
       @getActiveAceView().ace.notify "File needs to be under ~/Web folder", "error"


enable Turbolinks transition cache

enable Turbolinks transition cache

--- client/index.coffee
+++ client/index.coffee
@@ -33,6 +33,7 @@
 sprites.keys().forEach sprites

 Turbolinks.enableProgressBar()
+Turbolinks.enableTransitionCache()

 new TextInput()
 new StoryCollapse '#story-collapse-toggle'


Don't tokenize files that have more than 10000 lines

--- src/app/tokenized-buffer.coffee
+++ src/app/tokenized-buffer.coffee
@@ -62,8 +62,11 @@
     @invalidateRow(0)
     @trigger "changed", { start: 0, end: lastRow, delta: 0 }

+  tooBigToTokenize: ->
+    @screenLines.length > 10000
+
   tokenizeInBackground: ->
-    return if not @visible or @pendingChunk
+    return if not @visible or @pendingChunk or @tooBigToTokenize()
     @pendingChunk = true
     _.defer =>
       @pendingChunk = false


Fix deprecation notices

Fix deprecation notices

--- lib/pane-manager.coffee
+++ lib/pane-manager.coffee
@@ -51,40 +51,40 @@

     activate: ->
         # Layout interactions commands
-        atom.workspaceView.command "pane-manager:changeLayoutOne", => @changeLayout "layout1"
-        atom.workspaceView.command "pane-manager:changeLayoutTwo", => @changeLayout "layout2"
-        atom.workspaceView.command "pane-manager:changeLayoutThree", => @changeLayout "layout3"
-        atom.workspaceView.command "pane-manager:changeLayoutFour", => @changeLayout "layout4"
-        atom.workspaceView.command "pane-manager:changeLayoutFive", => @changeLayout "layout5"
-        atom.workspaceView.command "pane-manager:changeLayoutSix", => @changeLayout "layout6"
-        atom.workspaceView.command "pane-manager:changeLayoutSeven", => @changeLayout "layout7"
-        atom.workspaceView.command "pane-manager:changeLayoutEight", => @changeLayout "layout8"
-        atom.workspaceView.command "pane-manager:changeLayoutNine", => @changeLayout "layout9"
-        atom.workspaceView.command "pane-manager:changeLayoutTen", => @changeLayout "layout10"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutOne", => @changeLayout "layout1"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutTwo", => @changeLayout "layout2"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutThree", => @changeLayout "layout3"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutFour", => @changeLayout "layout4"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutFive", => @changeLayout "layout5"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutSix", => @changeLayout "layout6"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutSeven", => @changeLayout "layout7"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutEight", => @changeLayout "layout8"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutNine", => @changeLayout "layout9"
+        atom.commands.add "atom-workspace", "pane-manager:changeLayoutTen", => @changeLayout "layout10"

         # Move to pane commands
-        atom.workspaceView.command "pane-manager:moveToPaneOne", => @moveToPane 1
-        atom.workspaceView.command "pane-manager:moveToPaneTwo", => @moveToPane 2
-        atom.workspaceView.command "pane-manager:moveToPaneThree", => @moveToPane 3
-        atom.workspaceView.command "pane-manager:moveToPaneFour", => @moveToPane 4
-        atom.workspaceView.command "pane-manager:moveToPaneFive", => @moveToPane 5
-        atom.workspaceView.command "pane-manager:moveToPaneSix", => @moveToPane 6
-        atom.workspaceView.command "pane-manager:moveToPaneSeven", => @moveToPane 7
-        atom.workspaceView.command "pane-manager:moveToPaneEight", => @moveToPane 8
-        atom.workspaceView.command "pane-manager:moveToPaneNine", => @moveToPane 9
-        atom.workspaceView.command "pane-manager:moveToPaneTen", => @moveToPane 10
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneOne", => @moveToPane 1
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneTwo", => @moveToPane 2
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneThree", => @moveToPane 3
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneFour", => @moveToPane 4
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneFive", => @moveToPane 5
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneSix", => @moveToPane 6
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneSeven", => @moveToPane 7
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneEight", => @moveToPane 8
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneNine", => @moveToPane 9
+        atom.commands.add "atom-workspace", "pane-manager:moveToPaneTen", => @moveToPane 10

         # Focus to pane commands
-        atom.workspaceView.command "pane-manager:focusToPaneOne", => @focusToPane 1
-        atom.workspaceView.command "pane-manager:focusToPaneTwo", => @focusToPane 2
-        atom.workspaceView.command "pane-manager:focusToPaneThree", => @focusToPane 3
-        atom.workspaceView.command "pane-manager:focusToPaneFour", => @focusToPane 4
-        atom.workspaceView.command "pane-manager:focusToPaneFive", => @focusToPane 5
-        atom.workspaceView.command "pane-manager:focusToPaneSix", => @focusToPane 6
-        atom.workspaceView.command "pane-manager:focusToPaneSeven", => @focusToPane 7
-        atom.workspaceView.command "pane-manager:focusToPaneEight", => @focusToPane 8
-        atom.workspaceView.command "pane-manager:focusToPaneNine", => @focusToPane 9
-        atom.workspaceView.command "pane-manager:focusToPaneTen", => @focusToPane 10
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneOne", => @focusToPane 1
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneTwo", => @focusToPane 2
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneThree", => @focusToPane 3
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneFour", => @focusToPane 4
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneFive", => @focusToPane 5
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneSix", => @focusToPane 6
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneSeven", => @focusToPane 7
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneEight", => @focusToPane 8
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneNine", => @focusToPane 9
+        atom.commands.add "atom-workspace", "pane-manager:focusToPaneTen", => @focusToPane 10

     changeLayout: ( sLayout ) ->
         unless aLayoutConfig = atom.config.get "pane-manager.#{ sLayout }"


implement hmac battleId generation

implement hmac battleId generation

--- server/index.coffee
+++ server/index.coffee
@@ -1,5 +1,8 @@
+{createHmac} = require 'crypto'
+{_} = require 'underscore'
 {BattleQueue} = require './queue'
 {Battle} = require './battle'
+

 class @BattleServer
   constructor: ->
@@ -31,9 +34,13 @@
     battleId

   # Generate a random ID for a new battle.
-  # TODO: Use node's built in crypto library to generate a HMAC
-  generateBattleId: =>
-    Math.floor(Math.random() * 0xFFFFFF).toString(16)
+  generateBattleId: (players) =>
+    # TODO load key from config or env
+    hmac = createHmac('sha1', 'INSECURE KEY')
+    hmac.update((new Date).toISOString())
+    for player in players
+      hmac.update(player.clientId)
+    hmac.digest('hex')

   # Returns the battle with battleId.
   findBattle: (battleId) =>


Close <h2> block.

Close <h2> block.

--- coffee/figure.coffee
+++ coffee/figure.coffee
@@ -13,7 +13,7 @@

         # figure container element, a

         @container = $('#'+@properties.ID)
-        @container.append('
SciWiz Figure


Use destructuring

Use destructuring

--- src/browser/application.coffee
+++ src/browser/application.coffee
@@ -9,7 +9,7 @@

 {EventEmitter} = require 'events'
 _ = require 'underscore-plus'
-spawn = require('child_process').spawn
+{spawn} = require 'child_process'

 AppMenu = require './appmenu'
 AppWindow = require './appwindow'


fix missing error messages

fix missing error messages

--- lib/HtmlReport.coffee
+++ lib/HtmlReport.coffee
@@ -48,7 +48,7 @@

   _errorReport: (error) ->
     if error?
-      '
' + error.message + '
'
+      '
' + error + '
'
     else
       ''


add session price prop, glAccount may be null

--- client/skr/models/Sku.coffee
+++ client/skr/models/Sku.coffee
@@ -5,7 +5,7 @@
         id:                  {type:"integer"}
         default_vendor_id:   {type:"integer"}
         gl_asset_account_id: {type:"integer", default: ->
-            Skr.Models.GlAccount.default_ids?.asset
+            Skr.Models.GlAccount?.default_ids?.asset
         }
         default_uom_code:    {type:"string"}
         code:                {type:"code",   required:true}
@@ -15,12 +15,19 @@
         does_track_inventory:{type:"boolean", default:false}
         can_backorder:       {type:"boolean", default:false}

+    session:
+        price:               {type:"bigdec" }
+
+    derived:
+        display_price: deps: ['price'], fn: ->
+            Lanes.u.format.currency @price
+
     mixins: ['HasCodeField']

     associations:
         default_vendor:   { model: "Vendor", required: true}
         gl_asset_account: { model: "GlAccount", required: true, default: ->
-            Skr.Models.GlAccount.all.get(this.gl_asset_account_id)
+            Skr.Models.GlAccount?.all.get(this.gl_asset_account_id)
         }
         sku_locs:         { collection: "SkuLoc", required: true}
         sku_vendors:      { collection: "SkuVendor", required: true }


Add strict math option to core gruntfile [for alex.scown]

--- webapp/core/Gruntfile.coffee
+++ webapp/core/Gruntfile.coffee
@@ -78,6 +78,8 @@
           'target/classes/static/css/compiled.css': 'src/main/less/app.less',
           'target/classes/static/css/login.css': 'src/main/less/login.less',
           'target/classes/static/css/result-highlighting.css': 'src/main/less/result-highlighting.less'
+      options:
+        strictMath: true
     watch:
       buildTest:
         files: watchFiles


websockets service now emits a connection event (arg#1: socket.io socket object)

--- websockets/websockets.coffee
+++ websockets/websockets.coffee
@@ -1,7 +1,9 @@
 # modules
 socketIo = require 'socket.io'
+events = require 'events'

-class WebSockets
+
+class WebSockets extends events.EventEmitter
     # socket.io object
     io: null
     users: {}
@@ -15,6 +17,7 @@
     # and handles multiple connections by user
     handleConnection: ->
         @io.on 'connection', (socket) =>
+            @emit 'connection', socket
             socket.user = socket.handshake.session.user
             # if the client isn't authenticated, do not continue
             unless socket.user then return
@@ -59,14 +62,3 @@

 # export
 module.exports = new WebSockets
-
-# Message : {
-#   type: "message|notif"
-#   to: "public|team|side",
-#    msg: "content of message",
-#    author: {
-#        id: "ID",
-#        user: "Username"
-#    },
-#    cDate: "Datetime",
-#}


added icon support

added icon support

--- jquery-select7.coffee
+++ jquery-select7.coffee
@@ -1,9 +1,9 @@
 ###
 @name jquery-select7
-@version 0.0.7
+@version 0.1.1
 @author Se7enSky studio <info@se7ensky.com>
 ###
-###! jquery-select7 0.0.7 http://github.com/Se7enSky/jquery-select7 ###
+###! jquery-select7 0.1.1 http://github.com/Se7enSky/jquery-select7 ###

 plugin = ($) ->

@@ -13,8 +13,10 @@
 		s.replace(///^\s*///, '').replace(///\s*$///, '')
 	readOptionsFromSelect = (el) ->
 		(for option in $(el).find("option")
-			title: trim $(option).text()
-			value: $(option).attr("value") or trim $(option).text()
+			data = $(option).data()
+			data.title = trim $(option).text()
+			data.value = $(option).attr("value") or trim $(option).text()
+			data
 		)
 	readSelectedIndexFromSelect = (el) ->
 		selectVal = $(el).val()
@@ -41,8 +43,12 @@
 		pwnSelect: ->
 			@$el.hide()

+			classes = @$el.attr("class").split(" ")
+			classes.splice classes.indexOf("select7"), 1
+			classes.push "select7_noopts" if @options.length < 2
+
 			select7Markup = """
-

+

@@ -61,14 +67,18 @@
 			@selectedIndex = readSelectedIndexFromSelect @el
 			@selected = @options[@selectedIndex]
 			@$select7.find("[data-role='value']").attr("data-value", @selected.value).text @selected.title
+			@$select7.find("[data-role='value'] .select7__icon").remove()
+			@$select7.find("[data-role='value']").prepend """""" if @selected.icon

 		open: ->
 			return if @opened
+			return if @options.length < 2
 			@$drop = $ """

"""
 			for option, i in @options
 				continue if i is @selectedIndex
 				$option = $ """

"""
 				$option.text option.title
+				$option.prepend """""" if option.icon
 				@$drop.append $option
 			@$drop.on "click", ".select7__option", (e) =>
 				{i} = $(e.target).data()


Use ~/.atom/storage instead of ~/.atom/.storage

The serialization format is changing for telepath and this allows a clean
upgrade and the dotted directory does not seem needed since it is already
behind one dotted directory (.atom).

--- src/app/config.coffee
+++ src/app/config.coffee
@@ -15,7 +15,7 @@
 vendoredThemesDirPath = path.join(resourcePath, "vendor/themes")
 userThemesDirPath = path.join(configDirPath, "themes")
 userPackagesDirPath = path.join(configDirPath, "packages")
-userStoragePath = path.join(configDirPath, ".storage")
+userStoragePath = path.join(configDirPath, "storage")

 # Public: Handles all of Atom's configuration details.
 #


:lipstick:

:lipstick:

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -910,7 +910,7 @@
     @newSelections = @activeEditSession.getSelections()
     @updateDisplay(suppressAutoScroll: true)

-  requestDisplayUpdate: ()->
+  requestDisplayUpdate: ->
     return if @pendingDisplayUpdate
     @pendingDisplayUpdate = true
     _.nextTick =>


:lipstick:

:lipstick:

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -60,7 +60,7 @@
     if editSessionOrOptions instanceof EditSession
       editSession = editSessionOrOptions
     else
-      {editSession, @mini} = (editSessionOrOptions ? {})
+      {editSession, @mini} = editSessionOrOptions ? {}

     requireStylesheet 'editor'


No links for homeworks due on the current week

--- src/components/student-dashboard/homework-row.cjsx
+++ src/components/student-dashboard/homework-row.cjsx
@@ -36,8 +36,9 @@
       "#{event.complete_exercise_count}/#{event.exercise_count} complete"
     # In the future, the backend will signal whether the event has recovery
     # and feedback available and whether or not the student has already
-    # accessed it.  For now we assume if the event's compete it can be recovered
-    recoverable = event.complete
+    # accessed it.
+    # For now we assume if the event's compete and it's not on the current week it can be recovered
+    recoverable = event.complete and moment(event.due_at).startOf('isoweek').add(1,'week').isBefore(TimeStore.getNow())
     <EventRow {...@props} feedback={feedback} className="homework">
         {event.title}{@actionLinks() if recoverable}
     </EventRow>


This never should have been checked in.

This never should have been checked in.

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -1086,7 +1086,7 @@
     if fold = screenLine.fold
       lineAttributes = { class: 'fold line', 'fold-id': fold.id }
     else
-    lineAttributes = { class: 'line' }
+      lineAttributes = { class: 'line' }

     attributePairs = []
     attributePairs.push "#{attributeName}=\"#{value}\"" for attributeName, value of lineAttributes
@@ -1117,7 +1117,7 @@
       if invisibles.eol
         line.push("#{invisibles.eol}")

-    # line.push("") if fold
+    line.push("") if fold

     line.push('
')
     line.join('')


Fixing `non_amd` version check for vendors.

--- src/toaster/filetype/coffee/optimizer/vendors.coffee
+++ src/toaster/filetype/coffee/optimizer/vendors.coffee
@@ -19,7 +19,8 @@
       contents = fs.readFileSync vpath, 'utf-8'

       # if vendor is an AMD module, makes sure the define call is not anonymous
-      unless vname in @config.browser?.amd?.non_amd_vendors
+      non_amd = @config.browser?.amd?.non_amd_vendors
+      unless non_amd isnt null or vname in non_amd
         match_define_calls = /(define[\s]*\()[\s]*(function)/g
         contents = contents.replace match_define_calls, "$1'#{vname}',$2"


Reserve __super__ attribute

Reserve __super__ attribute

--- src/attributes.coffee
+++ src/attributes.coffee
@@ -124,7 +124,7 @@
   # @param [Number,String,Object] value the value for the attribute, relevant for !strictAttributes mode
   # @param [Object] options the options to modify valid attributes with
   @__validAttribute: (attribute, value, options) ->
-    reserved = ['__associations', '__errors', '__fields', '__links', '__queryParams']
+    reserved = ['__super__', '__associations', '__errors', '__fields', '__links', '__queryParams']

     if @klass().resourceLibrary.strictAttributes
       if options.readOnly


Removed unicode test

Removed unicode test


process service/network container types differently

--- imports/server/stateReconciler.coffee
+++ imports/server/stateReconciler.coffee
@@ -73,25 +73,33 @@
     unless mappedState in SERVICE_STATES
       throw "Service state '#{mappedState}' is not supported."
     if name = labels['bigboat/instance/name']
-      updateDoc = name: name
+      if (type = labels['bigboat/container/type']) is 'service'
+        updateDoc = name: name

-      if (appName = labels['bigboat/application/name']) and
-        (appVersion = labels['bigboat/application/version'])
-          updateDoc.app = name: appName, version: appVersion
+        if (appName = labels['bigboat/application/name']) and
+          (appVersion = labels['bigboat/application/version'])
+            updateDoc.app = name: appName, version: appVersion

-      updateDoc.startedBy = startedBy if startedBy = labels['bigboat/startedBy']
-      updateDoc.storageBucket = storageBucket if storageBucket = labels['bigboat/storage/bucket']
+        updateDoc.startedBy = startedBy if startedBy = labels['bigboat/startedBy']
+        updateDoc.storageBucket = storageBucket if storageBucket = labels['bigboat/storage/bucket']

-      updateDoc['agent.url'] = val if val = labels['bigboat/agent/url']
+        updateDoc['agent.url'] = val if val = labels['bigboat/agent/url']

-      service = labels['bigboat/service/name']
-      search = {name: name}
+        service = labels['bigboat/service/name']
+        search = {name: name}

-      updateDoc["services.#{service}.state"] = mappedState
-      updateDoc.status = "Starting #{service}" if mappedState is 'starting'
-      updateDoc.status = "Stopping #{service}" if mappedState is 'stopping'
+        updateDoc["services.#{service}.state"] = mappedState
+        updateDoc.status = "Starting #{service}" if mappedState is 'starting'
+        updateDoc.status = "Stopping #{service}" if mappedState is 'stopping'

-      Instances.upsert search, $set: updateDoc
+        Instances.upsert search, $set: updateDoc
+      else if type is 'net'
+        updateDoc = name: name
+        service = labels['bigboat/service/name']
+        updateDoc["services.#{service}.network"] = mappedState
+        console.log 'WAAAA', type, updateDoc
+        Instances.upsert {name: name}, $set: updateDoc
+      else console.log 'unknown container type', type

   #
   # Updates the internal state based on image pulls


Fix bug in incident cleanup script

Fix bug in incident cleanup script

--- collections/incidents.coffee
+++ collections/incidents.coffee
@@ -63,7 +63,7 @@
     # Fix empty location arrays, empty string counts and numeric geoname ids.
     incidents = Incidents.find().fetch()
     for incident in incidents
-      if incident.locations or incident.locations.length == 0
+      if incident.locations and incident.locations.length > 0
         Incidents.update(incident._id, {
           $set:
             locations: incident.locations.map((loc)->


Add `paper-spinner`

Add `paper-spinner`

--- snippets/paper-elements.cson
+++ snippets/paper-elements.cson
@@ -92,6 +92,9 @@
   "paper slider":
     "prefix": "paper-slider"
     "body": "<paper-slider min="${0:10}" max="${1:200}" value="${2:110}"></paper-slider>"
+  "paper spinner":
+    "prefix": "paper-spinner"
+    "body": "<paper-spinner${1: active}></paper-spinner>"
   "paper tabs":
     "prefix": "paper-tabs"
     "body": """


Use .icns file

Use .icns file

--- app.coffee
+++ app.coffee
@@ -103,7 +103,7 @@
   mainWindow.reloadArea = 'kan-game webview'
   if process.platform == 'darwin'
     if /electron$/i.test process.argv[0]
-      icon = nativeImage.createFromPath("#{ROOT}/assets/icons/poi.png")
+      icon = nativeImage.createFromPath("#{ROOT}/assets/icons/poi.icns")
       app.dock?.setIcon? icon
   else
     mainWindow.setMenu null


changing port so that it doesn't conflict with rails.

--- app.coffee
+++ app.coffee
@@ -63,8 +63,8 @@
 ss.client.packAssets() if ss.env is "production"

 if !module.parent
-  server = app.listen 3000
-  console.log "Express server listening on port 3000"
+  server = app.listen 4000
+  console.log "Express server listening on port 4000"
   ss.start server
   console.log "Socket stream started"


catch exceptions within the body of the domain error handler

--- app.coffee
+++ app.coffee
@@ -37,26 +37,28 @@
 	requestDomain.add req
 	requestDomain.add res
 	requestDomain.on "error", (err)->
-		# request a shutdown to prevent memory leaks
-		appIsOk = false
-		setTimeout(->
+		try
+			appIsOk = false
+			# request a shutdown to prevent memory leaks
+			beginShutdown()
 			if !res.headerSent
-				res.send(500)
-		, 3000)
-		logger = require('logger-sharelatex')
-		req =
-			body:req.body
-			headers:req.headers
-			url:req.url
-			key: req.key
-			statusCode: req.statusCode
-		err =
-			message: err.message
-			stack: err.stack
-			name: err.name
-			type: err.type
-			arguments: err.arguments
-		logger.err err:err, req:req, res:res, "uncaught exception thrown on request"
+				res.send(500, "uncaught exception")
+			logger = require('logger-sharelatex')
+			req =
+				body:req.body
+				headers:req.headers
+				url:req.url
+				key: req.key
+				statusCode: req.statusCode
+			err =
+				message: err.message
+				stack: err.stack
+				name: err.name
+				type: err.type
+				arguments: err.arguments
+			logger.err err:err, req:req, res:res, "uncaught exception thrown on request"
+		catch exception
+			logger.err err: exception, "exception in request domain handler"
 	requestDomain.run next

 app.get  "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.getFile


Fix option parsing

Fix option parsing

--- app.coffee
+++ app.coffee
@@ -9,10 +9,14 @@
 NumberList = (str) ->
   str.split(',').map(Number)

+DEFAULTS =
+  port: 80
+  httpsPort: 443
+
 commander
   .usage("Run it in the root of your project to start proxying requests as defined in the project's .vee file")
-  .option('-p, --port [80]', "Port to serve http requests from.  Comma seperate to bind onto multiple ports.", NumberList, [80])
-  .option('-s, --https-port [443]', "Port to serve https requests from (0 to disable).", NumberList, [443])
+  .option('-p, --port [80]', "Port to serve http requests from.  Comma seperate to bind onto multiple ports.", NumberList)
+  .option('-s, --https-port [443]', "Port to serve https requests from (0 to disable).", NumberList)
   .option('-d, --debug', "Output route matching debug info.", Boolean)
   .parse(process.argv)

@@ -60,15 +64,15 @@
   if project.name? and system?[project.name]?
     personal = system[project.name]

-  options = _.extend {}, defaults, project, personal, _.pick(commander, 'port', 'httpsPort', 'debug')
+  options = _.extend {}, DEFAULTS, defaults, project, personal, _.pick(commander, 'port', 'httpsPort', 'debug')

-  options.httpPort = options.port
+  options.httpPort = options.httpPort ? options.port
   delete options.port

   unless _.isArray options.httpPort
     options.httpPort = [options.httpPort]

-  if options.httpsPort? and not _.isArray options.httpsPort
+  if options.httpsPort and not _.isArray options.httpsPort
     options.httpsPort = [options.httpsPort]

   options.routes = _.extend {}, defaults.routes, project.routes, personal.routes


added redirect for logout

added redirect for logout

--- app.coffee
+++ app.coffee
@@ -77,7 +77,7 @@

 app.get '/logout', (req, res) ->
   req.logout()
-  res.redirect '/'
+  res.redirect req.query.redirect || '/'

 app.get '/account', authenticated, (req, res) ->
   res.send user: req.user


resolved deprecated sessionOptions warnings - resave, uninitialized

--- app.coffee
+++ app.coffee
@@ -13,7 +13,11 @@

 RedisStore =  require('connect-redis')(session);

-sessionOptions = { secret: process.env.EXPRESS_SESSION_SECRET or 'keyboard cat' }
+sessionOptions = {
+    secret: process.env.EXPRESS_SESSION_SECRET or 'keyboard cat',
+    resave: false,
+    saveUninitialized: false
+}

 if process.env.USE_REDIS_SESSION is '1'
   sessionOptions.store = new RedisStore()


Don't 404 everything

Don't 404 everything

--- app.coffee
+++ app.coffee
@@ -214,7 +214,7 @@
         app.post "#{prefix}/#{key.slice(5)}", value
 bind api, "", app

-app.get "*", (req, res) ->
+app.use (req, res) ->
   res.status(404).sendfile(__dirname + '/pages/404.html')

 # Start Server


add computation for dropdown positioning

--- src/coffee/ezBreadcrumbs.coffee
+++ src/coffee/ezBreadcrumbs.coffee
@@ -253,15 +253,7 @@
                 # add the hover class to the holder element
                 ($ this).addClass o.opts.holder.hoverClass

-                ps = o.el
-                    .find(".#{ o.opts.holder.class }")
-                    .offset()
-
                 o.dropdownWrapper
-                    .css(
-                        top:  (o.el.offset().top + o.el.outerHeight())
-                        left: ps.left
-                    )
                     .stop(true, true)
                     .fadeIn
                         duration: o.opts.holderAnimation.showDuration
@@ -355,6 +347,22 @@

                 o.windowWidth = o.browserWindow.width()

+            # set the positioning of the dropdown menu
+            holderOffset = o.el
+                .find(".#{ o.opts.holder.class }")
+                .offset()
+
+            # compute the right offset of the dropdownWrapper
+            rightOffset = current.width() - (o.dropdownWrapper.outerWidth() + holderOffset.left)
+
+            dropdownOffset =
+                top:  (o.el.offset().top + o.el.outerHeight())
+                left: 0
+
+            dropdownOffset.left = if holderOffset.left <= rightOffset then holderOffset.left else ((current.width() - o.dropdownWrapper.outerWidth()) / 2)
+
+            o.dropdownWrapper.css dropdownOffset
+
             return null

         o.getChildrenWidth = ->


add hover smartCompressionTime option

add hover smartCompressionTime option

--- src/coffee/ezBreadcrumbs.coffee
+++ src/coffee/ezBreadcrumbs.coffee
@@ -33,6 +33,7 @@
             onHide:       (obj) ->
             onAfterHide:  (obj) ->
         hoverTimeout:           500
+        smartCompressionTime:   200
         wrapperClass:           "#{ pluginName.toLowerCase() }-wrapper"
         wrappedClass:           "#{ pluginName.toLowerCase() }-wrapped"
         onBeforeCompress:       (obj) ->


hub/kucalc quota: fix standalone test

hub/kucalc quota: fix standalone test

--- src/smc-hub/test/kucalc/quota.coffee
+++ src/smc-hub/test/kucalc/quota.coffee
@@ -1,7 +1,11 @@
+# this tests kucalc's quota function
+#
+# after any change to quota.ts, be a good citizen and run this test or even extend it
+# ~/src/smc-hub$ SMC_DB_RESET=true SMC_TEST=true npx mocha test/kucalc/quota.coffee
+
+init     = require('./init')
 # Make loading typescript quota.ts just work, hopefully
 require('ts-node').register()
-
-init     = require('./init')
 db       = undefined
 setup    = (cb) -> (init.setup (err) -> db=init.db(); cb(err))
 teardown = init.teardown


fix https://github.com/sagemathinc/smc/issues/824 -- "jupyter server broken for directory name with backslashes"

--- src/smc-webapp/editor_jupyter.coffee
+++ src/smc-webapp/editor_jupyter.coffee
@@ -138,7 +138,7 @@
         @state = 'loading'
         @iframe_uuid = misc.uuid()
         @iframe = $("<iframe name=#{@iframe_uuid} id=#{@iframe_uuid} width=100%>")
-            .attr('src', "#{@server_url}#{@filename}")
+            .attr('src', misc.encode_path("#{@server_url}#{@filename}"))
             .attr('frameborder', '0')
             .attr('scrolling', 'no')
         @element.html('').append(@iframe)


Fixes typo.

Fixes typo.

--- lib/atom-isort.coffee
+++ lib/atom-isort.coffee
@@ -48,7 +48,7 @@
     else
       atom.notifications.addWarning '''
         Running isort on an unsaved file.
-        Settings detection for isort may not work crrectly.
+        Settings detection for isort may not work correctly.
         ''',
         dismissable: true


We can now assume the outSocket to be there

--- components/fileReader.coffee
+++ components/fileReader.coffee
@@ -26,14 +26,7 @@

 handleInput = (socket) ->
     socket.on "data", (data) ->
-        unless outSocket
-            timer = setTimeout ->
-                if outSocket
-                    readFile data, outSocket
-                    clearTimeout timer
-            , 200
-        else
-            readFile data, outSocket
+        readFile data, outSocket

 handleOutput = (socket) ->
     outSocket = socket


continued work on Veracode-Usage-Reports

--- node/src/Veracode-Usage-Reports.coffee
+++ node/src/Veracode-Usage-Reports.coffee
@@ -31,13 +31,19 @@
   transform_Usage_Report: =>
     raw_Data = @.latest_Usage_Report_Json()
     report = {}
-    fields = ['App', 'Version', 'Version ID']
-    for entry in raw_Data.take(2)
-      item = {}
-      for name in fields
-        item[name] = entry[name]
-      #console.log  item.App
-      report[item.App] = item
+    fields = ['App', 'App ID', 'Version', 'Version ID', 'Is Latest Pub Build']
+    for entry in raw_Data
+
+      if entry['App'] is 'myrecs-audit'
+      #if entry['Is Latest Pub Build'] is '0'
+
+        item = {}
+        for name in fields
+          item[name] = entry[name]
+
+        report[item.App] = item
+
+        console.log item
     return report


Bugfix: copy public address not working, fixed #9

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -377,7 +377,7 @@
         label: '将该地址复制到剪切版'
         action: (ev)=>
           $(ev.currentTarget).text '已复制到剪切版'
-          Electroni.clipboard.writeText url
+          Electron.clipboard.writeText url
 @action_share = (url)=>
   url = "upyun://#{@username}:#{@password}@#{@bucket}#{url}"
   msg = Messenger().post


UsuariosRoute doesn't exist

UsuariosRoute doesn't exist

--- app/assets/javascripts/routes/usuarios/usuarios_index_route.js.coffee
+++ app/assets/javascripts/routes/usuarios/usuarios_index_route.js.coffee
@@ -1,6 +0,0 @@
-Bandango.UsuariosIndexRoute = Ember.Route.extend
-  beforeModel: ->
-    Bandango.currentSession.requireLogin @
-
-  model: ->
-    @get("store").findAll "usuario"


Replace bare "Periods" word with switcher

--- tutor/src/components/task-plan/builder/index.cjsx
+++ tutor/src/components/task-plan/builder/index.cjsx
@@ -247,7 +247,9 @@
     <BS.Row className="common tutor-date-input">
       <BS.Col sm=4 md=3>
         {radio}
-        <label className="period" htmlFor='hide-periods-radio'>All Periods</label>
+        <label className="period" htmlFor='hide-periods-radio'>
+          All <CourseGroupingLabel courseId={@props.courseId} plural />
+        </label>
       </BS.Col>
       {@renderTaskPlanRow()}
     </BS.Row>


for checks that have runhost field added some logic that will only get run if its needed and won't screw up the save when the runhost field doesn't exist and/or doesn't change value

--- app/assets/javascripts/states/monitoring/views/check_editor.js.coffee
+++ app/assets/javascripts/states/monitoring/views/check_editor.js.coffee
@@ -40,17 +40,25 @@
       _.each command.get("options"), (opt_hash, opt_key) ->
         args[opt_key] = @$("input##{opt_key}").val()

-      # set runhost if exists
-      if @$("#runhost").length > 0
-        @check.set { agent_id: @$("#runhost").val() }
-        # this is a workaround because the .set below clears the change event, see here: http://stackoverflow.com/questions/5072435/backbone-js-not-registering-model-haschanged-event
-        if @check.hasChanged()
-          @check.save()
-
       @check.set {
           args: args
       }

+      # set runhost if exists
+      if @$("#runhost").length > 0
+
+        if @check.get("agent_id") != parseInt(@$("#runhost").val())
+          @check.set { agent_id: @$("#runhost").val() }
+          # this is a workaround because the .set below clears the change event, see here:
+          # http://stackoverflow.com/questions/5072435/backbone-js-not-registering-model-haschanged-event
+          if @check.hasChanged()
+            @check.save()
+            @hide_editor()
+            @parent.parent.redraw()
+        if @check.hasChanged()
+          @check.save()
+          @hide_editor()
+          @parent.parent.redraw()

       @hide_editor()


only return memberships for group

only return memberships for group

--- app/components/manage-group-funds-page/manage-group-funds-page.coffee
+++ app/components/manage-group-funds-page/manage-group-funds-page.coffee
@@ -27,7 +27,9 @@
         Error.set('group not found')

     $scope.csvData = ->
-      _.map Records.memberships.collection.data, (membership) ->
+      groupMemberships = _.filter Records.memberships.collection.data, (membership) ->
+        membership.groupId == groupId
+      _.map groupMemberships, (membership) ->
         [membership.member().email, membership.rawBalance]

     $scope.csvFileName = ->


fix testing (#6436)

fix testing (#6436)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>


iOS fake global context changed

iOS fake global context changed

--- process.coffee
+++ process.coffee
@@ -174,6 +174,9 @@
 				observe: ->
 			global._neft =
 				platform: 'ios'
+				native:
+					onData: ->
+					transferData: ->

 	NODE_MODULES =
 		fs: true


added a count method directly into spine/relation.

--- app/assets/javascripts/habberdash/dependencies/spine/relation.js.coffee
+++ app/assets/javascripts/habberdash/dependencies/spine/relation.js.coffee
@@ -12,6 +12,9 @@

   first: ->
     @all()[0]
+
+  count: ->
+    @all().length

   last: ->
     values = @all()


Bind content show of payments engine on click event of radio buttons

Firefox don't triggers `change` when the input is already selected

--- app/assets/javascripts/neighborly/projects/contributions/edit.js.coffee
+++ app/assets/javascripts/neighborly/projects/contributions/edit.js.coffee
@@ -21,7 +21,7 @@

       initialize: ->
         _.bindAll this, 'showContent'
-        this.$('.methods input').change this.showContent
+        this.$('.methods input').click this.showContent
         this.$('.methods input:first').click()

       showContent: (e)->


cleanup

cleanup

--- source/floor.coffee
+++ source/floor.coffee
@@ -45,7 +45,8 @@

   tileClicked: (x, y) ->
-    if Game.state.money > 500
-      if confirm("Do you want to add a desk for $500?")
-        new SmallDesk(x, y)
-        Game.state.money -= 500
+    #return new SmallDesk(x,y)
+
+    if Game.state.money > 500 and confirm("Do you want to add a desk for $500?")
+      new SmallDesk(x, y)
+      Game.state.money -= 500


Cleanup batch request

Cleanup batch request

* Adding leading slash to path
* Specify 'Connection: close' header
* Always call queue.done() since KM always returns a 200
* Call request.end()

--- src/kissmetrics-batch.coffee
+++ src/kissmetrics-batch.coffee
@@ -15,16 +15,14 @@
     request = http.request
       method: @HTTP_METHOD
       host: @HOST
-      path: "#{baseUrl}?_signature=#{signature}"
+      path: "/#{urlPath}?_signature=#{signature}"
       headers:
         'X-KM-ApiKey': apiKey
-    , ->
-      queue.done()
+        'Connection': 'close'

-    requestBody = JSON.stringify {data: queue.get()}
-    request.write requestBody
-
-    return request
+    request.end JSON.stringify(queue.get())
+    queue.done()
+    request

   @_generateSignature: (baseUrl, apiSecret) =>
     crypto = require 'crypto'


show what is being exported

show what is being exported

--- content/better-bibtex.coffee
+++ content/better-bibtex.coffee
@@ -154,6 +154,55 @@
     return original.apply(@, arguments)
 )(Zotero.Utilities.Internal.itemToExportFormat)

+Zotero.Translate.Export::translate = ((original) ->
+  return ->
+    try
+      do =>
+        debug("Zotero.Translate.Export::translate: #{if @_export then Object.keys(@_export) else 'no @_export'}", @_displayOptions)
+
+        ### requested translator ###
+        translatorID = @translator?[0]
+        translatorID = translatorID.translatorID if translatorID.translatorID
+        debug('Zotero.Translate.Export::translate: ', translatorID)
+
+        ### regular behavior for non-BBT translators, or if translating to string ###
+        return unless translatorID && @_displayOptions && Translators.byId[translatorID] && @location.path
+
+        if @_displayOptions.exportFileData # export directory selected
+          @_displayOptions.exportPath = @location.path
+        else
+          @_displayOptions.exportPath = @location.parent.path
+        @_displayOptions.exportFilename = @location.leafName
+
+        return unless @_displayOptions?['Keep updated']
+
+        if @_displayOptions.exportFileData
+          flash('Auto-export not registered', 'Auto-export is not supported when file data is exported')
+          return
+
+        switch @_export?.type
+          when 'library'
+            if @_export.id == Zotero.Libraries.userLibraryID
+              name = Zotero.Libraries.getName(@_export.id)
+            else
+              name = 'library ' + Zotero.Libraries.getName(@_export.id)
+
+          when 'collection'
+            name = @_export.collection.name
+
+          else
+            flash('Auto-export not registered', 'Auto-export only supported for groups, collections and libraries')
+            return
+
+        ### set up auto-export here ###
+
+        return
+
+    catch err
+      debug('Zotero.Translate.Export::translate error:', err)
+
+    return original.apply(@, arguments)
+)(Zotero.Translate.Export::translate)
 ###
   INIT
 ###


spec: Rewrite tests for new protocol API

--- spec/api-protocol-spec.coffee
+++ spec/api-protocol-spec.coffee
@@ -9,13 +9,8 @@
   text = 'valar morghulis'

   afterEach (done) ->
-    protocol.unregisterProtocol protocolName, -> done()
-
-  describe 'protocol.unregisterProtocol', ->
-    it 'returns error when scheme does not exist', (done) ->
-      protocol.unregisterProtocol 'not-exist', (error) ->
-        assert.notEqual error, null
-        done()
+    protocol.unregisterProtocol protocolName, ->
+      protocol.uninterceptProtocol 'http', -> done()

   describe 'protocol.register(Any)Protocol', ->
     emptyHandler = (request, callback) -> callback()
@@ -60,6 +55,12 @@
             done()
           error: (xhr, errorType, error) ->
             done(error)
+
+  describe 'protocol.unregisterProtocol', ->
+    it 'returns error when scheme does not exist', (done) ->
+      protocol.unregisterProtocol 'not-exist', (error) ->
+        assert.notEqual error, null
+        done()

   describe 'protocol.registerStringProtocol', ->
     it 'sends string as response', (done) ->
@@ -235,23 +236,107 @@
             assert.equal errorType, 'error'
             done()

-  describe 'protocol.isHandledProtocol', ->
+  describe 'protocol.isProtocolHandled', ->
     it 'returns true for file:', (done) ->
-      protocol.isHandledProtocol 'file', (result) ->
+      protocol.isProtocolHandled 'file', (result) ->
         assert.equal result, true
         done()

     it 'returns true for http:', (done) ->
-      protocol.isHandledProtocol 'http', (result) ->
+      protocol.isProtocolHandled 'http', (result) ->
         assert.equal result, true
         done()

     it 'returns true for https:', (done) ->
-      protocol.isHandledProtocol 'https', (result) ->
+      protocol.isProtocolHandled 'https', (result) ->
         assert.equal result, true
         done()

     it 'returns false when scheme is not registred', (done) ->
-      protocol.isHandledProtocol 'atom', (result) ->
+      protocol.isProtocolHandled 'no-exist', (result) ->
         assert.equal result, false
         done()
+
+    it 'returns true for custom protocol', (done) ->
+      emptyHandler = (request, callback) -> callback()
+      protocol.registerStringProtocol protocolName, emptyHandler, (error) ->
+        assert.equal error, null
+        protocol.isProtocolHandled protocolName, (result) ->
+          assert.equal result, true
+          done()
+
+    it 'returns true for intercepted protocol', (done) ->
+      emptyHandler = (request, callback) -> callback()
+      protocol.interceptStringProtocol 'http', emptyHandler, (error) ->
+        assert.equal error, null
+        protocol.isProtocolHandled 'http', (result) ->
+          assert.equal result, true
+          done()
+
+  describe 'protocol.intercept(Any)Protocol', ->
+    emptyHandler = (request, callback) -> callback()
+
+    it 'throws error when scheme is already intercepted', (done) ->
+      protocol.interceptStringProtocol 'http', emptyHandler, (error) ->
+        assert.equal error, null
+        protocol.interceptBufferProtocol 'http', emptyHandler, (error) ->
+          assert.notEqual error, null
+          done()
+
+    it 'does not crash when handler is called twice', (done) ->
+      doubleHandler = (request, callback) ->
+        callback(text)
+        callback()
+      protocol.interceptStringProtocol 'http', doubleHandler, (error) ->
+        $.ajax
+          url: 'http://fake-host'
+          success: (data) ->
+            assert.equal data, text
+            done()
+          error: (xhr, errorType, error) ->
+            done(error)
+
+    it 'sends error when callback is called with nothing', (done) ->
+      protocol.interceptBufferProtocol 'http', emptyHandler, (error) ->
+        $.ajax
+          url: 'http://fake-host'
+          success: (data) ->
+            done('request succeeded but it should not')
+          error: (xhr, errorType, error) ->
+            assert.equal errorType, 'error'
+            done()
+
+  describe 'protocol.interceptStringProtocol', ->
+    it 'can intercept http protocol', (done) ->
+      handler = (request, callback) -> callback(text)
+      protocol.interceptStringProtocol 'http', handler, (error) ->
+        $.ajax
+          url: 'http://fake-host'
+          success: (data) ->
+            assert.equal data, text
+            done()
+          error: (xhr, errorType, error) ->
+            done(error)
+
+  describe 'protocol.interceptBufferProtocol', ->
+    it 'can intercept http protocol', (done) ->
+      handler = (request, callback) -> callback(new Buffer(text))
+      protocol.interceptBufferProtocol 'http', handler, (error) ->
+        $.ajax
+          url: 'http://fake-host'
+          success: (data) ->
+            assert.equal data, text
+            done()
+          error: (xhr, errorType, error) ->
+            done(error)
+
+  describe 'protocol.uninterceptProtocol', ->
+    it 'returns error when scheme does not exist', (done) ->
+      protocol.uninterceptProtocol 'not-exist', (error) ->
+        assert.notEqual error, null
+        done()
+
+    it 'returns error when scheme is not intercepted', (done) ->
+      protocol.uninterceptProtocol 'http', (error) ->
+        assert.notEqual error, null
+        done()


More generic way of testing call arguments

--- spec/python-tools-spec.coffee
+++ spec/python-tools-spec.coffee
@@ -55,10 +55,8 @@
       spyOn(atom.workspace, 'open').andCallThrough()
       waitsForPromise ->
         pythonTools.jediToolsRequest('gotoDef').then ->
-          expect(atom.workspace.open).toHaveBeenCalledWith(
-            '/usr/lib/python2.7/json/__init__.py',
-            {initialLine: 1, initialColumn: 0, searchAllPanes: true},
-          )
+          path = atom.workspace.open.mostRecentCall.args[0]
+          expect(path).toMatch(/.*\/json\/__init__.py/)

   describe "when running the show usages command", ->
     editor = null


Search results need arrows. Closes #13

Search results need arrows. Closes #13

--- app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
+++ app/assets/javascripts/ample_assets/classes/ample_assets.js.coffee
@@ -14,7 +14,7 @@
     @keys_enabled = true
     ref = this
     default_options =
-      debug: false
+      debug: true
       expanded: false
       id: "ample-assets"
       handle_text: 'Assets'
@@ -231,6 +231,8 @@
       li = $('

').append(link)
       $("#asset-results ul").amplePanels('append', li)
       @load_img(link, el.sizes.tn)
+    @active_panel = $("#asset-results ul")
+

   build: (el) ->
     ref = this
@@ -307,10 +309,9 @@
     if @options.pages[i]['panel_selector']
       @active_panel = @options.pages[i]['panel_selector']
       $(@options.pages[i]['panel_selector']).amplePanels('enable')
-      $('nav.controls').show()
+    $('nav.controls').show()

   already_loaded: (i) ->
-    @log "already_loaded(#{i})"
     typeof @options.pages[i]['loaded'] == 'boolean' && @options.pages[i]['loaded']

   remove: (el) ->
@@ -418,8 +419,7 @@
     down = 40
     escape = 27
     $(document).keydown (e) =>
-      #console.log('keydown')
-      #return unless @keys_enabled or @active_panel
+      # return unless @keys_enabled or @active_panel
       switch e.keyCode
         when previous
           $(@active_panel).amplePanels('previous')


Add test for opening external files

Add test for opening external files

--- spec/python-tools-spec.coffee
+++ spec/python-tools-spec.coffee
@@ -49,6 +49,16 @@
       waitsForPromise ->
         pythonTools.jediToolsRequest('gotoDef').then ->
           expect(editor.getCursorBufferPosition()).toEqual new Point(9, 7)
+
+    it "opens appropriate file if required", ->
+      editor.setCursorBufferPosition(new Point(0, 9))
+      spyOn(atom.workspace, 'open').andCallThrough()
+      waitsForPromise ->
+        pythonTools.jediToolsRequest('gotoDef').then ->
+          expect(atom.workspace.open).toHaveBeenCalledWith(
+            '/usr/lib/python2.7/json/__init__.py',
+            {initialLine: 1, initialColumn: 0, searchAllPanes: true},
+          )

   describe "when running the show usages command", ->
     editor = null


#15 add missing ()

#15 add missing ()

--- app/assets/javascripts/controller/project_report_controller.coffee
+++ app/assets/javascripts/controller/project_report_controller.coffee
@@ -10,7 +10,7 @@
   cellClass : ""

   groupByIterator : (time) -> return time.userGID
-  getSecondLevelLabel : (time) -> @users[@groupByIterator time]
+  getSecondLevelLabel : (time) -> @users[@groupByIterator(time)]

   requestData : ->


Build progressive breadcrumbs

Build progressive breadcrumbs

--- src/components/breadcrumbs.cjsx
+++ src/components/breadcrumbs.cjsx
@@ -13,10 +13,12 @@
   render: ->
     steps = @props.model.steps

-    unansweredStepCount = 0
+    # Make sure the 1st incomplete step is displayed.
+    # Useful when the student clicks back to review a previous step
+    # (ie the reading step)
+    showedFirstIncompleteStep = false
+
     stepButtons = for step, i in steps
-      unless step.is_completed
-        unansweredStepCount += 1

       classes = ['btn step']
       classes.push(step.type)
@@ -35,9 +37,11 @@
         # classes.push('disabled')
         title ?= "Step Completed (#{step.type}). Click to review"

+      else if showedFirstIncompleteStep
+        continue
       else
-        classes.push('btn-default')
-        title ?= "Click to view #{step.type}"
+        showedFirstIncompleteStep = true
+

       <button type='button' className={classes.join(' ')} title={title} onClick={@props.goToStep(i)}><i className="fa fa-fw #{step.type}"></i></button>


add temp beta content

add temp beta content

--- project.coffee
+++ project.coffee
@@ -16,7 +16,7 @@

-            <button name="kelp">Kelp</button>
+            <button name="kelp">Overview</button>
             <button name="your-task">Your Task</button>
             <button name="goals">Goals</button>
             <button name="results">Results</button>
@@ -26,28 +26,15 @@

-
Finding Kelp

-

-                Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
-

-
-

-                Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
-

-
-

-                Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
-

-
-

-                Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
-

-
+
Kelp Overview

+
Giant kelp forests produce a dense floating canopy that is clearly visible on Landsat satellite imagery. Landsat Thematic Mapper (TM) imagery is freely available to the public and provides global coverage every ~16 days from 1984-present. By providing classifications of changes in kelp canopy cover over the past 30 years on global scales, this project will identify regions where kelp forests have experience significant long-term changes. We will then identify the likely environmental and anthropogenic drivers of these observed changes.

+
There is currently no long-term data on kelp canopy changes for most of the globe, so obtaining reliable classifications for any region (South Africa, Tasmania, New Zealand), would be a substantial improvement over our current data. Our ultimate goal would be to obtain global coverage over the entire Landsat TM time series (1984-present).

-
Your Task

-
This will be the your task section

+
What is your task?

+
You are asked to trace the outline of giant kelp forests on Landsat satellite imagery. Kelp forests are highly distinctive on these images and we believe that by showing a few images of example forests, you will be able to identify forests without any specialist knowledge.

+
There are currently many automated processing routines available for classifying satellite imagery. We have experimented with many of these, however they are not well suited for the nearshore coastal environment. Landsat was developed for terrestrial vegetation. The signal from floating aquatic vegetation is much weaker than that of terrestrial vegetation, and so we are working on the edge of Landsat’s signal to noise capabilities. High variability in nearshore conditions (e.g. clouds, sunglint, turbidity) complicates the situation. All of the automated processing routines we have used require extensive manual editing to produce an acceptable product.