Clarify the function parameters.

Clarify the function parameters.

--- app/javascripts/services.coffee
+++ app/javascripts/services.coffee
@@ -1,7 +1,16 @@
 svcs = angular.module 'qiprofile.services', ['ngResource']

 svcs.factory 'Subject', ['$q', '$resource', ($q, $resource) ->
-  $resource '/api/subjects/:number/', null,
+  # The Subject resource recognizes a 'get' query method on the
+  # subject id, but qiprofile does not call this in practice.
+  # Fetching a Subject is done by a query on the subject project,
+  # collection and number, which returns a singleton or empty
+  # array.
+  $resource '/api/subjects/:id', null,
+    query:
+      method: 'GET'
+      isArray: true
+      transformResponse: (data) -> angular.fromJson(data)._items
     detail:
       method: 'GET'
       url: '/api/subject-detail/:id/'
@@ -23,15 +32,18 @@

 svcs.factory 'Helpers', ->
-  # Copies the given source object properties into the
-  # destination object.
-  copyContent: (source, dest) ->
+  # Copies the given source detail object properties into the
+  # destination object, with the following exception:
+  # * fields which begin with an underscore are not copied
+  #   (including the _id field)
+  copyDetail: (source, dest) ->
     # Copy the detail properties into the parent object.
     srcProps = Object.getOwnPropertyNames(source)
     destProps = Object.getOwnPropertyNames(dest)
     fields = _.difference(srcProps, destProps)
     for field in fields
-      dest[field] = source[field]
+      if field[0] != '_'
+        dest[field] = source[field]

   # If the given attribute value is a string, then this function
   # resets it to the parsed date.
@@ -41,7 +53,7 @@
     if typeof date == 'string' or date instanceof String
       # Reset the attribute to a date.
       obj[attr] = moment(date)
-
+

 svcs.factory 'Chart', ->
   # Builds the nvd3 chart format for the given input resource
@@ -165,7 +177,7 @@
         # @returns the number of decimals to display for the
         #   given value
         defaultValuePrecision = (value) ->
-          if value == 0 or value > 1
+          if not value or value > 1
             0
           else
             1 + defaultValuePrecision(value * 10)
@@ -330,27 +342,48 @@
   # Configures the modeling tables.
   configureTable: (sessions) ->
     sessionsWithChangeProperties = (sessions) ->
+      # @param current the current modeling parameters
+      # @param previous the previous modeling parameters
+      # @returns the session objects extended to include
+      #   the percent change properties for all but the
+      #   first session
       addSessionChangeProperties = (current, previous) ->
+        # @param current the current modeling parameters
+        # @param previous the previous modeling parameters
+        # @returns the percent change properties object
         extension = (current, previous) ->
+          # @param current the current value
+          # @param previous the previous value
+          # @returns the percent change
           percent_change = (current, previous) ->
             (current - previous)/previous * 100
+
           # Return an object with the change properties.
-          delta_k_trans_pct_change: percent_change(current.modeling.delta_k_trans, previous.modeling.delta_k_trans)
-          fxl_k_trans_pct_change: percent_change(current.modeling.fxl_k_trans, previous.modeling.fxl_k_trans)
-          fxr_k_trans_pct_change: percent_change(current.modeling.fxr_k_trans, previous.modeling.fxr_k_trans)
-          v_e_pct_change: percent_change(current.modeling.v_e, previous.modeling.v_e)
-          tau_i_pct_change: percent_change(current.modeling.tau_i, previous.modeling.tau_i)
+          delta_k_trans_pct_change: percent_change(current.delta_k_trans, previous.delta_k_trans)
+          fxl_k_trans_pct_change: percent_change(current.fxl_k_trans, previous.fxl_k_trans)
+          fxr_k_trans_pct_change: percent_change(current.fxr_k_trans, previous.fxr_k_trans)
+          v_e_pct_change: percent_change(current.v_e, previous.v_e)
+          tau_i_pct_change: percent_change(current.tau_i, previous.tau_i)
+
+        # If this is not the first session's modeling object,
+        # then extend the object with the percent change properties,
+        # otherwise, return the first modeling object unchanged.
         if previous
-          _.extend current.modeling, extension(current, previous)
+          _.extend current, extension(current, previous)
         else
           current

+      # The previous modeling object.
       prev = null
+      # The modeling objects extended with change properties.
       result = []
-      for curr in sessions
-        result.push addSessionChangeProperties(curr, prev)
-        prev = curr
-      # Return the result
+      # Extend all but the first modeling object with change properties.
+      for sess in sessions
+        curr = sess.modeling
+        if curr
+          result.push addSessionChangeProperties(curr, prev)
+          prev = curr
+      # Return the result.
       result

     # Return the configuration object.
@@ -380,7 +413,7 @@
       sessionDetailLink = (session) ->
         "/quip/#{ session.subject.collection.toLowerCase() }/subject/" +
         "#{ session.subject.number }/session/#{ session.number }?" +
-        "project=#{ session.subject.project }&detail=#{ session.detail_id }"
+        "project=#{ session.subject.project }&detail=#{ session.detail }"

       # Select the SVG element.
       svg = d3.select(chart.container)
@@ -519,10 +552,7 @@
       # The registration image select button colors.
       COLORS = ['LightGreen', 'LightYellow', 'LightCyan', 'LemonChiffon']

-      if session.registrations.length == 1
-        key = 'Realigned'
-      else
-        key = registration.name
+      key = registration.title

       # TODO - Wrap the key in a registration parameters pop-up hyperlink.
       # Return the data series format object.
@@ -532,7 +562,7 @@

     # The scan data series configuration.
     scan_data =
-      key: 'Scan'
+      key: session.scan.title
       values: coordinates(session.scan.intensity.intensities)
       color: 'Indigo'

@@ -567,12 +597,13 @@

 svcs.factory 'Image', ['$rootScope', 'File', ($rootScope, File) ->
-  # A root scope {parent id: {filename: content} image cache.
-  $rootScope.images = {}
+  # The root scope {parent id: [Image objects]} cache.
+  if not $rootScope.images
+    $rootScope.images = {}

   # If there are file arguments, then this function
-  # caches the images for the given parent object.
-  # Otherwise, this function returns the cached images,
+  # caches the Image objects for the given parent object.
+  # Otherwise, this function returns the cached image objects,
   # or undefined if there is no cache entry for the object.
   cache = (parent, files...) ->
     if files.length
@@ -675,7 +706,7 @@
   # cached object image content data is not loaded until the image
   # object load() function is called.
   #
-  # @param parent the ImageContainer scan or registration object
+  # @param parent the Scan or Registration object
   # @returns the image objects
   imagesFor: (parent) ->
     cache(parent) or cache(parent, parent.files...)


Add browser based validation for the real name field (#5412)

* Add browser based validation for the real name field

* Update app/partials/register-form.cjsx

Co-Authored-By: Jim O'Donnell <1cd02e31b43620d7c664e038ca42a060d61727b9@eatyourgreens.org.uk>

--- app/partials/register-form.cjsx
+++ app/partials/register-form.cjsx
@@ -28,6 +28,7 @@
     email: 'Email address'
     emailConflict: 'An account with this address already exists'
     realName: 'Real name'
+    realNamePatternHelp: "Enter a name, not an email address",
     whyRealName: 'We’ll use this to give you credit in scientific papers, posters, etc'
     agreeToPrivacyPolicy: 'You agree to our %(link)s (required)'
     privacyPolicy: 'privacy policy'
@@ -164,7 +165,14 @@
           <Translate content="registerForm.realName" />
           <Translate className="form-help info right-align" content="registerForm.optional" />
         </span>
-        <input type="text" ref="realName" className="standard-input full" disabled={@props.user?} />
+        <input
+          type="text"
+          pattern='[^@]+'
+          ref="realName"
+          className="standard-input full"
+          disabled={@props.user?}
+          title={counterpart('registerForm.realNamePatternHelp')}
+        />
         <Translate component="span" className="form-help info" content="registerForm.whyRealName" />
       </label>


<server> Config - Preparing for drivers loading after start.

--- server/config.coffee
+++ server/config.coffee
@@ -1,25 +1,42 @@
 americano = require 'americano'
+Realtimer = require 'cozy-realtime-adapter'
+
 config =
-    common: [
-        americano.bodyParser()
-        americano.methodOverride()
-        americano.errorHandler
-            dumpExceptions: true
-            showStack: true
-        americano.static __dirname + '/../client/public',
-            maxAge: 86400000
-    ]
+	common:
+		use: [
+			americano.bodyParser()
+			americano.methodOverride()
+			americano.errorHandler
+				dumpExceptions: true
+				showStack: true
+			americano.static __dirname + '/../client/public',
+				maxAge: 86400000
+		]
+		afterStart: (app, server) ->

-    development: [
-        americano.logger 'dev'
-    ]
+			app.server = server
+
+			# @todo Load Drivers
+			# actuatorsDrivers = ...
+			# sensorsDrivers = ...

-    production: [
-        americano.logger 'short'
-    ]
+			# Pass reference to controllers:
+			# require('./controllers/actuator').setDrivers actuatorsDrivers
+			# require('./controllers/sensor').setDrivers sensorsDrivers

-    plugins: [
-        'cozydb'
-    ]
+			# Initialize Realtime
+			realtime = RealtimeAdapter server, ['nest.*']);
+
+	development: [
+		americano.logger 'dev'
+	]
+
+	production: [
+		americano.logger 'short'
+	]
+
+	plugins: [
+		'cozydb'
+	]

 module.exports = config


added logging method

added logging method

--- both/publisher.coffee
+++ both/publisher.coffee
@@ -2,6 +2,11 @@
   @setupTemplate: (templateName, definition) ->
     check templateName, String
     check definition, Publisher.Definition
+
+    if Meteor.isServer
+      Publisher._log
+        msg: 'setupTemplate called on server'
+        level: 'warn'

     if Meteor.isClient
       template = Template[templateName]
@@ -39,3 +44,7 @@

   @Definition: Definition
   @Utilities: Utilities
+  @_log: (options) ->
+    levels = ['log', 'info', 'debug', 'warn', 'error']
+    options.level = 'log' unless options.level in levels
+    console[options.level] options.msg


Remove a redundant setting.

Remove a redundant setting.

--- src/install.coffee
+++ src/install.coffee
@@ -124,7 +124,6 @@
       nodeModulesDirectory = path.join(installDirectory, 'node_modules')
       fs.makeTreeSync(nodeModulesDirectory)
       installOptions.cwd = installDirectory
-      installOptions.streaming = true if @verbose

     @fork @atomNpmPath, installArgs, installOptions, (code, stderr='', stdout='') =>
       if code is 0


Capitalize Skroutz in OrderStash plugin

Capitalize Skroutz in OrderStash plugin

--- src/plugins/order_stash.coffee
+++ src/plugins/order_stash.coffee
@@ -247,21 +247,21 @@

-      Αποθήκευση της παραγγελίας σου στο skroutz;
+      Αποθήκευση της παραγγελίας σου στο Skroutz;

-        Γιατί να αποθηκεύσω την παραγγελία μου στο skroutz;
+        Γιατί να αποθηκεύσω την παραγγελία μου στο Skroutz;

         Ώστε να μπορείς να ανατρέξεις σε πληροφορίες σχετικά με τις αγορές που έκανες,
-        μέσα στο λογαριασμό σου στο skroutz.
+        μέσα στο λογαριασμό σου στο Skroutz.

-        Τα δεδομένα αυτά είναι προσωπικά και παραμένουν ασφαλή στο skroutz.
+        Τα δεδομένα αυτά είναι προσωπικά και παραμένουν ασφαλή στο Skroutz.


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/metered.coffee
+++ src/metered.coffee
@@ -107,7 +107,7 @@
   player.include Metered

   player.meter
-
+    border
     showName: true
     name: 'health'
     position:


add metadata logic to opf file model

add metadata logic to opf file model

--- scripts/gh-book/opf-file.coffee
+++ scripts/gh-book/opf-file.coffee
@@ -34,9 +34,12 @@

     branch: true # This element will show up in the sidebar listing

+    addAction: =>
+      require ['cs!views/layouts/workspace/bookshelf'], (Bookshelf) =>
+        Bookshelf::editBook(@)
+
     initialize: (options) ->
       options.root = @
-

       @$xml = $($.parseXML defaultOpf(options))

@@ -84,6 +87,150 @@
         if value != $title.text()
           $title.text(value)
           @_save()
+
+      tagHelper = (tagName, value, attributes, skipSave) =>
+        return if not value
+
+        container = @$xml.find('metadata')
+        selector = tagName.replace(':', '\\:')
+        template = "<#{tagName}"
+
+        _.forIn attributes, (value, key) ->
+          selector += "[#{key}=\"#{value}\"]"
+          template += " #{key}=\"#{value}\""
+
+        @$xml.find(selector).remove()
+        template += "></#{tagName}>"
+
+        container.append('    ')
+        $(template).text(value).appendTo(container)
+        container.append('\n')
+        @_save() if not skipSave
+
+      if @_isNew
+        now = new Date()
+        tagHelper(
+          'date',
+          "#{now.getFullYear()}-#{now.getMonth()+1}-#{now.getDate()}",
+          {'event': 'publication'},
+          true
+        )
+
+      @on 'change:language', (model, value, options) =>
+        tagHelper('dc:language', value, {type: "dcterms:RFC4646"})
+      @on 'change:description', (model, value, options) =>
+        tagHelper('dc:description', value)
+      @on 'change:rights', (model, value, options) =>
+        tagHelper('dc:rights', value)
+
+      metaHelper = (value, attributes) =>
+        tagHelper('meta', value, attributes)
+
+      @on 'change:rightsUrl', (model, value, options) =>
+        metaHelper(value, {property: "schema:useRightsUrl"})
+      @on 'change:dateModified', (model, value, options) =>
+        metaHelper(value, {properties: "dcterms:modified"})
+
+      tagGroupHelper = (tagName, values, attributes) =>
+        # make sure we actually have content
+        values = values.filter (i) -> i
+        return if not values.length
+
+        container = @$xml.find('metadata')
+        selector = tagName.replace(':', '\\:')
+        template = "<#{tagName}"
+
+        _.forIn attributes, (value, key) ->
+          selector += "[#{key}=\"#{value}\"]"
+          template += " #{key}=\"#{value}\""
+
+        existing = @$xml.find(selector).remove()
+        template += "></#{tagName}>"
+
+        _.each values, (value) ->
+          container.append('    ')
+          $(template).text(value).appendTo(container)
+          container.append('\n')
+
+        @_save()
+
+      @on 'change:subject', (model, value, options) =>
+        tagGroupHelper(
+          'dc:subject',
+          value,
+          {type: "http:\\github.com/Connexions/rhaptos.cnxmlutils/rhaptos/cnxmlutils/schema"}
+        )
+      @on 'change:rightsHolders', (model, value, options) =>
+        tagGroupHelper(
+          'meta',
+          value,
+          {property: "dcterms:rightsHolder"}
+        )
+      @on 'change:keywords', (model, value, options) =>
+        # make sure we actually have content
+        value = value.filter (i) -> i
+        return if not value.length
+
+        container = @$xml.find('metadata')
+        template = "<dc:subject></dc:subject>"
+        @$xml.find('dc\\:subject').not('[type]').remove()
+
+        _.each(value, (keyword) ->
+          container.append('    ')
+          $(template).text(keyword).appendTo(container)
+          container.append('\n')
+        )
+
+        @_save()
+
+      creatorHelper = (type, shortType, creators) =>
+
+        # make sure we actually have content
+        creators = creators.filter (i) -> i
+        return if not creators.length
+
+        # remove the existing ones so we don't get dupes
+        @$xml.find('dc\\:creator[id^="'+type+'"]').remove()
+        @$xml.find('meta[refines^="#'+type+'"]').remove()
+
+        container = @$xml.find('metadata')
+
+        template = "<dc:creator id=\"#{type}\"></dc:creator>"
+        refinesTemplate = "<meta refines=\"##{type}\" property=\"role\" scheme=\"marc:relators\"></meta>"
+
+        i = 1
+        _.each(creators, (creator) ->
+          id = type + '-' + i++
+
+          # coffeescript's regex parser is dumb and needs the character
+          # class for that leading space
+          fileAs = creator.split(/[ ](?=[^ ]+$)/).reverse().join(', ')
+
+          container.append('    ')
+          newCreator = $(template).text(creator)
+            .attr('id', id)
+            .attr('file-as', fileAs)
+            .appendTo(container)
+          container.append('\n    ')
+          $(refinesTemplate)
+            .attr('refines', '#' + id)
+            .text(shortType)
+            .appendTo(container)
+          container.append('\n')
+        )
+
+        @_save()
+
+      @on 'change:authors', (model, value, options) =>
+        creatorHelper('author', 'aut', value)
+      @on 'change:publishers', (model, value, options) =>
+        creatorHelper('publisher', 'pbl', value)
+      @on 'change:editors', (model, value, options) =>
+        creatorHelper('editor', 'edt', value)
+      @on 'change:translators', (model, value, options) =>
+        creatorHelper('translator', 'trl', value)
+      @on 'change:illustrators', (model, value, options) =>
+        creatorHelper('illustrator', 'ill', value)

       # When a title changes on one of the nodes in the ToC:
       #


GameObject.Effect

GameObject.Effect

--- src/game_object_effect.coffee
+++ src/game_object_effect.coffee
@@ -6,11 +6,8 @@

       player = GameObject()

-      fadedOut = false
-
       # this will fade the player object out over the next 2 seconds.
-      player.fadeOut 2, (player) ->
-        fadedOut = true
+      player.fadeOut 2

       30.times ->
         player.update()


feat(gruntfile.coffee): update coffee task to look for protractor.coffee

--- gruntfile.coffee
+++ gruntfile.coffee
@@ -308,6 +308,7 @@
           findModuleFilesIn('src/common/')
           '**/*.spec.coffee'
           '**/*.e2e.coffee'
+          '**/*.protractor.coffee'
         ]
         cwd: '<%= build_dir %>'
         dest: '<%= build_dir %>'


Modernize get colors and use testutils

Modernize get colors and use testutils

--- spec/GetColors.coffee
+++ spec/GetColors.coffee
@@ -1,43 +1,12 @@
 noflo = require 'noflo'
 unless noflo.isBrowser()
   chai = require 'chai' unless chai
-  fs = require 'fs'
-  Canvas = require('noflo-canvas').canvas
-  GetColors = require '../components/GetColors.coffee'
+  path = require 'path'
+  baseDir = path.resolve __dirname, '../'
+  testutils = require './testutils'
 else
-  GetColors = require 'noflo-image/components/GetColors.js'
-
-
-createCanvas = (width, height) ->
-  if noflo.isBrowser()
-    canvas = document.createElement 'canvas'
-    canvas.width = width
-    canvas.height = height
-  else
-    canvas = new Canvas width, height
-  return canvas
-
-getImageData = (name, callback) ->
-  if noflo.isBrowser()
-    id = 'http://localhost:8000/spec/fixtures/'+name
-    image = new Image()
-    image.onload = ->
-      callback image
-    image.src = id
-  else
-    id = 'spec/fixtures/'+name
-    fs.readFile id, (err, data) ->
-      image = new Canvas.Image
-      image.src = data
-      callback image
-  return id
-
-getCanvasWithImage = (name, callback) ->
-  id = getImageData name, (img) ->
-    canvas = createCanvas img.width, img.height
-    canvas.getContext('2d').drawImage(img, 0, 0)
-    callback canvas
-  return id
+  baseDir = '/noflo-image'
+  testutils = require 'noflo-image/spec/testutils.js'

 describe 'GetColors component', ->
   c = null
@@ -45,16 +14,21 @@
   colors = null
   canvas = null
   error = null
-  beforeEach ->
-    c = GetColors.getComponent()
-    ins = noflo.internalSocket.createSocket()
-    colors = noflo.internalSocket.createSocket()
-    canvas = noflo.internalSocket.createSocket()
-    error = noflo.internalSocket.createSocket()
-    c.inPorts.canvas.attach ins
-    c.outPorts.colors.attach colors
-    c.outPorts.canvas.attach canvas
-    c.outPorts.error.attach error
+  beforeEach (done) ->
+    @timeout 4000
+    loader = new noflo.ComponentLoader baseDir
+    loader.load 'image/GetColors', (err, instance) ->
+      return done err if err
+      c = instance
+      ins = noflo.internalSocket.createSocket()
+      colors = noflo.internalSocket.createSocket()
+      canvas = noflo.internalSocket.createSocket()
+      error = noflo.internalSocket.createSocket()
+      c.inPorts.canvas.attach ins
+      c.outPorts.colors.attach colors
+      c.outPorts.canvas.attach canvas
+      c.outPorts.error.attach error
+      done()

   describe 'when instantiated', ->
     it 'should have a input ports', ->
@@ -87,15 +61,13 @@
       colors.once "begingroup", (group) ->
         groups.push group
       colors.once "data", (colors) ->
-        chai.expect(colors).to.be.an 'array'
         chai.expect(colors).to.have.length expected.length
-        chai.expect(colors[0]).to.be.an 'array'
         chai.expect(colors[0]).to.have.length 3
         chai.expect(colors).to.deep.equal expected
         chai.expect(groups).to.have.length 1
         chai.expect(groups[0]).to.equal id
         done()
-      id = getCanvasWithImage input, (canvas) ->
+      id = testutils.getCanvasWithImageNoShift input, (canvas) ->
         ins.beginGroup id
         ins.send canvas
         ins.endGroup()
@@ -106,11 +78,10 @@
       canvas.once "begingroup", (group) ->
         groups.push group
       canvas.once "data", (canvas) ->
-        chai.expect(canvas).to.be.an 'object'
         chai.expect(groups).to.have.length 1
         chai.expect(groups[0]).to.equal id
         done()
-      id = getCanvasWithImage input, (canvas) ->
+      id = testutils.getCanvasWithImage input, (canvas) ->
         ins.beginGroup id
         ins.send canvas
         ins.endGroup()
@@ -138,12 +109,10 @@
     ]
     it 'should extract the colors and output css strings', (done) ->
       colors.once "data", (colors) ->
