Convert to using tabs so as to allow editor diversity.

--- guts/getControllers.coffee
+++ guts/getControllers.coffee
@@ -3,35 +3,35 @@
 _ = require 'lodash'

 module.exports = () ->
-
-        # For performance reasons, we'll do this asynchronously.
-        fs.readdirAsync('./controllers/').then (controllers) ->
+
+	# For performance reasons, we'll do this asynchronously.
+	fs.readdirAsync('./controllers/').then (controllers) ->

-                #This probably isn't necessary: I wanted to parse through and grab all these controllers
-                # asynchronously, so I threw them in a promise
-                makeAsyncControllerParse = (file) ->
-                        return new Promise (resolve, reject) ->
-                                if file.match(/.+\.js/g) != null || file.match(/.+\.coffee/g) != null
-                                        # When requiring the module, we don't really want to specify an extension.
-                                        # Let's get rid of it.
-                                        myObject = {}
-                                        name = file.replace('.js', '').replace('.coffee', '')
+		#This probably isn't necessary: I wanted to parse through and grab all these controllers
+		# asynchronously, so I threw them in a promise
+		makeAsyncControllerParse = (file) ->
+			return new Promise (resolve, reject) ->
+				if file.match(/.+\.js/g)? or file.match(/.+\.coffee/g)?
+					# When requiring the module, we don't really want to specify an extension.
+					# Let's get rid of it.
+					myObject = {}
+					name = file.replace('.js', '').replace('.coffee', '')

-                                        # Require the controller, feed it into the controllers
-                                        myObject[name] = require "../controllers/#{name}"
-                                        resolve(myObject)
+					# Require the controller, feed it into the controllers
+					myObject[name] = require "../controllers/#{name}"
+					resolve(myObject)

-                # Create a big ol' object of all the promises so that we have a handle on all the files.
-                #
-                # We're utilizing lodash's "chain" function simply because it gives us somewhat sane list
-                # comprehension comparable to linq.
-                promiseObject =
-                        do
-                                _.chain controllers
-                                .map makeAsyncControllerParse
-                                .reduce _.extend
-                                .value
+		# Create a big ol' object of all the promises so that we have a handle on all the files.
+		#
+		# We're utilizing lodash's "chain" function simply because it gives us somewhat sane list
+		# comprehension comparable to linq.
+		promiseObject =
+			do
+				_.chain controllers
+				.map makeAsyncControllerParse
+				.reduce _.extend
+				.value

-                # Once everything is done from above, we return back the collective promise object and
-                # pipe that to whomever feels fit.
-                return Promise.props promiseObject
+		# Once everything is done from above, we return back the collective promise object and
+		# pipe that to whomever feels fit.
+		return Promise.props promiseObject


copy variations to new model

copy variations to new model

--- public/views/product_detail_view.coffee
+++ public/views/product_detail_view.coffee
@@ -14,6 +14,8 @@
       p = new Product(url: matchingVariationItem.get("link").href)
       p.fetch(
         success: (newModel) =>
+          newModel.set("variationItems", @model.attributes.variationItems)
+          newModel.set("variationAttributes", @model.attributes.variationAttributes)
           @model = newModel
           @render())


Remove pasted <br> tags

Remove pasted <br> tags

--- vendor/assets/javascripts/control.js.coffee
+++ vendor/assets/javascripts/control.js.coffee
@@ -212,6 +212,7 @@
     # Remove any other elements they may have pasted in.
     @input.children(':not(.token)').each ->
       $(this).replaceWith($(this).html())
+    @input.find('br').remove()

     # Replace tokens with #id#.
     equation = @fixHtmlTags(@input.html())


Refactor helper function

Refactor helper function

--- vendor/assets/javascripts/regionFlow.coffee
+++ vendor/assets/javascripts/regionFlow.coffee
@@ -203,9 +203,9 @@
   # @params elem - jQuery object
   #
   getBlockParent:(elem) ->
-    arr = ['b', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'a', 'bdo', 'br', 'img', 'map', 'object', 'q', 'script', 'span', 'sub', 'sup', 'button', 'input', 'label', 'select', 'textarea']
-    blockElem = if $.inArray(elem.prop('tagName').toLowerCase(), arr) > -1
-      @getBlockParent(elem.parent(), arr)
+    inlineEls = ['B', 'BIG', 'I', 'SMALL', 'TT', 'ABBR', 'ACRONYM', 'CITE', 'CODE', 'DFN', 'EM', 'KBD', 'STRONG', 'SAMP', 'VAR', 'A', 'BDO', 'BR', 'IMG', 'MAP', 'OBJECT', 'Q', 'SCRIPT', 'SPAN', 'SUB', 'SUP', 'BUTTON', 'INPUT', 'LABEL', 'SELECT', 'TEXTAREA']
+    blockElem = if $.inArray(elem.prop('tagName'), inlineEls) > -1
+      @getBlockParent(elem.parent(), inlineEls)
     else
       elem
     return blockElem


Fix improper syntax

Fix improper syntax

--- src/trix/views/attachment_view.coffee
+++ src/trix/views/attachment_view.coffee
@@ -37,7 +37,7 @@

     if @attachment.isPending()
       data.trixSerialize = false
-      progressElement = makeElement("progress", className: "attachment_progress", max: 100, value: 0, "data-trix-mutable": true)
+      progressElement = makeElement(tagName: "progress", className: "attachment_progress", max: 100, value: 0, "data-trix-mutable": true)
       figure.appendChild(progressElement)

     if href = @attachment.getHref()


Fixed dom bug with group insertion.

Fixed dom bug with group insertion.

--- app/assets/javascripts/backbone/views/form_items_view.js.coffee
+++ app/assets/javascripts/backbone/views/form_items_view.js.coffee
@@ -46,7 +46,7 @@
         ELMO.app.loading(false)

   add_new_group: (data) ->
-    $('<li>').html(data).appendTo('.form-items-list')
+    $('.form-items-list').append(data)

   update_group_on_edit: (data) ->
     @form_item_being_edited.replaceWith(data)


MyNinja DataSource bug fixed.

MyNinja DataSource bug fixed.

--- server/src/Component/Builtin/Database/MyNinja.coffee
+++ server/src/Component/Builtin/Database/MyNinja.coffee
@@ -6,7 +6,8 @@
         @_validate = params?.validate

     init: ->
-        @_mysql = @component 'DataSource.MySQL', @_dataSourceName
+        @_mysql = @component 'DataSource.MySQL'
+        @_mysql.setDataSource @_dataSourceName
         @_validator = @component 'Validator', validate: @_validate
         @_cherries = @component 'Cherries'
         @$ = @component 'QueryBuilder'


Tests: Parallel-sets: Change test name

Tests: Parallel-sets: Change test name

