Minor style fix (Sign in link)

Minor style fix (Sign in link)

--- client/landing/app/MainApp/maincontroller/mainviews/mainview.coffee
+++ client/landing/app/MainApp/maincontroller/mainviews/mainview.coffee
@@ -120,7 +120,8 @@

     @header.getElement().innerHTML = ''

-    @header.addSubView @logo = new KDCustomHTMLView
+    @header.addSubView wrapper = new KDView
+    wrapper.addSubView @logo = new KDCustomHTMLView
       tagName   : "a"
       domId     : "koding-logo"
       cssClass  : if entryPoint?.type is 'group' then 'group' else ''
@@ -130,7 +131,7 @@
         homeRoute = if KD.isLoggedIn() then "/Activity" else "/Home"
         KD.getSingleton('router').handleRoute homeRoute, {entryPoint}

-    @header.addSubView loginLink = new CustomLinkView
+    wrapper.addSubView loginLink = new CustomLinkView
       domId       : 'header-sign-in'
       title       : 'Already a user? Sign in.'
       icon        :


exact names in css map

exact names in css map

--- oridomi.coffee
+++ oridomi.coffee
@@ -37,15 +37,20 @@
 prefixList = ['Webkit', 'Moz', 'ms']

 # A map of the CSS3 properties needed to support oriDomi, with shorthand names as keys.
-  origin:             'transformOrigin'
-  transformStyle:     'transformStyle'
-  transitionProp:     'transitionProperty'
-  transitionDuration: 'transitionDuration'
-  transitionEasing:   'transitionTimingFunction'
-  perspective:        'perspective'
-  perspectiveOrigin:  'perspectiveOrigin'
-  backface:           'backfaceVisibility'
-  boxSizing:          'boxSizing'
+css = new ->
+  @[key] = key for key in [
+    'transform'
+    'transformOrigin'
+    'transformStyle'
+    'transitionProperty'
+    'transitionDuration'
+    'transitionTimingFunction'
+    'perspective'
+    'perspectiveOrigin'
+    'backfaceVisibility'
+    'boxSizing'
+  ]
+  @

 # This function checks for the presence of CSS properties on the test div.
 testProp = (prop) ->


Add permissions prop to manage steps

Add permissions prop to manage steps

--- components/ManageSteps/ManageSteps.coffee
+++ components/ManageSteps/ManageSteps.coffee
@@ -8,6 +8,7 @@
 ManageSteps = React.createClass
   propTypes:
     projectId: React.PropTypes.string.isRequired
+    permissions: React.PropTypes.array

   componentWillMount: ->
     { loadStepsByProject, projectId } = this.props


Need to set URLs on 'contact_gallery' too?

--- components/contact/contact_partner.coffee
+++ components/contact/contact_partner.coffee
@@ -5,6 +5,7 @@
 Partner = require '../../models/partner.coffee'
 CurrentUser = require '../../models/current_user.coffee'
 { SESSION_ID, ARTSY_URL } = require('sharify').data
+{ readCookie } = require '../util/cookie.coffee'
 formTemplate = -> require('./templates/inquiry_form.jade') arguments...
 headerTemplate = -> require('./templates/inquiry_partner_header.jade') arguments...

@@ -45,9 +46,13 @@
     analytics.track.funnel 'Sent artwork inquiry',
       label: analytics.modelNameAndIdToLabel('artwork', @artwork.get('id'))
     @model.set
-      artwork: @artwork.id
+      artwork        : @artwork.id
       contact_gallery: true
-      session_id: SESSION_ID
+      session_id     : SESSION_ID
+      referring_url  : readCookie('force-referrer')
+      landing_url    : readCookie('force-session-start')
+      inquiry_url    : window.location.pathname
+
     super

   postRender: =>


Fix affairs/affairs miss-redirection

Fix affairs/affairs miss-redirection

--- app/assets/javascripts/app/controllers/people/affairs/affairs.js.coffee
+++ app/assets/javascripts/app/controllers/people/affairs/affairs.js.coffee
@@ -164,7 +164,7 @@
       @unload_dependencies()

   show_owner: (e) ->
-    window.location = "/people/#{@affair.owner_id}?folding=person_affairs"
+    window.location = "/people/#{@affair.owner_id}#affairs"

   destroy: (e) ->
     if confirm(I18n.t('common.are_you_sure'))


Fix failing BOM ng-spec

Fix failing BOM ng-spec

--- spec/javascripts/unit/bulk_order_management_spec.js.coffee
+++ spec/javascripts/unit/bulk_order_management_spec.js.coffee
@@ -29,9 +29,9 @@
       scope.initialise "api_key"
       httpBackend.flush()

-      expect(scope.suppliers).toEqual [{ id : '', name : 'All' }, 'list of suppliers']
-      expect(scope.distributors).toEqual [ { id : '', name : 'All' }, 'list of distributors' ]
-      expect(scope.orderCycles).toEqual [ { id : '', name : 'All' }, 'oc1', 'oc2', 'oc3' ]
+      expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers']
+      expect(scope.distributors).toEqual [ { id : '0', name : 'All' }, 'list of distributors' ]
+      expect(scope.orderCycles).toEqual [ { id : '0', name : 'All' }, 'oc1', 'oc2', 'oc3' ]

       expect(scope.initialiseVariables.calls.length).toBe 1
       expect(scope.fetchOrders.calls.length).toBe 1


Request profiles for refs in posts feeds

--- lib/assets/javascripts/views/posts_feed.js.coffee
+++ lib/assets/javascripts/views/posts_feed.js.coffee
@@ -14,7 +14,7 @@
     @types = options.types || TentStatus.config.feed_types
     @feed_headers = options.headers || {}

-    default_queries = [{ profiles: 'entity', max_refs: 1 }, { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], mentions: 'subscribing', profiles: 'entity' }]
+    default_queries = [{ profiles: 'entity,refs', max_refs: 1 }, { types: [TentStatus.config.POST_TYPES.STATUS_REPLY], mentions: 'subscribing', profiles: 'entity,refs' }]
     @feed_queries = _.map options.feed_queries || default_queries, (q) => q.types ?= @types; q
     @collection_contexts = _.map @feed_queries, (feed_params) =>
       TentStatus.Collections.Posts.generateContext('feed', { params: feed_params, headers: @feed_headers })


WFGH: err on @leave

WFGH: err on @leave

--- workers/social/lib/social/models/campaigns/wfgh.coffee
+++ workers/social/lib/social/models/campaigns/wfgh.coffee
@@ -54,7 +54,7 @@

   @leave = (account, callback) ->

-    callback null, 'n/a'
+    callback message : 'n/a'

   @getStats = (account, callback) ->


made viewportsize explicit

made viewportsize explicit

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -37,7 +37,7 @@
 		.pipe(phantomjs({
 			reporter: "dot"
 			phantomjs:
-				# viewportSize: {width: 1024, height: 768}
+				viewportSize: {width: 400, height: 300}
 				useColors: true
 				loadImages: false
 		}))
@@ -126,13 +126,13 @@
 	return gulp
 		.src("test/phantomjs/index.html")
 		.pipe(phantomjs(
+			reporter: "dot"
 			phantomjs:
 				hooks: "mocha-phantomjs-istanbul"
 				coverageFile: coverageFile
-				# viewportSize: {width: 1024, height: 768}
+				viewportSize: {width: 400, height: 300}
 				useColors: true
 				loadImages: false
-			reporter: "dot"
 		))
 		.on "finish", ->
 			gulp.src(coverageFile)


chore(stats): generate stats when creating changelog

--- gulpfile.coffee
+++ gulpfile.coffee
@@ -237,7 +237,7 @@
 		.on 'error', onError

 # Generate CHANGELOG
-gulp.task 'changelog', ->
+gulp.task 'changelog', ['stats'], ->
 	options =
 		repository: pkg.repository.url
 		version: pkg.version


Save group VM. wip.

Save group VM. wip.

--- client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
+++ client/landing/app/Applications/Groups.kdapplication/Views/groupcreation.coffee
@@ -147,7 +147,7 @@

               Good for:
               #{d.meta.goodFor}
-              VM's
+              users

             #{d.description}