-        chai.expect(colors).to.be.an 'array'
         chai.expect(colors).to.have.length expected.length
-        chai.expect(colors[0]).to.be.a 'string'
         chai.expect(colors).to.deep.equal expected
         done()
-      id = getCanvasWithImage input, (canvas) ->
+      id = testutils.getCanvasWithImageNoShift input, (canvas) ->
         ins.beginGroup id
         ins.send canvas
         ins.endGroup()
@@ -152,20 +121,19 @@
       input = '1x1.gif'
       id = null
       colors.once "data", (colors) ->
-        chai.expect(colors).to.be.an 'array'
         chai.expect(colors).to.have.length 0
         done()
-      id = getCanvasWithImage input, (canvas) ->
+      id = testutils.getCanvasWithImage input, (canvas) ->
         ins.send canvas
   describe 'when given not an image', ->
     it 'should return an error', (done) ->
       error.on "data", (err) ->
-        chai.expect(err).to.be.an 'object'
+        chai.expect(err).to.be.instanceof Error
         done()
       ins.send ''
   describe 'when given null', ->
     it 'should return an error', (done) ->
       error.on "data", (err) ->
-        chai.expect(err).to.be.an 'object'
+        chai.expect(err).to.be.instanceof Error
         done()
       ins.send null


Use explicit event subscription methods in specs