--- scripts/test-instance/parallel-sets.coffee
+++ scripts/test-instance/parallel-sets.coffee
@@ -43,7 +43,6 @@
   [
     { name: 'activity edit' }
     { name: 'activity delete' }
-    { name: 'teams teamschannels' }
   ]

   [
@@ -64,6 +63,7 @@

   [
     { name: 'ide terminal' }
+    { name: 'teams teamschannels' }
     # { name: 'pricing invalidcarddetails' }
   ]


reordered toolbar buttons. resolved issue #22


tool_events

--- bokehjs/src/coffee/common/tool_events.coffee
+++ bokehjs/src/coffee/common/tool_events.coffee
@@ -4,5 +4,12 @@
 class ToolEvents extends HasProperties
   type: 'ToolEvents'

+  defaults: () ->
+    return {
+      geometries: []
+      name: null
+      tags: []
+    }
+
 module.exports =
   Model: ToolEvents


fix error in arc radius compute

fix error in arc radius compute

--- bokehjs/src/coffee/renderer/glyph/arc.coffee
+++ bokehjs/src/coffee/renderer/glyph/arc.coffee
@@ -8,8 +8,7 @@

   _map_data: () ->
     if @distances.radius.units == "data"
-      rd = @fields.radius_dimension.fixed_value
-      @sradius = @sdist(@renderer["#{rd}mapper"], @[rd], @radius)
+      @sradius = @sdist(@renderer.xmapper, this.x, @radius)
     else
       @sradius = @radius


DNodeServer now ends all DNode connections when close() is called.

Without doing this, test suite doesn't terminate since the connections
keep the event loop active.  This is a temporary solution - once we
implement browser#close(), the browsers will be responsible for closing
their own client connections.

--- src/browser/dnode_server.coffee
+++ src/browser/dnode_server.coffee
@@ -3,8 +3,10 @@

 class DNodeServer extends EventEmitter
     constructor : (httpServer, browsers) ->
+        @conns = conns = []
         @server = DNode((remote, conn) ->
             console.log("Incoming connection")
+            conns.push(conn)
             browser = null
             dom = null

@@ -103,6 +105,9 @@
             process.nextTick( () => @emit('ready'))

     close : () ->
+        for conn in @conns
+            if conn?
+                conn.end()
         @server.once('close', () => @emit('close'))
         @server.close()


cleanup on builtin.primitive to use the new functions

--- src/builtin/primitive.coffee
+++ src/builtin/primitive.coffee
@@ -7,38 +7,38 @@
   # would make sense in that context.  However, if it doesn't, we default to the
   # quirky implementation used by Python for generic objects.
   __lt__: (operand) ->
-    if Helper::isSubInstance operand, @
-      return @value < operand.value
+    if issubinstance operand, @
+      new Bool @value < operand.value
     else
       super operand

   __le__: (operand) ->
-    if Helper::isSubInstance operand, @
-      return @value <= operand.value
+    if issubinstance operand, @
+      new Bool <= operand.value
     else
       super operand

   __eq__: (operand) ->
-    if Helper::isSubInstance operand, @
-      return @value == operand.value
+    if issubinstance operand, @
+      new Bool @value == operand.value
     else
       super operand

   __ne__: (operand) ->
-    if Helper::isSubInstance operand, @
-      return @value != operand.value
+    if issubinstance operand, @
+      new Bool @value != operand.value
     else
       super operand

   __ge__: (operand) ->
-    if Helper::isSubInstance operand, @
-      return @value >= operand.value
+    if issubinstance operand, @
+      new Bool @value >= operand.value
     else
       super operand

   __gt__: (operand) ->
-    if Helper::isSubInstance operand, @
-      return @value > operand.value
+    if issubinstance operand, @
+      new Bool @value > operand.value
     else
       super operand

@@ -109,4 +109,4 @@
   # **Consideration** What is the difference between __repr__ and __str__?
   # How does this affect console.log / print / concatenation?
   __str__: ->
-    return @value.toString()
+    new Str @value.toString()


Add ability to list directories in Dropbox auth

--- salvus/page/dropbox.cjsx
+++ salvus/page/dropbox.cjsx
@@ -8,7 +8,12 @@
             selected: null
           }
         render: ->
-
Please select a folder

+          folders = @props.folders.map (folder) ->
{folder}

+
Please select a folder from <tt>{@props.folderPath}</tt>
+

+              {folders}
+

+

       DropboxButton = React.createClass
         authorize: ->
@@ -30,12 +35,21 @@
           {
             client: null
             authorized: false
+            folders: []
+            folderPath: null
           }
+        readFolder: (path) ->
+          @state.client.readdir path, null, (error, files, stat, stats) =>
+            folders = stats.filter((entry) -> entry.isFolder).map((entry) -> entry.path)
+            @setState { folderPath: path, folders: folders }
         setClient: (client) ->
-          @setState { client: client, authorized: true }
+          @setState { client: client, authorized: true }, ->
+            @readFolder('/')
+        setFolderSelection: (path) ->
         render: ->
           if @state.authorized
-            <DropboxFolderSelector client={@state.client} />
+            <DropboxFolderSelector client={@state.client} folders={@state.folders} folderPath={@state.folderPath}
+              setFolderPath={@readFolder} setFolderSelection={@setFolderSelection} />
           else
             <DropboxButton setClient={@setClient} />


only send to nsca once at most

only send to nsca once at most

--- server/lib/server/services-presence.coffee
+++ server/lib/server/services-presence.coffee
@@ -52,7 +52,7 @@
           return callback err  if err?
           callback null, worker?.hostname ? null

-module.exports = (req, res) ->
+module.exports = do (failing = no) -> (req, res) ->
   {params:{service}, query} = req

   protocol = KONFIG.broker.webProtocol ? 'https:'
@@ -77,10 +77,13 @@

   else unless services.length
     # FAILURE! send an alert to opsview.
-    nsca.sendStatus 'Services presence', 2, 'Service loadbalancing failure detected!'
+    unless failing
+      failing = yes
+      nsca.sendStatus 'Services presence', 2, 'Service loadbalancing failure detected!'
     # Fail-over to the value hard-coded into the config.
     res.send "\"#{ KONFIG.client.runtimeOptions.broker.sockJS }\""

   else
+    failing = no
     i = genericServices.seq++ % services.length
     res.send "\"#{ protocol }//#{ services[i] }\""


wodema

wodema

--- parts/prophet-info.cjsx
+++ parts/prophet-info.cjsx
@@ -14,13 +14,13 @@
       null
   else
     if cond > 49
-      'text-shadow': '0 0 3px #FFFF00'
+      textShadow: '0 0 3px #FFFF00'
     else if cond < 20
-      'text-shadow': '0 0 3px #DD514C'
+      textShadow: '0 0 3px #DD514C'
     else if cond < 30
-      'text-shadow': '0 0 3px #F37B1D'
+      textShadow: '0 0 3px #F37B1D'
     else if cond < 40
-      'text-shadow': '0 0 3px #FFC880'
+      textShadow: '0 0 3px #FFC880'
     else
       null


Allow params in nested from add button

Allow params in nested from add button

--- vendor/assets/javascripts/ab_admin/jquery_nested_form.js.coffee
+++ vendor/assets/javascripts/ab_admin/jquery_nested_form.js.coffee
@@ -20,13 +20,18 @@

     guid = $(content).find('[name$="[fileupload_guid]"]').val()
     content = content.replace(new RegExp(guid, 'g'), new_id) if guid
+    $content = $(content)
+
+    if $el.data('params')
+      for k, v of $el.data('params')
+        $content.find("[name$='[#{k}]']").val(v)

     if $el.data('container')
       $cont = $($el.data('container'))
-      field = $(content).prependTo($cont)
+      field = $content.prependTo($cont)
     else
       $cont = $el
-      field = $(content).insertBefore($cont)
+      field = $content.insertBefore($cont)

     $el.closest("form").trigger
       type: "nested:fieldAdded"


Optimize host

Optimize host


Fixes inconsistent behavior of CFM menu [#158700996] - sometimes clicking on an item with a submenu would close the menu rather than opening the submenu - sometimes clicking on the "anchor" would fail to open or close the menu appropriately - the original bug was listed as being touch related but in many cases it was more timing-related - the fix here is to eliminate the timer mechanism for closing the menu in favor of a class-based one

--- src/code/views/dropdown-view.coffee
+++ src/code/views/dropdown-view.coffee
@@ -43,6 +43,8 @@
         name
       )

+cfmMenuClass = 'cfm-menu'
+
 DropDown = React.createClass

   displayName: 'Dropdown'
@@ -54,27 +56,22 @@

   componentWillMount: ->
     if window.addEventListener
-      window.addEventListener 'mousedown', @checkBlur, true
-      window.addEventListener 'touchstart', @checkBlur, true
+      window.addEventListener 'mousedown', @checkClose, true
+      window.addEventListener 'touchstart', @checkClose, true

   componentWillUnmount: ->
     if window.removeEventListener
-      window.removeEventListener 'mousedown', @checkBlur, true
-      window.removeEventListener 'touchstart', @checkBlur, true
+      window.removeEventListener 'mousedown', @checkClose, true
+      window.removeEventListener 'touchstart', @checkClose, true

-  checkBlur: ->
-    if @state.showingMenu
-      @blur()
-
-  blur: ->
-    @unblur()
-    timeout = setTimeout ( => @setState {showingMenu: false, subMenu: false} ), 500
-    @setState {timeout: timeout}
-
-  unblur: ->
-    if @state.timeout
-      clearTimeout(@state.timeout)
-    @setState {timeout: null}
+  checkClose: (evt) ->
+    # if the click is on the menu, let the menu handle it
+    elt = evt.target
+    while elt?
+      return if typeof elt.className is "string" and elt.className.indexOf(cfmMenuClass) >= 0
+      elt = elt.parentNode
+    # otherwise, close the menu
+    @setState {showingMenu: false, subMenu: false}

   setSubMenu: (subMenu) ->
     @setState subMenu: subMenu
@@ -87,13 +84,13 @@
     item.action?()

   render: ->
-    menuClass = if @state.showingMenu then 'menu-showing' else 'menu-hidden'
+    menuClass = "#{cfmMenuClass} #{if @state.showingMenu then 'menu-showing' else 'menu-hidden'}"
     select = (item) =>
       ( => @select(item))
     (div {className: 'menu'},
       if @props.items?.length > 0
         (div {},
-          (div {className: 'menu-anchor', onClick: => @select(null)},
+          (div {className: "#{cfmMenuClass} menu-anchor", onClick: => @select(null)},
             (svg {version: '1.1', width: 16, height: 16, viewBox: '0 0 16 16', enableBackground: 'new 0 0 16 16'},
               (g {},
                 (rect {y: 2, width: 16, height: 2})
@@ -102,7 +99,7 @@
               )
             )
           )
-          (div {className: menuClass, onMouseLeave: @blur, onMouseEnter: @unblur},
+          (div {className: menuClass},
             (ul {},
               (DropdownItem {key: index, item: item, select: @select, setSubMenu: @setSubMenu}) for item, index in @props.items
             )


remove fizIEResize option

remove fizIEResize option

--- src/coffee/ezBreadcrumbs.coffee
+++ src/coffee/ezBreadcrumbs.coffee
@@ -13,7 +13,6 @@
         dropdownWrapperClass:   "#{ pluginName.toLowerCase() }-dropdown-wrapper"
         enhanceAnimation:       true
         exposeItems:            false
-        fixIEResize:            false
         holder:
             class:      "#{ pluginName.toLowerCase() }-holder"
             hoverClass: "#{ pluginName.toLowerCase() }-holder-hovered"


Fake it till you make it

Fake it till you make it

--- src/services/openmapquest_api.coffee
+++ src/services/openmapquest_api.coffee
@@ -31,9 +31,9 @@
             if address
               country  = address.country_code?.toUpperCase()
               state    = address.state
-              if address.city && address.city == address.county && address.suburb
+              if address.suburb == 'Ehmen'
                 # location = "#{address.city} #{address.suburb}"
-                location = address.suburb
+                location = 'Ehmen'
               else
                 location = address.city || address.village || address.county || state
               throw "openmapquest_api: Failed to reverse geocode a country for (#{lat},#{lon})" if !country


oops forgot a damn .only

oops forgot a damn .only


remove some console output

remove some console output

--- src/git-timetracking.coffee
+++ src/git-timetracking.coffee
@@ -8,14 +8,12 @@
 defaultInitTime = 10

 parsePauseTime = (val) ->
-	console.log "parse"
 	if val?
 		parseInt val
 	else
 		defaultPauseTime

 parseInitTime = (val) ->
-	console.log "parse"
 	if val?
 		parseInt val
 	else


Warn before creating new notebook.

Warn before creating new notebook.

--- src/core/components/notebook.coffee
+++ src/core/components/notebook.coffee
@@ -373,12 +373,13 @@
     _.showHelp()

   createNotebook = ->
-    currentTime = (new Date()).getTime()
-    deserialize 'Untitled Flow', null,
-      cells: [
-        type: 'cs'
-        input: ''
-      ]
+    _.confirm 'This action will replace your active notebook.\nAre you sure you want to continue?', { acceptCaption: 'Create New Notebook', declineCaption: 'Cancel' }, (accept) ->
+      currentTime = (new Date()).getTime()
+      deserialize 'Untitled Flow', null,
+        cells: [
+          type: 'cs'
+          input: ''
+        ]

   duplicateNotebook = ->
     deserialize "Copy of #{_localName()}", null, serialize()


fix indentation

fix indentation

--- src/hubot-confluence.coffee
+++ src/hubot-confluence.coffee
@@ -50,12 +50,12 @@
       return

     if res.statusCode isnt 200
-        msg.send "Error processing your request"
-        msg.send "Check hubot logs for more information"
-        console.log("Status Code: " + res.statusCode)
-        console.log("Response body:")
-        console.log(body)
-        return
+      msg.send "Error processing your request"
+      msg.send "Check hubot logs for more information"
+      console.log("Status Code: " + res.statusCode)
+      console.log("Response body:")
+      console.log(body)
+      return

     content = JSON.parse(body)


fix single value slider value parsing

fix single value slider value parsing

--- vendor/assets/javascripts/simple_form_extension/slider.coffee
+++ vendor/assets/javascripts/simple_form_extension/slider.coffee
@@ -2,7 +2,7 @@
   constructor: (@$el) ->
     value = if (values = @$el.data('slider-value')?.toString().split(','))
       if values.length <= 1
-        values[0]
+        parseInt(values[0], 10)
       else
         (parseInt(value, 10) for value in values)


fix format

fix format

--- src/client/sharer/facebook.coffee
+++ src/client/sharer/facebook.coffee
@@ -10,11 +10,12 @@

     @opts.method   ||= @defaultMethod

-    @params.href ?= @params.url if @opts.method=='share'
-    @params.link ?= @params.url if @opts.method=='feed'
+    @params.href = @params.url if @opts.method=='share'
+    @params.link = @params.url if @opts.method=='feed'
     delete @params.url

-    return @sharePopup()   if isMobile()     or @opts.display=='popup'
+
+    return @sharePopup()   if isMobile()     or (@opts.display || @params.display)=='popup'
     return @sharePromise() if opts.anonymous or @currentUser.hasIdentity('facebook')

     return @_ensureLogin().then(@sharePromise)
@@ -23,6 +24,7 @@
     @api.message({provider:@provider, path:"ui.#{@opts.method}"},@params)

   sharePopup : ()=>
+    debugger
     # params.redirect_uri = hull.config('orgUrl')+"/api/v1/services/facebook/callback?data="+btoa(params)
     @opts.redirect_uri = window.location.href
     @opts.app_id = Hull?.config()?.services?.settings?.facebook_app?.appId


ComputeProviders: Koding: do not allow to update with the same storage amount

--- workers/social/lib/social/models/computeproviders/koding.coffee
+++ workers/social/lib/social/models/computeproviders/koding.coffee
@@ -233,6 +233,10 @@
               return callback new KodingError \
               """Requested new size exceeds allowed
                  limit of #{userPlan.storage}GB.""", "UsageLimitReached"
+            else if resize == machine.getAt 'meta.storage_size'
+              return callback new KodingError \
+              """Requested new size is same with current
+                 storage size (#{resize}GB).""", "WrongParameter"

             fieldsToUpdate['meta.storage_size'] = resize


Add a fourth part type 'neither' for the case when the top and bottom of the element are both offscreen.

--- angular-inview.coffee
+++ angular-inview.coffee
@@ -146,7 +146,7 @@
 triggerInViewCallback = (event, item, inview, isTopVisible, isBottomVisible) ->
 	if inview
 		elOffsetTop = getBoundingClientRect(item.element[0]).top + window.pageYOffset
-		inviewpart = (isTopVisible and 'top') or (isBottomVisible and 'bottom') or 'both'
+		inviewpart = (isTopVisible and isBottomVisible and 'neither') or (isTopVisible and 'top') or (isBottomVisible and 'bottom') or 'both'
 		# The callback will be called only if a relevant value has changed.
 		# However, if the element changed it's position (for example if it has been
 		# pushed down by dynamically loaded content), the callback will be called anyway.


remove error for firefox 61... (I don't know how to deal with 61.0.1 so screw it)

--- src/smc-webapp/client_browser.coffee
+++ src/smc-webapp/client_browser.coffee
@@ -103,12 +103,11 @@

         # Wait until load totally done.
         setTimeout(@_firefox60_bug, 5000)
-        setInterval(@_firefox60_bug, 60000)

     _firefox60_bug: =>
         {name, version} = require('misc/browser').get_browser()
-        if name == 'Firefox' and (version == '59' or version == '60' or version == '61')
-            @alert_message(type:'error', block:'true', timeout:10000, message:"Firefox versions 59 and 60 and 61 have a MAJOR bug. You *must* use a different web browser. See https://tinyurl.com/y9hphj39 and https://tinyurl.com/yboeepsf")
+        if name == 'Firefox' and (version == '59' or version == '60')
+            @alert_message(type:'error', block:'true', timeout:10000, message:"Firefox versions 59 and 60 have a MAJOR bug. You *must* use a different web browser. See https://tinyurl.com/y9hphj39 and https://tinyurl.com/yboeepsf")

     _setup_window_smc: () =>
         # if we are in DEBUG mode, inject the client into the global window object


Fix linter errors

Fix linter errors

--- src/context-menu-manager.coffee
+++ src/context-menu-manager.coffee
@@ -35,11 +35,11 @@
     for selector, items of object
       for label, commandOrSubmenu of items
         if typeof commandOrSubmenu is 'object'
-          submenu = [];
+          submenu = []
           for submenuLabel, command of commandOrSubmenu
             if submenuLabel is command is '-'
-              submenu.push({type: 'separator'});
-            else
+              submenu.push({type: 'separator'})
+            else
               submenu.push({label: submenuLabel, command: command})
           @addBySelector(selector, {label: label, submenu: submenu}, {devMode})
         else


add xref to inline elements

add xref to inline elements

--- src/textorum/tinymce/testload.coffee
+++ src/textorum/tinymce/testload.coffee
@@ -31,7 +31,7 @@
   processor.importStylesheet(forwardStylesheet)
   # TODO: Switch to class setup, get these params from main textorum plugin
   # Elements to bring across as <span> rather than <div>
-  processor.setParameter(null, "inlineelements", "bold,italic,monospace,underline,sub,sup,named-content,ext-link,inline-graphic,inline-formula")
+  processor.setParameter(null, "inlineelements", "bold,italic,monospace,underline,sub,sup,named-content,ext-link,inline-graphic,inline-formula,xref")
   # Elements to bring over without changing their element name
   processor.setParameter(null, "fixedelements", "table,thead,tbody,td,tr,th")


Release Season 5 results one day later

Release Season 5 results one day later


added orchestrations

added orchestrations

--- src/middleware/stats.coffee
+++ src/middleware/stats.coffee
@@ -43,6 +43,13 @@
         sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name, timer # Per Channel
         sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.nonPrimaryRoutes.' + route.name + '.StatusCodes' + route.response.status, timer # Per Channel

+    if ctx.mediatorResponse?
+      if ctx.mediatorResponse.orchestrations?
+        for ochestration in ctx.mediatorResponse.orchestrations
+          orchestratrionDuration = ochestration.response.timestamp - ochestration.request.timestamp
+          sdc.timing domain + '.Channels.' + ctx.authorisedChannel._id + '.' ochestration.name, orchestratrionDuration
+
+
   catch error
     logger.error error, done


Added red [ENTER] to select prompt

Added red [ENTER] to select prompt

--- src/service_selector.coffee
+++ src/service_selector.coffee
@@ -32,7 +32,13 @@
     # Update the services menu
     width = process.stdout.getWindowSize()[0]
     `util.print("\u001b[2J\u001b[0;0H")`
-    @cursor.hide().write "Select a Printer:\n"
+    @cursor
+      .hide()
+      .write("Select a Printer with ")
+      .red()
+      .write("[ENTER]")
+      .white()
+      .write(":\n")
     max = @services.list.length - 1
     @selected_index = max if @selected_index > max
     @selected_index = 0 if @selected_index < 0


document playlist.coffee

document playlist.coffee

--- app/models/playlist.coffee
+++ app/models/playlist.coffee
@@ -1,25 +1,31 @@
 Sounds = require '../models/sounds'

+# The **Playlist** model has `title`, `description`, `color`
+# and `selected` attributes. It also holds a `sounds` collection,
+# associated by the `playlist_id` foreign key on each `Sound`.
 module.exports = class Playlist extends Backbone.Model

+  # Set some useful defaults.
   defaults:
     title: 'SoundTape'
     description: ''
-    #color: '#F60'
     selected: false

+  # Populate the playlist from the `Sounds` store.
   initialize: ->
     app.sounds.fetch()
-    sounds = app.sounds.where(playlist_id: @id)
-    @sounds = new Sounds(sounds)
+    sounds = app.sounds.where playlist_id:@id
+    @sounds = new Sounds sounds
+    @sounds.on 'finished', @playNext
     @set 'color', @randomColor() unless @get('color')?
-    @sounds.on 'finished', @playNext
-    super

-  # src: http://goo.gl/yKUXW
+  # Generate a random color for the playlist,
+  # based on [goo.gl/yKUXW](http://goo.gl/yKUXW).
   randomColor: ->
     '#' + Math.floor(Math.random() * 16777215).toString(16)

+  # Find the next sound in the playlist and play it
+  # (triggered by the `finished` event on the `Sound` model).
   playNext: (sound) =>
     index = @sounds.indexOf sound
     next = @sounds.at(index - 1)


Reorganize triangles vertex in grid results

--- src/geom/triangular_grid.coffee
+++ src/geom/triangular_grid.coffee
@@ -148,32 +148,33 @@
   triangleAtPosition: (pos) ->
     [a,b,c] = []
     yStep = pos.y % 4
-
+    yStep = 3 + yStep if yStep < 0
+
     switch yStep
       when 0
         xRef = pos.x * @triangleSize
         yRef = (pos.y / 2) * @rowHeight
-        a = new agt.geom.Point(xRef - @halfSize, yRef)
+        a = new agt.geom.Point(xRef, yRef + @rowHeight)
         b = new agt.geom.Point(xRef + @halfSize, yRef)
-        c = new agt.geom.Point(xRef, yRef + @rowHeight)
+        c = new agt.geom.Point(xRef - @halfSize, yRef)
       when 1
         xRef = pos.x * @triangleSize + @halfSize
         yRef = (pos.y - 1) / 2 * @rowHeight
-        a = new agt.geom.Point(xRef - @halfSize, yRef + @rowHeight)
+        a = new agt.geom.Point(xRef, yRef)
         b = new agt.geom.Point(xRef + @halfSize, yRef + @rowHeight)
-        c = new agt.geom.Point(xRef, yRef)
+        c = new agt.geom.Point(xRef - @halfSize, yRef + @rowHeight)
       when 2
         xRef = pos.x * @triangleSize + @halfSize
         yRef = pos.y / 2 * @rowHeight
-        a = new agt.geom.Point(xRef - @halfSize, yRef)
+        a = new agt.geom.Point(xRef, yRef + @rowHeight)
         b = new agt.geom.Point(xRef + @halfSize, yRef)
-        c = new agt.geom.Point(xRef, yRef + @rowHeight)
+        c = new agt.geom.Point(xRef - @halfSize, yRef)
       when 3
         xRef = pos.x * @triangleSize
         yRef = (pos.y - 1) / 2 * @rowHeight
-        a = new agt.geom.Point(xRef - @halfSize, yRef + @rowHeight)
+        a = new agt.geom.Point(xRef, yRef)
         b = new agt.geom.Point(xRef + @halfSize, yRef + @rowHeight)
-        c = new agt.geom.Point(xRef, yRef)
+        c = new agt.geom.Point(xRef - @halfSize, yRef + @rowHeight)

     try
       new agt.geom.Triangle(a,b,c)


fix($n): Expose nylas-exports as $n on the console

--- src/global/nylas-exports.coffee
+++ src/global/nylas-exports.coffee
@@ -168,4 +168,5 @@
   # Testing
   @get "NylasTestUtils", -> require '../../spec/nylas-test-utils'

+window.$n = NylasExports
 module.exports = NylasExports


Fix save/load logic for passed URN's.

Fix save/load logic for passed URN's.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -171,6 +171,9 @@
   for child in $('#collection_form').children()
     if $(child).attr('id') && !$(child).prop('disabled') && ($(child).attr('type') != 'hidden')
       localStorage["#{collection}:#{$(child).attr('id')}"] = get_value_for_form_input(child)
+    else if ($(child).attr('id') == $('input[data-urn=true]').attr('id')) && (parse_query_string()[$(child).attr('id')]?)
+      # save the passed URN
+      localStorage["#{collection}:#{$(child).attr('id')}"] = parse_query_string()[$(child).attr('id')]
   $('#collection_form').after $('<div>').attr('class','alert alert-success').attr('id','save_success').append('Saved.')
   scroll_to_bottom()
   $('#save_success').fadeOut 1800, ->
@@ -214,6 +217,9 @@
       else if localStorage["#{collection}:#{$(child).attr('id')}"]?
         if $(child).attr('class') == 'pagedown_container'
           $(child).find('.wmd-input').val(localStorage["#{collection}:#{$(child).attr('id')}"])
+        else if $(child).attr('id') == $('input[data-urn=true]').attr('id')
+          # push URN into hash parameters
+          history.replaceState(null,'',window.location.href.replace("#{location.hash}","#{location.hash}&#{$(child).attr('id')}=#{localStorage["#{collection}:#{$(child).attr('id')}"]}"))
         else
           $(child).val(localStorage["#{collection}:#{$(child).attr('id')}"])

@@ -273,6 +279,7 @@
   check_table_access $(collection).attr('class')

   # update various inputs after we've actually put the form in the DOM
+  load_collection_form()
   set_author_name()
   construct_latest_urn (urn) ->
     $('input[data-urn=true]').attr('value',urn)
@@ -438,7 +445,6 @@
         push_selected_collection()

         build_collection_form selected_collection
-        load_collection_form()
         unless get_cookie 'access_token'
           $('.container > h1').after $('<div>').attr('class','alert alert-warning').attr('id','oauth_access_warning').append('You have not authorized this application to access your Google Fusion Tables. ')
           $('#oauth_access_warning').append $('<a>').attr('href',google_oauth_url()).append('Click here to authorize.')


Revert unfinished OAuth changes accidentally committed during move.

--- src/js/collection-editor.coffee
+++ src/js/collection-editor.coffee
@@ -1,13 +1,3 @@
-google_oauth_parameters_for_fusion_tables =
-  response_type: 'token'
-  client_id: '891199912324.apps.googleusercontent.com'
-  redirect_uri: 'http://dl.dropbox.com/u/360471/cite-collection-editor/oauthcallback'
-  scope: 'https://www.googleapis.com/auth/fusiontables.readonly'
-  approval_prompt: 'auto'
-
-google_oauth_url =
-  "https://accounts.google.com/o/oauth2/auth?#{$.param(google_oauth_parameters_for_fusion_tables)}"
-
 build_input_for_valuelist = (valuelist) ->
   select = $('<select>')
   values = $(valuelist).find('value')
@@ -41,7 +31,6 @@
   form = $('<form>').attr('id','collection_form')
   properties = $(collection).find('citeProperty')
   add_property_to_form(property,form) for property in properties
-  form.append $('<a>').attr('href',google_oauth_url).append('OAuth Test')
   $('.container').append form

 $(document).ready ->


Permissions: separate the logic from constructor method to new method addPermissionsView

--- client/Dashboard/views/permissionview.coffee
+++ client/Dashboard/views/permissionview.coffee
@@ -9,6 +9,7 @@

     @addPermissionsView()

+  addPermissionsView: ->
     group = @getData()

     @loader           = new KDLoaderView
@@ -61,6 +62,19 @@
     super
     @loader.show()
     @loaderText.show()
+    group.fetchRoles (err,roles)=>
+      return KD.showError err if err
+      group.fetchPermissions (err, permissionSet)=>
+        return KD.showError err if err
+        @addSubView permissions = new PermissionsForm {permissionSet,roles}, group
+        permissions.on 'RoleWasAdded', (newPermissions,role)=>
+          permissions.destroy()
+          @addPermissionsView()
+          @loader.show()
+          @loaderText.show()
+
+        @loader.hide()
+        @loaderText.hide()

   pistachio:->
     """


MR comment fix

MR comment fix

--- client/Intro/views/registerinlineform.coffee
+++ client/Intro/views/registerinlineform.coffee
@@ -46,10 +46,9 @@
         @button.showLoader()
         if @username.input.getValue() is ""
           @username.showError "Please enter a username."
-          @button.hideLoader()
         if @email.input.getValue() is ""
           @email.showError "Please enter an email."
-          @button.hideLoader()
+        @button.hideLoader()

     @on "SubmitFailed", (msg)=>
       # if msg is "Wrong password"


paymentController : listen for tokenCreated event and write token to appstorage

--- client/Main/payment/paymentcontroller.coffee
+++ client/Main/payment/paymentcontroller.coffee
@@ -152,6 +152,13 @@
         else if createAccount
           { cardFirstName: firstName, cardLastName: lastName } = billing
           { JUser } = KD.remote.api
+
+          me = KD.whoami()
+          me.once "tokenCreated", (token) ->
+            storageController = KD.singletons.appStorageController
+            paymentApp = storageController.storage "PaymentDetails", "1.0"
+            paymentApp.setValue "UserToken", token
+
           JUser.convert { firstName, lastName, email }, (err) ->
             JUser.logout ->
               KD.singletons.dock.getView().show()


modal now hides/shows itself by listening to store

--- src/components/exercise.cjsx
+++ src/components/exercise.cjsx
@@ -23,9 +23,6 @@

   componentWillUnmount: ->
     ExerciseStore.removeChangeListener(@update)
-
-  showPublishedModal: ->
-    @setState(hasPublished: true)

   sync: -> ExerciseActions.sync(@props.exerciseId)


set propTypes

set propTypes

--- src/components/exercise.cjsx
+++ src/components/exercise.cjsx
@@ -13,7 +13,8 @@

 Exercise = React.createClass
-  exerciseId:   React.PropTypes.string.isRequired
+  propTypes:
+    exerciseId:   React.PropTypes.string.isRequired

   getInitialState: -> {}
   update: -> @forceUpdate()


Link project popular tags to subject tag search

--- app/talk/popular-tags.cjsx
+++ app/talk/popular-tags.cjsx
@@ -13,12 +13,13 @@
   tagsRequest: ->
     talkClient.type('tags/popular').get section: @props.section, limit: 20, page_size: 20

-  tag: (tag, i) ->
+  tag: (talkTag, i) ->
+    tag = talkTag.name
     {owner, name} = @props.params
     if owner and name
-
<Link params={{owner, name}} query={query: tag.name} to="project-talk-search">#{tag.name}</Link>{' '}

+
<Link params={{owner, name, tag}} to="project-talk-tags">#{tag}</Link>{' '}

     else
-
<Link query={query: tag.name} to="talk-search">#{tag.name}</Link>{' '}

+
<Link query={query: tag} to="talk-search">#{tag}</Link>{' '}

   render: ->


fixes bug where the new block field didnt work in open channels

--- apps/channel/client.coffee
+++ apps/channel/client.coffee
@@ -82,7 +82,7 @@
       channel: channel
       el: $ ".grid"

-    if current_user.canEditChannel channel
+    if current_user.canAddToChannel channel
       new NewBlockView
         el: $ ".grid__block--new-block"
         model: channel


fix channel delete button

fix channel delete button

--- apps/channel/client.coffee
+++ apps/channel/client.coffee
@@ -27,11 +27,13 @@

   events:
     'click .delete-channel' : 'showConfirmation'
+    'click .delete-channel a' : 'showConfirmation'
     'click .delete-channel--confirmation__yes' : 'deleteChannel'
     'click .delete-channel--confirmation__no'  : 'hideConfirmation'
     'click .connect_button': 'loadConnectView'

   initialize: (options)->
+    console.log 'initialize', @$('.delete-channel')
     @channel = options.channel
     @blocks = options.blocks

@@ -67,6 +69,7 @@
     window.location.href = @channel.href()

   showConfirmation: ->
+    console.log 'showConfirmation'
     @$('.delete-channel--confirmation').addClass 'is-active'

   hideConfirmation: ->
@@ -99,6 +102,7 @@
       @setupExportView()
       @setupEditTitleView()
       @setUpDragView() unless $('body').hasClass 'is-mobile'
+      @delegateEvents()

     @setupEditDescriptionView()
     @maybeSetEmpty()


Mimics error response schema

Mimics error response schema

--- apps/statuses/index.coffee
+++ apps/statuses/index.coffee
@@ -9,9 +9,18 @@

 statuses.map ([code, status]) ->
   send = (_req, res) ->
+    response = switch code
+      when 400, 404, 500
+        code: code
+        message: status
+        description: "#{code}: #{status}"
+
+      else
+        status: status
+
     res
       .status code
-      .send status: status
+      .send response

   app
     .get "/statuses/#{code}", send


app-updates: Fix for update button.

app-updates: Fix for update button.

--- client/app/Applications/Apps.kdapplication/Views/appview.coffee
+++ client/app/Applications/Apps.kdapplication/Views/appview.coffee
@@ -170,9 +170,10 @@
         @openButton.show()

       appName = app.manifest.name
-      if appsController.isAppUpdateAvailable appName, manifests[appName]?.version
+      version = manifests?[appName]?.version # strange, but it's not working with { ... }
+
+      if version and appsController.isAppUpdateAvailable appName, version
         @updateButton.show()
-

     {icns, name, version, authorNick} = app.manifest
     thumb = if icns and (icns['256'] or icns['512'] or icns['128'] or icns['160'] or icns['64'])


Teams: Creation: Set company name as default value if any

--- client/landing/site.landing/coffee/teams/teamssignupform.coffee
+++ client/landing/site.landing/coffee/teams/teamssignupform.coffee
@@ -10,7 +10,7 @@

     team        = KD.utils.getTeamData()
     email       = team.invitation?.email
-
+    companyName = team.signup?.companyName

     @email = new LoginInputViewWithLoader
       inputOptions   :
@@ -28,6 +28,7 @@
       inputOptions    :
         name          : 'companyName'
         placeholder   : 'Name your team'
+        defaultValue  : companyName  if companyName
         attributes    : testpath : 'company-name'
         validate      :
           event       : 'blur'
@@ -38,6 +39,7 @@

     # make the placeholders go away
     @email.inputReceivedKeyup()        if email
+    @companyName.inputReceivedKeyup()  if companyName

     @button = new KDButtonView
       title       : 'Sign up'


added an annoucements

added an annoucements

--- source/javascripts/announcements.coffee
+++ source/javascripts/announcements.coffee
@@ -5,3 +5,5 @@
     '53': "On Monday, September 1st, מִנְחָה and עַרְבִית will take place at Bnai Sephardim, 3670 Stirling Road, at 7:15 PM.  Followed by Derashot from local Sephardic Rabbis."
   '5775':
     '22': "Mazal Tov to the Harari family on Marc's Bar Mitzva!"
+    '25': "Sunday, March 22nd: הַכְנָסַת סֵפֶר תּוֹרָה"
+    '26': "Sunday, March 22nd: הַכְנָסַת סֵפֶר תּוֹרָה"


fix visual glitch with sphere lines

fix visual glitch with sphere lines

--- public/js/Canvas.coffee
+++ public/js/Canvas.coffee
@@ -1,4 +1,5 @@
 class Canvas
+	controlFactor: 4 * (Math.sqrt(2) - 1) / 3
 	constructor: ->
 		@elt = document.createElement('canvas')
 		document.getElementsByTagName('body')[0].appendChild(@elt)
@@ -40,9 +41,9 @@
 		@ctx.moveTo(startX, startY)
 		midX = (startX + endX) / 2
 		midY = ((startY + endY) / 2) + height
-		controlHeight = height * 4 * (Math.sqrt(2) - 1) / 3
-		@ctx.bezierCurveTo(startX, startY + controlHeight, midX - controlHeight, midY, midX, midY)
-		@ctx.bezierCurveTo(midX + controlHeight, midY, endX, endY + controlHeight, endX, endY)
+		controlHeight = height * @controlFactor
+		@ctx.bezierCurveTo(startX, startY + controlHeight, midX - Math.abs(controlHeight), midY, midX, midY)
+		@ctx.bezierCurveTo(midX + Math.abs(controlHeight), midY, endX, endY + controlHeight, endX, endY)
 		@ctx.strokeStyle = color
 		@ctx.lineWidth = width
 		@ctx.stroke()
@@ -51,9 +52,9 @@
 		@ctx.moveTo(startX, startY)
 		midX = ((startX + endX) / 2) + height
 		midY = (startY + endY) / 2
-		controlHeight = height * 4 * (Math.sqrt(2) - 1) / 3
-		@ctx.bezierCurveTo(startX + controlHeight, startY, midX, midY - controlHeight, midX, midY)
-		@ctx.bezierCurveTo(midX, midY + controlHeight, endX  + controlHeight, endY, endX, endY)
+		controlHeight = height * @controlFactor
+		@ctx.bezierCurveTo(startX + controlHeight, startY, midX, midY - Math.abs(controlHeight), midX, midY)
+		@ctx.bezierCurveTo(midX, midY + Math.abs(controlHeight), endX  + controlHeight, endY, endX, endY)
 		@ctx.strokeStyle = color
 		@ctx.lineWidth = width
 		@ctx.stroke()


Rewrites some test descriptions for better readability

--- spec/unit/services/playlist.spec.coffee
+++ spec/unit/services/playlist.spec.coffee
@@ -27,31 +27,31 @@

   describe '#next', ->

-    it 'should move forward the list', ->
+    it 'moves forward the list', ->
       @playlist.next()
       expect(@playlist.position).toEqual 1

-    it 'should cycle the list if current is the last element', ->
+    it 'cycles the list if current is the last element', ->
       @playlist.position = @playlist.list.length - 1
       @playlist.next()
       expect(@playlist.position).toEqual 0

-    it 'should return the next element', ->
+    it 'returns the next element', ->
       item = @playlist.next()
       expect(@playlist.list.indexOf(item)).toEqual 1

   describe '#previous', ->

-    it 'should move backwards the list', ->
+    it 'moves backwards the list', ->
       @playlist.position = 1
       @playlist.previous()
       expect(@playlist.position).toEqual 0

-    it 'should cycle the list backwards if current is the first element', ->
+    it 'cycles the list backwards if current is the first element', ->
       @playlist.previous()
       expect(@playlist.position).toEqual (@playlist.list.length - 1)

-    it 'should return the previous element', ->
+    it 'returns the previous element', ->
       @playlist.position = 1
       item = @playlist.previous()
       expect(@playlist.list.indexOf(item)).toEqual 0
@@ -61,7 +61,7 @@
     beforeEach ->
       @item = 'new_video'

-    it 'should add an item to the list', ->
+    it 'adds an item to the list', ->
       oldLength = @playlist.list.length
       @playlist.add @item
       expect(@playlist.list.length).toEqual (oldLength + 1)
@@ -71,7 +71,7 @@
     beforeEach ->
       @item = @playlist.current()

-    it 'should remove an item from the array', ->
+    it 'removes an item from the array', ->
       @playlist.remove @item
       expect(@item).not.toEqual @playlist.current()


client/router: add disabled routes functionality

Signed-off-by: Umut Sirin <4055abbc0e40a1254e715b952ccd414607e0b365@gmail.com>

--- client/app/lib/kodingrouter.coffee
+++ client/app/lib/kodingrouter.coffee
@@ -5,6 +5,8 @@
 KodingAppsController = require './kodingappscontroller'
 HomeGetters          = require 'home/flux/getters'
 LocalStorage = require 'app/localstorage'
+isGroupDisabled = require './util/isGroupDisabled'
+isAdmin = require './util/isAdmin'

 module.exports = class KodingRouter extends kd.Router

@@ -53,6 +55,11 @@
       return KodingAppsController.loadInternalApp name, (err, res) =>
         return kd.warn err  if err
         kd.utils.defer => @handleRoute route, options
+
+    group = kd.singletons.groupsController.getCurrentGroup()
+
+    if isGroupDisabled(group) and not @isRouteAllowed route
+      return @handleRoute @getGroupDisabledRoute route

     return @handleRoute @getDefaultRoute()  if /<|>/.test route
     super route, options
@@ -99,6 +106,26 @@
       return '/Welcome'

+  isRouteAllowed: (route) ->
+
+    role = if isAdmin() then 'admin' else 'member'
+
+    allowedRoutes =
+      admin: [
+        '/Home/team-billing'
+        '/Disabled/Admin'
+      ]
+      member: [
+        '/Disabled/Member'
+        '/Disabled/Member/notify-success'
+      ]
+
+    return route in allowedRoutes[role]
+
+
+  getGroupDisabledRoute: (originalRoute) ->
+
+    if isAdmin() then '/Disabled/Admin' else '/Disabled/Member'

   setPageTitle: (title = 'Koding') -> kd.singletons.pageTitle.update title


speed 0

speed 0

--- src/test/test_config.coffee
+++ src/test/test_config.coffee
@@ -20,13 +20,13 @@

   test 'config children', (t) ->
     count = 0
-    world.map_children ->
-      count = count + 1
+    world.map_children -> count = count + 1
     t.ok count > 2, "count children"
     t.end()

   test 'config game', (t) ->
     t.ok game = world.find_path('.game'), "Got game"
+    t.equal game.get('speed'), 0, 'Speed 0'
     t.end()

   test 'config program', (t) ->


stub button test back in

stub button test back in

--- src/test/test_render.coffee
+++ src/test/test_render.coffee
@@ -80,6 +80,9 @@
     t.ok tags = render_mock(row_dict).body, 'render rows'
     t.end()

+  test "render buttons", (t) ->
+    t.end()
+
   test "render selection", (t) ->
     t.end()


add a quick comment

add a quick comment

--- test-integration/helpers/task.coffee
+++ test-integration/helpers/task.coffee
@@ -2,6 +2,8 @@
 _ = require 'underscore'

 {TestHelper} = require './test-element'
+
+# all convenience functions for helping with task tests will be seen here.

 class TaskHelper extends TestHelper
   constructor: (test, testElementLocator) ->


added test

added test

--- test/crystal/validate/exports.coffee
+++ test/crystal/validate/exports.coffee
@@ -44,7 +44,19 @@
         }
         (-> new crystal config)
           .should.not.throw "`exports.ConfigGenerator.description` is required."
-
+
+      it 'should not require a schema'
+        # object
+        config = {
+          exports: {
+            ConfigGenerator: {
+              type: 'generator'
+            }
+          }
+        }
+        (-> new crystal config)
+          .should.not.throw "`exports.ConfigGenerator.schema` is required."
+
       it 'should require a type', () ->
         # object
         config = {


Contextualizes tests

Contextualizes tests

--- test/mocha_configuration_test.coffee
+++ test/mocha_configuration_test.coffee
@@ -75,15 +75,19 @@

   describe 'to_args', ->

-    it 'returns an array of strings', ->
-      mocha_configuration = new MochaConfiguration
-        run_options: {}
-        default_mocha_options: Mycha.default_mocha_options
-        files: []
-      result = mocha_configuration.to_args()
-      expect(result.length).to.equal 5
-      expect(result).to.include '--compilers'
-      expect(result).to.include 'coffee:coffee-script'
-      expect(result).to.include '--reporter'
-      expect(result).to.include 'dot'
-      expect(result).to.include '--colors'
+    context 'normal behavior', ->
+
+      beforeEach ->
+        mocha_configuration = new MochaConfiguration
+          run_options: {}
+          default_mocha_options: Mycha.default_mocha_options
+          files: []
+        @result = mocha_configuration.to_args()
+
+      it 'returns an array of strings', ->
+        expect(@result.length).to.equal 5
+        expect(@result).to.include '--compilers'
+        expect(@result).to.include 'coffee:coffee-script'
+        expect(@result).to.include '--reporter'
+        expect(@result).to.include 'dot'
+        expect(@result).to.include '--colors'


fix pending spec

fix pending spec

--- test/model/local_storage.spec.coffee
+++ test/model/local_storage.spec.coffee
@@ -51,14 +51,12 @@
       expect(Serenade.Cache.retrieve(Test, 5)).to.not.exist

-  context.skip "with `as` option", ->
+  context "with `as` option", ->
     it 'persists to cache with ', ->
       class Test extends Serenade.Model
         @collection "names", serialize: true
-        @localStorage on: "save", as: (id) -> "some-test-#{id}"
+        @localStorage as: (id) -> "some-test-#{id}"

       test = new Test(id: 5, names: [{ first: "Jonas" }])

-      console.log Test.name
       expect(Serenade.Cache.retrieve(Test, 5).names[0].first).to.eql("Jonas")
-      expect(Serenade.Cache.retrieve(Test, 5).names[0].first).to.eql("Peter")


Add a "Continue" button

Add a "Continue" button

--- app/lib/tutorial.cjsx
+++ app/lib/tutorial.cjsx
@@ -70,12 +70,15 @@
         step._key ?= Math.random()
         <MediaCard key={step._key} className="tutorial-step" src={step.media}>
           <Markdown>{step.content}</Markdown>
-          {if i is @props.steps.length - 1
-

-

-

-                <button type="submit" className="major-button">Let’s go!</button>
-

-
}
+

+

+            {if i is @props.steps.length - 1
+              <button type="submit" className="major-button">Let’s go!</button>
+            else
+              <button type="button" className="standard-button" onClick={@handleNextClick}>Continue</button>}
+

         </MediaCard>}
     </StepThrough>
+
+  handleNextClick: ->
+    @refs.stepThrough.goNext()


Add in Studio confirmation box before editing OE problems

--- common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
+++ common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee
@@ -63,6 +63,8 @@
     else
       @createXMLEditor()

+    @confirmTaskRubricModification()
+
   ###
   Creates the XML Editor and sets it as the current editor. If text is passed in,
   it will replace the text present in the HTML template.
@@ -93,6 +95,8 @@
       # Hide markdown-specific toolbar buttons
       $(@element.find('.editor-bar')).hide()

+  confirmTaskRubricModification: ->
+    return confirm("Before you edit, please note that if you alter the tasks block or the rubric block of this question after students have submitted responses, it may result in their responses and grades being deleted!  Use caution when altering problems that have already been released to students.")
   ###
   Have the user confirm the one-way conversion to XML.
   Returns true if the user clicked OK, else false.


fix localstorage

fix localstorage

--- src-public/common/states/controller.coffee
+++ src-public/common/states/controller.coffee
@@ -15,7 +15,7 @@
     $mdSidenav(menuId).close()
     console.log 'Fermer le menu'

-  $scope.member = Avatar.getMember localStorageService('trello_email')
+  $scope.member = Avatar.getMember localStorageService.get('trello_email')

   $scope.project = ->
     $state.go 'tab.project'


Switch to /3 URLs; remove '.json' from all URLs.

--- src/ext/modules/proxy.coffee
+++ src/ext/modules/proxy.coffee
@@ -68,7 +68,7 @@
     try
       [ root, version, name ] = path.split '/'
       [ base, other ] = name.split '?'
-      if base isnt 'Typeahead.json' and base isnt 'Jobs.json'
+      if base isnt 'Typeahead' and base isnt 'Jobs'
         _.trackEvent 'api', base, version
     catch e

@@ -119,7 +119,7 @@
         go null, transform result

   requestExec = (ast, go) ->
-    doPost '/1/Rapids.json', { ast: ast }, (error, result) ->
+    doPost '/3/Rapids', { ast: ast }, (error, result) ->
       if error
         go error
       else
@@ -131,72 +131,72 @@

   requestInspect = (key, go) ->
     opts = key: encodeURIComponent key
-    requestWithOpts '/1/Inspect.json', opts, go
+    requestWithOpts '/3/Inspect', opts, go

   requestCreateFrame = (opts, go) ->
-    doPost '/2/CreateFrame.json', opts, go
+    doPost '/3/CreateFrame', opts, go

   requestSplitFrame = (frameKey, splitRatios, splitKeys, go) ->
     opts =
       dataset: frameKey
       ratios: encodeArrayForPost splitRatios
       dest_keys: encodeArrayForPost splitKeys
-    doPost '/2/SplitFrame.json', opts, go
+    doPost '/3/SplitFrame', opts, go

   requestFrames = (go) ->
-    doGet '/3/Frames.json', (error, result) ->
+    doGet '/3/Frames', (error, result) ->
       if error
         go error
       else
         go null, result.frames

   requestFrame = (key, go) ->
-    doGet "/3/Frames.json/#{encodeURIComponent key}", (error, result) ->
+    doGet "/3/Frames/#{encodeURIComponent key}", (error, result) ->
       if error
         go error
       else
         go null, head result.frames

   requestFrameSummary = (key, go) ->
-    doGet "/3/Frames.json/#{encodeURIComponent key}/summary", (error, result) ->
+    doGet "/3/Frames/#{encodeURIComponent key}/summary", (error, result) ->
       if error
         go error
       else
         go null, head result.frames

   requestDeleteFrame = (key, go) ->
-    doDelete "/3/Frames.json/#{encodeURIComponent key}", go
+    doDelete "/3/Frames/#{encodeURIComponent key}", go

   requestRDDs = (go) ->
-    doGet '/3/RDDs.json', (error, result) ->
+    doGet '/3/RDDs', (error, result) ->
       if error
         go error
       else
         go null, result.rdds

   requestColumnSummary = (key, column, go) ->
-    doGet "/3/Frames.json/#{encodeURIComponent key}/columns/#{encodeURIComponent column}/summary", (error, result) ->
+    doGet "/3/Frames/#{encodeURIComponent key}/columns/#{encodeURIComponent column}/summary", (error, result) ->
       if error
         go error
       else
         go null, head result.frames

   requestJobs = (go) ->
-    doGet '/2/Jobs.json', (error, result) ->
+    doGet '/3/Jobs', (error, result) ->
       if error
         go new Flow.Error 'Error fetching jobs', error
       else
         go null, result.jobs

   requestJob = (key, go) ->
-    doGet "/2/Jobs.json/#{encodeURIComponent key}", (error, result) ->
+    doGet "/3/Jobs/#{encodeURIComponent key}", (error, result) ->
       if error
         go new Flow.Error "Error fetching job '#{key}'", error
       else
         go null, head result.jobs

   requestCancelJob = (key, go) ->
-    doPost "/2/Jobs.json/#{encodeURIComponent key}/cancel", {}, (error, result) ->
+    doPost "/3/Jobs/#{encodeURIComponent key}/cancel", {}, (error, result) ->
       if error
         go new Flow.Error "Error canceling job '#{key}'", error
       else
@@ -207,7 +207,7 @@
     opts =
       src: encodeURIComponent path
       limit: limit
-    requestWithOpts '/2/Typeahead.json/files', opts, go
+    requestWithOpts '/3/Typeahead/files', opts, go

   requestImportFiles = (paths, go) ->
     tasks = map paths, (path) ->
@@ -217,12 +217,12 @@

   requestImportFile = (path, go) ->
     opts = path: encodeURIComponent path
-    requestWithOpts '/2/ImportFiles.json', opts, go
+    requestWithOpts '/3/ImportFiles', opts, go

   requestParseSetup = (sourceKeys, go) ->
     opts =
       source_keys: encodeArrayForPost sourceKeys
-    doPost '/2/ParseSetup.json', opts, go
+    doPost '/3/ParseSetup', opts, go

   requestParseSetupPreview = (sourceKeys, parseType, separator, useSingleQuotes, checkHeader, columnTypes, go) ->
     opts =
@@ -232,7 +232,7 @@
       single_quotes: useSingleQuotes
       check_header: checkHeader
       column_types: encodeArrayForPost columnTypes
-    doPost '/2/ParseSetup.json', opts, go
+    doPost '/3/ParseSetup', opts, go

   requestParseFiles = (sourceKeys, destinationKey, parseType, separator, columnCount, useSingleQuotes, columnNames, columnTypes, deleteOnDone, checkHeader, chunkSize, go) ->
     opts =
@@ -247,7 +247,7 @@
       check_header: checkHeader
       delete_on_done: deleteOnDone
       chunk_size: chunkSize
-    doPost '/2/Parse.json', opts, go
+    doPost '/3/Parse', opts, go

   patchUpModels = (models) ->
     for model in models
@@ -261,34 +261,34 @@
     models

   requestModels = (go, opts) ->
-    requestWithOpts '/3/Models.json', opts, (error, result) ->
+    requestWithOpts '/3/Models', opts, (error, result) ->
       if error
         go error, result
       else
         go error, patchUpModels result.models

   requestModel = (key, go) ->
-    doGet "/3/Models.json/#{encodeURIComponent key}", (error, result) ->
+    doGet "/3/Models/#{encodeURIComponent key}", (error, result) ->
       if error
         go error, result
       else
         go error, head patchUpModels result.models

   requestDeleteModel = (key, go) ->
-    doDelete "/3/Models.json/#{encodeURIComponent key}", go
+    doDelete "/3/Models/#{encodeURIComponent key}", go

   requestModelBuilders = (go) ->
-    doGet "/3/ModelBuilders.json", go
+    doGet "/3/ModelBuilders", go

   requestModelBuilder = (algo, go) ->
-    doGet "/3/ModelBuilders.json/#{algo}", go
+    doGet "/3/ModelBuilders/#{algo}", go

   requestModelInputValidation = (algo, parameters, go) ->
-    doPost "/3/ModelBuilders.json/#{algo}/parameters", (encodeObjectForPost parameters), go
+    doPost "/3/ModelBuilders/#{algo}/parameters", (encodeObjectForPost parameters), go

   requestModelBuild = (algo, parameters, go) ->
     _.trackEvent 'model', algo
-    doPost "/3/ModelBuilders.json/#{algo}", (encodeObjectForPost parameters), go
+    doPost "/3/ModelBuilders/#{algo}", (encodeObjectForPost parameters), go

   requestPredict = (destinationKey, modelKey, frameKey, go) ->
     opts = if destinationKey
@@ -296,14 +296,14 @@
     else
       {}

-    doPost "/3/Predictions.json/models/#{encodeURIComponent modelKey}/frames/#{encodeURIComponent frameKey}", opts, (error, result) ->
+    doPost "/3/Predictions/models/#{encodeURIComponent modelKey}/frames/#{encodeURIComponent frameKey}", opts, (error, result) ->
       if error
         go error
       else
         go null, head result.model_metrics

   requestPrediction = (modelKey, frameKey, go) ->
-    doGet "/3/ModelMetrics.json/models/#{encodeURIComponent modelKey}/frames/#{encodeURIComponent frameKey}", (error, result) ->
+    doGet "/3/ModelMetrics/models/#{encodeURIComponent modelKey}/frames/#{encodeURIComponent frameKey}", (error, result) ->
       if error
         go error
       else
@@ -327,13 +327,13 @@
         _go null, (prediction for prediction in predictions when prediction)

     if modelKey and frameKey
-      doGet "/3/ModelMetrics.json/models/#{encodeURIComponent modelKey}/frames/#{encodeURIComponent frameKey}", go
+      doGet "/3/ModelMetrics/models/#{encodeURIComponent modelKey}/frames/#{encodeURIComponent frameKey}", go
     else if modelKey
-      doGet "/3/ModelMetrics.json/models/#{encodeURIComponent modelKey}", go
+      doGet "/3/ModelMetrics/models/#{encodeURIComponent modelKey}", go
     else if frameKey
-      doGet "/3/ModelMetrics.json/frames/#{encodeURIComponent frameKey}", go
+      doGet "/3/ModelMetrics/frames/#{encodeURIComponent frameKey}", go
     else
-      doGet "/3/ModelMetrics.json", go
+      doGet "/3/ModelMetrics", go

 #
 #  requestObjects = (type, go) ->
@@ -350,65 +350,65 @@
 #

   requestObjects = (type, go) ->
-    doGet "/3/NodePersistentStorage.json/#{encodeURIComponent type}", unwrap go, (result) -> result.entries
+    doGet "/3/NodePersistentStorage/#{encodeURIComponent type}", unwrap go, (result) -> result.entries

   requestObject = (type, name, go) ->
-    doGet "/3/NodePersistentStorage.json/#{encodeURIComponent type}/#{encodeURIComponent name}", unwrap go, (result) -> JSON.parse result.value
+    doGet "/3/NodePersistentStorage/#{encodeURIComponent type}/#{encodeURIComponent name}", unwrap go, (result) -> JSON.parse result.value

   requestDeleteObject = (type, name, go) ->
-    doDelete "/3/NodePersistentStorage.json/#{encodeURIComponent type}/#{encodeURIComponent name}", go
+    doDelete "/3/NodePersistentStorage/#{encodeURIComponent type}/#{encodeURIComponent name}", go

   requestPutObject = (type, name, value, go) ->
-    uri = "/3/NodePersistentStorage.json/#{encodeURIComponent type}"
+    uri = "/3/NodePersistentStorage/#{encodeURIComponent type}"
     uri += "/#{encodeURIComponent name}" if name
     doPost uri, { value: JSON.stringify value, null, 2 }, unwrap go, (result) -> result.name

   requestUploadObject = (type, name, formData, go) ->
-    uri = "/3/NodePersistentStorage.json/#{encodeURIComponent type}"
+    uri = "/3/NodePersistentStorage/#{encodeURIComponent type}"
     uri += "/#{encodeURIComponent name}" if name
     doUpload uri, formData, unwrap go, (result) -> result.name

   requestUploadFile = (key, formData, go) ->
-    doUpload "/3/PostFile.json?destination_key=#{encodeURIComponent key}", formData, go
+    doUpload "/3/PostFile?destination_key=#{encodeURIComponent key}", formData, go

   requestCloud = (go) ->
-    doGet '/1/Cloud.json', go
+    doGet '/3/Cloud', go

   requestTimeline = (go) ->
-    doGet '/2/Timeline.json', go
+    doGet '/3/Timeline', go

   requestProfile = (depth, go) ->
-    doGet "/2/Profiler.json?depth=#{depth}", go
+    doGet "/3/Profiler?depth=#{depth}", go

   requestStackTrace = (go) ->
-    doGet '/2/JStack.json', go
+    doGet '/3/JStack', go

   requestRemoveAll = (go) ->
     doDelete '/3/DKV', go

   requestLogFile = (nodeIndex, fileType, go) ->
-    doGet "/3/Logs.json/nodes/#{nodeIndex}/files/#{fileType}", go
+    doGet "/3/Logs/nodes/#{nodeIndex}/files/#{fileType}", go

   requestNetworkTest = (go) ->
-    doGet '/2/NetworkTest.json', go
+    doGet '/3/NetworkTest', go

   requestAbout = (go) ->
-    doGet '/3/About.json', go
+    doGet '/3/About', go

   requestShutdown = (go) ->
-    doPost "/2/Shutdown", {}, go
+    doPost "/3/Shutdown", {}, go

   requestEndpoints = (go) ->
-    doGet '/1/Metadata/endpoints.json', go
+    doGet '/3/Metadata/endpoints', go

   requestEndpoint = (index, go) ->
-    doGet "/1/Metadata/endpoints.json/#{index}", go
+    doGet "/3/Metadata/endpoints/#{index}", go

   requestSchemas = (go) ->
-    doGet '/1/Metadata/schemas.json', go
+    doGet '/3/Metadata/schemas', go

   requestSchema = (name, go) ->
-    doGet "/1/Metadata/schemas.json/#{encodeURIComponent name}", go
+    doGet "/3/Metadata/schemas/#{encodeURIComponent name}", go

   getLines = (data) ->
     filter (split data, '\n'), (line) -> if line.trim() then yes else no


structured logging for http

structured logging for http

--- web.coffee
+++ web.coffee
@@ -7,8 +7,19 @@
 storage  = require("storage").init()
 util     = require("util")

+express.logger.format "method", (req, res) ->
+  req.method.toLowerCase()
+
+express.logger.format "url", (req, res) ->
+  req.url.replace('"', '&quot')
+
+express.logger.format "user-agent", (req, res) ->
+  (req.headers["user-agent"] || "").replace('"', '')
+
 app = express.createServer(
-  express.logger(),
+  express.logger
+    buffer: false
+    format: "http method=\":method\" url=\":url\" status=\":status\" elapsed=\":response-time\" from=\":remote-addr\" agent=\":user-agent\""
   express.bodyParser())

 app.get "/", (req, res) ->


Fix sizeToContainer - actually set width

--- coffee/core/util.coffee
+++ coffee/core/util.coffee
@@ -14,6 +14,7 @@
     $canvas = $(canvas)
     $container = $canvas.parent()
     resize = =>
+      canvas.style.width = "#{$container.width()}px"
       canvas.style.height = "#{$container.height()}px"
       canvas.setAttribute('width', $canvas.width())
       canvas.setAttribute('height', $canvas.height())


idea for including different functionalities

--- app/assets/javascripts/components/button/index.cjsx
+++ app/assets/javascripts/components/button/index.cjsx
@@ -1,4 +1,6 @@
 # @cjsx React.DOM
+
+ResizeButton   = require './resize-button'

 React = require 'react'

@@ -25,7 +27,10 @@

   render: ->
-
+    if @props.type == "delete"
+
+    else @props.type == "resize"
+      <ResizeButton></ResizeButton>


Clean up chart list load method

Clean up chart list load method

--- app/assets/javascripts/lib/models/chart_list.coffee
+++ app/assets/javascripts/lib/models/chart_list.coffee
@@ -38,27 +38,33 @@
   #
   # Returns the newly created chart object or false if something went wrong
   load: (chart_id, holder_id = null, options = {}) =>
-    @chart_requests = []
-
-    # if the chart is already there...
-    current = @chart_in_holder holder_id
-    if current && current.get('chart_id') == chart_id
-      return false unless options.force
+    if @should_load_chart(chart_id, holder_id, options)
+      App.debug """Loading chart: ##{chart_id} in #{holder_id}
+                 #{window.location.origin}/admin/output_elements/#{chart_id}"""
+
+      @chart_requests.push(@request_output_element(chart_id, holder_id, options))
+
+      @last()
+
+  should_load_chart: (chart_id, holder_id = null, options = {}) =>
+    should_load = true
+    current     = @chart_in_holder(holder_id)
+
+    if current && current.get('chart_id') == chart_id && !options.force
+      should_load = false

     # if we want to replace a locked chart...
     locked_charts = App.settings.get 'locked_charts'
+
     if locked_charts[holder_id]
       if options.force
-        @chart_in_holder(holder_id).toggle_lock(false)
-      else
-        return false unless options.ignore_lock
-
-    App.debug """Loading chart: ##{chart_id} in #{holder_id}
-               #{window.location.origin}/admin/output_elements/#{chart_id}"""
-
-    @chart_requests.push(@request_output_element(chart_id, holder_id, options))
-
-    @last()
+        # TODO: Remove the toggle_lock method because this method should return
+        # a boolean but now also excecutes code in between which is confusing.
+        current.toggle_lock(false)
+      else if !options.ignore_lock
+        should_load = false
+
+    should_load

   request_output_element: (chart_id, holder_id = null, options = {}) =>
     $.ajax
@@ -92,18 +98,19 @@
             @delete_container(holder_id) unless @chart_holders[holder_id]
           return false

-        # Remember what we were showing in that position
         if old_chart = @chart_in_holder holder_id
           old_chart.delete()
           @remove old_chart

+        # Remember what we were showing in that position
         @chart_holders[holder_id] = new_chart

-        ## Pass the gqueries to the chart
+        # Pass the gqueries to the chart
         for s in data.series
           s.owner = holder_id
           new_chart.series.add(s)

+        # Render the chart only if it is not new chart
         if options.ignore_lock == undefined
           @add new_chart
           App.call_api() unless options.wait
@@ -125,6 +132,8 @@
   #
   load_charts: =>
     # The accordion takes care of setting @default_chart_id
+    @chart_requests = []
+
     settings = App.settings.get 'locked_charts'

     # safe copy of the settings hash
@@ -317,4 +326,3 @@
         beforeClose: ->
           # don't leave stale charts around!
           charts.prune()
-


refactor phase removal per Mike's suggestions

--- app/assets/javascripts/manuscript_manager.js.coffee
+++ app/assets/javascripts/manuscript_manager.js.coffee
@@ -74,7 +74,7 @@
           for flow, position in @state.flows
             Tahi.manuscriptManager.Column {
               addFunction: @addColumn
-              removeFunction: @removeColumn
+              onRemove: @removeColumn
               updateName: @updateColumnName
               position: flow.position
               name: flow.name
@@ -199,16 +199,15 @@
         success: (data) => @insertPhase(data.phase)

     removeColumn: (phaseId) ->
-      =>
-        $.ajax
-          url: "/phases"
-          method: 'DELETE'
-          dataType: 'json'
-          data:
-            id: phaseId
-          success: =>
-            newFlows = _(@state.flows.slice(0)).reject (flow)-> flow.id == phaseId
-            @setState flows: newFlows
+      $.ajax
+        url: "/phases"
+        method: 'DELETE'
+        dataType: 'json'
+        data:
+          id: phaseId
+        success: =>
+          newFlows = _(@state.flows.slice(0)).reject (flow)-> flow.id == phaseId
+          @setState flows: newFlows

   Column: React.createClass
     displayName: "Column"
@@ -230,12 +229,15 @@

           (span {className: "glyphicon glyphicon-pencil edit-icon column-icon"}, "")
           if !@props.tasks.length
-            (span {className: "glyphicon glyphicon-remove remove-icon column-icon", onClick: @props.removeFunction(@props.phase_id)}, "")
+            (span {className: "glyphicon glyphicon-remove remove-icon column-icon", onClick: @remove}, "")
         )
         (div {className: 'column-content'},
           (ul {className: 'cards'},
             @manuscriptCards()
       )))
+
+    remove: ->
+      @props.onRemove @props.phase_id

     cancelEdit: ->
       $(@getDOMNode()).find("h2").text(@props.name)


replace old default tags

replace old default tags

--- app/assets/javascripts/views/current_tags.js.coffee
+++ app/assets/javascripts/views/current_tags.js.coffee
@@ -1,6 +1,7 @@
 Voice.CurrentTagsView = Ember.View.extend({

   allUsersBinding: 'Voice.allUsers'
+  defaultTags:      Ember.keys(env.defaultTags)

   actions:
@@ -17,9 +18,7 @@

   extractTagsFrom: (users) ->
     users.mapProperty('email').concat(
-      users.mapProperty('name').map((n) -> "##{n}").concat(
-        Ember.keys(env.defaultTags)
-      )
+      users.mapProperty('name').map((n) -> "##{n}").concat @defaultTags
     )

@@ -41,13 +40,18 @@
       return false

+  removeDefaultTags: (tag) ->
+    tags = @get('controller.content.tags')
+    if @defaultTags.indexOf(tag) > -1
+      @defaultTags.forEach (dt) -> tags.removeObject(dt)
+
+
   removeFromTags: (tag) ->
     entry = @get('controller.content')
     tags  = entry.get('tags')

-    if tags.indexOf(tag) > -1
-      tags.removeObject(tag)
-      entry.save()
+    tags.removeObject(tag)
+    entry.save()

   addToTags: (tag) ->
@@ -55,6 +59,7 @@
     tags  = entry.get('tags')

     unless tags.indexOf(tag) > -1
+      @removeDefaultTags(tag)
       tags.addObject(tag)
       entry.save()


just making sure API keys work

just making sure API keys work

--- src/scripts/opendns-umbrella.coffee
+++ src/scripts/opendns-umbrella.coffee
@@ -13,37 +13,34 @@
 # Author:
 #   Scott J Roberts - @sroberts

-VIRUSTOTAL_API_KEY = process.env.VIRUSTOTAL_API_KEY
-vt_url = "https://www.virustotal.com"
-
-vt_file_report_url = vt_url + "/vtapi/v2/file/report"
-vt_url_report_url = vt_url + "/vtapi/v2/url/report"
-vt_ip_report_url = vt_url + "/vtapi/v2/ip-address/report"
+VIRUSTOTAL_API_KEY = process.env.OPENDNS_KEY
+opendns_base = "https://investigate.api.opendns.com/"

 module.exports = (robot) ->
-  robot.respond /virustotal hash (.*)/i, (msg) ->
+  robot.respond /opendns (.*)/i, (msg) ->

-    if VIRUSTOTAL_API_KEY?
-      hash = msg.match[1].toLowerCase()
-      data = "apikey=#{encodeURIComponent VIRUSTOTAL_API_KEY}&resource=#{encodeURIComponent hash}"
-
-      robot.http(vt_file_report_url)
-        .post(data) (err, res, body) ->
-          if res.statusCode is 200
-            vt_json = JSON.parse(body)
-
-            if vt_json.response_code == 1
-              summary = """VirusTotal Result: #{vt_json.resource}
-              - Scanned at: #{vt_json.scan_date}
-              - Results:    #{vt_json.positives}/#{vt_json.total}
-              - Link:       #{vt_json.permalink}
-              """
-
-              msg.send summary
-
-            else
-              msg.send "VirusTotal URL Analysis: #{vt_json.verbose_msg}"
-          else
-            msg.send "Error: Couldn't access #{vt_url}."
+    if OPENDNSKEY_KEY?
+      msg.send "#{OPENDNS_KEY}"
+    #   hash = msg.match[1].toLowerCase()
+    #   data = "apikey=#{encodeURIComponent VIRUSTOTAL_API_KEY}&resource=#{encodeURIComponent hash}"
+    #
+    #   robot.http(vt_file_report_url)
+    #     .post(data) (err, res, body) ->
+    #       if res.statusCode is 200
+    #         vt_json = JSON.parse(body)
+    #
+    #         if vt_json.response_code == 1
+    #           summary = """VirusTotal Result: #{vt_json.resource}
+    #           - Scanned at: #{vt_json.scan_date}
+    #           - Results:    #{vt_json.positives}/#{vt_json.total}
+    #           - Link:       #{vt_json.permalink}
+    #           """
+    #
+    #           msg.send summary
+    #
+    #         else
+    #           msg.send "VirusTotal URL Analysis: #{vt_json.verbose_msg}"
+    #       else
+    #         msg.send "Error: Couldn't access #{vt_url}."
     else
-      msg.send "VirusTotal API key not configured. Get one at https://www.virustotal.com/en/user/ in the API tab"
+      msg.send "OpenDNS API key not configured."


webapp: safeguard to only show banner after its trigger time

--- src/smc-webapp/redux_account.coffee
+++ src/smc-webapp/redux_account.coffee
@@ -266,7 +266,8 @@
         if not sgi2?           # not set means there is no timestamp → show banner
             return true
         sgi2_dt = new Date(sgi2)
-        return sgi2_dt < new Date('2017-08-26T13:00:00.000Z')
+        start_dt = new Date('2017-08-26T13:00:00.000Z')
+        return start_dt < webapp_client.server_time() and sgi2_dt < start_dt

 # Register account store
 # Use the database defaults for all account info until this gets set after they login


remove console.log from tests

remove console.log from tests

--- test/acceptance/coffee/SettingADocumentTests.coffee
+++ test/acceptance/coffee/SettingADocumentTests.coffee
@@ -127,7 +127,6 @@
 			@newLines = []
 			while JSON.stringify(@newLines).length < Settings.max_doc_length + 64 * 1024
 				@newLines.push("(a long line of text)".repeat(10000))
-			console.log("newlines size",JSON.stringify(@newLines).length)
 			DocUpdaterClient.setDocLines @project_id, @doc_id, @newLines, @source, @user_id, false, (error, res, body) =>
 				@statusCode = res.statusCode
 				setTimeout done, 200
@@ -159,7 +158,6 @@
 			while JSON.stringify(@newLines).length < 2 * 1024 * 1024 # limit in HTTPController
 				@newLines.push("(a long line of text)".repeat(10000))
 			@newLines.pop() # remove the line which took it over the limit
-			console.log("newlines size",JSON.stringify(@newLines).length)
 			DocUpdaterClient.setDocLines @project_id, @doc_id, @newLines, @source, @user_id, false, (error, res, body) =>
 				@statusCode = res.statusCode
 				setTimeout done, 200


Removed old calls to removed method

Removed old calls to removed method

--- app/assets/javascript/fancy_login/login_register_fancybox.coffee
+++ app/assets/javascript/fancy_login/login_register_fancybox.coffee
@@ -114,7 +114,6 @@

         if $(@element).hasClass('not-registered')
           $('#loginbox-header h5').text("Regístrate en #{$('#loginbox-header h5').data('appName')}")
-          self.getRegistrationsCount();
           $("#loginbox-sign-in").css("display","none")
           $("#loginbox-sign-up").css("display","block")
         else
@@ -129,7 +128,6 @@
       if target is "#signup"
         $("#loginbox-sign-in").fadeOut =>
           $('#loginbox-header h5').text("Regístrate en #{$('#loginbox-header h5').data('appName')}")
-          @getRegistrationsCount();
           $("#loginbox-sign-up").fadeIn()
       else if target is "#signin"
         $("#loginbox-sign-up").fadeOut ->


Update view.coffee

Update view.coffee

--- app/assets/javascripts/rademade_admin/app/form-popup/view.coffee
+++ app/assets/javascripts/rademade_admin/app/form-popup/view.coffee
@@ -26,8 +26,7 @@
     @$el.remove()

   renderFromUrl : (url) ->
-    $.get url, (html) =>
-      @$el.html html
+    @$el.load url, ->
       @_updatePosition()
       @delegateEvents()
       @_init()
@@ -61,4 +60,4 @@
     popupView = new FormPopup.View
       model : model
     popupView.render()
-    popupView+    popupView


Add option to disable banner in console

Add option to disable banner in console

--- lib/assets/javascripts/unpoly/log.coffee
+++ lib/assets/javascripts/unpoly/log.coffee
@@ -28,10 +28,13 @@
     Debugging information includes which elements are being [compiled](/up.syntax)
     and which [events](/up.event) are being emitted.
     Note that errors will always be printed, regardless of this setting.
-  @internal
+  @param {boolean} [options.banner=true]
+    Whether to show the unpoly banner in the console.
+  @stable
   ###
   config = new up.Config ->
     enabled: sessionStore.get('enabled')
+    banner: true

   reset = ->
     config.reset()
@@ -86,6 +89,8 @@
       console[stream](message, args...)

   printBanner = ->
+    return unless config.banner
+
     # The ASCII art looks broken in code since we need to escape backslashes
     logo = " __ _____  ___  ___  / /_ __\n" +
            "/ // / _ \\/ _ \\/ _ \\/ / // /  #{up.version}\n" +
@@ -111,6 +116,7 @@

   up.on 'up:framework:boot', printBanner
+
   up.on 'up:framework:reset', reset

   setEnabled = (value) ->


allow USSD flows to start someone else in a flow

--- static/coffee/flows/services.coffee
+++ static/coffee/flows/services.coffee
@@ -432,7 +432,7 @@
         { type:'lang', name:'Set Language', verbose_name:'Set language for contact', icon: 'icon-language', filter:ALL }
         { type:'channel', name:'Set Channel', verbose_name:'Set preferred channel', icon: 'icon-phone', filter:[TEXT, VOICE] }
         { type:'flow', name:'Start Another Flow', verbose_name:'Start another flow', icon: 'icon-tree', flows:true, filter:[TEXT,VOICE] }
-        { type:'trigger-flow',   name:'Start Someone in a Flow', verbose_name:'Start someone else in a flow', icon: 'icon-tree', flows:true, filter:[TEXT,VOICE] }
+        { type:'trigger-flow',   name:'Start Someone in a Flow', verbose_name:'Start someone else in a flow', icon: 'icon-tree', flows:true, filter:[TEXT,VOICE,USSD] }
       ]

       @rulesets = [


Add transaction for save

Add transaction for save

--- src/managers/employer_size_manager.coffee
+++ src/managers/employer_size_manager.coffee
@@ -34,7 +34,7 @@
             transaction.commit()
             cb null, employer

-          employer.save().then onSuccess, rollbackAndCallback
+          employer.save({ transaction }).then onSuccess, rollbackAndCallback

     models.sequelize.transaction().then onSuccessOfStartTransaction, onFailure


Fix reentrancy issue in makeSelectorObservable. Should call observeState after connection to the store, so observers can see knock-on effects from initial changes or store rehydration.

--- src/makeSelectorObservable.coffee
+++ src/makeSelectorObservable.coffee
@@ -47,10 +47,11 @@
 			lastSeenValue = undefined
 			observeState = ->
 				val = selector(componentInstance.store.getState())
-				if val isnt lastSeenValue then lastSeenValue = val; selector.next(val)
+				if val isnt lastSeenValue
+					lastSeenValue = val; selector.next(val)

+			@__unsubscriber = componentInstance.store.subscribe(observeState)
 			observeState()
-			@__unsubscriber = componentInstance.store.subscribe(observeState)
 		else
 			@__unsubscriber?(); delete @__unsubscriber
 		undefined


Addeed graph things

Addeed graph things

--- src/render/ui-control.coffee
+++ src/render/ui-control.coffee
@@ -88,8 +88,14 @@
             treelist = []
             treelist.push treeView.tree
             $('#tree').treeview({data: treelist})
+            chromeTabs.addNewTab($shell_tabs, {title:"OneDrive Activity"})
+            fs.readFile(BaseDir + '\\update_graphIndex.html',{encoding:'utf-8'},(error, data) ->
+                      console.log data
+                      if error
+                          $('div.viewer').html(error)
+                      else

-
+                          $('div.viewer').html(data))

 initDisplay()
 tabListener()


microflo: Respect serial port specified by address

--- src/runtimes/microflo.coffee
+++ src/runtimes/microflo.coffee
@@ -42,9 +42,11 @@
     @on 'connected', @updatecontainer

     # Setup runtime
+    # TODO: remove hardcoding of baudrate and debugLevel
     baudRate = 9600
-    serialPort = '/dev/ttyUSB1'
     debugLevel = 'Error'
+    address = @getAddress()
+    serialPort = address.replace 'serial://', ''
     @setupRuntime baudRate, serialPort, debugLevel

     # HACK: sends initial message, which hooks up receiving as well


Add some asserts to the tests of historical directive

--- test/unit/directives/history.coffee
+++ test/unit/directives/history.coffee
@@ -6,11 +6,14 @@
     $rootScope = null
     $compile = null

+    _jqo = angular.element
+
     template = """
-

+

-
+    """
+    script = """


Fix spelling of 'multiple'

Fix spelling of 'multiple'

--- test/unit/model/user.coffee
+++ test/unit/model/user.coffee
@@ -175,7 +175,7 @@
         user.displayName.should.equal 'Ickle Test'
         done()

-    it 'I can find mulitple users by their shared email address', (done) ->
+    it 'I can find multiple users by their shared email address', (done) ->
       User.findByEmail 'ickletest@example.org', (err, users) ->
         should.exist users
         users.should.have.a.length 2


qemux86-64.coffee: Set yocto version to warrior

Signed-off-by: Florin Sarbu <3a837a37a2ac9072a088641ec330e0e5d5b86f4e@balena.io>

--- qemux86-64.coffee
+++ qemux86-64.coffee
@@ -38,7 +38,7 @@
 		machine: 'qemux86-64'
 		image: 'resin-image'
 		fstype: 'resinos-img'
-		version: 'yocto-sumo'
+		version: 'yocto-warrior'
 		deployArtifact: 'resin-image-qemux86-64.resinos-img'
 		compressed: true


- fix issue when listening to update events on a single resource

--- modules/components/delivery_service.cjsx
+++ modules/components/delivery_service.cjsx
@@ -134,6 +134,7 @@
     RSVP.all(requests)
       .then(@setStateFromStore)
       .then(@startPollingBoundResources)
+      .then(@startListeningToBoundResources)
       .then(@resourcesDidLoad)

@@ -154,6 +155,37 @@
       newState["#{resourceKey}#{Utils.capitalize @metaKey}"] = resources[@metaKey]

     @setState newState
+
+
+  startPollingBoundResources: =>
+    for sub in @boundResources
+      do (sub) =>
+        if sub.eventName in ['fetch', 'reset']
+          @listenTo sub.store, sub.eventName, sub.callback ? @setStateFromStore
+          sub.store.startPolling sub.options
+
+
+  startListeningToBoundResources: =>
+    for sub in @boundResources
+      do (sub) =>
+        # ensure local creates/updates are synced to the state
+        if sub.eventName in ['reset', 'resetonce']
+          @listenTo sub.store, 'create', (resource, resources) =>
+            (sub.callback ? @setStateFromStore) resources
+          @listenTo sub.store, 'destroy', (resources) =>
+            (sub.callback ? @setStateFromStore) resources
+          @listenTo sub.store, 'update', (resource, resources) =>
+            (sub.callback ? @setStateFromStore) resources
+
+        # when fetching a single resource, trigger an update when that specific resource is updated
+        if sub.eventName in ['fetch', 'fetchonce']
+          @listenTo sub.store, 'update', (resource, resources) =>
+            if parseInt(resource[sub.store.resourceKey].id) is parseInt(sub.options?.id)
+              (sub.callback ? @setStateFromStore) resource
+
+
+  stopPolling: ->
+    sub.store.stopPolling() for sub in @boundResources

   stopListeningToBoundResources: =>
@@ -168,29 +200,6 @@
       @stopListening sub.store, 'update'

-  startPollingBoundResources: =>
-    for sub in @boundResources
-      do (sub) =>
-        if sub.eventName in ['fetch', 'reset']
-          @listenTo sub.store, sub.eventName, sub.callback ? @setStateFromStore
-          sub.store.startPolling sub.options
-        # ensure local creates/updates are synced to the state
-        if sub.eventName in ['reset', 'resetonce']
-          @listenTo sub.store, 'create', (resource, resources) =>
-            (sub.callback ? @setStateFromStore) resources
-          @listenTo sub.store, 'destroy', (resources) =>
-            (sub.callback ? @setStateFromStore) resources
-
-        # always listen for updates
-        @listenTo sub.store, 'update', (resource, resources) =>
-          if parseInt(resource[sub.store.resourceKey].id) is parseInt(sub.options?.id)
-            (sub.callback ? @setStateFromStore) resource
-
-
-  stopPolling: ->
-    sub.store.stopPolling() for sub in @boundResources
-
-
   render: ->
     <div/>


cherrypicking @jvilk's pull request (#97)

--- browser/mockconsole.coffee
+++ browser/mockconsole.coffee
@@ -136,6 +136,7 @@
     error "Command handle called before it was set."

   extern.commandHandle = config.commandHandle ? errorCommandHandle
+  extern.onreprompt = config.onreprompt ? null

   extern.reset = ->
     # NOP
@@ -155,6 +156,8 @@
       extern.reprompt()

   extern.reprompt = () ->
+    if typeof extern.onreprompt == "function"
+      extern.onreprompt()
     setTimeout(runNextCommand, 10)

   extern.promptText = (text) ->


webapp/editor/sagews: reverting layout button, that fix is broken, too

--- src/smc-webapp/editor.coffee
+++ src/smc-webapp/editor.coffee
@@ -1418,9 +1418,6 @@

             # Save for next time
             @_last_layout = @_layout
-        else
-            layout = @_layout ? 0
-            btn.find(".webapp-editor-layout-#{layout}").show()

         # Workaround a major and annoying bug in Safari:
         #     https://github.com/philipwalton/flexbugs/issues/132


make it so Sage worksheet processes start when they are open, not on first eval.  Will make SMC feel much faster.

--- src/smc-webapp/sagews.coffee
+++ src/smc-webapp/sagews.coffee
@@ -108,6 +108,16 @@
             @readonly = @_syncstring.get_read_only()  # TODO: harder problem -- if file state flips between read only and not, need to rerender everything...
             @init_hide_show_gutter()  # must be after @readonly set
             @process_sage_updates(caller:"constructor")   # MUST be after @readonly is set.
+
+            if not @readonly
+                # Kick the worksheet process into gear if it isn't running already
+                #console.log 'start worksheet...'
+                @introspect_line
+                    line     : "return?"
+                    timeout  : 30
+                    preparse : false
+                    cb       : (err) =>
+                        #console.log 'worksheet started', err

             @status cb: (err, status) =>
                 if not status?.running
@@ -663,7 +673,7 @@
         f = (cb) =>
             timeout = Math.min(10, 1.4*timeout)
             @introspect_line
-                line     : "open?"
+                line     : "return?"
                 timeout  : timeout
                 preparse : false
                 cb       : (resp) =>


PendingInvitationsView: Remove unnecessary require.

--- client/admin/lib/views/invitations/pendinginvitationsview.coffee
+++ client/admin/lib/views/invitations/pendinginvitationsview.coffee
@@ -1,6 +1,5 @@
 kd                    = require 'kd'
 remote                = require('app/remote').getInstance()
-isKoding              = require 'app/util/isKoding'
 checkFlag             = require 'app/util/checkFlag'
 InvitedItemView       = require './inviteditemview'
 KDCustomHTMLView      = kd.CustomHTMLView


add additional normalize test cases

add additional normalize test cases

--- tests/unit/scripts/normalize.coffee
+++ tests/unit/scripts/normalize.coffee
@@ -5,14 +5,89 @@
 #= require tandem/editor

-describe('Document', ->
-  describe('.normalize()', ->
+describe('Normalize', ->
+  describe('document', ->
     editor = new Tandem.Editor('editor-container')
     tests = [{
       name: 'initialize empty document'
       before: ''
       expected: '

'
     }, {
+      name: 'remove redundant block elements'
+      before:
+        '

+

+            Hey
+

+

+

+

+

+

+                What
+

+

+

+
'
+      expected:
+        '

+          Hey
+

+

+          What
+
'
+    }, {
+      name: 'break block elements'
+      before:
+        '

+

+            Hey
+

+

+            What
+

+
'
+      expected:
+        '

+          Hey
+

+

+          What
+
'
+    }, {
+      name: 'break list elements'
+      before:
+        '

+

+
One

+
Two

+
Three

+

+
'
+      expected:
+        '

+          One
+

+

+          Two
+

+

+          Three
+
'
+    }]
+
+    _.each(tests, (test) ->
+      it('shoud ' + test.name, ->
+        editor.iframeDoc.body.innerHTML = Tandem.Utils.cleanHtml(test.before)
+        editor.doc.rebuildLines()
+        expect(Tandem.Utils.cleanHtml(editor.iframeDoc.body.innerHTML)).to.equal(Tandem.Utils.cleanHtml(test.expected))
+      )
+    )
+  )
+
+  describe('line', ->
+    editor = new Tandem.Editor('editor-container')
+    tests = [{
       name: 'tranform equivalent styles'
       before:
         '

@@ -84,47 +159,13 @@
           Span

'
     }, {
-      name: 'remove redundant block elements'
-      before:
-        '

-

-            Hey
-

-

-

-

-

-

-                What
-

-

-

-
'
+      name: 'wrap text nodes'
+      before:
+        '
Hey
'
       expected:
         '

           Hey
-

-

-          What
-
'
-    }, {
-      name: 'break block elements'
-      before:
-        '

-

-            Hey
-

-

-            What
-

-
'
-      expected:
-        '

-          Hey
-

-

-          What
-
'
+
'
     }]
     # TODO test disallowed tags

@@ -132,7 +173,7 @@
       it('shoud ' + test.name, ->
         editor.iframeDoc.body.innerHTML = Tandem.Utils.cleanHtml(test.before)
         editor.doc.rebuildLines()
-        expect(editor.iframeDoc.body.innerHTML).to.equal(Tandem.Utils.cleanHtml(test.expected))
+        expect(Tandem.Utils.cleanHtml(editor.iframeDoc.body.innerHTML)).to.equal(Tandem.Utils.cleanHtml(test.expected))
       )
     )
   )


Extract only the contents of the body from the pages' html

--- src/scripts/models/content.coffee
+++ src/scripts/models/content.coffee
@@ -1,4 +1,5 @@
 define (require) ->
+  $ = require('jquery')
   _ = require('underscore')
   Backbone = require('backbone')

@@ -12,6 +13,16 @@
     defaults:
       title: 'Untitled'
       content: 'No content'
+
+    parse: (response, options) ->
+      # jQuery can not build a jQuery object with <head> or <body> tags,
+      # and will instead move all elements in them up one level.
+      # Use a regex to extract everything in the body and put it into a div instead.
+      $body = $('<div>' + response.content.replace(/^[\s\S]*<body.*?>|<\/body>[\s\S]*$/g, '') + '</div>')
+      $body.find('.title').eq(0).remove()
+      response.content = $body.html()
+
+      return response

   return class Content extends Backbone.Model
     url: () -> "/content/#{@id}"


string_extensions

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -64,7 +64,7 @@

 "Constant".constantize()
 # => Constant
-# notice this isn't a string. Useful for calling methods on class named Constant.
+# notice this isn't a string. Useful for calling methods on class with the same name as `this`.
 </pre></code>

 @name constantize


Alter student notes label

Alter student notes label

--- src/smc-webapp/course/students_panel.cjsx
+++ src/smc-webapp/course/students_panel.cjsx
@@ -619,7 +619,7 @@
         <Row key='note' style={styles.note}>
             <Col xs=2>
                 <Tip title="Notes about this student" tip="Record notes about this student here. These notes are only visible to you, not to the student.  In particular, you might want to include an email address or other identifying information here, and notes about late assignments, excuses, etc.">
-                    Notes
+                    Private Student Notes
                 </Tip>
             </Col>
             <Col xs=10>


Allow enter to submit student name

Allow enter to submit student name

--- src/smc-webapp/course/students_panel.cjsx
+++ src/smc-webapp/course/students_panel.cjsx
@@ -515,13 +515,19 @@

         student_project_id = @props.student.get('project_id')
         if student_project_id?
-            <Tip placement='right'
-                 title='Student project'
-                 tip='Open the course project for this student.'>
-                <Button onClick={@open_project}>
-                    <Icon name="edit" /> Open student project
-                </Button>
-            </Tip>
+            <ButtonToolbar>
+                <ButtonGroup>
+                    <Button onClick={@open_project}>
+                        <Tip placement='right'
+                             title='Student project'
+                             tip='Open the course project for this student.'
+                        >
+                            <Icon name="edit" /> Open student project
+                        </Tip>
+                    </Button>
+                </ButtonGroup>
+                {@render_edit_name()}
+            </ButtonToolbar>
         else
             <Tip placement='right'
                  title='Create the student project'
@@ -533,14 +539,15 @@

     render_edit_name: ->
         if @state.editing_student
-            <div>
-                <Button onClick={@save_student_name} bsStyle='success'>
+            disable_save = @props.student_name.first == @state.edited_first_name and @props.student_name.last == @state.edited_last_name
+            <ButtonGroup>
+                <Button onClick={@save_student_name} bsStyle='success' disabled={disable_save}>
                     <Icon name='save'/> Save
                 </Button>
                 <Button onClick={=>@setState(@getInitialState())} >
                     Cancel
                 </Button>
-            </div>
+            </ButtonGroup>
         else
             <Button onClick={@show_edit_name_dialogue}>
                 <Icon name='address-card-o'/> Edit student...
@@ -669,11 +676,8 @@

     render_panel_header: ->
         <Row>
-            <Col md=2>
+            <Col md=8>
                 {@render_project_access()}
-            </Col>
-            <Col md=6>
-                {@render_edit_name()}
             </Col>
             <Col md=4>
                 {@render_delete_button()}


Cosmetic: spotted by @dvv

Cosmetic: spotted by @dvv

--- tests/html/src/tests.coffee
+++ tests/html/src/tests.coffee
@@ -242,7 +242,6 @@
         expect(3)
         r = newSockJS('/invalid_url', protocol)
         ok(r)
-        counter =
         r.onopen = (e) ->
             fail(true)
         r.onmessage = (e) ->


updating buttons on one side no longer overrides previously-set buttons on the other side.

--- src/models/NavigationBar.coffee
+++ src/models/NavigationBar.coffee
@@ -107,22 +107,23 @@
         params.titleImagePath = ""

       if options.buttons?
-        params.buttons = {}
-
-        @buttonCallbacks = {}

         locations = ["right", "left"]

         for location in locations
-          steroids.debug "steroids.navigationBar.update constructing location #{location}"
-          @buttonCallbacks[location] = []
-          params.buttons[location] = []

           if options.buttons[location]?
+            if not params.buttons?
+              params.buttons = {}
+            if not @buttonCallbacks?
+              @buttonCallbacks = {}
+            steroids.debug "steroids.navigationBar.update constructing location #{location}"
+            @buttonCallbacks[location] = []
+            params.buttons[location] = []
+
             for button in options.buttons[location]
-              navButton = new NavigationBarButton(button)
-              parameters = navButton.toParams()
-              callback = navButton.getCallback()
+              parameters = button.toParams()
+              callback = button.getCallback()

               steroids.debug "steroids.navigationBar.update adding button #{JSON.stringify(parameters)} to location #{location}"
               params.buttons[location].push parameters