"""
@@ -235,14 +235,51 @@
       formData.requestType = formData.privacy
       formData.privacy     = 'private'

-    KD.remote.api.JGroup.create formData, (err, group)=>
-      if err
-        callback? err
-        new KDNotificationView title: err.message, duration: 1000
-      else
-        callback? err, group
-        @getSingleton("groupsController").showGroupCreatedModal group
-        @destroy()
+    # Copy account creator's billing information
+    KD.remote.api.JRecurlyPlan.getUserAccount (err, data)=>
+      warn err
+      if err or not data
+        data = {}
+
+      # These will go into Recurly module
+      delete data.cardNumber
+      delete data.cardMonth
+      delete data.cardYear
+      delete data.cardCV
+
+      modal = createAccountPaymentMethodModal data, (newData, onError, onSuccess)=>
+        # These will go into Recurly module
+        newData.username    = 'group_unnamed'
+        newData.ipAddress   = '0.0.0.0'
+        newData.firstName   = 'Group'
+        newData.lastName    = 'Unnamed'
+        newData.email       = 'group@example.com'
+        newData.pin         = 'xxx'
+        newData.accountCode = yes
+
+        formData.plan.subscribe newData, (err, account)=>
+          if err
+            # Show error messages here.
+            onError err
+          else
+            # DONE
+            onSuccess()
+
+            console.log account
+
+            KD.remote.api.JGroup.create formData, (err, group)=>
+              if err
+                callback? err
+                new KDNotificationView title: err.message, duration: 1000
+              else
+                account.attachToGroup group, (err, account)=>
+                  console.log "Attach"
+                  console.log err
+                  console.log account
+                  console.log "-------"
+                  callback? err, group
+                  @getSingleton("groupsController").showGroupCreatedModal group
+                  @destroy()

   makeSlug: ->


More decimal places in "total cost" dashboard

Values in billions will be shown with one decimal place (previously
zero), while trillions will get two.

Closes #1651

--- app/assets/javascripts/lib/views/constraint_view.coffee
+++ app/assets/javascripts/lib/views/constraint_view.coffee
@@ -59,7 +59,9 @@

     out = switch key
       when 'total_energy_cost'
-        Metric.euros_to_string(result * 1000000000)
+        # One decimal place when billions, two when trillions.
+        precision = if result < 1000 then 1 else 2
+        Metric.euros_to_string(result * 1000000000, null, precision)
       when 'costs_fte'
         Metric.euros_to_string(result)
       when 'household_energy_cost'


Don't paste newlines into simple field types

--- vendor/assets/javascripts/mercury/regions/simple.js.coffee
+++ vendor/assets/javascripts/mercury/regions/simple.js.coffee
@@ -147,6 +147,22 @@
       @focus()
       Mercury.trigger('region:focused', {region: @})

+    @element.on 'paste', (event) =>
+      return if @previewing || Mercury.region != @
+      if @specialContainer
+        event.preventDefault()
+        return
+      return if @pasting
+      Mercury.changes = true
+      @handlePaste(event.originalEvent)
+
+
+  handlePaste: (event) ->
+    # get the text content from the clipboard and fall back to using the sanitizer if unavailable
+    @execCommand('insertHTML', {value: event.clipboardData.getData('text/plain').replace(/\n/g, ' ')})
+    event.preventDefault()
+    return
+

   focus: ->
     @element.focus()


work with atom source files

work with atom source files

--- lib/eval.coffee
+++ lib/eval.coffee
@@ -1,12 +1,19 @@
 vm = require 'vm'
+path = require 'path'
 coffee = require 'coffee-script'
 parse = require './parse'

 module.exports =

+  getAtomPath: (p) ->
+    if m = p.match /atom[\\/]src[\\/](.*)\.coffee/
+      [_, name]= m
+      p = path.join process.resourcesPath, 'app.asar', 'src', name + '.js'
+    p
+
   module: (path) ->
     return 'window' unless path?
-    return "require('#{parse.escape(path)}')"
+    return "require('#{parse.escape @getAtomPath path}')"

   indent: (code) ->
     code.split('\n').map((n) -> '  ' + n).join('\n')


Also assign email to local record

Also assign email to local record

--- assets/javascripts/routes/authenticated_route.js.coffee
+++ assets/javascripts/routes/authenticated_route.js.coffee
@@ -11,6 +11,7 @@
           firstName: response.user.first_name,
           lastName: response.user.last_name,
           role: response.user.role
+          email: response.user.email

         if @store.recordIsLoaded("user", userAttributes.id)
           @store.find("user", userAttributes.id).then (user)=>


Move creation of status bar element to constructor

* This change ensures @statusBarElement always exists.

--- lib/main.coffee
+++ lib/main.coffee
@@ -56,7 +56,13 @@
             'hydrogen:toggle-inspector-size': -> Inspector.toggleInspectorSize()
             'hydrogen:close-inspector': -> Inspector.closeInspector()

+        @statusBarElement = document.createElement('div')
+        @statusBarElement.classList.add('hydrogen')
+        @statusBarElement.classList.add('status-container')
+        @statusBarElement.onclick = @showKernelCommands.bind @
+
         @editor = atom.workspace.getActiveTextEditor()
+
         @subscriptions.add atom.workspace.observeActivePaneItem (item) =>
             @editor = item
             @setStatusBarElement()
@@ -71,11 +77,6 @@

     consumeStatusBar: (statusBar) ->
-        console.log 'making status bar'
-        @statusBarElement = document.createElement('div')
-        @statusBarElement.classList.add('hydrogen')
-        @statusBarElement.classList.add('status-container')
-        @statusBarElement.onclick = @showKernelCommands.bind @
         @statusBarTile = statusBar.addLeftTile
             item: @statusBarElement, priority: 100


account/shortcuts: add missing unicode symbols for mac

--- client/account/lib/views/accounteditshortcutsrow.coffee
+++ client/account/lib/views/accounteditshortcutsrow.coffee
@@ -14,11 +14,28 @@
 DESCRIPTION_TRUNC_SEP = ' '

 # On Mac we display corresponding unicode chars for the following keys.
+# See: http://macbiblioblog.blogspot.nl/2005/05/special-key-symbols.html
+#
 MAC_UNICODE =
-  shift   : '⇧'
-  command : '⌘'
-  alt     : '⌥'
-  ctrl    : '^'
+  shift       : '⇧'
+  command     : '⌘'
+  alt         : '⌥'
+  ctrl        : '&#x2303'
+  tab         : '&#x21e5'
+  'caps lock' : '&#x21ea'
+  space       : '&#x2423'
+  enter       : '&#x23ce'
+  backspace   : '&#x232b'
+  home        : '&#x21f1'
+  end         : '&#x21f2'
+  'page up'   : '&#x21de'
+  'page down' : '&#x21df'
+  left        : '&#x2190'
+  up          : '&#x2191'
+  right       : '&#x2192'
+  down        : '&#x2193'
+  esc         : '&#x238b'
+  'num lock'  : '&#x21ed'

 # Determines the text conversion method to use when displaying bindings.
 convertCase = _.capitalize


CredentialListItem: use better styles for buttons, replace bootstrap button with use button

--- client/admin/lib/views/stacks/credentiallistitem.coffee
+++ client/admin/lib/views/stacks/credentiallistitem.coffee
@@ -12,32 +12,30 @@
     super options, data

     delegate  = @getDelegate()
-    { owner, title } = @getData()
+    { owner, title, verified } = @getData()

     @deleteButton = new kd.ButtonView
-      # iconOnly : yes
-      cssClass : "delete"
-      title    : "delete"
+      cssClass : 'solid compact outline red'
+      title    : 'DELETE'
       callback : delegate.lazyBound 'deleteItem', this

     @showCredentialButton = new kd.ButtonView
-      # iconOnly : yes
-      cssClass : "show"
-      title    : "show"
-      disabled : !owner
+      cssClass : 'solid compact outline'
+      title    : 'SHOW'
       callback : delegate.lazyBound 'showItemContent', this

-    @bootstrapButton = new kd.ButtonView
-      # iconOnly : yes
-      cssClass : "bootstrap"
-      title    : "bootstrap"
-      callback : delegate.lazyBound 'bootstrap', this
+    @useButton = new kd.ButtonView
+      cssClass : 'solid compact outline green'
+      title    : 'SELECT'
+      callback : =>
+        delegate.emit 'ItemSelected', @getData()
+
+    @useButton.hide()  unless verified

     @verifyButton = new kd.ButtonView
-      # iconOnly : yes
-      cssClass : "verify"
-      title    : "verify"
       callback : delegate.lazyBound 'verify', this
+      cssClass : 'solid compact outline'
+      title    : 'VERIFY'

   pistachio: ->
@@ -47,6 +45,6 @@
     </div>
     <div class='buttons'>
       {{> @showCredentialButton}}{{> @deleteButton}}
-      {{> @bootstrapButton}}{{> @verifyButton}}
+      {{> @useButton}}{{> @verifyButton}}
     </div>
     """


fix #2 do nothing when not item was selected.

--- lib/view.coffee
+++ lib/view.coffee
@@ -108,6 +108,7 @@

   confirmAndContinue: ->
     selectedItem = @getSelectedItem()
+    return unless selectedItem?
     this[@action](fs.normalize(selectedItem))

     selectedItemView = @getSelectedItemView()


Use reduceOptions in VersionSelectView

Use reduceOptions in VersionSelectView

--- BackofficeBundle/Resources/public/coffee/widget/VersionSelectView.coffee
+++ BackofficeBundle/Resources/public/coffee/widget/VersionSelectView.coffee
@@ -5,7 +5,11 @@
     'change': 'changeVersion'

   initialize: (options) ->