--- spec/file-spec.coffee
+++ spec/file-spec.coffee
@@ -30,16 +30,16 @@

   describe "when the file has not been read", ->
     describe "when the contents of the file change", ->
-      it "triggers 'contents-changed' event handlers", ->
-        file.on 'contents-changed', changeHandler = jasmine.createSpy('changeHandler')
+      it "notifies ::onDidChange observers", ->
+        file.onDidChange changeHandler = jasmine.createSpy('changeHandler')
         fs.writeFileSync(file.getPath(), "this is new!")

         waitsFor "change event", ->
           changeHandler.callCount > 0

     describe "when the contents of the file are deleted", ->
-      it "triggers 'contents-changed' event handlers", ->
-        file.on 'contents-changed', changeHandler = jasmine.createSpy('changeHandler')
+      it "notifies ::onDidChange observers", ->
+        file.onDidChange changeHandler = jasmine.createSpy('changeHandler')
         fs.writeFileSync(file.getPath(), "")

         waitsFor "change event", ->
@@ -50,9 +50,9 @@
       file.readSync()

     describe "when the contents of the file change", ->
-      it "triggers 'contents-changed' event handlers", ->
+      it "notifies ::onDidChange observers", ->
         changeHandler = jasmine.createSpy('changeHandler')
-        file.on 'contents-changed', changeHandler
+        file.onDidChange changeHandler
         fs.writeFileSync(file.getPath(), "this is new!")

         waitsFor "change event", ->
@@ -65,14 +65,14 @@
         waitsFor "second change event", ->
           changeHandler.callCount > 0

-    describe "when the file is removed", ->
-      it "triggers 'remove' event handlers", ->
-        removeHandler = jasmine.createSpy('removeHandler')
-        file.on 'removed', removeHandler
+    describe "when the file is deleted", ->
+      it "notifies ::onDidDelete observers", ->
+        deleteHandler = jasmine.createSpy('deleteHandler')
+        file.onDidDelete(deleteHandler)
         fs.removeSync(file.getPath())

         waitsFor "remove event", ->
-          removeHandler.callCount > 0
+          deleteHandler.callCount > 0

     describe "when a file is moved (via the filesystem)", ->
       newPath = null
@@ -83,9 +83,9 @@
       afterEach ->
         if fs.existsSync(newPath)
           fs.removeSync(newPath)
-          removeHandler = jasmine.createSpy('removeHandler')
-          file.on 'removed', removeHandler
-          waitsFor "remove event", 30000, -> removeHandler.callCount > 0
+          deleteHandler = jasmine.createSpy('deleteHandler')
+          file.onDidDelete(deleteHandler)
+          waitsFor "remove event", 30000, -> deleteHandler.callCount > 0

       it "it updates its path", ->
         moveHandler = jasmine.createSpy('moveHandler')