-    @options = options
+    @options = @reduceOption(options, [
+      'currentVersion'
+      'versions'
+      'domContainer'
+    ])
     @loadTemplates [
       "OpenOrchestraBackofficeBundle:BackOffice:Underscore/widgetVersionSelect"
       "OpenOrchestraBackofficeBundle:BackOffice:Underscore/widgetVersion"


return a 400 status code for invalid moves

was previously returning 500

--- services/web/app/coffee/Features/Editor/EditorHttpController.coffee
+++ services/web/app/coffee/Features/Editor/EditorHttpController.coffee
@@ -128,8 +128,12 @@
 		folder_id = req.body.folder_id
 		user_id = AuthenticationController.getLoggedInUserId(req)
 		EditorController.moveEntity project_id, entity_id, folder_id, entity_type, user_id, (error) ->
-			return next(error) if error?
-			res.sendStatus 204
+			if error?.message == 'destination folder is a child folder of me'
+				res.status(400).json(req.i18n.translate('invalid_file_name'))
+			else if error?
+				next(error)
+			else
+				res.sendStatus 204

 	deleteDoc: (req, res, next)->
 		req.params.entity_type  = "doc"


Don't raise an error if `output.blg` cannot be found.

--- services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -137,7 +137,7 @@
 								entries.warnings = entries.warnings.concat(biberLogEntries.warnings)
 							proceed()
 						.error (e) ->
-							console.error ">> error", e
+							# it's not an error for the output.blg file to not be present
 							proceed()
 					# # # #
 				.error () ->


Improved placement of popovers.

Improved placement of popovers.

--- app/assets/javascripts/app/controllers/_application_controller.js.coffee
+++ app/assets/javascripts/app/controllers/_application_controller.js.coffee
@@ -287,11 +287,11 @@
     # show ticket popup
     ui = @
     $('.ticket-data').popover(
-      trigger: 'hover'
-      html:    true
-      delay:   { show: 500, hide: 1200 }
-#      placement: 'bottom'
-      placement: position
+      trigger:    'hover'
+      container:  'body'
+      html:       true
+      delay:      { show: 500, hide: 1200 }
+      placement:  position
       title: ->
         ticket_id = $(@).data('id')
         ticket = App.Ticket.retrieve( ticket_id )
@@ -313,11 +313,11 @@

     # show user popup
     $('.user-data').popover(
-      trigger: 'hover'
-      html:    true
-      delay:   { show: 500, hide: 1200 }
-#      placement: 'bottom'
-      placement: position
+      trigger:    'hover'
+      container:  'body'
+      html:       true
+      delay:      { show: 500, hide: 1200 }
+      placement:  position
       title: ->
         user_id = $(@).data('id')
         user = App.User.find( user_id )
@@ -359,11 +359,11 @@

     # show organization popup
     $('.organization-data').popover(
-      trigger: 'hover'
-      html:    true
-      delay:   { show: 500, hide: 1200 }
-#      placement: 'bottom'
-      placement: position
+      trigger:    'hover'
+      container:  'body'
+      html:       true
+      delay:      { show: 500, hide: 1200 }
+      placement:  position
       title: ->
         organization_id = $(@).data('id')
         organization = App.Organization.find( organization_id )
@@ -401,10 +401,11 @@
     # show user popup
     controller = @
     $(data.selector).popover(
-      trigger: 'hover'
-      html:    true
-      delay:   { show: 500, hide: 5200 }
-      placement: data.position
+      trigger:    'hover'
+      container:  'body'
+      html:       true
+      delay:      { show: 500, hide: 5200 }
+      placement:  data.position
       title: ->
         $(@).find('[title="*"]').val()


Prevent modal from closing when user clicks outside body

--- lineman/app/services/modal_service.coffee
+++ lineman/app/services/modal_service.coffee
@@ -10,3 +10,4 @@
         controller:  modal.controller
         resolve:     resolve
         size:        (modal.size || '')
+        backdrop:    'static'


tiny bit of branding in the log output

tiny bit of branding in the log output

--- platform.coffee
+++ platform.coffee
@@ -181,4 +181,4 @@
 				@listen port

 				winston.info "#{cfg.app.name} running"
-				winston.info "ExpressJS listening on :#{port}"
+				winston.info "platform-ng listening via express on :#{port}"


fix invoices list when an invoice is missing an invoiced_date

--- src/components/widgets/invoices-list/invoices-list.directive.coffee
+++ src/components/widgets/invoices-list/invoices-list.directive.coffee
@@ -36,17 +36,20 @@
       minDate = false
       dates = _.flatten _.map(w.content.entities, ((e) -> _.map(e.invoices, ((i) -> i.invoice_date)) ))
       for date in dates
-        parsedDate = date.split('-')
-        y = parsedDate[0]
-        m = parsedDate[1]-1
-        d = parsedDate[2]
-        newDate = new Date(y,m,d)
-        minDate ||= newDate
-        minDate = Math.min(minDate, newDate)
-        maxDate = Math.max(maxDate, newDate)
+        unless _.isEmpty(date)
+          parsedDate = date.split('-')
+          y = parsedDate[0]
+          m = parsedDate[1]-1
+          d = parsedDate[2]
+          newDate = new Date(y,m,d)
+          minDate ||= newDate
+          minDate = Math.min(minDate, newDate)
+          maxDate = Math.max(maxDate, newDate)

-        $scope.defaultFrom = $filter('date')(minDate, 'yyyy-MM-dd')
-        $scope.defaultTo = $filter('date')(maxDate, 'yyyy-MM-dd')
+      # Rare case where not a single invoice has an invoice date...
+      minDate ||= new Date(new Date().getFullYear() - 10, 0, 1)
+      $scope.defaultFrom = $filter('date')(minDate, 'yyyy-MM-dd')
+      $scope.defaultTo = $filter('date')(maxDate, 'yyyy-MM-dd')

   # No need to put this under initContext because it won't change after a settings update


Client: Activity: Updated  onChange event for readable code

--- client/activity/lib/components/channelparticipantavatars/index.coffee
+++ client/activity/lib/components/channelparticipantavatars/index.coffee
@@ -198,7 +198,8 @@

     matchResult = value.match /^@(.*)/

-    if matchResult then query = matchResult[1] else query = value
+    query = value
+    query = matchResult[1]  if matchResult

     { channel, user  } = ActivityFlux.actions


introducing new raw, command escaping

introducing new raw, command escaping


por alguna razon siempre desaparece appconfig

--- client/angular/components/thread_page/context_panel/context_panel.coffee
+++ client/angular/components/thread_page/context_panel/context_panel.coffee
@@ -7,6 +7,7 @@
 LmoUrlService  = require 'shared/services/lmo_url_service'
 FlashService   = require 'shared/services/flash_service'
 I18n           = require 'shared/services/i18n'
+AppConfig      = require 'shared/services/app_config.coffee'

 { listenForTranslations, listenForReactions } = require 'shared/helpers/listen'
 { scrollTo }                                  = require 'shared/helpers/layout'


Guard against invalid dates

Guard against invalid dates

--- src/kerb.coffee
+++ src/kerb.coffee
@@ -30,7 +30,11 @@

   robot.respond /what'?s on kerb (.+)\??/i, (res) ->
     date = chrono.parseDate(res.match[1])
-    tradersForDate(date, res)
+
+    if date
+      tradersForDate(date, res)
+    else
+      res.send 'Sorry, I didn\'t understand that date. Try something like "what\'s on kerb tomorrow?" or "what\'s on kerb on Thursday?"'

   tradersForDate = (date, res) ->
     dateId = "#date-#{ date.toISOString().slice(0, 10) }"


Fix syntax error

Fix syntax error

--- scripts/bungie-data-helper.coffee
+++ scripts/bungie-data-helper.coffee
@@ -200,7 +200,7 @@
         message += "*#{stat.name}*: #{stat.value}/#{maxValue} (#{percentage.toFixed(0)}%)\n"

       percentage = percentageTotal / stats.length
-      message += "*Overall percentage:* #{percentage.toFixed(0)%}\n"
+      message += "*Overall percentage:* #{percentage.toFixed(0)}%\n"
       message += '\n'

     attachment =


Added the possibility for plugin using ! syntax

--- src/molo.coffee
+++ src/molo.coffee
@@ -82,8 +82,16 @@
       if cache.hasOwnProperty(i)
         cacheDeps.push cache[i]
       else
-        # If a '#!' is put in front of the dependency, it will assume the module is in the same script file
-        if root.molo.scriptLoader or i.indexOf('#!') isnt 0
+        # Plugin functionality
+        pluginName = i.split('!')[1]
+
+        scriptLoader = root.molo.scriptLoader
+
+        # If a script is defined as 'my/script!' it will try to resolve the
+        # dependency on the same page
+        scriptLoader = false if pluginName is ''
+
+        if scriptLoader
           if isJavaScriptFile(root.molo.paths[name])
             # If the path is complete (e.g. 'js/lib/mymodule.js') take that as a path
             scriptPath = root.molo.paths[name]
@@ -128,8 +136,10 @@
       maxDeps = queue[q].require.length
       curDeps = 0

-      (curDeps++ if cache[d]) for d in queue[q].require
-
+      for d in queue[q].require
+        depBasename = d.split('!')[0]
+        curDeps++ if cache[depBasename]
+
       # Reload module and delete item from queue
       if curDeps is maxDeps
         modName = q
@@ -163,6 +173,9 @@
     delete cache[name] if cache[name]
     delete queue[name] if queue[name]

+  # Provide plugin functionality
+  root.molo.plugins = {}
+
   # Shorthand function to load script files
   root.molo.main = (dependencies) ->
     dependencies = [dependencies] if typeof dependencies is 'string'


dont specify content_type, needs to stay multipart/form-data

--- lib/assets/javascripts/easiest_ever_ajax_uploads.coffee
+++ lib/assets/javascripts/easiest_ever_ajax_uploads.coffee
@@ -37,7 +37,7 @@
         type: "POST"
         data: fd
         processData: false
-        contentType: file.type
+        # contentType: file.type
         crossDomain: true
         success: (data, textStatus, jqXHR) ->
           location = jqXHR.getResponseHeader("Location")


quick and dirty support of sleep for withings

NB: This konnector should be fully rewrited to use the layer system. Currently
it is hard to read and maintain.

--- server/konnectors/withings.coffee
+++ server/konnectors/withings.coffee
@@ -26,6 +26,7 @@
 authUrl = 'https://auth.withings.com/fr/'
 accountUrl = 'https://healthmate.withings.com/index/service/account'
 measureUrl = 'https://healthmate.withings.com/index/service/measure'
+activityUrl = 'https://healthmate.withings.com/index/service/v2/activity'
 aggregateUrl = 'https://healthmate.withings.com/index/service/v2/aggregate'

 # Models
@@ -52,6 +53,7 @@
     vendor: {type: String, default: 'Withings'}

 Steps = require '../models/steps'
+Sleep = require '../models/sleep'

 Weight.all = (callback) ->
     Weight.request 'byDate', callback
@@ -102,13 +104,14 @@

         end = Math.ceil((new Date).getTime() / 1000)
         start = moment()
-        start = start.years 2008
+        start = start.year 2008
         start = start.month 0
         start = start.date 1
         start = Math.ceil(start.valueOf() / 1000)

-        @fetchData email, password, start, end, callback
-        log.info 'import finished'
+        @fetchData email, password, start, end, (err) ->
+            log.info 'import finished'
+            callback err

     # Fetch data from withings website and save them as Cozy objects
@@ -191,7 +194,7 @@
                             return callback err if err

                             startDate = moment()
-                                .years(2014)
+                                .year(2014)
                                 .month(0)
                                 .date(1)
                                 .format('YYYY-MM-DD')
@@ -214,15 +217,78 @@
                             onMeasures = (err, res, body) ->
                                 return callback err if err
                                 measures = JSON.parse body
-                                saveActivityMeasures measures, callback
+                                saveActivityMeasures measures, ->
+                                    options =
+                                        sessionid: sessionid
+                                        userid: userid
+
+                                    fetchAndSaveSleepMeasures options, callback

                             request.post aggregateUrl, options, onMeasures
+
+
+fetchAndSaveSleepMeasures = (options, callback) ->
+    opts =
+        strictSSL: false
+        form:
+            sessionid: options.sessionid
+            userid: options.userid
+            subcategory: 37
+            startdateymd: '2013-12-24'
+            enddateymd: moment().format 'YYYY-MM-DD'
+            appname: 'my2'
+            appliver: '36871d49'
+            apppfm: 'web'
+            action: 'getbyuserid'
+
+    log.info 'Fetching sleep data...'
+    request.post activityUrl, opts, (err, res, body) ->
+        return callback err if err
+        log.info 'Fetching sleep data done.'
+        measures = JSON.parse body
+        saveSleepMeasures measures.body.series, callback
+
+
+saveSleepMeasures = (measures, callback) ->
+    sleepsMeasures = []
+    for measure in measures
+        sleepsMeasures.push new Sleep
+            vendor: 'Withings'
+            date: moment measure.date, 'YYYY-MM-DD'
+            awakeDuration: measure.data.wakeupduration
+            lightSleepDuration: measure.data.lightsleepduration
+            deepSleepDuration: measure.data.deepsleepduration
+            awakeTime: measure.data.wakeupcount
+            sleepDuration: (
+                measure.data.lightsleepduration + \
+                measure.data.deepsleepduration
+            )
+    log.info "#{measures.length} found"
+
+    Sleep.all (err, sleeps) ->
+        return callback err if err
+
+        sleepMap = {}
+        for sleep in sleeps
+            date = moment(sleep.date).format 'YYYY-MM-DD'
+            sleepMap[date] = true
+
+        SleepsToSave = sleepsMeasures.filter (measure) ->
+            date = moment(measure.date).format 'YYYY-MM-DD'
+            return not sleepMap[date]?
+
+        log.info "Saving sleeps..."
+        async.eachSeries sleepsToSave, (sleep, next) ->
+            Sleep.create sleep, next
+        , (err) ->
+            log.info "#{sleepsToSave.length} new sleep measures saved."
+            callback()

 hashMeasuresByDate = (measures) ->
     hash = {}
-    for m in measures
-        date = moment m.date
+    for measure in measures
+        date = moment measure.date
         hash[date] = true
     hash

@@ -274,7 +340,7 @@
             "#{bloodPressuresToSave.length} blood pressure measures to save")

         saveAll = (modelClass, models, done) ->
-            async.forEach models, (model, callback) ->
+            async.eachSeries models, (model, callback) ->
                 modelClass.create model, callback
             , (err) ->
                 done err
@@ -365,3 +431,65 @@
         return callback err if err
         processData stepsMeasures

+
+
+# Fetch sleep data from another url (it looks it's required by the withings
+# API).
+fetchAndSaveSleepMeasures = (options, callback) ->
+    opts =
+        strictSSL: false
+        form:
+            sessionid: options.sessionid
+            userid: options.userid
+            subcategory: 37
+            startdateymd: '2013-12-24'
+            enddateymd: moment().format 'YYYY-MM-DD'
+            appname: 'my2'
+            appliver: '36871d49'
+            apppfm: 'web'
+            action: 'getbyuserid'
+
+    log.info 'Fetching sleep data...'
+    request.post activityUrl, opts, (err, res, body) ->
+        return callback err if err
+        log.info 'Fetching sleep data done.'
+        measures = JSON.parse body
+        saveSleepMeasures measures.body.series, callback
+
+
+# Save sleep measures if they are not already recorded.
+saveSleepMeasures = (measures, callback) ->
+    sleepsMeasures = []
+    for measure in measures
+        sleepsMeasures.push new Sleep
+            vendor: 'Withings'
+            date: moment measure.date, 'YYYY-MM-DD'
+            awakeDuration: measure.data.wakeupduration
+            lightSleepDuration: measure.data.lightsleepduration
+            deepSleepDuration: measure.data.deepsleepduration
+            awakeTime: measure.data.wakeupcount
+            sleepDuration: (
+                measure.data.lightsleepduration + \
+                measure.data.deepsleepduration
+            )
+    log.info "#{measures.length} found"
+
+    Sleep.all (err, sleeps) ->
+        return callback err if err
+
+        sleepMap = {}
+        for sleep in sleeps
+            date = moment(sleep.date).format 'YYYY-MM-DD'
+            sleepMap[date] = true
+
+        sleepsToSave = sleepsMeasures.filter (measure) ->
+            date = moment(measure.date).format 'YYYY-MM-DD'
+            return not sleepMap[date]?
+
+        log.info "Saving sleeps..."
+        async.eachSeries sleepsToSave, (sleep, next) ->
+            Sleep.create sleep, next
+        , (err) ->
+            log.info "#{sleepsToSave.length} new sleep measures saved."
+            callback()
+


Updated news

Updated news

--- server/materials/data/news.coffee
+++ server/materials/data/news.coffee
@@ -3,9 +3,8 @@

 export default allNews = () ->
     return news([
-        newsItem("Очных занятий до 10 апреля не будет. С 10 апреля планируется возобновление очных занятий.", String.raw"""
-            Очных занятий до 10 апреля не будет. Занимайтесь из дома. Если хотите, я готов организовать консультации по zoom, пишите.
-            С 10 апреля планируется возобновление очных занятий.
+        newsItem("С 10 апреля возобновляются очные занятия", String.raw"""
+            С 10 апреля возобновляются очные занятия — как во ВШЭ, так и в лицее 40, по обычному расписанию.
         """),

         newsItem("Опрос про алгопрог", String.raw"""


Added missing super call.

Added missing super call.

--- app/assets/javascripts/app/lib/app_post/websocket.js.coffee
+++ app/assets/javascripts/app/lib/app_post/websocket.js.coffee
@@ -46,6 +46,7 @@
   client_id:                undefined

   constructor: (@args) ->
+    super

     # bind to send messages
     App.Event.bind 'ws:send', (data) =>


fix drag and drop watchers 2

fix drag and drop watchers 2

--- app/assets/javascripts/uploadcare/widget/dragdrop.js.coffee
+++ app/assets/javascripts/uploadcare/widget/dragdrop.js.coffee
@@ -33,9 +33,9 @@
   onDelay = 0
   offDelay = if $.browser.opera then 200 else 1

+  active = false
   $ ->
     # Trigger an event on watched elements when dragging
-    active = false
     $(window).on 'mouseenter dragend', => delayedDragState off, offDelay
     $('body').on 'dragenter', (e) => delayedDragState on, onDelay
     $('body').on 'dragleave', (e) =>


Fix dotstorm image scaling, again.

Fix dotstorm image scaling, again.

--- plugins/dotstorm/assets/dotstorm/js/editor.coffee
+++ plugins/dotstorm/assets/dotstorm/js/editor.coffee
@@ -254,17 +254,10 @@
           canvas = document.createElement("canvas")
           canvas.width = 640
           canvas.height = 480
-          srcAspect = img.width / img.height
-          destAspect = canvas.width / canvas.height
-          if img.width < canvas.width and img.height < canvas.height
-            w = img.width
-            h = img.height
-          else if img.width > img.height
-            w = canvas.width
-            h = w * img.height / img.width
-          else
-            h = canvas.height
-            w = h * img.width / img.height
+          aspect = img.height / img.width
+          scale = Math.min(1, canvas.width / img.width, canvas.height / img.height)
+          w = img.width * scale
+          h = w * aspect
           x = (canvas.width - w)/2
           ctx = canvas.getContext('2d')
           ctx.drawImage(img, x, 0, w, h)


Always log extra options if any is enabled

--- lib/debug.coffee
+++ lib/debug.coffee
@@ -56,7 +56,8 @@

   init: ->
     @log "Debug Mode"
-    @log "Extra Options: #{process.env.DEBUG_EXTRA}" if extraOpts.size > 0
+    if extraOpts.size is 1 then @_log "Extra Option: #{process.env.DEBUG_EXTRA}"
+    else if extraOpts.size > 1 then @_log "Extra Options: #{process.env.DEBUG_EXTRA}"

 # For the Browser Process
 class DebugBrowser extends DebugBase


OPCE6: fixed rte.setStyle function and selection.clone

--- lib/y-rte.coffee
+++ lib/y-rte.coffee
@@ -62,22 +62,23 @@
         throw new Error "Expecting numbers as arguments"
       if not (rte instanceof Rte)
         throw new Error "Expecting an rte instance as third argument, got #{rte}"
-      @startPos = @_relativeFromAbsolute start, rte
-      @endPos = @_relativeFromAbsolute end, rte
-      @style = style
-
-      @left = rte.getWord @startPos.word
-      @right = rte.getWord @endPos.word
+
+      @rte = rte
+
+      @startPos = @_relativeFromAbsolute start
+      @endPos = @_relativeFromAbsolute end
+      @setStyle style
+
+      @left = @rte.getWord @startPos.word
+      @right = @rte.getWord @endPos.word

       @left.left.push @
       @right.right.push @

-      rte._rte.selections.push @
+      @rte._rte.selections.push @
+

     else throw new Error "Wrong set of parameters #{start}, #{end}, #{rte}, #{style}"
-
-    if not _.isUndefined(style)
-      @style = style

     @startPos.lt = @endPos.lt = (selection) ->
       @word < selection.word or (@word == selection.word and @pos <= selection.pos)
@@ -89,17 +90,16 @@
   # position within word
   #
   # @param [Integer] position index of position to find
-  # @param [Rte] rte a rich text editor instance
-  _relativeFromAbsolute: (position, rte)->
+  _relativeFromAbsolute: (position)->
     index = 0

     while position > 0
-      if index >= rte._rte.words.length
+      if index >= @rte._rte.words.length
         return {word: index, pos: 0}
-      if rte._rte.words[index].word.length > position
+      if @rte._rte.words[index].word.length > position
         return {word: index, pos: position}
       else
-        position -= rte._rte.words[index].word.length
+        position -= @rte._rte.words[index].word.length
         index += 1
     return {word: index, pos: position}

@@ -155,22 +155,25 @@
   # Try to merge the given selection with this selection
   #
   # @param [Selection] selection the selection to merge to
-  # @param [Rte] rte a Rich text editor instance
   #
   # @example
   #   1                 2                   3
   #   [  left selection ][  right selection ]
   #    becomes
   #   [           right selection           ]
-  merge: (selection, rte) ->
-    if @.style != selection.style
+  merge: (selection) ->
+    if @ == selection
+      return
+    if @style != selection.style
       return false
     if @atLeftOf selection
       leftSel = @
       rightSel = selection
-    else
+    else if selection.atLeftOf @
       leftSel = selection
       rightSel = @
+    else
+      return

     # unbind words from left selection, remove it from selection list
     # expand the selection at right to the left end of previous left selection
@@ -179,8 +182,7 @@
     rightSel.left = leftSel.left

     rightSel.startPos = leftSel.startPos
-
-    rte.removeSel leftSel
+    @rte.removeSel leftSel

   # Unbind selection from word
   unbind: ->
@@ -189,6 +191,24 @@

     @left = null
     @right = null
+
+  # Unbind selection from word
+  bind: ->
+    @left.left.push(@)
+    @right.right.push(@)
+
+  # Clone the current selection and apply style
+  # @parameter [String] style the new style
+  clone: (style) ->
+    newSel = new Selection 0, 0, @rte, style
+    newSel.unbind()
+    newSel.startPos = @startPos
+    newSel.endPos = @endPos
+    newSel.left = @left
+    newSel.right = @right
+    newSel.bind()
+
+    newSel

 # Class describing the Rich Text Editor type
 #
@@ -444,39 +464,18 @@
     if not selection.isValid()
       throw new Error "Invalid selection"

-    # In case the selection already has a style which is different
-    if selection.style != style
-      selection = new Selection(selection.start, selection.end, style)
-
-    # If no style add it
-    if _.isUndefined selection.style
-      selection.style = style
+    selection.style = style or ""

     # Link the boundary words to selection
-    leftWord = @getWord selection.startPos.word
-    rightWord = @getWord selection.endPos.word
-
-    selection.left = leftWord
-    selection.right = rightWord
-
-    leftWord.left.push(selection)
-    rightWord.right.push(selection)
-
+    leftWord = selection.left
+    rightWord = selection.right
     # Merge left…
-    for tmpSelection in leftWord.right
-      tmpSelection.merge(selection, @)
+    for tmpSelection in leftWord.right when tmpSelection
+      tmpSelection.merge selection
     # …and right (only happens when selections are contiguous or overlapping
     # and have same style)
-    for tmpSelection in rightWord.left
-      tmpSelection.merge(selection, @)
-
-    # Try to merge with previous / next contiguous selection
-    {prevWord, position} = @_jump(selection.startPos, -1)
-    {nextWord, position} = @_jump(selection.endPos,   +1)
-    for s in prevWord.selections when selection.style == s.style
-      s.merge selection, @
-    for s in nextWord.selections when selection.style == s.style
-      s.merge selection, @
+    for tmpSelection in rightWord.left when tmpSelection
+      tmpSelection.merge selection

   # Apply a delta to the object
   # @see http://quilljs.com/docs/deltas/


make archive and unarchive parallelLimit to speed it up

--- services/docstore/app/coffee/DocArchiveManager.coffee
+++ services/docstore/app/coffee/DocArchiveManager.coffee
@@ -20,7 +20,7 @@
 			jobs = _.map docs, (doc) ->
 				(cb)->
 					DocArchive.archiveDoc project_id, doc, cb
-			async.series jobs, callback
+			async.parallelLimit jobs, 5, callback

 	archiveDoc: (project_id, doc, callback)->
@@ -52,7 +52,7 @@
 						return cb()
 					else
 						DocArchive.unarchiveDoc project_id, doc._id, cb
-			async.series jobs, callback
+			async.parallelLimit jobs, 5, callback

 	unarchiveDoc: (project_id, doc_id, callback)->
 		logger.log project_id: project_id, doc_id: doc_id, "getting doc from s3"


can just return the function itself

can just return the function itself

--- resources/assets/coffee/_classes/user-card.coffee
+++ resources/assets/coffee/_classes/user-card.coffee
@@ -70,8 +70,7 @@

     options =
       events:
-        render: ->
-          reactTurbolinks.boot()
+        render: reactTurbolinks.boot
       style:
         def: false
         tip: false


Client: Teams: Updated inviteMembers handler to prevent sending invitation to yourself.

--- client/admin/lib/views/invitations/invitesomeoneview.coffee
+++ client/admin/lib/views/invitations/invitesomeoneview.coffee
@@ -9,6 +9,7 @@
 KDNotificationView  = kd.NotificationView
 showError           = require 'app/util/showError'
 InvitationInputView = require './invitationinputview'
+whoami              = require 'app/util/whoami'

 module.exports = class InviteSomeoneView extends KDView

@@ -75,24 +76,30 @@
     invites = []
     admins  = []

-    for view in @inputViews
-      value = view.email.getValue().trim()
-
-      continue  unless value
-
-      result = if not value then no else view.email.validate()
-
-      if value and not result
-        showError 'That doesn\'t seem like a valid email address.'
-        return view.email.setClass 'validation-error'
-
-      invites.push invite = view.serialize()
-      admins.push invite.email  if invite.role is 'admin'
-
-    if admins.length
-      @notifyAdminInvites invites, admins
-    else
-      @handleInvitationRequest invites
+    whoami().fetchEmail (err, email) =>
+
+      for view in @inputViews
+        value = view.email.getValue().trim()
+
+        continue  unless value
+
+        result = if not value then no else view.email.validate()
+
+        if value is email
+          showError 'You can not invite yourself!'
+          return view.email.setClass 'validation-error'
+
+        if value and not result
+          showError 'That doesn\'t seem like a valid email address.'
+          return view.email.setClass 'validation-error'
+
+        invites.push invite = view.serialize()
+        admins.push invite.email  if invite.role is 'admin'
+
+      if admins.length
+        @notifyAdminInvites invites, admins
+      else
+        @handleInvitationRequest invites

   notifyAdminInvites: (invites, admins) ->


When the editor view closes, destroy the editor.

Otherwise Aloha.editables grows with stale editors.

--- scripts/views/workspace/content/aloha-edit.coffee
+++ scripts/views/workspace/content/aloha-edit.coffee
@@ -55,6 +55,9 @@
       # at all.
       $(Aloha, 'body').off 'aloha-smart-content-changed.updatemodel'

+      # Kill old editable, otherwise they accumulate on Aloha.editables.
+      Aloha.getEditableById(@$el.attr('id')).destroy()
+
     onRender: () ->
       # update model after the user has stopped making changes


also delete doc ops

also delete doc ops

--- services/docstore/app/coffee/HealthChecker.coffee
+++ services/docstore/app/coffee/HealthChecker.coffee
@@ -36,8 +36,9 @@
 						cb()
 					else
 						cb("health check lines not equal #{body.lines} != #{lines}")
+			(cb)->
+				db.docs.remove {_id: doc_id, project_id: project_id}, cb
+			(cb)->
+				db.docOps.remove {doc_id: doc_id}, cb
 		]
-		async.series jobs, (err)->
-			if err?
-				callback(err)
-			db.docs.remove {_id: doc_id, project_id: project_id}, callback
+		async.series jobs, callback


Use NotificationCenter instead of alerts

--- core/app/backbone/views/wheel/interactive_wheel_view.coffee
+++ core/app/backbone/views/wheel/interactive_wheel_view.coffee
@@ -22,7 +22,7 @@
         # TODO: This is not a proper undo. Should be restored to the current
         #       state when the request fails.
         @turnOffActiveOpinionType opinion_type
-        alert "Something went wrong while setting your opinion on the Factlink, please try again"
+        FactlinkApp.NotificationCenter.error "Something went wrong while setting your opinion on the Factlink, please try again."

   unsetActiveOpinionType: (fact_id, opinion_type) ->
     @turnOffActiveOpinionType opinion_type
@@ -36,7 +36,7 @@

       error: =>
         @turnOnActiveOpinionType opinion_type
-        alert "Something went wrong while removing your opinion on the Factlink, please try again"
+        FactlinkApp.NotificationCenter.error "Something went wrong while removing your opinion on the Factlink, please try again."

   turned_off_topinion_types: ->
     believe:    is_user_opinion: false


fixed coffeelint errors on workers/social/lib/social/models/payment/subscription.coffee

--- workers/social/lib/social/models/payment/subscription.coffee
+++ workers/social/lib/social/models/payment/subscription.coffee
@@ -1,3 +1,4 @@
+# coffeelint: disable=no_implicit_braces
 jraphical   = require 'jraphical'
 payment     = require 'koding-payment'
 KodingError = require '../../error'
@@ -7,9 +8,9 @@

 module.exports = class JPaymentSubscription extends jraphical.Module

-  {secure, dash, daisy, signature} = require 'bongo'
-
-  {partition} = require 'bongo/lib/util'
+  { secure, dash, daisy, signature } = require 'bongo'
+
+  { partition } = require 'bongo/lib/util'

   JUser    = require '../user'
   JPayment = require './index'
@@ -88,7 +89,7 @@
         targetType  : this
         as          : 'linked subscription'

-  @fetchUserSubscriptions = secure ({ connection:{ delegate }}, callback) ->
+  @fetchUserSubscriptions = secure ({ connection:{ delegate } }, callback) ->
     delegate.fetchPaymentMethods (err, paymentMethods) =>
       return callback err  if err
       subscriptions = {}
@@ -99,7 +100,7 @@
           queue.fin()
       dash queue, -> callback null, subscriptions

-  @checkUserSubscription = secure ({connection:{delegate}}, planCode, callback)->
+  @checkUserSubscription = secure ({ connection:{ delegate } }, planCode, callback) ->
     throw Error 'reimplement this!'

   isOwnedBy: (account, callback) ->
@@ -172,17 +173,17 @@
   checkUsage: (product, multiplyFactor, callback) ->
     [callback, multiplyFactor] = [multiplyFactor, callback]  unless callback
     multiplyFactor ?= 1
-    {quantities} = product
+    { quantities } = product

     unless quantities?
       quantities = {}
       quantities[product.planCode] = 1

     spend = quantities
-    @checkQuota {@usage, @couponCode, spend, multiplyFactor}, callback
+    @checkQuota { @usage, @couponCode, spend, multiplyFactor }, callback

   checkQuota: (options, callback) ->
-    {usage, spend, couponCode, multiplyFactor} = options
+    { usage, spend, couponCode, multiplyFactor } = options
     multiplyFactor ?= 1
     spend ?= {}

@@ -216,7 +217,7 @@
       callback null, nonce.nonce

   debit: ({ pack, multiplyFactor, shouldCreateNonce }, callback) ->
-    return callback new KodingError "Your subscription is currently locked"  if @transactionLock
+    return callback new KodingError 'Your subscription is currently locked'  if @transactionLock

     multiplyFactor    ?= 1
     shouldCreateNonce ?= no
@@ -228,16 +229,16 @@
         if err
           callback err  if err
           @update $set: transactionLock: no, (err) ->
-            console.warn "Transaction lock reset failed"  if err
+            console.warn 'Transaction lock reset failed'  if err
           return

         { quantities } = pack

         op = $set: (Object.keys quantities).reduce( (memo, key) =>
-            memo["usage.#{ key }"] =
-              (@usage[key] ? 0) + quantities[key] * multiplyFactor
-            memo
-          , {})
+          memo["usage.#{ key }"] =
+            (@usage[key] ? 0) + quantities[key] * multiplyFactor
+          memo
+        , {})

         op.$set.transactionLock = no

@@ -245,7 +246,7 @@
           if err
             callback err  if err
             @update $set: transactionLock: no, (err) ->
-              console.warn "Transaction lock reset failed"  if err
+              console.warn 'Transaction lock reset failed'  if err
             return

           if shouldCreateNonce
@@ -258,13 +259,13 @@

     @isOwnedBy delegate, (err, hasTarget) =>
       return callback err  if err
-      return callback { message: 'Access denied!' }  unless hasTarget
+      return callback new KodingError 'Access denied!'  unless hasTarget

       options.shouldCreateNonce ?= yes

       @debit options, callback

-  credit: ({pack}, callback) ->
+  credit: ({ pack }, callback) ->
     @debit { pack, multiplyFactor: -1 }, callback

   credit$: secure (client, options, callback) ->
@@ -288,7 +289,7 @@

     queue = [
       =>
-        newPlan.checkQuota {@usage}, (err) -> queue.next err
+        newPlan.checkQuota { @usage }, (err) -> queue.next err
       ->
         newPlan.subscribe paymentMethodId, subOptions, (err, newSub) ->
           return callback err  if err
@@ -303,7 +304,7 @@
       ->
         account.addSubscription newSubscription, (err) -> queue.next err
       =>
-        {quantities} = newPlan
+        { quantities } = newPlan
         newSubscription.update $set: { @usage, quantities }, (err) -> queue.next err
       ->
         callback null, newSubscription
@@ -316,14 +317,14 @@
     options.subOptions =
       startsAt: @getEndDate()

-    if "nosync" not in @tags
+    if 'nosync' not in @tags
       options.operation = (continuation) =>
         @cancel continuation

     @applyTransition options, callback

   upgrade: (options, callback) ->
-    if "nosync" not in @tags
+    if 'nosync' not in @tags
       options.operation = (continuation) =>
         @terminate options.oldPlan, continuation

@@ -340,11 +341,11 @@
     , (err, paymentMethods) =>
       return callback err  if err
       unless paymentMethods?.length is 1
-        return callback { message: 'Unrecognized payment method!' }
+        return callback new KodingError 'Unrecognized payment method!'

       @isOwnedBy delegate, (err, hasTarget) =>
         return callback err  if err
-        return callback { message: 'Access denied!' }  unless hasTarget
+        return callback new KodingError 'Access denied!'  unless hasTarget

         oldPlan = null
         newPlan = null
@@ -371,31 +372,31 @@
           else
             @upgrade transitionOptions, callback

-  debitPack: ({tag, multiplyFactor}, callback) ->
+  debitPack: ({ tag, multiplyFactor }, callback) ->
     multiplyFactor ?= 1
     JPaymentPack = require './pack'
     JPaymentPack.one tags: $in: [tag], {}, (err, pack) =>
       return callback err  if err
-      return callback new KodingError "pack not found"  unless pack
-      @debit {pack, multiplyFactor}, callback
-
-  creditPack: ({tag, multiplyFactor}, callback) ->
+      return callback new KodingError 'pack not found'  unless pack
+      @debit { pack, multiplyFactor }, callback
+
+  creditPack: ({ tag, multiplyFactor }, callback) ->
     multiplyFactor ?= 1
     multiplyFactor *= -1
-    @debitPack {tag, multiplyFactor}, callback
+    @debitPack { tag, multiplyFactor }, callback

   @createFreeSubscription = (account, callback) ->
     JPaymentPlan = require './plan'
-    JPaymentPlan.one tags: "nosync", (err, plan) ->
+    JPaymentPlan.one tags: 'nosync', (err, plan) ->
       return \
         if err then callback err
-        else if not plan then callback new KodingError "nosync plan not found"
-
-      {planCode, quantities, tags} = plan
+        else if not plan then callback new KodingError 'nosync plan not found'
+
+      { planCode, quantities, tags } = plan
       freePlanSubscription = new JPaymentSubscription
         planCode   : planCode
         quantity   : 1
-        status     : "active"
+        status     : 'active'
         feeAmount  : 0
         quantities : quantities
         tags       : tags
@@ -406,11 +407,13 @@
           return callback new KodingError "couldn't add subscription to account: #{err}"  if err
           callback null, freePlanSubscription

-  @isFreeSubscripton:(planCode, callback)->
-    @one {planCode}, (err, subscription)->
+  @isFreeSubscripton:(planCode, callback) ->
+    @one { planCode }, (err, subscription) ->
       return callback err  if err
       return callback null, yes  unless subscription
       # default free vm for users
-      return callback null, yes  if "nosync" in subscription.tags
+      return callback null, yes  if 'nosync' in subscription.tags
       # if none of the above, they are not free
       return callback null, no
+
+


FIX: Amend incorrect template path for file uploader

--- src/app/common/file-uploader/file-uploader.coffee
+++ src/app/common/file-uploader/file-uploader.coffee
@@ -3,7 +3,7 @@
 .directive 'fileUploader', ->
   restrict: 'E'
   replace: true
-  templateUrl: 'common/partials/templates/file-uploader.tpl.html'
+  templateUrl: 'common/file-uploader/file-uploader.tpl.html'
   scope:
     # Files map a key (file name to be uploaded) to a value (containing a
     # a display name, and the type of file that is to be accepted, where


Simulation changes to support accumulator and transfer links - transfer-modifier links are used to update flow nodes in first (settling) pass - accumulator links are not considered in the first (settling) pass - in first (settling) pass, multiple inputs are combined using limiting factor average if the node's output is to a collector [#136566677] - accumulator links are scaled by 100 by default [#136566739] - transfer links are scaled by 100 if they do not have a transfer-modifier

--- src/code/models/simulation.coffee
+++ src/code/models/simulation.coffee
@@ -7,54 +7,52 @@
   else
     return val

+combineInputs = (inValues, useScaledProduct) ->
+  return null if not inValues?.length
+  return inValues[0] if inValues.length is 1
+
+  if useScaledProduct
+    # scaled product is computed as (n1 * n2 * ...) / 100^(n-1)
+    numerator = _.reduce(inValues, ((prod, value) -> prod * value), 1)
+    denominator = Math.pow(100, inValues.length - 1)
+  else
+    # simple arithmetic mean
+    numerator = _.reduce(inValues, ((sum, value) -> sum + value), 0)
+    denominator = inValues.length
+
+  numerator / denominator
+
 RangeIntegrationFunction = (incrementAccumulators) ->

   # if we've already calculated a currentValue for ourselves this step, return it
-  if @currentValue
-    return @currentValue
-  if @isAccumulator and not incrementAccumulators
-    return @previousValue
-
-  links = @inLinks('range')
+  return @currentValue if @currentValue?
+  return @previousValue if @isAccumulator and not incrementAccumulators
+
+  # regular nodes and flow nodes only have 'range' and 'transfer-modifier' links
+  links = @inLinks('range').concat(@inLinks('transfer-modifier'))
   count = links.length
-  nextValue = 0
+
+  # if we have no incoming links, we always remain our previous or initial value
+  # collectors aren't calculated in this phase, but they do capture initial/previous values
+  startValue = if @previousValue? then @previousValue else @initialValue
+  return startValue if @isAccumulator or count < 1
+
   value = 0
-
-  # if we have no incoming links, we always remain our previous or initial value
-  if count < 1
-    return @previousValue or @initialValue
-
-  if @isAccumulator
-    value = if @previousValue? then @previousValue else @initialValue            # start from our last value
-    _.each links, (link) =>
-      return unless link.relation.isDefined
-      sourceNode = link.sourceNode
-      inV = sourceNode.previousValue
-      return unless inV               # we simply ignore nodes with no previous value
-      outV = @previousValue or @initialValue
-
-      inV = scaleInput(inV, sourceNode, this)
-
-      # Here we set maxIn to zero because we want to substract inV when
-      # we are in a `(maxIn - inV)` formula for accumulators (decreases by)
-      # Need a better long-term solution for this.
-      nextValue = link.relation.evaluate(inV, outV, 0)
-      value += nextValue
-  else
-    _.each links, (link) =>
-      if not link.relation.isDefined
-        count--
-        return
-      sourceNode = link.sourceNode
-      inV = if sourceNode.previousValue? then sourceNode.previousValue else sourceNode.initialValue
-      inV = scaleInput(inV, sourceNode, this)
-      outV = @previousValue or @initialValue
-      nextValue = link.relation.evaluate(inV, outV, link.sourceNode.max, @max)
-      value += nextValue
-    if count >= 1
-      value = value / count
-    else
-      value = @previousValue or @initialValue       # we had no defined inbound links
+  inValues = []
+
+  _.each links, (link) =>
+    return unless link.relation.isDefined
+    sourceNode = link.sourceNode
+    inV = if sourceNode.previousValue? then sourceNode.previousValue else sourceNode.initialValue
+    inV = scaleInput(inV, sourceNode, this)
+    outV = startValue
+    inValues.push link.relation.evaluate(inV, outV, link.sourceNode.max, @max)
+
+  # for now, if any a node points to a collector, it should use the scaled product
+  # ultimately this should be a default which can be overridden by the user, in
+  # which case the setting would presumably become part of the node model
+  useScaledProduct = !!(_.find @outLinks(), (link) -> link.targetNode.isAccumulator)
+  value = combineInputs(inValues, useScaledProduct)

   # if we need to cap, do it at end of all calculations
   value = @filterFinalValue value
@@ -62,37 +60,36 @@
   value

 SetAccumulatorValueFunction = (nodeValues) ->
-  # get add, subtract and transfer links
-  links = @inLinks('accumulator').concat(@inLinks('transfer'))
+  # collectors only have accumulator and transfer links
+  links = @inLinks('accumulator').concat(@inLinks('transfer')).concat(@outLinks('transfer'))
   return unless links.length > 0

-  value = 0
+  linkScaleFactor = 100
+
+  deltaValue = 0
   for link in links
     {sourceNode, relation, transferNode} = link
     inV = nodeValues[sourceNode.key]
     switch relation.type
       when 'accumulator'
         # TODO: check if outV is really equal to inV here - I'm not clear what outV is in this case'
-        value += relation.evaluate(inV, inV, sourceNode.max, @max)
+        deltaValue += relation.evaluate(inV, inV, sourceNode.max, @max) / linkScaleFactor

       when 'transfer'
-        # TODO: check with Dan if the transfer value really is meant to be a percentage
         transferValue = nodeValues[transferNode.key]
-        transferPercentage = transferValue / Math.max(transferNode.max, transferValue)
-        for modifierLink in transferNode.inLinks('transfer-modifier')
-          transferPercentage *= modifierLink.relation.evaluate(inV, inV, sourceNode.max, transferNode.max)
-
-        finalTransferValue = inV * transferPercentage
-        value += finalTransferValue
-        # TODO: check with Dan if we take 1/100th of the value or 1/Math.pow(100,links.length)
-        sourceValue = sourceNode.currentValue - (finalTransferValue / 100)
-        sourceNode.currentValue = sourceNode.filterFinalValue sourceValue
-
-  delta = value / Math.pow(100, links.length)
+        # transfer values are scaled unless they have a modifier
+        if not transferNode.inLinks('transfer-modifier').length
+          transferValue /= linkScaleFactor
+
+        if link.sourceNode is @
+          deltaValue -= transferValue
+        else if link.targetNode is @
+          deltaValue += transferValue
+
   # accumulators hold their values in previousValue which is confusing
   # (this done because the accumulator values is only computed on the first of the 20 loops in RangeIntegrationFunction)
   # TODO: possibly change RangeIntegrationFunction function to make this more clear
-  @currentValue = @filterFinalValue @previousValue + delta
+  @currentValue = @filterFinalValue @previousValue + deltaValue

 module.exports = class Simulation

@@ -168,26 +165,26 @@
     # integration function on each node that only pulls values from immediate parents.
     # Note that this "pushing" may not do anything of value in a closed loop, as the values
     # will simply move around the circle.
-    # We then run the simulation an additional 20 times, and the average the 20 results to
+    # We then run the simulation an additional 20 times, and average the 20 results to
     # obtain a final value.
-    # The number "20" used is arbitrary, but large enough not to affect loops the we expect
+    # The number "20" used is arbitrary, but large enough not to affect loops that we expect
     # to see in Sage. In any loop, if the number of nodes in the loop and the number of times
-    # we iterate are not dividible by each other, we'll see imbalances, but the effect of the
+    # we iterate are not divisible by each other, we'll see imbalances, but the effect of the
     # imbalance is smaller the more times we loop around.

     # Changes to accomodate data flows:
     #
-    # There are now two types of nodes: normal and transfer and four types of links: range,
-    # accumulator, transfer and transfer-modifier.  Transfer nodes are created automatically
+    # There are now three types of nodes: normal, collector, and transfer and four types of links:
+    # range, accumulator, transfer and transfer-modifier.  Transfer nodes are created automatically
     # between two accumulator nodes when the link type is set to transfer and are automatically
-    # removed is the link is changed away from transfer or either of the nodes in the link
+    # removed if the link is changed away from transfer or either of the nodes in the link
     # is changed from not being an accumulator.  Range links are the type of the original links -
     # they are pure functions that transmit a value from a source (domain) to a target (range) node
     # and are the only links evaluated during the 20 step cumulative value calculation.
-    # Once each node's cummulative value obtained and then averaged across the nodes the accumulator
+    # Once each node's cumulative value is obtained and then averaged across the nodes, the accumulator
     # values are updated by checking the accumulator and transfer links into any accumulator node.
     # The transfer links values are then modified by the transfer-modifier links which are links
-    # from the source node of a transfer link to the tranfer node of the transfer link.
+    # from the source node of a transfer link to the transfer node of the transfer link.

     step = =>
       # push values down chain
@@ -207,9 +204,9 @@
         node._cumulativeValue = 0

       # set the accumulator values
-      _.each @nodes, (node) ->
-        if node.isAccumulator
-          node.setAccumulatorValue nodeValues
+      collectorNodes = _.filter @nodes, (node) -> node.isAccumulator
+      _.each collectorNodes, (node) ->
+        node.setAccumulatorValue nodeValues

       time++
       @generateFrame(time)


redirect to /i/ university

redirect to /i/ university

--- services/web/app/coffee/Features/StaticPages/UniversityController.coffee
+++ services/web/app/coffee/Features/StaticPages/UniversityController.coffee
@@ -1,74 +1,16 @@
-request = require("request")
 settings = require("settings-sharelatex")
 logger = require("logger-sharelatex")
-_ = require("underscore")
-ErrorController = require "../Errors/ErrorController"
-StaticPageHelpers = require("./StaticPageHelpers")
-sanitize = require('sanitizer')
 Settings = require("settings-sharelatex")
-contentful = require('contentful')
-marked = require("marked")
 sixpack = require("../../infrastructure/Sixpack")
-AuthenticationController = require '../Authentication/AuthenticationController'

 module.exports = UniversityController =

 	getPage: (req, res, next)->
-		url = req.url?.toLowerCase()
-		universityUrl = "#{settings.apis.university.url}#{url}"
-		if StaticPageHelpers.shouldProxy(url)
-			return UniversityController._directProxy universityUrl, res
-
-		logger.log url:url, "proxying request to university api"
-		request.get universityUrl, (err, r, data)->
-			if r?.statusCode == 404
-				return UniversityController.getContentfulPage(req, res, next)
-			if err?
-				return res.send 500
-			data = data.trim()
-			try
-				data = JSON.parse(data)
-				data.content = data.content.replace(/__ref__/g, sanitize.escape(req.query.ref))
-			catch err
-				logger.err err:err, data:data, "error parsing data from data"
-			res.render "university/university_holder", data
-
+		url = req.url?.toLowerCase().replace(".html","")
+		return res.redirect("/i/#{url}")

 	getIndexPage: (req, res)->
-		user_id = AuthenticationController.getLoggedInUserId(req)
-		client = sixpack.client(user_id?.toString() || req.ip)
-		client.participate 'instapage-pages', ['default', 'instapage'], (err, response)->
-			if response?.alternative?.name == "instapage"
-				return res.redirect("/i/university")
-			else
-				req.url = "/university/index.html"
-				UniversityController.getPage req, res
+		return res.redirect("/i/university")

-	_directProxy: (originUrl, res)->
-		upstream = request.get(originUrl)
-		upstream.on "error", (error) ->
-			logger.error err: error, "university proxy error"
-		upstream.pipe res
-
-	getContentfulPage: (req, res, next)->
-		console.log Settings.contentful
-		if !Settings.contentful?.uni?.space? and !Settings.contentful?.uni?.accessToken?
-			return ErrorController.notFound(req, res, next)
-
-		client = contentful.createClient({
-			space: Settings.contentful?.uni?.space
-			accessToken: Settings.contentful?.uni?.accessToken
-		})
-
-		url = req.url?.toLowerCase().replace("/university/","")
-		client.getEntries({content_type: 'caseStudy', 'fields.slug':url})
-			.catch (e)->
-				return res.send 500
-			.then (entry)->
-				if !entry? or !entry.items? or entry.items.length == 0
-					return ErrorController.notFound(req, res, next)
-				viewData = entry.items[0].fields
-				viewData.html = marked(viewData.content)
-				res.render "university/case_study", viewData:viewData


suddenly, a wild max-height appears

suddenly, a wild max-height appears

--- app/assets/javascripts/main.coffee
+++ app/assets/javascripts/main.coffee
@@ -45,6 +45,12 @@
               else
                 (max_width / width) * height

+  if max_height > 300
+    old_max_height = max_height
+    max_height = 300
+    max_width = (height / old_max_height) * max_width
+
+  #console.log "width: #{width} mw: #{max_width} height: #{height} mh: #{max_height}"
   result = result.replace(/
]*>/,
                           "
")
   result


inverted controls

inverted controls

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -239,11 +239,11 @@
 			cam.move [0,0,-moveValueStrafe]

 		#Rotate up
+		if keyboard.isKeyDown(KEY_DOWN)
+			cam.pitch moveValueRotate
+
+		#Rotate down
 		if keyboard.isKeyDown(KEY_UP)
-			cam.pitch moveValueRotate
-
-		#Rotate down
-		if keyboard.isKeyDown(KEY_DOWN)
 			cam.pitch -moveValueRotate

 		#Rotate right


Fix minor bug on app.react.Gesture.

Fix minor bug on app.react.Gesture.

--- app/client/js/react/gesture.coffee
+++ app/client/js/react/gesture.coffee
@@ -89,7 +89,7 @@
         props.onKeyUp = (e) =>
           onKeyUp e if onKeyUp
           # Ignore unfocusable tab index.
-          return if @getDOMNode().tabIndex == -1
+          return if e.target.tabIndex == -1
           # Enter/backspace to dispatch tap.
           # TODO: Wait for polymer-gestures fix.
           if e.key in ['Enter', ' ']
@@ -146,7 +146,7 @@
       onTap: (e) ->
         @props.onTap e if @props.onTap
         @showCoverTemporarily_()
-        if @getDOMNode().tagName == 'A'
+        if e.target.tagName == 'A'
           @delegateAnchorTapToRoutingClickHandler_ e
         return


Correct input label to be "enrollment code"

--- src/course/invite-code-input.cjsx
+++ src/course/invite-code-input.cjsx
@@ -40,8 +40,8 @@

       <ErrorList course={@props.course} />

-        <BS.Input type="text" ref="input" label="Course invitation code"
-          placeholder="Enter code" autoFocus
+        <BS.Input type="text" ref="input" label="Enter the two-word enrollment code"
+          placeholder="enrollment code" autoFocus
           onKeyPress={@onKeyPress}
           buttonAfter={button} />


Select: Fixed a bug where options were instances of CUI.Icon and they were not being shown correctly in the ItemList

--- src/elements/Select/Select.coffee
+++ src/elements/Select/Select.coffee
@@ -272,7 +272,10 @@
 			# console.warn "Select.displayValue", @getUniqueId(), @getData(), @getName(), @getValue()
 			if found_opt
 				if found_opt.icon
-					@__checkbox.setIcon(found_opt.icon)
+					if found_opt.icon instanceof CUI.Icon
+						@__checkbox.setIcon(found_opt.icon.copy())
+					else
+						@__checkbox.setIcon(found_opt.icon)
 				else
 					@__checkbox.setIcon(null)


Remote unused Controller#currentId.

Remote unused Controller#currentId.

--- src/chaplin/controllers/controller.coffee
+++ src/chaplin/controllers/controller.coffee
@@ -14,7 +14,6 @@
     _(@prototype).extend EventBroker

     view: null
-    currentId: null

     # Internal flag which stores whether `redirectTo`
     # was called in the current action
@@ -63,7 +62,7 @@
       @unsubscribeAllEvents()

       # Remove properties which are not disposable
-      properties = ['currentId', 'redirected']
+      properties = ['redirected']
       delete this[prop] for prop in properties

       # Finished


Remove debug log statement

Remove debug log statement

--- lib/assets/javascripts/views/editable_multiselect.js.coffee
+++ lib/assets/javascripts/views/editable_multiselect.js.coffee
@@ -10,8 +10,6 @@

     value = @input_el.value
     @input_el.value = ""
-
-    console.log('Add value', value)

     option_el = document.createElement('option')
     option_el.value = value


In smartphones, in the edit registration view, when clicking the top-left navbar button the user is directed to the read view (with the sidebar visible by default).

--- app/assets/javascripts/ng-controllers/ng-DeviseCtrl.js.coffee
+++ app/assets/javascripts/ng-controllers/ng-DeviseCtrl.js.coffee
@@ -3,11 +3,26 @@
 ########################################################

 angular.module('feedbunch').controller 'DeviseCtrl',
-['timerFlagSvc', (timerFlagSvc)->
+['$scope', '$window', 'timerFlagSvc', 'sidebarVisibleSvc',
+($scope, $window, timerFlagSvc, sidebarVisibleSvc)->
+
+  #--------------------------------------------
+  # APPLICATION INITIALIZATION
+  #--------------------------------------------
+
+  # The top-left navbar button in smarthpones will be a link to the read view
+  sidebarVisibleSvc.set false

   # If there is a devise alert, show it and close it after 5 seconds
   timerFlagSvc.start 'error_devise'

   # If there is a rails alert, show it and close it after 5 seconds
   timerFlagSvc.start 'error_rails'
+
+  #--------------------------------------------
+  # When clicking on the top-left navbar button (only visible in smartphone-sized viewports),
+  # go to the read view, with the sidebar visible by default
+  #--------------------------------------------
+  $scope.toggle_sidebar_visible = ->
+    $window.location.href = '/read'
 ]


doc & typo

doc & typo

--- app/views/CNeditor/CNeditor.coffee
+++ app/views/CNeditor/CNeditor.coffee
@@ -835,7 +835,10 @@
         pop.segments = segments
         pop.style.left = edges.left + 'px'
         pop.style.top = edges.top + 20 + 'px'
-        pop.urlInput.value = segments[0].href
+        href = segments[0].href
+        if href == '' or href == 'http:///'
+            href = 'http://'
+        pop.urlInput.value = href
         txt = ''
         txt += seg.textContent for seg in segments
         pop.textInput.value = txt
@@ -896,7 +899,7 @@
                 cont   : segments[l-1].firstChild
                 offset : segments[l-1].firstChild.length
             bps = [bp1,bp2]
-            @_applyAhrefToSegments(segments[0], segments[l-1], bps, false, 'A', '')
+            @_applyAhrefToSegments(segments[0], segments[l-1], bps, false, '')
             # Position selection
             rg = document.createRange()
             bp1 = bps[0]
@@ -1075,7 +1078,9 @@

         # 2- decide if we apply metaData or remove it
         # For this we go throught each line and each selected segment to check
-        # if metaData is applied or not.
+        # if metaData is applied or not. For instance if all segments are strong
+        # the action is to un-strongify. If one segment is not bold, then the
+        # action is to strongify.
         isAlreadyMeta = true
         for range in linesRanges
             isAlreadyMeta = isAlreadyMeta \
@@ -1123,8 +1128,6 @@
             return @_checkIfAhrefIsEverywhere(range, others[0])
         else
             return @_checkIfCSSIsEverywhere(range,meta,)
-
-

     _checkIfCSSIsEverywhere : (range, CssClass) ->
         # Loop  on segments to decide wich action is to be done on all
@@ -1143,13 +1146,7 @@
                 segment  = segment.nextSibling
                 stopNext = (segment == endSegment)

-
-
     _checkIfAhrefIsEverywhere : (range, href) ->
-        # Loop  on segments to decide wich action is to be done on all
-        #    segments. For instance if all segments are strong the action is
-        #    to un-strongify. If one segment is not bold, then the action is
-        #    to strongify.
         segment    = range.startContainer.parentNode
         endSegment = range.endContainer.parentNode
         stopNext   = (segment == endSegment)
@@ -1275,7 +1272,7 @@
         # 4- apply the required style
         if metaData == 'A'
             bps = [bp1,bp2]
-            @_applyAhrefToSegments(startSegment, endSegment, bps, addMeta, metaData, others[0])
+            @_applyAhrefToSegments(startSegment, endSegment, bps, addMeta, others[0])
         else
             @_applyCssToSegments(startSegment, endSegment, addMeta, metaData)

@@ -1285,14 +1282,35 @@
         return [bp1,bp2]

+    ###*
+     * Test if a segment already has the meta : same type, same class and other
+     * for complex meta (for instance href for <a>)
+     * @param  {element}  segment  The segment to test
+     * @param  {string}  metaData the type of meta data : A or a CSS class
+     * @param  {array}  others   An array of the other parameter of the meta,
+     *                           for instance si metaData == 'A',
+     *                           others[0] == href
+     * @return {Boolean}          True if the segment already have the meta data
+    ###
     _isAlreadyMeta : (segment, metaData, others) ->
         if metaData == 'A'
             return segment.nodeName == 'A' && segment.href == others[0]
         else
             return segment.classList.contains(metaData)

-
-    _applyAhrefToSegments : (startSegment, endSegment, bps, addMeta, metaData, href) ->
+    ###*
+     * Applies or remove a meta data of type "A" (link) on a succession of
+     * segments (from startSegment to endSegment which must be on the same line)
+     * @param  {element} startSegment The first segment to modify
+     * @param  {element} endSegment   The last segment to modify (must be in the
+     *                                same line as startSegment)
+     * @param  {Array} bps          [{cont,offset}...] An array of breakpoints
+     *                              to update if their container is modified
+     *                              while applying the meta data.
+     * @param  {Boolean} addMeta      True to apply the meta, False to remove
+     * @param  {string} href         the href to use if addMeta is true.
+    ###
+    _applyAhrefToSegments : (startSegment, endSegment, bps, addMeta, href) ->
         segment  =  startSegment
         stopNext = (segment == endSegment)
         loop
@@ -1326,6 +1344,15 @@
         return null

+    ###*
+     * Applies or remove a CSS class to a succession of segments (from
+     * startsegment to endSegment which must be on the same line)
+     * @param  {element} startSegment The first segment to modify
+     * @param  {element} endSegment   The last segment to modify (must be in the
+     *                                same line as startSegment)
+     * @param  {Boolean} addMeta      True to apply the meta, False to remove
+     * @param  {String} cssClass     The name of the CSS class to add or remove
+    ###
     _applyCssToSegments : (startSegment, endSegment, addMeta, cssClass) ->
         segment  =  startSegment
         stopNext = (segment == endSegment)


added (recursive) variable assignment/expansion

--- src/lib/parser/mist-parser.coffee
+++ src/lib/parser/mist-parser.coffee
@@ -15,15 +15,23 @@
 inspect = (v)->console.log require('util').inspect v, colors: on, depth: null

 expand = (str, vars)->
-  str.replace /\$\(\s*([a-z0-9_]+)\s*\)/gi, (m, name, offset)->
-    if not vars[name]? then throw {
-      message: "variable not defined: #{name}"
-      column: offset + 3 # +1 for 1-based offset, +2 for $(
-    }
-    return vars[name]
+  perform = ->
+    str = str.replace /\$\(\s*([a-z0-9_]+)\s*\)/gi, (m, name, offset)->
+      if not vars[name]? then throw {
+        message: "variable not defined: #{name}"
+        column: offset + 3 # +1 for 1-based offset, +2 for $(
+      }
+      return vars[name]
+  loop
+    break if str is perform()
+  return str

 doVariableAssignment = (line, enabled, vars)->
-
+  m = line.match /^[\s\t]*([a-z0-9_]+)[\s\t]*(\+?=)[\s\t]*(.+?)[\s\t]*$/i
+  if m
+    switch m[2]
+      when '=' then vars[m[1]] = m[3]
+      when '+=' then vars[m[1]] = "#{vars[m[1]]} #{m[3]}"
   return line

 filters = [


transforming the teacher content toggle into a button

--- src/components/reference-book/navbar.cjsx
+++ src/components/reference-book/navbar.cjsx
@@ -31,9 +31,9 @@

   renderTeacher: ->
     <BS.Nav navbar right>
-      <BS.NavItem className="teacher-edition" onClick={@props.showTeacherEdition}>
+      <BS.Button className="teacher-edition" onClick={@props.showTeacherEdition}>
         {@props.teacherLinkText}
-      </BS.NavItem>
+      </BS.Button>
     </BS.Nav>

   render: ->


wrap controls and use own markup for classname

--- src/components/reference-book/navbar.cjsx
+++ src/components/reference-book/navbar.cjsx
@@ -23,12 +23,17 @@
     {section, ecosystemId} = @props
     title = ReferenceBookStore.getPageTitle({section, ecosystemId})

-    <BS.Nav navbar>
-

+

+

         <ChapterSection section={section} />
+

+

         {title}
-

-    </BS.Nav>
+

+

+
+  wrapControl: (element, i) ->
+
{element}

   render: ->
     <BS.Navbar fixedTop fluid>
@@ -44,9 +49,9 @@
       </BS.Nav>
       {@renderSectionTitle()}
       <BS.Nav className='full-width-only' navbar right>
+        {React.Children.map(@props.extraControls, @wrapControl)}

       </BS.Nav>
-      {@props.extraControls}
     </BS.Navbar>


added custom key mapping for movetableft and movetabright to fix #1100

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -161,6 +161,8 @@
   "J": "previousTab"
   "gt": "nextTab"
   "gT": "previousTab"
+  "<<": "moveTabLeft"
+  ">>": "moveTabRight"
   "g0": "firstTab"
   "g$": "lastTab"


Focus draggable on click or drop

Focus draggable on click or drop

So we can work with mouse AND keyboard.

eg: click, then arrow keys.
or: click, drag, drop, then arrow keys.

Also, dragging will focus immediately.

--- backend/app/assets/javascripts/spree/backend/taxons.js.coffee
+++ backend/app/assets/javascripts/spree/backend/taxons.js.coffee
@@ -12,6 +12,9 @@
   lowerDraggable = (draggable) ->
     draggable.next().insertBefore(draggable)
     sortupdate(draggable)
+
+  focusDraggable = (e) ->
+    $(e.srcElement).focus()

   moveDraggable = (e) ->
     if e.keyCode == $.ui.keyCode.UP
@@ -30,8 +33,11 @@

   sortable = $('#taxon_products').sortable()
     .on
+      sortstart: focusDraggable
+      sortstop: focusDraggable
       sortupdate: saveSort
     .on
+      click: focusDraggable
       keydown: moveDraggable
     , '.sort_item'


ListViewTree

ListViewTree

- Click event on nodes should only be stopped if an
  action was performed.

--- src/elements/ListView/ListViewTree.coffee
+++ src/elements/ListView/ListViewTree.coffee
@@ -161,10 +161,12 @@
 			handle = CUI.DOM.matchSelector(row, ".cui-tree-node-handle")[0]
 			if handle.hasClass("cui-tree-node-is-closed") # and not $target.hasClass("no-children")
 				if prep_target(ev, info, handle)
+					ev.stopPropagation()
 					run_trigger(ev, info, "open", node)
 					return
 			else if handle.hasClass("cui-tree-node-is-open")
 				if prep_target(ev, info, handle)
+					ev.stopPropagation()
 					run_trigger(ev, info, "close", node)
 					return
 			return
@@ -191,7 +193,6 @@
 					return

 				if CUI.DOM.closest(target, ".cui-tree-node-handle")
-					ev.stopPropagation()
 					action_on_handle(ev, info, row, node)

 				return
@@ -228,7 +229,6 @@
 				if isPromise(ret)
 					ret.fail =>
 						# if the node did not select, we try the handle
-						ev.stopPropagation()
 						action_on_handle(ev, info, row, node)

 				return


Client: Sidebar: Feed: Deleted showPopularMessages prop

--- client/activity/lib/components/publicfeedpane/feedpane.coffee
+++ client/activity/lib/components/publicfeedpane/feedpane.coffee
@@ -17,7 +17,6 @@
     isDataLoading       : no
     onInviteOthers      : kd.noop
     showItemMenu        : yes
-    showPopularMessages : no

   componentDidMount: ->
@@ -64,9 +63,7 @@
         className="FeedThreadPane-header"
         thread={@props.thread} />
       <FeedInputWidget thread={@props.thread} />
-      <FeedPaneTabContainer
-        thread={@props.thread}
-        showPopularMessages={@props.showPopularMessages} />
+      <FeedPaneTabContainer thread={@props.thread}/>
       <FeedList
         isMessagesLoading={@isThresholdReached}
         messages={@getMessages()} />


Client: Activity: Feed: Added groupifyLink to fix conflict problems

--- client/activity/lib/components/publicfeedpane/feedpane.coffee
+++ client/activity/lib/components/publicfeedpane/feedpane.coffee
@@ -12,6 +12,7 @@
 KDReactorMixin       = require 'app/flux/base/reactormixin'
 ImmutableRenderMixin = require 'react-immutable-render-mixin'
 ActivitySharePopup   = require 'activity/components/activitysharepopup'
+groupifyLink         = require 'app/util/groupifyLink'

 module.exports = class FeedPane extends React.Component


Removed introId from make a new app button.

--- client/app/Applications/StartTab.kdapplication/AppView.coffee
+++ client/app/Applications/StartTab.kdapplication/AppView.coffee
@@ -45,7 +45,6 @@
       icon        : yes
       iconClass   : "plus-black"
       title       : "Make a new App"
-      introId     : "makeANewApp"
       callback    : =>
         @appsController.makeNewApp()


reverses pagination so UI doesn't appear to start at the end

--- client/controllers/eventMap.coffee
+++ client/controllers/eventMap.coffee
@@ -9,8 +9,8 @@
   @pageNum = new ReactiveVar(0)
   @eventsPerPage = 8
   @templateEvents = new ReactiveVar null
-  @disablePrev = new ReactiveVar false
-  @disableNext = new ReactiveVar true
+  @disablePrev = new ReactiveVar true
+  @disableNext = new ReactiveVar false
   @selectedEvents = new Meteor.Collection null

 Template.eventMap.onRendered ->
@@ -80,8 +80,8 @@
           eventIndex += 1

     instance.templateEvents.set templateEvents
-    instance.disablePrev.set if eventIndex < filteredEvents?.length then false else true
-    instance.disableNext.set if currentPage is 0 then true else false
+    instance.disableNext.set if eventIndex < filteredEvents?.length then false else true
+    instance.disablePrev.set if currentPage is 0 then true else false
     if instance.allMapMarkers
       map.removeLayer instance.allMapMarkers
     MapHelpers.addMarkersToMap map, instance, filteredMapLocations
@@ -122,11 +122,9 @@

 Template.eventMap.events
   "click .event-list-next:not('.disabled')": (event, template) ->
+    paginate template, 1
+  "click .event-list-prev:not('.disabled')": (event, template) ->
     paginate template, -1
-
-  "click .event-list-prev:not('.disabled')": (event, template) ->
-    paginate template, 1
-

 Template.markerPopup.helpers
   getEvents: () ->


Additional test for multi-character separators - it just works

--- coffee/spec/calculator.spec.coffee
+++ coffee/spec/calculator.spec.coffee
@@ -21,6 +21,9 @@
   it "should return 10 for '//;\\n1;2;3;4'", ->
     expect(calculator.add '//;\n1;2;3;4').toEqual 10

+  it "should return 10 for '//***\n1***2***3***4", ->
+    expect(calculator.add '//***\n1***2***3***4').toEqual 10
+
   it "should throw an exception with a negative number", ->
     expect(-> calculator.add '2,-3,4').toThrow 'Negative numbers are not allowed: -3'


some fixes for register form

some fixes for register form

--- components/forms/KDFormView.coffee
+++ components/forms/KDFormView.coffee
@@ -11,7 +11,7 @@
         inputs = inputs.concat KDFormView.findChildInputs subView

     return inputs
-
+
   ###
   INSTANCE LEVEL
   ###
@@ -24,13 +24,13 @@
     @valid = null
     @setCallback options.callback
     @customData = {}
-
+
   childAppended:(child)->
     child.associateForm? @
     if child instanceof KDInputView
       @propagateEvent KDEventType: 'inputWasAdded', child
     super
-
+
   bindEvents:()->
     @getDomElement().bind "submit",(event)=>
       @handleEvent event
@@ -39,13 +39,13 @@
   setDomElement:()->
     cssClass = @getOptions().cssClass ? ""
     @domElement = $ "<form class='kdformview #{cssClass}'></form>"
-
+
   getCustomData:(path)->
     if path
       JsPath.getAt @customData, path
     else
       @customData
-
+
   addCustomData:(path, value)->
     if 'string' is typeof path
       JsPath.setAt @customData, path, value
@@ -55,39 +55,39 @@

   removeCustomData:(path)->
     path = path.split '.' if 'string' is typeof path
-    [pathUntil..., last] = path
+    [pathUntil..., last] = path
     isArrayElement = not isNaN +last
     if isArrayElement
       JsPath.spliceAt @customData, pathUntil, last
     else
       JsPath.deleteAt @customData, path
-
+
   serializeFormData:(data={})->
     for inputData in @getDomElement().serializeArray()
       data[inputData.name] = inputData.value
     data
-
+
   getData: ->
     formData = $.extend {},@getCustomData()

     @serializeFormData formData
-
+
     formData

   focusFirstElement:-> KDFormView.findChildInputs(@)[0].$().trigger "focus"
-
+
   setCallback:(callback)-> @formCallback = callback

   getCallback:()-> @formCallback
-
+
   reset:=> @$()[0].reset()
-
+
   submit:(event)=>

     if event
       event.stopPropagation()
       event.preventDefault()
-
+
     form                = @
     inputs              = KDFormView.findChildInputs @
     validationCount     = 0
@@ -122,7 +122,7 @@
             # check if all inputs were valid
             if validInputs.length is toBeValidatedInputs.length
               # put valid inputs to formdata
-              formData[input.getName()] = input.getValue() for input in validInputs
+              formData[input.getName()] = input.getValue() for input in validInputs
             else
               valid = no
             # tell validation was finished
@@ -130,5 +130,5 @@
       inputToBeValidated.validate null, event

     # if no validation is required mimic as all were validated
-    form.emit "FormValidationFinished" if toBeValidatedInputs.length is 0
+    form.emit "FormValidationFinished" if toBeValidatedInputs.length is 0


AM I THE ONLY ONE AROUND HERE

AM I THE ONLY ONE AROUND HERE

--- src/scripts/meme_generator.coffee
+++ src/scripts/meme_generator.coffee
@@ -27,6 +27,7 @@
 #   hubot memegen <text> you're gonna have a bad time - Bad Time Ski Instructor
 #   hubot memegen one does not simply <text> - Lord of the Rings Boromir
 #   hubot memegen it looks like (you|you're) <text> - Generates Clippy
+#	hubot memegen AM I THE ONLY ONE AROUND HERE <text> - The Big Lebowski
 #
 # Author:
 #   skalnik
@@ -108,6 +109,11 @@
         regex: /(memegen )?(it looks like you're|it looks like you) (.*)/i,
         generatorID: 20469,
         imageID: 1159769
+      },
+      {
+      	regex: /(memegen )?(AM I THE ONLY ONE AROUND HERE) (.*)/i,
+      	generatorID: 953639,
+      	imageID: 4240352
       }
     ]


Add warning for naming files . or ..

Add warning for naming files . or ..

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -398,6 +398,10 @@
         else if @props.file_search.indexOf('/') == 0
             <Alert style={marginTop: '10px', fontWeight : 'bold'} bsStyle='danger'>
                 Warning: Names cannot begin with /
+            </Alert>
+        else if ['.', '..'].indexOf(@props.file_search) > -1
+            <Alert style={marginTop: '10px', fontWeight : 'bold'} bsStyle='danger'>
+                Warning: Cannot create a file named . or ..
             </Alert>
         # Non-empty search and there is a file divisor ('/')
         else if @props.file_search.length > 0 and last_folder_index > 0


Possible re-working of #1627.

Possible re-working of #1627.

--- content_scripts/mode_insert.coffee
+++ content_scripts/mode_insert.coffee
@@ -19,7 +19,7 @@
         # the right thing to do for most common use cases.  However, it could also cripple flash-based sites and
         # games.  See discussion in #1211 and #1194.
         target.blur()
-      else if target.shadowRoot and @insertModeLock
+      else if target?.shadowRoot and @insertModeLock
         # An editable element in a shadow DOM is focused; blur it.
         @insertModeLock.blur()
       @exit event, event.srcElement
@@ -61,16 +61,18 @@
           # event inside the shadow DOM. This fixes #853.
           shadowRoot = event.target.shadowRoot
           eventListeners = {}
-          for type in ["focus", "blur"]
+          for type in [ "focus", "blur" ]
             eventListeners[type] = do (type) ->
-              (event) ->
-                handlerStack.bubbleEvent type, event
-                if type == "blur"
-                  # Unregister the event listeners.
-                  for ltype, listener of eventListeners
-                    shadowRoot.removeEventListener ltype, listener, true
+              (event) -> handlerStack.bubbleEvent type, event
+            shadowRoot.addEventListener type, eventListeners[type], true

-            shadowRoot.addEventListener type, eventListeners[type], true
+          handlerStack.push
+            _name: "shadow-DOM-input-mode"
+            blur: (event) ->
+              if event.target.shadowRoot == shadowRoot
+                handlerStack.remove()
+                for type, listener of eventListeners
+                  shadowRoot.removeEventListener type, listener, true

     # Only for tests.  This gives us a hook to test the status of the permanently-installed instance.
     InsertMode.permanentInstance = @ if @permanent


ComputeProvider:[backend] Check given credential key from JCredential

--- workers/social/lib/social/models/computeproviders/computeprovider.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.coffee
@@ -12,10 +12,19 @@
   digitalocean : require './digitalocean'
   engineyard   : require './engineyard'

-checkCredential = (cred, callback)->
-  if cred is 1
-  then callback null, {cred:"whoooho"}
-  else callback new KodingError "Credential failed.", "AccessDenied"
+checkCredential = (client, pubKey, callback)->
+
+  console.log pubKey
+
+  JCredential = require './credential'
+  JCredential.fetchByPublicKey client, pubKey, (err, credential)->
+
+    if err or not credential?
+      console.warn err  if err
+      callback new KodingError "Credential failed.", "AccessDenied"
+    else
+      callback null, credential
+

 module.exports = class ComputeProvider extends Base

@@ -57,13 +66,13 @@
       else
         arguments[1].provider = provider

-      if not credential
+      if not credential?
         return callback new KodingError \
           "Credential is required.", "MissingCredential"

       args = [ arguments... ]

-      checkCredential credential, (err, cred)=>
+      checkCredential client, credential, (err, cred)=>

         if err then return callback err
         args[1].credential = cred


add missing timeout

add missing timeout

--- src/smc-hub/test/api/copy_between_projects.coffee
+++ src/smc-hub/test/api/copy_between_projects.coffee
@@ -188,6 +188,7 @@

     it "copies a public file to different target dir", (done) ->
         # src:'B2/doc2.txt'(public) tgt:'FOO/abc.txt'
+        @timeout(15000)
         api.call
             event : 'copy_public_path_between_projects'
             body  :


Component-Sidebar: Leave Shared VM context is changed

--- client/app/lib/components/sidebarmachineslistitem/leavesharedmachinewidget.coffee
+++ client/app/lib/components/sidebarmachineslistitem/leavesharedmachinewidget.coffee
@@ -16,7 +16,7 @@

     modal = new ContentModal
       title   : 'Are you sure?'
-      content : "
This will remove the shared VM from your sidebar. If you want to get back to the collaboration session later, you will need to manually go to this session's URL.
"
+      content : "
This will remove the shared VM from your sidebar. You won't be able to access it unless you are invited again.
"
       cssClass : 'content-modal'
       buttons :
         No         :