@@ -99,13 +99,13 @@
         runs ->
           expect(file.getPath()).toBe newPath

-      it "maintains 'contents-changed' events set on previous path", ->
+      it "maintains ::onDidChange observers that were subscribed on the previous path", ->
         moveHandler = null
         moveHandler = jasmine.createSpy('moveHandler')
         file.on 'moved', moveHandler
         changeHandler = null
         changeHandler = jasmine.createSpy('changeHandler')
-        file.on 'contents-changed', changeHandler
+        file.onDidChange changeHandler

         fs.moveSync(filePath, newPath)

@@ -122,9 +122,9 @@
     describe "when a file is deleted and the recreated within a small amount of time (git sometimes does this)", ->
       it "triggers a contents change event if the contents change", ->
         changeHandler = jasmine.createSpy("file changed")
-        removeHandler = jasmine.createSpy("file removed")
-        file.on 'contents-changed', changeHandler
-        file.on 'removed', removeHandler
+        deleteHandler = jasmine.createSpy("file deleted")
+        file.onDidChange changeHandler
+        file.onDidDelete deleteHandler

         expect(changeHandler).not.toHaveBeenCalled()

@@ -140,7 +140,7 @@
           changeHandler.callCount == 1

         runs ->
-          expect(removeHandler).not.toHaveBeenCalled()
+          expect(deleteHandler).not.toHaveBeenCalled()
           fs.writeFileSync(filePath, "Hallelujah!")
           changeHandler.reset()


if not's replaced by existential operator.

--- src/models/NavigationBar.coffee
+++ src/models/NavigationBar.coffee
@@ -113,10 +113,8 @@
         for location in locations

           if options.buttons[location]?
-            if not params.buttons?
-              params.buttons = {}
-            if not @buttonCallbacks?
-              @buttonCallbacks = {}
+            params.buttons ?= {}
+            @buttonCallbacks ?= {}
             steroids.debug "steroids.navigationBar.update constructing location #{location}"
             @buttonCallbacks[location] = []
             params.buttons[location] = []


dont register service worker right now

dont register service worker right now

--- vue/src/main.coffee
+++ vue/src/main.coffee
@@ -8,7 +8,7 @@
 import app from '@/app.vue'
 import marked from '@/marked'
 import '@/observe_visibility'
-import './registerServiceWorker'
+# import './registerServiceWorker'
 import { initLiveUpdate } from '@/shared/helpers/cable'
 import { pick } from 'lodash'
 import * as Sentry from '@sentry/browser'


syncstring: fix an issue with offline patches and snapshots

--- src/smc-util/syncstring.coffee
+++ src/smc-util/syncstring.coffee
@@ -24,7 +24,7 @@
 # the---on reconnect---do extra work to ensure that all snapshots are up to
 # date (in case snapshots were made when we were offline), and mark the sent
 # field of patches that weren't saved.
-OFFLINE_THRESH_S = 30
+OFFLINE_THRESH_S = 5*60

 {EventEmitter} = require('events')
 immutable = require('immutable')
@@ -910,6 +910,12 @@
             patch    : JSON.stringify(x.patch)
             snapshot : @_patch_list.value(time, force)
             user     : x.user
+        if force
+            # CRITICAL: We are sending the patch/snapshot later, but it was valid.
+            # It's important to make this clear or _handle_offline will
+            # recompute this snapshot and try to update sent on it again,
+            # which leads to serious problems!
+            obj.sent = time
         x.snapshot = obj.snapshot  # also set snapshot in the @_patch_list, which helps with optimization
         @_patches_table.set(obj, 'none')
         # save the snapshot time in the database
@@ -1008,7 +1014,7 @@
         return

     # Check if any patches that just got confirmed as saved are relatively old; if so,
-    # we mark them as such and also possibly recompute snaphshots.
+    # we mark them as such and also possibly recompute snapshots.
     _handle_offline: (data) =>
         #dbg = @dbg("_handle_offline")
         #dbg("data='#{misc.to_json(data)}'")


Remove some logging

Remove some logging

--- src/smc-webapp/app_shared.cjsx
+++ src/smc-webapp/app_shared.cjsx
@@ -93,8 +93,6 @@
             />

     on_click: (e) ->
-        console.log "Clicked!", @props.name, @actions('page')
-        console.log redux.getStore('page').get("active_top_tab")
         if @props.name?
             @actions('page').set_active_tab(@props.name)
         @props.on_click?()


quick fix for bug where you see "To be implemented" when loading cocalc sometimes

--- src/smc-webapp/app_shared.cjsx
+++ src/smc-webapp/app_shared.cjsx
@@ -78,10 +78,8 @@
                 v.push <ProjectsPage key={'projects'}/>
             when 'account'
                 v.push <AccountPage key={'account'}/>
-            when 'about'
+            when 'help', 'about'
                 v.push <HelpPage key={'about'}/>
-            when 'help'
-                v.push
To be implemented

             when 'file-use'
                 v.push <FileUsePage redux={redux} key={'file-use'}/>
             when 'notifications'
@@ -89,7 +87,7 @@
             when 'admin'
                 v.push <AdminPage redux={redux} key={'admin'}/>
             when undefined
-                v.push
Broken... active_top_tab is undefined

+                v.push
Please click a button on the top tab.

     if v.length == 0
         # this happens upon loading a URL for a project, but the project isn't open yet.


Lower temporary folder

Lower temporary folder

--- src/objects/remote.proto.coffee
+++ src/objects/remote.proto.coffee
@@ -20,11 +20,18 @@
       ''
     }#{file}"

+tmp = ->
+  n = 10
+  while n--
+    f = folder install2, '.' + Math.random().toFixed(3).replace(/.*[.]/, '')
+    unless f.y()
+      return f
+  throw Error "Temporary folder not found"
+
 extract = (self)->
   echo "Extracting:", msi self
   v = ver self
-  extract2 = folder cache, v
-    .rm true
+  extract2 = do tmp
   code = run 1, true, 'msiexec', '/a', msi(self, true),
     "TARGETDIR=", run._, extract2, '/qb'
   if code
@@ -50,7 +57,7 @@
   npmrc.WriteLine """

     # <hack dirty src="#{PACKAGE.homepage}">
-    prefix=${APPDATA}\\#{PACKAGE.mingzi}\\#{junction.$.bn()}
+    prefix=${USERPROFILE}\\.#{PACKAGE.mingzi}\\#{junction.$.bn()}
     # </hack>
     """
   npmrc.Close()


TODOを追加, デルタでやる

TODOを追加, デルタでやる

--- public/js/hue.coffee
+++ public/js/hue.coffee
@@ -27,6 +27,7 @@
     $.ajax param

   getLights: () ->
+    # TODO: jsonからライトの数だけ取り出してreturn
     dfd = $.Deferred()
     uri = '/' + @user + 'lights'
     method = 'GET'
@@ -39,6 +40,7 @@
         return dfd.reject()

   lightTrriger: (light, trigger) ->
+    # TODO: jsonから成功か否かを判別してbooleanでreturn
     uri = '/' + @user + '/lights/' + light.toString() + '/state'
     method = 'PUT'
     data =
@@ -52,6 +54,7 @@
         return dfd.promise()

   changeBri: (light, bri) ->
+    # TODO: jsonから成功か否かを取り出してbooleanでreturn
     uri = '/' + @user + '/lights/' + light.toString() + '/state'
     method = 'PUT'
     data =
@@ -65,6 +68,7 @@
         return dfd.promise()

   effectTrriget: (light, trigger) ->
+    # TODO: jsonから成功か否かを取り出してbooleanでreturn
     uri = '/' + @user + '/lights/' + light.toString() + '/state'
     method = 'PUT'
     data =


put number of tokens in station data

put number of tokens in station data

--- build/stations.coffee
+++ build/stations.coffee
@@ -20,6 +20,7 @@
 		id:			data.id
 		name:		data.name
 		weight:		data.weight
+		tokens:		util.locations.stations.tokenize(data.name).split(' ').length
 		relevance:	0

 set.on 'end', () ->


Added Packet.finalize method as a stub

Added Packet.finalize method as a stub

--- src/wrathnet/net/packet.coffee
+++ src/wrathnet/net/packet.coffee
@@ -10,13 +10,14 @@
 #

 # Denotes a network packet
-class WrathNet.net.Packet
+class WrathNet.net.Packet extends WrathNet.datastructures.ByteBuffer

   # Creates a new packet optionally with given opcode
-  constructor: (opcode, outgoing=true) ->
+  constructor: (opcode, length) ->
+    super length

     # Holds the opcode for this packet
     @opcode = opcode
-
-    # Whether this packet is outgoing or incoming
-    @outgoing = outgoing
+
+  finalize: ->
+    return @


Log errors when processing incoming messages

--- src/xmpp/backend_pubsub.coffee
+++ src/xmpp/backend_pubsub.coffee
@@ -210,7 +210,9 @@
                 else
                     cb2 null, update
             , (err, updates) =>
-                # FIXME: don't ignore err
+                if err
+                    logger.error "incoming message: #{err.stack or err.message or err}"
+                    return
                 if updates? and updates.length > 0
                     updates.sender = sender
                     updates.actor = sender


uuuuugh

uuuuugh

--- tests/youtube-info.coffee
+++ tests/youtube-info.coffee
@@ -4,6 +4,7 @@
 expect = require('chai').expect

 describe 'when user links', ->
+  room = null

   beforeEach ->
     room = helper.createRoom()


Extract method _set_args_for_current_signature (also, convert tabs to spaces).

--- src/TinyMockJS.coffee
+++ src/TinyMockJS.coffee
@@ -1,23 +1,23 @@
 #
-# MethodSignature is an internal data structure that represents
+# MethodSignature is an internal data structure that represents
 # a mocked method.  Stores the method name, the expected arguments,
-# the value to return or throw, and whether it has actually been
+# the value to return or throw, and whether it has actually been
 # called.  For example, the following:
 #
 #   my_mock = (new Mock).expects("my_method").args(1,2,3).returns(42)
 #
 # would result in a MethodSignature with @method_name = "my_method",
-# @args = [1,2,3], @returns = 42, @throws = undefined, and @called =
+# @args = [1,2,3], @returns = 42, @throws = undefined, and @called =
 # false.  Doing:
 #
 #   my_mock.my_method(1,2,3)
 #
-# would result in @called = true.  Note that a mocked function can
+# would result in @called = true.  Note that a mocked function can
 # either return a value or throw an error but not both, so either
 # @returns or @throws (or both) must be undefined.
 #
 class MethodSignature
-
+
   constructor: (method_name) ->
     @method_name = method_name
     @args = []
@@ -39,7 +39,7 @@
   #
   equals: (other) ->
     @matches(other.method_name, other.args...)
-
+
   #
   # Returns true if this signature has the specified method
   # name and arguments.  For example:
@@ -60,8 +60,8 @@

 #
 # Mock represents the mock of some object.  @signatures is a list of
-# mocked methods, which are added with the .expects(), .args(),
-# .returns(), and .throws() functions.  These must be called in a
+# mocked methods, which are added with the .expects(), .args(),
+# .returns(), and .throws() functions.  These must be called in a
 # specific order, expects then args then returns or throws, so:
 #
 #   my_mock.expects("my_method").args(123).returns(456)
@@ -71,7 +71,7 @@
 #   my_mock.returns(456).args(123).expects("my_method")
 #
 # is not.  @state is used to remember the last function called so that
-# we can enforce this order.  Note that args, returns and throws are
+# we can enforce this order.  Note that args, returns and throws are
 # optional.  Also note that either returns or throws can be used, but
 # not both on the same signature.
 #
@@ -95,7 +95,7 @@
   constructor: ->
     @signatures = []
     @state = undefined
-
+
   #
   # my_mock = (new Mock).expects("my_method")
   # my_mock.my_method()
@@ -106,18 +106,18 @@
     @[ method_name ] ?= _build_mocked_method(@, method_name)
     _set_state(@, "expects")
     @
-
+
   #
   # my_mock = (new Mock).expects("my_method").args(1,2,3)
   # my_mock.my_method(1,2,3)
   #
   args: (args...) ->
     _check_args_usage(@, args...)
-    _current_signature(@).args = args
+    _set_args_for_current_signature(@, args)
     _check_for_duplicate_signatures(@)
     _set_state(@, "args")
     @
-
+
   #
   # my_mock = (new Mock).expects("my_method").returns(123)
   # console.log my_mock.my_method()   # prints 123
@@ -128,7 +128,7 @@
     _current_signature(@).returns = value
     _set_state(@, "returns")
     @
-
+
   #
   # my_mock = (new Mock).expects("my_method").throws("an error")
   # try
@@ -144,14 +144,14 @@
     @

-
+
 #
 # Private Mock 'methods'; making them functions keeps the Mock object uncluttered.
 #

 _set_state = (mock, state) ->
   mock.state = state
-
+
 _is_state_in = (mock, states...) ->
   mock.state in states

@@ -169,11 +169,11 @@
 _check_returns_usage = (mock, value) ->
   _throw_returns_usage() unless value?
   _throw_returns_must_be_after_expects_or_args() unless _is_state_in(mock, "expects", "args")
-
+
 _check_throws_usage = (mock, error) ->
   _throw_throws_usage(error) unless error?
   _throw_throws_must_be_after_expects_or_args() unless _is_state_in(mock, "expects", "args")
-
+
 _check_for_duplicate_signatures = (mock) ->
   # TODO: use each with index and slice to avoid last element
   signatures = mock.signatures
@@ -184,7 +184,7 @@

 _current_signature = (mock) ->
   mock.signatures[0]
-
+
 _current_method_name = (mock) ->
   _current_signature(mock)?.method_name

@@ -204,45 +204,48 @@
 _build_errors = (mock) ->
   errors = ""
   for signature in mock.signatures when signature.called == false
-    errors += "'#{signature.method_name}(#{signature.args})' was never called\n"
+    errors += "'#{signature.method_name}(#{signature.args})' was never called\n"
   errors
+
+_set_args_for_current_signature = (mock, args) ->
+  _current_signature(mock).args = args

 _throw_expects_usage = ->
   throw "you need to supply a method name to .expects(), e.g. my_mock.expects('my_method')"

 _throw_reserved_word = (reserved) ->
   throw "you cannot do my_mock.expects('#{reserved}'); '#{reserved}' is a reserved method name"
-
+
 _throw_args_usage = ->
   throw "you need to supply at least one argument to .args(), e.g. my_mock.expects('my_method').args(42)"
-
+
 _throw_args_must_be_after_expects = ->
   throw ".args() must be called immediately after .expects(), e.g. my_mock.expects('my_method').args(42)"
-
+
 _throw_duplicate_expectation = (signature) ->
   throw "#{signature} is a duplicate expectation"
-
+
 _throw_returns_usage = ->
   throw "you need to supply an argument to .returns(), e.g. my_mock.expects('my_method').returns(123)"
-
+
 _throw_returns_must_be_after_expects_or_args = ->
   throw ".returns() must be called immediately after .expects() or .args()"
-
+
 _throw_throws_usage = ->
   throw "you need to supply an argument to .throws(), e.g. my_mock.expects('my_method').throws('an error')"

 _throw_throws_must_be_after_expects_or_args = ->
   throw ".throws() must be called immediately after .expects() or .args()"
-
+
 _throw_unknown_expectation = (signature) ->
   throw "#{signature} does not match any expectations"

-
-
-#
-# mock() ensures that _build_errors() is called on mock objects.  It
+
+
+#
+# mock() ensures that _build_errors() is called on mock objects.  It
 # takes a function (the test code) argument, creates five mock objects,
-# invokes the function with the five mocks, and then calls _build_errors()
+# invokes the function with the five mocks, and then calls _build_errors()
 # on those mocks, throwing an error if any errors are found.
 #
 mock = (fn) ->
@@ -252,6 +255,6 @@
   throw errors unless errors == ""

-
+
 (exports ? window).mock = mock
 (exports ? window).Mock = Mock


Fix methods.

Fix methods.

--- src/redux-axios-reducers.coffee
+++ src/redux-axios-reducers.coffee
@@ -144,7 +144,8 @@
   transformConfig: (config) ->
     config = super(config)
     id = (
-      (config.data and config.data.id) or (config.params and config.params.id))
+      config.id or (config.data and config.data.id) or
+      (config.params and config.params.id))
     config.url += "/#{id}" if id
     return config


Add android "Fatal" logs as error

Add android "Fatal" logs as error

Added the "F" character in the definition of errors.


Restore emoji responses when editing (fix #543)

--- client/message.coffee
+++ client/message.coffee
@@ -2531,6 +2531,9 @@

           <MessageAuthor message={historified} also={history?}/>

+            {unless read
+              <EmojiButtons message={message} can={can}/>
+            }
             {if editing

                 {if editStopping
@@ -2546,11 +2549,9 @@
                   <ReplyButtons message={message} prefix="Another "/>
                 }

-            else unless read
-              <>
-                <EmojiButtons message={message} can={can}/>
+            else
+              if can.reply and not read
                 <ReplyButtons message={message}/>
-              </>
             }


Remove leftover console.log

Remove leftover console.log

From 490619a0ebc4e3e62ed099d6d74acec2b253d4c4

--- client/message.coffee
+++ client/message.coffee
@@ -2753,7 +2753,7 @@
          subtitle={messageAuthorSubtitle message, author}/>
       </React.Fragment>
     }
-    {console.log (_.keys also); if also and not _.isEmpty also
+    {if also and not _.isEmpty also
       for author, updated of also
         <React.Fragment key={author}>
           {', ' if count++}


Bumped to version 2.15.5 to be able to make a tagged build for testing purposes in Jenkins.

--- client/version.coffee
+++ client/version.coffee
@@ -1 +1 @@
-@version = '2.15.4'
+@version = '2.15.5'


removo koa nostalgic

removo koa nostalgic

--- server/server.coffee
+++ server/server.coffee
@@ -8,11 +8,6 @@
 # 1. encrypt response
 # 2. client auth
 # 3. SSL
-
-#apiRouter.use (req, res, next) ->
-#  console.log JSON.stringify req.params
-#  log "requested #{req.params.query} from #{req.ip}"
-#  next()

 apiRouter.get '/ruc/:query', (req, res) ->
   log "requested #{req.params.query} from #{req.ip}"
@@ -45,26 +40,3 @@

 log = (l) -> console.log l

-#path = require('path')
-#send = require 'koa-send'
-#router = require('koa-router')()
-#koa = require 'koa'
-#app = koa()
-#
-#elClient = new elasticsearch.Client
-#  log: 'error'
-#  keepAlive: true
-#
-#router.get '/api/what', (next) ->
-#  genify = require('thunkify-wrap').genify
-#  search = genify(elClient.search)
-#  res = yield search {index: 'my-index', q: 'what'}
-#  this.body = {answer: "you send #{res}"}
-#app.use(router.routes())
-#
-#distDir = __dirname + '/../client/dist'
-#app.use (next) ->
-#  yield send(this, this.path, {root: distDir})
-#
-#app.listen 3000
-