bugfixing destroy-/start-server and checkPaths. refs #61

--- lib/atom-ternjs-manager.coffee
+++ lib/atom-ternjs-manager.coffee
@@ -30,9 +30,12 @@
     @registerHelperCommands()
     @provider.init(this)
     @initServers()
+    @disposables.push atom.project.onDidChangePaths (paths) =>
+      @destroyServer(paths)
+      @checkPaths(paths)
+      @setActiveServerAndClient()

   init: ->
-    @setActiveServerAndClient()
     @initialised = true
     @registerEvents()
     @registerCommands()
@@ -74,17 +77,18 @@

   startServer: (dir) ->
     Server = require './atom-ternjs-server' if !Server
-    if !@getServerForProject(dir)
-      idxServer = @servers.push(new Server(dir)) - 1
-      @servers[idxServer].start (port) =>
-        client = @getClientForProject(dir)
-        if !client
-          Client = require './atom-ternjs-client' if !Client
-          clientIdx = @clients.push(new Client(this, dir)) - 1
-          @clients[clientIdx].port = port
-        else
-          client.port = port
-        @init() if @servers.length is @clients.length
+    return if @getServerForProject(dir)
+    idxServer = @servers.push(new Server(dir)) - 1
+    @servers[idxServer].start (port) =>
+      client = @getClientForProject(dir)
+      if !client
+        Client = require './atom-ternjs-client' if !Client
+        clientIdx = @clients.push(new Client(this, dir)) - 1
+        @clients[clientIdx].port = port
+      else
+        client.port = port
+      @init() if @servers.length is @clients.length and !@initialised
+      @setActiveServerAndClient()

   setActiveServerAndClient: (URI) ->
     if !URI
@@ -105,22 +109,24 @@
       @client = false

   checkPaths: (paths) ->
-    serverIdx = null
     for path in paths
       dir = atom.project.relativizePath(path)[0]
       @startServer(dir)
+
+  destroyServer: (paths) ->
+    return unless @servers.length
+    serverIdx = undefined
     for i in [0..@servers.length - 1]
       if paths.indexOf(@servers[i].rootPath) is -1
         serverIdx = i
-    if serverIdx
-      server = @servers[serverIdx]
-      client = getClientForProject(server.rootPath)
-      client.unregisterEvents()
-      client = null
-      server.stop()
-      server = null
-      @servers.splice(serverIdx, 1)
-      @setActiveServerAndClient()
+    return if serverIdx is undefined
+    server = @servers[serverIdx]
+    client = @getClientForProject(server.rootPath)
+    client.unregisterEvents()
+    client = null
+    server.stop()
+    server = null
+    @servers.splice(serverIdx, 1)

   getServerForProject: (rootPath) ->
     for server in @servers
@@ -140,8 +146,6 @@
     return true

   registerEvents: ->
-    @disposables.push atom.project.onDidChangePaths (paths) =>
-      @checkPaths(paths)
     @disposables.push atom.commands.add 'atom-text-editor', 'tern:references': (event) =>
       if !@reference
         Reference = require './atom-ternjs-reference'


code cleanup

code cleanup

Signed-off-by: Steven Hoffman <46f1a0bd5592a2f9244ca321b129902a06b53e03@fustrate.com>

--- vendor/assets/javascripts/fustrate/components/modal.coffee
+++ vendor/assets/javascripts/fustrate/components/modal.coffee
@@ -53,11 +53,10 @@
       @buttons[button.data('button')] = button

   setTitle: (title, { icon } = {}) =>
+    icon ?= @constructor.icon if icon isnt false
+
     if icon
       $('.modal-title span', @modal).html "#{Fustrate.icon icon} #{title}"
-    else if @constructor.icon and icon isnt false
-      $('.modal-title span', @modal)
-        .html "#{Fustrate.icon @constructor.icon} #{title}"
     else
       $('.modal-title span', @modal).html title


Simplify resize and make it work outside of doc

--- lib/baconjs/from-resize.coffee
+++ lib/baconjs/from-resize.coffee
@@ -1,14 +1,16 @@
-Bacon = require("baconjs")
+Bacon = require 'baconjs'

 # Observe mouse move and provide callback functions to react accordingly
 #
-# @param mouseDownEvent: {Event} Initial event that should be called prior to starting to observe changes here
-module.exports = (mouseDownEvent) ->
-  mouseDoneStream = Bacon.fromEvent(document.body, "mouseup").merge(Bacon.fromEvent(document.body, "mouseleave"))
-  mouseDoneStream.onValue ->
-    return Bacon.noMore # to stop listening to events on both merged streams (mouseup/mouseleave)
-
-  return Bacon.combineWith (initialEvent, moveEvent) ->
-    clientX: moveEvent.clientX - initialEvent.clientX
-    clientY: moveEvent.clientY - initialEvent.clientY
-  , Bacon.constant(mouseDownEvent), Bacon.fromEvent(document.body, "mousemove").takeUntil(mouseDoneStream)
+# @param initialEvent: {Event} Initial event that should be called prior to starting to observe changes here
+module.exports = (initialEvent) ->
+  mouseUpStream = Bacon.fromEvent document, 'mouseup'
+  mouseUpStream.onValue -> Bacon.noMore # to stop listening to events
+  Bacon.fromEvent document, 'mousemove'
+    .takeUntil mouseUpStream
+    .map (event) ->
+      if event.which is 1 # left button
+        clientX: event.clientX - initialEvent.clientX
+        clientY: event.clientY - initialEvent.clientY
+      else
+        Bacon.noMore


Wrap CLUE key in <strong> tag

Wrap CLUE key in <strong> tag

--- src/components/learning-guide/statistics.cjsx
+++ src/components/learning-guide/statistics.cjsx
@@ -16,7 +16,7 @@

         { for key, value of @props.section.clue
           value = value.join(' ') if _.isArray(value)
-
{key}: {value}
}
+
{key}: {value}
}


use forEachProvider helper in computeprovider test cases

--- workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
+++ workers/social/lib/social/models/computeproviders/computeprovider.test.coffee
@@ -9,11 +9,13 @@
   generateDummyUserFormData } = require '../../../../testhelper'
 { withConvertedUserAndCredential } = require \
   '../../../../testhelper/models/computeproviders/credentialhelper'
-{ withConvertedUserAnd } = require  \
+{ forEachProvider
+  withConvertedUserAnd } = require  \
   '../../../../testhelper/models/computeproviders/computeproviderhelper'

-{ PROVIDERS, revive }   = require './computeutils'
-ComputeProvider         = require './computeprovider'
+{ notImplementedMessage } = require './providerinterface'
+{ PROVIDERS, revive }     = require './computeutils'
+ComputeProvider           = require './computeprovider'

 # this function will be called once before running any test
@@ -45,25 +47,20 @@
         client.r  = { account }
         queue     = []

-        for providerSlug, provider of PROVIDERS
+        forEachProvider (providerSlug, provider) ->
           options = { provider }
-          queue.push ->
-            options = { provider }
-            ComputeProvider.ping client, options, (err, data) ->
-              expect(err).to.not.exist
-              expect(data).to.be.a 'string'
-              queue.next()
-
-        queue.push -> done()
-
-        daisy queue
+
+          ComputeProvider.ping client, options, (err, data) ->
+            expect(err).to.not.exist
+            expect(data).to.be.a 'string'
+        , done

   describe '#ping$()', ->

     it 'should not be able to ping if user doesnt have the right to ping', (done) ->

-      expectAccessDenied ComputeProvider, 'ping$', { provider : PROVIDERS.google }, done
+      expectAccessDenied ComputeProvider, 'ping$', { provider : PROVIDERS.aws }, done

   describe '#create()', ->
@@ -112,48 +109,53 @@
     it 'should be able to fetch availabe object', (done) ->

       withConvertedUserAndCredential ({ client, credential }) ->
-
-        options =
-          provider: 'google'
-          credential : credential
-
-        ComputeProvider.fetchAvailable client, options, (err, data) ->
-          expect(err?.message).to.not.exist
-          expect(data).to.be.an 'array'
-          done()
+        forEachProvider (providerSlug) ->
+
+          options =
+            provider   : providerSlug
+            credential : credential
+
+          ComputeProvider.fetchAvailable client, options, (err, data) ->
+            return expect(err.message).to.be.equal 'Not implemented yet.'  if err
+            expect(err).to.not.exist
+            expect(data).to.be.an 'array'
+        , done

     it 'should fail if no provider is given', (done) ->

       withDummyClient ({ client }) ->
-        ComputeProvider.fetchAvailable client, { provider : null }, (err, data) ->
-          expect(err?.message).to.be.equal 'No such provider.'
-          done()
+        forEachProvider (providerSlug) ->
+          ComputeProvider.fetchAvailable client, { provider : null }, (err, data) ->
+            expect(err?.message).to.be.equal 'No such provider.'
+        , done

     it 'should fail if credential is not provided', (done) ->

       withDummyClient ({ client }) ->
-
-        options =
-          provider: 'google'
-
-        ComputeProvider.fetchAvailable client, options, (err, data) ->
-          expect(err?.message).to.be.equal 'Credential is required.'
-          done()
+        forEachProvider (providerSlug) ->
+          options = { provider : providerSlug }
+
+          ComputeProvider.fetchAvailable client, options, (err, data) ->
+            expect(err?.message).to.satisfy (msg) ->
+              return msg in [notImplementedMessage, 'Credential is required.']
+        , done

     it 'should fail if given credential is not valid', (done) ->

       withDummyClient ({ client }) ->
-
-        options =
-          provider: 'google'
-          credential : 'invalid'
-
-        ComputeProvider.fetchAvailable client, options, (err, data) ->
-          expect(err?.message).to.be.equal 'Credential failed.'
-          done()
+        forEachProvider (providerSlug) ->
+
+          options =
+            provider   : providerSlug
+            credential : 'invalid'
+
+          ComputeProvider.fetchAvailable client, options, (err, data) ->
+            expect(err?.message).to.satisfy (msg) ->
+              return msg in [notImplementedMessage, 'Credential failed.']
+        , done

   describe '#fetchUsage', ->


generate html

generate html

--- lib/models/result-model.coffee
+++ lib/models/result-model.coffee
@@ -34,4 +34,26 @@

     generateView: ->
       return ResultItemView.setup(@)
-
+
+    encodeHtmlEntity: (str) ->
+      buf = []
+      buf.unshift(['&#', str[i].charCodeAt(), ';'].join('')) for i in [str.length-1...-1] by -1
+      return buf.join('')
+
+    generateHTML: (index) ->
+      html  = '
'
+      html += '
'
+      html += '[' + @projectPath + ']'
+      html += ''
+      html += '' + @htmlFileName + ''
+      html += '
'
+      if not @isJustFile
+        html += '
'
+        html += ':'
+        html += '' + @lineNumber + ''
+        html += ''
+        html += '' + @encodeHtmlEntity(@functionName) + ''
+        html += ''
+        html += '
' + @htmlLineText + '
'
+        html += '
'
+      return html


Add filename as a data attribute for file based theming

--- lib/project/result-view.coffee
+++ lib/project/result-view.coffee
@@ -8,11 +8,12 @@
 class ResultView extends View
   @content: (model, filePath, result) ->
     iconClass = if fs.isReadmePath(filePath) then 'icon-book' else 'icon-file-text'
+    fileBasename = path.basename(filePath)

     @li class: 'path list-nested-item', 'data-path': _.escapeAttribute(filePath), =>
       @div outlet: 'pathDetails', class: 'path-details list-item', =>
         @span class: 'disclosure-arrow'
-        @span class: iconClass + ' icon'
+        @span class: iconClass + ' icon', 'data-name': fileBasename
         @span class: 'path-name bright', filePath.replace(atom.project.getPath()+path.sep, '')
         @span outlet: 'description', class: 'path-match-number'
       @ul outlet: 'matches', class: 'matches list-tree'


Campaign: added update static method for campaigns

--- workers/social/lib/social/models/campaigns/campaign.coffee
+++ workers/social/lib/social/models/campaigns/campaign.coffee
@@ -12,6 +12,7 @@
       static      :
         create    : (signature Object, Function)
         activate  : (signature String, Function)
+        update    : (signature String, Object, Function)
         deactivate: (signature String, Function)

@@ -64,6 +65,21 @@
         .save callback

+  @update = secure (client, name, content, callback) ->
+
+    permit client, (err, permitted) ->
+
+      return callback err                          unless permitted
+      return callback message : 'Name is missing!' unless name
+
+      JStorage.one { name }, (err, campaign) ->
+
+        return callback null, no  if err or not campaign
+
+        campaign.update $set : { content } , callback
+
+
+
   @activate = secure (client, name, callback) ->

     permit client, (err, permitted) =>


Removed compiling cs->js log.

Removed compiling cs->js log.

--- build_detail.coffee
+++ build_detail.coffee
@@ -249,7 +249,7 @@
             return
           yield fs.writeFileAsync tgt_path, tgt
           yield fs.removeAsync src_path
-          log "Compiled #{tgt_path}"
+          #log "Compiled #{tgt_path}"
           )()
       next()
     .on 'end', async ->


Add UUID via context passing

Add UUID via context passing

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -125,7 +125,7 @@
         assert _.isFunction(@entrypoint), noEntrypoint
         assert @entrypoint.remote?.compile, notRemote
         context = scripts: [], sources: [], styles: [], sheets: []
-        context.doctype = "<!DOCTYPE html>"
+        context.doctype = "<!DOCTYPE html>"; context.uuid = @uuid
         @prelude context, request, (context) =>
             context = @deployContext context
             length = @compileContext(context).length


minor changes/fixes and reorderings

minor changes/fixes and reorderings

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -57,7 +57,7 @@
 # This ABC constitutes a primary tool for writing UI/UX with Granite.
 # Please consult the implementation for more information on the system.
 # Also, please reference parent class for the important external APIs.
-module.exports.Screenplay = class Screenplay extends VisualBillets
+assert module.exports.Screenplay = class Screenplay extends VisualBillets

     # This is a marker that indicates to some internal subsystems
     # that this class has to be considered abstract and therefore


make sure there's no dups in the invokes

--- library/membrane/visual.coffee
+++ library/membrane/visual.coffee
@@ -154,6 +154,7 @@
         emptySources = "context JS sources are empty"
         u = (v) -> v.match(RegExp "^(.+)/(.+)$")[2]
         context.sheets = _.unique context.sheets
+        context.invokes = _.unique context.invokes
         context.styles = _.unique context.styles, u
         assert context.scripts = _.unique scripts, u
         assert _.isArray sources = _.unique sources


transfer a couple of defs to the billets

--- library/nucleus/service.coffee
+++ library/nucleus/service.coffee
@@ -94,20 +94,6 @@
     # is asynchronously wired in, so consult with `async` package.
     # Please be sure invoke the `next` arg to proceed, if relevant.
     ignition: (request, response, next) -> next()
-
-    # Either obtain or set the HTTP location of the current service.
-    # This method is a proxy that forwards the invocation to the
-    # service constructor, for the purpose of easy access to service
-    # location when programmatically operating on the instances. Do
-    # refer to the original constructor method for more information.
-    location: -> @constructor.location arguments...
-
-    # This method is a tool for obtaining a fully qualified path to
-    # access to the resource, according to the HTTP specification.
-    # This includes details such as host, port, path and alike. The
-    # method knows how to disambiguate between SSL and non SSL paths.
-    # Refer to the original constructor method for more information.
-    qualified: -> @constructor.qualified arguments...

     # This is an instance method that will be invoked by the router
     # on every service, prior to actually registering it. Method is


Switch mode if needed for new discussion buttons

--- resources/assets/coffee/react/beatmap-discussions/nominations.coffee
+++ resources/assets/coffee/react/beatmap-discussions/nominations.coffee
@@ -98,7 +98,7 @@
                 text: osu.trans 'beatmaps.feedback.button'
                 icon: 'fas fa-bullhorn'
                 props:
-                  onClick: @focusNewDiscussion
+                  onClick: @focusNewDiscussionWithModeSwitch

       # show hype meter and nominations when beatmapset is: wip, pending or qualified
       else
@@ -156,7 +156,7 @@
                       text: osu.trans 'beatmaps.nominations.disqualify'
                       icon: 'fas fa-thumbs-down'
                       props:
-                        onClick: @focusNewDiscussionForDisqualification
+                        onClick: @focusNewDiscussionWithModeSwitch
                   else if mapCanBeNominated && @userCanNominate()
                     if @props.currentDiscussions.unresolvedIssues > 0
                       # wrapper 'cuz putting a title/tooltip on a disabled button is no worky...
@@ -263,7 +263,7 @@
       onAfter: callback

-  focusNewDiscussionForDisqualification: =>
+  focusNewDiscussionWithModeSwitch: =>
     # Switch to generalAll tab just in case currently in event tab
     # and thus new discussion box isn't visible.
     $.publish 'beatmapsetDiscussions:update',


use the production version of React

use the production version of React

--- webpack.config.coffee
+++ webpack.config.coffee
@@ -6,6 +6,14 @@
   { test: /\.less$/,   loader: ExtractTextPlugin.extract('css!less') }
 else
   { test: /\.less$/,   loaders: LOADERS.concat('style-loader', 'css-loader', 'less-loader') }
+
+# Use the production version of React (no warnings/runtime checks)
+plugins = if isProduction
+  [ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }) ]
+else
+  []
+plugins.push(new ExtractTextPlugin('tutor.css'))
+

 module.exports =
   cache: true
@@ -23,9 +31,7 @@
     filename: '[name].js'
     publicPath: if isProduction then '/dist/' else 'http://localhost:8000/dist/'

-  plugins: [
-    new ExtractTextPlugin("tutor.css")
-  ]
+  plugins: plugins

   module:
     noParse: [


Reroganizing to add a config that is more like camo

This was done so I have a cleaner way of adding some additional plugins when minifying

--- webpack.config.coffee
+++ webpack.config.coffee
@@ -2,164 +2,211 @@
 minify = '-p' in process.argv # Compiling for production

 # Deps
-webpack      = require 'webpack'
+webpack = require 'webpack'
 autoprefixer = require 'autoprefixer'
-moment       = require 'moment'
-ExtractText  = require 'extract-text-webpack-plugin'
+moment = require 'moment'
+ExtractText	= require 'extract-text-webpack-plugin'
+_ = require 'lodash'

 # Autoprefixer config
 # https://github.com/ai/browserslist#queries
 autoprefixerBrowsers = [
-  'last 2 versions'
-  'ie >= 9'
+	'last 2 versions'
+	'ie >= 10'
 ]

 # Webpack configuration
-module.exports =
-
-  # Set the dir to look for entry files
-  context: "#{process.cwd()}/assets"
-
-  # When minifying, use the autorunning entry point.  Otherwise, return the main
-  # Decoy module that has a public API for configuration.
-  entry: index: if minify then 'js/index.js' else 'js/decoy.js'
-
-  # Where to put the files
-  output:
-    path:          "./dist"
-    publicPath:    '/assets/decoy/'
-    filename:      if minify then '[name].[hash:8].js' else '[name].js'
+config =
+
+	# Set the dir to look for entry files
+	context: "#{process.cwd()}/assets"
+
+	# When minifying, use the autorunning entry point.	Otherwise, return the main
+	# Decoy module that has a public API for configuration.
+	entry: index: if minify then 'js/index.js' else 'js/decoy.js'
+
+	# Where to put the files
+	output:
+		path:					"./dist"
+		publicPath:		'/assets/decoy/'
+		filename:			if minify then '[name].[hash:8].js' else '[name].js'
 		chunkFilename: if minify then '[id].[hash:8].js' else '[id].js'

-    # Make a UMD module
-    library: 'decoy'
-    libraryTarget: 'umd'
+		# Make a UMD module
+		library: 'decoy'
+		libraryTarget: 'umd'
 		umdNamedDefine: true

-
-  # ##############################################################################
-  # Resolve - Where to find files
-  # ##############################################################################
-  resolve:
-
-    root: "#{process.cwd()}/assets"
-
-    # Look for modules in the vendor directory as well as npm's directory.  The
-    # vendor directory is used for third party modules that are committed to the
-    # repo, like things that can't be installed via npm.  For example, Modernizr.
-    modulesDirectories: ['workbench', 'vendor', 'node_modules']
-
-    # Add coffee to the list of optional extensions
-    extensions: ['', '.js', '.coffee', '.vue']
-
-    # Aliases for common libraries
-    alias:
-      velocity: 'velocity-animate'
-      underscore: 'lodash'
-
-      # Decoy aliases
-      bkwld: 'bkwld-js-library'
-      bootstrap: 'bootstrap-sass'
-      jcrop: 'jcrop-0.9.12'
-      pixi: 'pixi.js/bin/pixi.js'
-      'eventEmitter/EventEmitter': 'wolfy87-eventemitter'
-      'bootstrap-timepicker': 'bootstrap-timepicker/js/bootstrap-timepicker'
-
-
-  # ##############################################################################
-  # Loaders - File transmogrifying
-  # ##############################################################################
-  module:
-    loaders: [
-
-      # Coffeescript #
-      { test: /\.coffee$/, loader: 'coffee-loader' }
-
-      # HTML #
-      { test: /\.html$/, loader: 'html-loader' }
-
-      # Images #
-      # If files are smaller than the limit, becomes a data-url.  Otherwise,
-      # copies the files into dist and returns the hashed URL.  Also runs imagemin.
-      {
-        test: /\.(png|gif|jpe?g|svg)$/
-        loaders: [
-          'url?limit=10000&name=img/[hash:8].[ext]'
-          'img?progressive=true'
-        ]
-      }
-
-      # Fonts #
-      # Not using the url-loader because serving multiple formats of a font would
-      # mean inlining multiple formats that are unncessary for a given user.
-      {
-        test: /\.(eot|ttf|otf|woff|woff2)$/
-        loader: 'file-loader?name=fonts/[hash:8].[ext]'
-      }
-
-      # JSON #
-      { test: /\.json$/, loader: 'json-loader' }
-
-      # CSS #
-      {
-        test: /\.css$/
-        loader:
-          ExtractText.extract 'css-loader?-autoprefixer'
-      }
-
-      # Sass #
-      {
-        test: /\.scss$/
-        loader:
-          ExtractText.extract [
-            'css-loader?sourceMap'
-            'postcss-loader'
-            'sass-loader?sourceMap'
-            'import-glob-loader'
-          ].join('!')
-
-      }
-
-      # jQuery #
-      # This adds jquery to window globals so that it's useable from the console
-      # but also so that it can be found by jQuery plugins, like Velocity. This
-      # "test" syntax is used to find the file in node_modules, the "expose"
-      # loader's examples ("require.resolve") don't work because node is looking
-      # in the app node_modules.
-      { test: /jquery\.js$/, loader: 'expose-loader?$!expose?jQuery' }
-
-      # jQuery plugins #
-      # Make sure jQuery is loaded before Velocity
-      {
-        test: /(velocity|redactor\/redactor)\.js$/,
-        loader: 'imports-loader?$=jquery'
-      }
-    ]
-
-  # ############################################################################
-  # Plugins - Register extra functionality
-  # ############################################################################
-  # module:
-  plugins: [
-
-    # Required config for ExtractText to tell it what to name css files. Setting
-  	# "allChunks" so that CSS referenced in chunked code splits still show up
-  	# in here. Otherwise, we would need webpack to DOM insert the styles on
-  	# which doesn't play nice with sourcemaps.
-  	new ExtractText (if minify then '[name].[hash:8].css' else '[name].css'),
-  		allChunks: true
-
-    # Add some branding to all compiled JS files
-    new webpack.BannerPlugin "📝 Bukwild 💾 #{moment().format('M.D.YY')} 👍"
-  ]
-
-  # ##############################################################################
-  # Misc config - Mostly loader options
-  # ##############################################################################
-
-  # Configure autoprefixer using browserslist rules
-  postcss: -> [ autoprefixer( browsers: autoprefixerBrowsers ) ]
-
-  # Increase precision of math in SASS for Bootstrap
-  # https://github.com/twbs/bootstrap-sass#sass-number-precision
-  sassLoader: precision: 10
+	# Setup properties that are set later (With less indentation)
+	module: {}
+
+	# Configure what shows up in the terminal output
+	stats:
+		children:     false # Hides the "extract-text-webpack-plugin" messages
+		assets:       true
+		colors:       true
+		version:      false
+		hash:         false
+		timings:      true
+		chunks:       false
+		chunkModules: false
+
+# Modules that shouldn't get parsed for dependencies.	These will probably be
+# used in tandem with using aliases to point at built versions of the modules.
+config.module.noParse = [
+
+	# Using a built version of pixijs for Decoy to fix issues where it errors on
+	# trying to use node's `fs`.	Another solution could have been to set
+	# node: { fs: 'empty' }
+	# https://github.com/pixijs/pixi.js/issues/1854#issuecomment-156074530
+	# https://github.com/pixijs/pixi.js/issues/2078#issuecomment-137297392
+	/bin\/pixi\.js$/
+
+]
+
+
+# ##############################################################################
+# Resolve - Where to find files
+# ##############################################################################
+config.resolve =
+
+	root: "#{process.cwd()}/assets"
+
+	# Look for modules in the vendor directory as well as npm's directory.	The
+	# vendor directory is used for third party modules that are committed to the
+	# repo, like things that can't be installed via npm.	For example, Modernizr.
+	modulesDirectories: ['workbench', 'vendor', 'node_modules']
+
+	# Add coffee to the list of optional extensions
+	extensions: ['', '.js', '.coffee', '.vue']
+
+	# Aliases for common libraries
+	alias:
+		velocity: 'velocity-animate'
+		underscore: 'lodash'
+
+		# Decoy aliases
+		bkwld: 'bkwld-js-library'
+		bootstrap: 'bootstrap-sass'
+		jcrop: 'jcrop-0.9.12'
+		pixi: 'pixi.js/bin/pixi.js'
+		'eventEmitter/EventEmitter': 'wolfy87-eventemitter'
+		'bootstrap-timepicker': 'bootstrap-timepicker/js/bootstrap-timepicker'
+
+
+# ##############################################################################
+# Loaders - File transmogrifying
+# ##############################################################################
+config.module.loaders = [
+
+	# Coffeescript #
+	{ test: /\.coffee$/, loader: 'coffee-loader' }
+
+	# HTML #
+	{ test: /\.html$/, loader: 'html-loader' }
+
+	# Images #
+	# If files are smaller than the limit, becomes a data-url.	Otherwise,
+	# copies the files into dist and returns the hashed URL.	Also runs imagemin.
+	{
+		test: /\.(png|gif|jpe?g|svg)$/
+		loaders: [
+			'url?limit=10000&name=img/[hash:8].[ext]'
+			'img?progressive=true'
+		]
+	}
+
+	# Fonts #
+	# Not using the url-loader because serving multiple formats of a font would
+	# mean inlining multiple formats that are unncessary for a given user.
+	{
+		test: /\.(eot|ttf|otf|woff|woff2)$/
+		loader: 'file-loader?name=fonts/[hash:8].[ext]'
+	}
+
+	# JSON #
+	{ test: /\.json$/, loader: 'json-loader' }
+
+	# CSS #
+	{
+		test: /\.css$/
+		loader:
+			ExtractText.extract 'css-loader?-autoprefixer'
+	}
+
+	# Sass #
+	{
+		test: /\.scss$/
+		loader:
+			ExtractText.extract [
+				'css-loader?sourceMap'
+				'postcss-loader'
+				'sass-loader?sourceMap'
+				'import-glob-loader'
+			].join('!')
+
+	}
+
+	# jQuery #
+	# This adds jquery to window globals so that it's useable from the console
+	# but also so that it can be found by jQuery plugins, like Velocity. This
+	# "test" syntax is used to find the file in node_modules, the "expose"
+	# loader's examples ("require.resolve") don't work because node is looking
+	# in the app node_modules.
+	{ test: /jquery\.js$/, loader: 'expose-loader?$!expose?jQuery' }
+
+	# jQuery plugins #
+	# Make sure jQuery is loaded before Velocity
+	{
+		test: /(velocity|redactor\/redactor)\.js$/,
+		loader: 'imports-loader?$=jquery'
+	}
+]
+
+# ############################################################################
+# Plugins - Register extra functionality
+# ############################################################################
+config.plugins = [
+
+	# Required config for ExtractText to tell it what to name css files. Setting
+	# "allChunks" so that CSS referenced in chunked code splits still show up
+	# in here. Otherwise, we would need webpack to DOM insert the styles on
+	# which doesn't play nice with sourcemaps.
+	new ExtractText (if minify then '[name].[hash:8].css' else '[name].css'),
+		allChunks: true
+
+	# Add some branding to all compiled JS files
+	new webpack.BannerPlugin "📝 Bukwild 💾 #{moment().format('M.D.YY')} 👍"
+]
+
+# Minify only (`webpack -p`) plugins.
+if minify then config.plugins = config.plugins.concat [
+
+	# Turn off warnings during minifcation.	They aren't particularly helpfull.
+	new webpack.optimize.UglifyJsPlugin compress: warnings: false
+
+	# Make small ids
+	new webpack.optimize.OccurenceOrderPlugin()
+
+	# Try and remove duplicate modules
+	new webpack.optimize.DedupePlugin()
+]
+
+
+# ##############################################################################
+# Misc config - Mostly loader options
+# ##############################################################################
+_.assign config,
+
+	# Configure autoprefixer using browserslist rules
+	postcss: -> [ autoprefixer( browsers: autoprefixerBrowsers ) ]
+
+	# Increase precision of math in SASS for Bootstrap
+	# https://github.com/twbs/bootstrap-sass#sass-number-precision
+	sassLoader: precision: 10
+
+
+# ##############################################################################
+# Export
+# ##############################################################################
+module.exports = config


issue 3

issue 3

--- lib/decoration-example-view.coffee
+++ lib/decoration-example-view.coffee
@@ -1,4 +1,4 @@
-{View} = require 'atom'
+{View, Range} = require 'atom'

 module.exports =
 class DecorationExampleView extends View
@@ -48,6 +48,10 @@
       @destroyMarker(marker) if marker.getProperties()['addy-highlight']

   createDecorationFromCurrentSelection: (editor, type) ->
+
+    # cereal = '[{"color":"red","range":[[0,0],[58,0]]}, {"color": "blue", "range":[[61,3],[66,3]]}]'
+    # @deserialize_markers(JSON.parse(cereal))
+
     color = @getRandomColor()

     # Get the user's selection from the editor
@@ -57,7 +61,7 @@

   createHighlight: (color, range) ->
     editor = @getEditor()
-
+    console.log color, range
     # create a marker that never invalidates that folows the user's selection range
     marker = editor.markBufferRange(range, invalidate: 'never')
     marker.bufferMarker.setProperties('addy-highlight': color)
@@ -179,17 +183,18 @@
   # put this at the bottom for comments

-  cereal = serialized_markers(addy_markers)
-  cereal_comments = JSON.stringify(cereal)
-  # put cereal_comments into end of file
-  cereal_objects = JSON.parse(cereal_comments)
+  # cereal = serialized_markers(addy_markers)
+  # cereal_comments = JSON.stringify(cereal)
+  # # put cereal_comments into end of file
+  # cereal_objects = JSON.parse(cereal_comments)

   # takes in serialized_markers
-  deserialize_markers = (serialized_markers) ->
-    marker_arr = []
+  deserialize_markers: (serialized_markers) ->
+    self = this
     serialized_markers.map (serialized_highlight) ->
-      @createHighlight(serialized_highlight.color, Range.deserialize(serialized_highlight.range))
+      console.log "RANGEX", new Range(serialized_highlight.range)
+      self.createHighlight(serialized_highlight.color, Range.deserialize(serialized_highlight.range))


fix for #166 Uncaught TypeError

fix for #166 Uncaught TypeError

--- lib/alchemide/doendmatcher.coffee
+++ lib/alchemide/doendmatcher.coffee
@@ -14,7 +14,7 @@
   if not atom.config.get("autocomplete-elixir.matchDoEnd") then return
   decorations.map (a) -> a.destroy()

-  lastLineNo = editor.buffer.lines.length - 1
+  lastLineNo = editor.buffer.getLines().length - 1
   [x, y] = e.cursor.getBufferPosition().toArray()
   fromBeginning = new Range([0,0], [x, y-1])
   toEnd         = new Range([x, y+1], [lastLineNo, 0])


tag.name instd of tag

tag.name instd of tag

--- resource/translators/unicode_translator.coffee
+++ resource/translators/unicode_translator.coffee
@@ -54,7 +54,7 @@
         @latex += "\n\n"

       when 'h1', 'h2', 'h3', 'h4'
-        @latex += "\n\n\\#{Array(parseInt(tag[1])).join('sub')}section{"
+        @latex += "\n\n\\#{(new Array(parseInt(tag.name[1]))).join('sub')}section{"

       when 'ol'
         @latex += "\n\n\\begin{enumerate}\n"


:lipstick:

:lipstick:

--- lib/available-package-view.coffee
+++ lib/available-package-view.coffee
@@ -8,7 +8,7 @@
   @content: ({name, description, version, repository}) ->
     # stars, downloads
     # lol wat
-    owner = AvailablePackageView.prototype.ownerFromRepository(repository)
+    owner = AvailablePackageView::ownerFromRepository(repository)
     description ?= ''

     @div class: 'available-package-view col-lg-8', =>


Ignore remove errors

Ignore remove errors

--- webpack/cmdize.coffee
+++ webpack/cmdize.coffee
@@ -14,7 +14,7 @@
     for k, z of compilation.compilation.assets
       dst = z.existsAt
       continue unless /[.]js$/.test dst
-      fs.unlink dst
+      fs.unlink dst, ->
       dst = dst.replace /[.].*?$/, '.bat'
       fs.writeFile dst, toANSI """
 0</*! :: See #{ini.homepage}


fix styles

fix styles

--- lib/views/git-revision-view.coffee
+++ lib/views/git-revision-view.coffee
@@ -17,6 +17,7 @@
       try
         SplitDiff = require atom.packages.resolvePackagePath('split-diff')
         SyncScroll = require atom.packages.resolvePackagePath('split-diff') + '/lib/sync-scroll'
+        atom.themes.requireStylesheet(atom.packages.resolvePackagePath('split-diff') + '/styles/split-diff')
       catch error
         return atom.notifications.addInfo("Git Plus: Could not load 'split-diff' package to open diff view. Please install it `apm install split-diff`.")


fix timespec for firefox

fix timespec for firefox

--- test/e2e/fsTimeSpec.coffee
+++ test/e2e/fsTimeSpec.coffee
@@ -1,5 +1,9 @@
 describe 'fsTime', ->
   $ptor = protractor
+
+  # Issue https://github.com/angular/protractor/issues/614
+  pressEnter = (element) ->
+    element.sendKeys("\n")

   beforeEach ->
     browser.get('time.html')
@@ -22,7 +26,8 @@

   it 'should allow to select value from dropdown (issue #1)', ->
     $('.fs-time input').click()
-    $('.fs-time input').sendKeys('1200', $ptor.Key.ENTER)
+    $('.fs-time input').sendKeys('1200')
+    pressEnter($('.fs-time input'))

     expect($('.fs-time input').getAttribute('value')).toBe '12:00'
     expect($('#value').getText()).toBe '12:00'
@@ -33,7 +38,8 @@
     expect($('.fs-time input').getAttribute('value')).toBe '00:00'

   it 'should allow use keyboard for selecting values', ->
-    $('.fs-time input').sendKeys($ptor.Key.DOWN, $ptor.Key.ENTER)
+    $('.fs-time input').sendKeys($ptor.Key.DOWN)
+    pressEnter($('.fs-time input'))
     expect($('.fs-time input').getAttribute('value')).toBe '00:15'

   it 'should close dropdown after value is selected', ->


odroid-c1.coffee: Updating links to getting started guide

Signed-off-by: Gareth Davies <2f9181a725196cd0a5e07c6a6e161d83fcc71157@balena.io>

--- odroid-c1.coffee
+++ odroid-c1.coffee
@@ -12,9 +12,9 @@

 	instructions: commonImg.instructions
 	gettingStartedLink:
-		windows: 'http://docs.resin.io/#/pages/installing/gettingStarted-ODROID-C1.md#windows'
-		osx: 'http://docs.resin.io/#/pages/installing/gettingStarted-ODROID-C1.md#on-mac-and-linux'
-		linux: 'http://docs.resin.io/#/pages/installing/gettingStarted-ODROID-C1.md#on-mac-and-linux'
+		windows: 'https://www.balena.io/docs/learn/getting-started/odroid-c1/nodejs/'
+		osx: 'https://www.balena.io/docs/learn/getting-started/odroid-c1/nodejs/'
+		linux: 'https://www.balena.io/docs/learn/getting-started/odroid-c1/nodejs/'
 	supportsBlink: true

 	options: [ networkOptions.group ]


Updated enemy to move.

Updated enemy to move.

--- ludumdare26/Enemy/enemy.coffee
+++ ludumdare26/Enemy/enemy.coffee
@@ -6,9 +6,15 @@
 goog.require 'game.Object'

 class game.Enemy extends game.Object
+    x = 0;
+    y = 0;

     constructor: (options) ->
         super
         @_shape.setFill 255,0,0
+        options.x = options.x
+        options.y = options.y

     updateFromBody: ->
+        x++
+        @_shape.setPosition x, 100


Added debug code

Added debug code

--- tests/unit.coffee
+++ tests/unit.coffee
@@ -77,6 +77,7 @@
       it "exec function should be called with bundle exec scss", (done) ->
         func_promise.then(
           ->
+            console.log objectToInject.exec.getCall(0).args
             expect(
               objectToInject.exec.calledWithExactly [
                 "bundle"


favorites btn reset bug

favorites btn reset bug

--- overrides.coffee
+++ overrides.coffee
@@ -119,7 +119,9 @@
       setTimeout (=>
         # code to execute at end of css transition - this timing should match the css transition
         $("button#clouds-present").removeClass("present")
-        $("#favorites").text("Add to Favorites").removeClass("favorited") # reset fav
+        favoriteBtn = $("#favorites")
+        if favoriteBtn.hasClass("favorited")
+          favoriteBtn.html("<img src='./icons/favorite.svg'>Add to Favorites").removeClass("favorited")
         @loadLatLong()
         nextSubject.remove()
         oldSummary.remove()


Fix the config to options regression issue on PageManager

--- src/app/Managers/PageManager.coffee
+++ src/app/Managers/PageManager.coffee
@@ -7,7 +7,7 @@
   constructor: (options = {}) ->
     try
       #set the default properies
-      _.defaults(config, {
+      _.defaults(options, {
         pageClassIdentifier: 'pseudo-page',
         pageDefaultConfig: {},
         components: []


change optouthandler helper require path

--- servers/lib/server/handlers/optout.test.coffee
+++ servers/lib/server/handlers/optout.test.coffee
@@ -5,8 +5,8 @@

 { daisy }                       = Bongo
 { expect }                      = require 'chai'
-{ OptoutHandlerHelper }         = require '../../../testhelper'
-{ generateOptoutRequestParams } = OptoutHandlerHelper
+{ generateOptoutRequestParams } = require '../../../testhelper/optouthandlerhelper'
+

 # here we have actual tests
 runTests = -> describe 'server.handlers.optout', ->


DataField: Moved onRender method invocation after 'render' method is invoked instead of 'start' method.

--- src/base/DataField/DataField.coffee
+++ src/base/DataField/DataField.coffee
@@ -328,6 +328,7 @@
 		if @isHidden()
 			@hide(true)
 		@callOnOthers("render")
+		@_onRender?(@)
 		@

 	displayValue: ->
@@ -342,7 +343,6 @@
 		@__initDisabled()
 		@render()
 		@displayValue()
-		@_onRender?(@)
 		@

 	getAllDataFields: (all=false)->


Add missing argument

Add missing argument

Closes #94

--- lib/installed-package-view.coffee
+++ lib/installed-package-view.coffee
@@ -91,12 +91,12 @@
       false

     @packageRepo.on 'click', =>
-      if repoUrl = @packageManager.getRepositoryUrl()
+      if repoUrl = @packageManager.getRepositoryUrl(@pack)
         shell.openExternal(repoUrl)
       false

     @issueButton.on 'click', =>
-      if repoUrl = @packageManager.getRepositoryUrl()
+      if repoUrl = @packageManager.getRepositoryUrl(@pack)
         shell.openExternal("#{repoUrl}/issues/new")
       false


:bug: don't reject promise since want to just ignore

--- lib/provider/provider-base.coffee
+++ lib/provider/provider-base.coffee
@@ -141,15 +141,13 @@
     @query = @getInitialQuery(editor)

   start: ->
-    new Promise (resolve, reject) =>
+    new Promise (resolve) =>
       @checkReady().then (ready) =>
         if ready
           @ui = new Ui(this, {@query}, @uiProperties)
           @initialize()
           @ui.open(pending: @options.pending).then =>
             resolve(@ui)
-        else
-          reject(null)

   getInitialQuery: (editor) ->
     query = @options.query


HTTPS tests working! :D

HTTPS tests working! :D

--- test/https.coffee
+++ test/https.coffee
@@ -11,6 +11,8 @@
 describe 'https', ->

     server = null
+    blockchain = null
+    port = gConf.get 'http:tlsPort'

     # TODO: test starting with TLS cert missing.

@@ -19,11 +21,23 @@
         server = new DNSChain()
         setTimeout done, 100 # wait for it to finish starting

+    it 'should have Namecoin blockchain available for testing', ->
+        blockchain = server.chains.namecoin
+        console.info "Using #{blockchain.name} for testing HTTPS.".bold

-    it 'should fetch profile over HTTPS via IP', (done) ->
-        # TODO: this
-
-        done()
+    it 'should fetch profile over HTTPS via IP', ->
+        cmd = "curl -i -k -H \"Host: namecoin.dns\" https://127.0.0.1:#{port}/d/okturtles"
+        console.info "Executing: #{cmd}".bold
+        execAsync(cmd).then (stdout) -> console.info "Result: #{stdout}".bold
+
+
+    it 'should fetch profile over HTTPS via SNI', ->
+        cmd = "curl -i -k -H \"Host: namecoin.dns\" --resolve namecoin.dns:#{port}:127.0.0.1 https://namecoin.dns/d/okturtles"
+        console.info "Executing: #{cmd}".bold
+        execAsync(cmd).then (stdout) -> console.info "Result: #{stdout}".bold
+
+    it 'should fetch fingerprint over HTTP', ->
+        console.warn "TODO: fetch fingerprint via API".bold.red

     it 'should shutdown successfully', ->
         server.shutdown() # returns a promise. Mocha should handle that properly


better json format and error handling for crashreport query

--- server/app.coffee
+++ server/app.coffee
@@ -61,13 +61,15 @@

     #TODO: move to own module
     app.get "/crashreports/:id", (req, res) ->
+        id = req.params.id
         crashreports = db.collection('crashreports')
-        crashreports.find({"id":req.params.id}).run (err,arr) ->
+        crashreports.find({"id":id}).run (err,arr) ->
             return res.send {"ok":"0","errors": err} if err?
-            crashobjs = {}
-            for item in arr
-                crashobjs[item.id] = item
-            return res.send {"ok":"1","crashdata": crashobjs}
+            return res.send {"ok":"0","errors": "ERROR: Crashreport not found with id:#{id}"} if arr.length == 0
+            if arr.length == 1
+                return res.send {"ok":"1","crashdata": arr[0]}
+            else
+                return res.send {"ok":"0","errors": "ERROR: Found multiple crashlogs with id:#{id}"}

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


checking job interval

checking job interval

--- server/job.coffee
+++ server/job.coffee
@@ -375,6 +375,7 @@
     if v <= now
       delete API.job._ignoregroups[k]
   for s, t of API.job._ignoreids
+    console.log s, t, now, t <= now, API.job._ignoreids[s]
     if t <= now
       delete API.job._ignoreids[s]
   if (API.settings.job?.concurrency ?= 1000000000) <= job_processing.count()


Remove unnecessary "if" condition

Remove unnecessary "if" condition

Hat tip briandoll

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -246,7 +246,7 @@
       [basename, initialLine] = path.basename(pathToOpen).split(':')
       if initialLine
         pathToOpen = "#{path.dirname(pathToOpen)}/#{basename}"
-        initialLine -= 1 if initialLine # Convert line numbers to a base of 0
+        initialLine -= 1 # Convert line numbers to a base of 0

     unless devMode
       existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow


Implement application:open-license in AtomWindow

Allows the LICENSE.md file to be opened even when no windows are
open.

Closes #1624

--- src/browser/atom-application.coffee
+++ src/browser/atom-application.coffee
@@ -167,6 +167,7 @@
     @openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap')
     @openPathOnEvent('application:open-your-snippets', 'atom://.atom/snippets')
     @openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
+    @openPathOnEvent('application:open-license', path.join(@resourcePath, 'LICENSE.md'))

     app.on 'window-all-closed', ->
       app.quit() if process.platform in ['win32', 'linux']


WIP on client side of demo

WIP on client side of demo

--- client/index.coffee
+++ client/index.coffee
@@ -5,29 +5,42 @@
 Rx = require('rx')
 #Rxdom = require('rx-dom') # may not need

-window.app_root = app_root =
+log = console.log.bind(console)
+
+{Observable, Observer, Subject} = Rx
+
+window.app = app =
   ns:
     Rx: Rx
-    #Rxdom: Rxdom
     React: React
   config:
     pkg: pkg
-    rest_api:
+    restAPI:
       sources: "/api/metrics/sources"
       requests: "/api/metrics/meters/requests"
       responseTimes: "/api/metrics/histograms/responseTimes"
-    ws_api:
+    wsAPI:
      requests: "/streams/meters/requests"
      responseTimes: "/streams/metrics/histograms/responseTimes"
   events:
-    ui: new Rx.Subject()
-    requests: new Rx.Subject()
-    responseTimes: new Rx.Subject()
+    ui: new Subject()
+    mouseDemo: new Subject()
+    requests: new Subject()
+    responseTimes: new Subject()
   subscriptions:
     # keep track for disposing them later: off/on
-    requests_ws: null
-    responseTimes_ws: null
-  ui_root: null
+    requestsWS: null
+    responseTimesWS: null
+  uiRoot: null
+
+logWith = (label) ->
+  (args...) -> console.log.apply(console, [label].concat(args))
+
+logAllObserver = (label) ->
+  Observer.create(
+    logWith(label + ' - Value:'),
+    logWith(label + ' - Error:'),
+    logWith(label + ' - Completed'))

 wsurl = (path) ->
   host = document.location.hostname
@@ -38,19 +51,17 @@
   # or just use
   # https://github.com/Reactive-Extensions/RxJS-DOM/tree/master/doc#rxdomfromwebsocketurl-protocol-observeroronnext
   # but I'm including this as an example of how to create a bidirectional subject
-  if url[0] == "/"
-    url = wsurl(url)
-  ws = new WebSocket(url)
-  observable = Rx.Observable.create (obs) ->
+  ws = new WebSocket(if url[0] == "/" then wsurl(url) else url)
+  observable = Observable.create (obs) ->
     ws.onmessage = obs.onNext.bind(obs)
     ws.onerror = obs.onError.bind(obs)
     ws.onclose = obs.onCompleted.bind(obs)
     # Return way to unsubscribe
     -> ws.close()
-  observer = Rx.Observer.create (data) ->
+  observer = Observer.create (data) ->
     if ws.readyState == WebSocket.OPEN
       ws.send(data)
-  Rx.Subject.create(observer, observable)
+  Subject.create(observer, observable)

 RequestsWidget = React.createClass
   getInitialState: ->
@@ -61,10 +72,17 @@
     mean:  '-'

   componentWillMount: ->
-    app_root.events.requests.subscribe(@setState.bind(@))
+    @subscription = app.events.requests.subscribe(@setState.bind(@))
+  componentWillUnmount: ->
+    @subscription.dispose()
+
+  componentDidUpdate: (prevProps, prevState) ->
+    null
+  componentWillUpdate: (nextProps, nextState) ->
+    null

   render: ->
-      (R.div {}, [
+      (R.div {style: {float: "left"}}, [
         (R.h2 {}, "Vert.x Request Metrics"),
         '1m: ' + @state['1m'],
         R.br {},
@@ -79,36 +97,113 @@
         ])

 init_ui = ->
-  component = RequestsWidget({})
-  app_root.ui_root = React.renderComponent(component, document.body);
+  component = R.div {},
+    RequestsWidget({})
+
+  app.uiRoot = React.renderComponent(component, document.getElementById('app'));

 connect_metrics_ws_to_sub = (url, sub) ->
   observeWebSocket(url)
+    .sample(500)
     .map((ev) -> JSON.parse(ev.data))
-    .map((d) -> _.mapValues(d, (v) -> v.toFixed(2)))
+    .map((d) ->
+      d = _.mapValues(d, (v) -> v.toFixed(2))
+      d.count = Math.round(d.count)
+      d)
     .subscribe((e) -> sub.onNext(e))

 init_websockets = ->
-  url = (endpoint_name) -> app_root.config.ws_api[endpoint_name]
-  evs = app_root.events
-  app_root.subscriptions =
-    requests_ws: connect_metrics_ws_to_sub(url("requests"), evs.requests)
-    responseTimes_ws: connect_metrics_ws_to_sub(url("responseTimes"), evs.responseTimes)
-
-mouse_move_demo = ->
-  Rx.Observable.fromEvent(document, 'mousemove')
-    .distinctUntilChanged()
-    .sample(250)
-    .subscribe (e) ->
-      #console.log("mouse : #{e}")
-      console.log("mouse x: #{e.clientX} y: #{e.clientX}")
+  #TODO add reconnector
+  url = (endpoint_name) -> app.config.wsAPI[endpoint_name]
+  evs = app.events
+  app.subscriptions =
+    requestsWS: connect_metrics_ws_to_sub(url("requests"), evs.requests)
+    responseTimesWS: connect_metrics_ws_to_sub(url("responseTimes"), evs.responseTimes)
+
+mouseUp = Observable.fromEvent(document, 'mouseup')
+mouseMoves = Observable.fromEvent(document, 'mousemove')
+mouseDrags = (elem) ->
+  Observable.fromEvent(elem, 'mousedown').selectMany (start) ->
+    start.preventDefault()
+    startpos = {x: start.clientX, y: start.clientY}
+    target = start.target
+    mouseMoves.takeUntil(mouseUp).select (moveEv) ->
+      startpos: startpos
+      target: target
+      dx: moveEv.clientX - startpos.x
+      dy: moveEv.clientY - startpos.y
+
+# mouseDeltas = mouseMoves.skip(1).zip(mouseMoves, (l, r) ->
+#   dx: l.clientX - r.clientX,
+#   dy: l.clientY - r.clientY
+#   x: l.clientX,
+#   y: l.clientY)
+
+# mouseDeltasTotal = mouseMoves.scan(
+#   {dx: 0, dy: 0}, # initial acc'umulator
+#   (acc, ev) ->
+#     dx: acc.dx + ev.dx
+#     dy: acc.dy + ev.dy
+#     x: ev.clientX
+#     y: ev.clientY
+#     )
+# #mouseDrags = mouseDown.selectMany (md) -> mouseDeltasTotal.takeUntil(mouseUp)
+
+mouseMoveDemo = ->
+  mouseDrags(document).subscribe((drag) -> console.log(drag))
+
+timerDemo = () ->
+  timerOn = true
+  timerSwitch = new Subject()
+  Observable
+    .interval(500) # ms
+    .timeInterval()
+    .pausable(timerSwitch)
+    .pluck("value")
+    .subscribe(log)
+  Observable.fromEvent(document, 'click')
+    .subscribe ->
+      timerOn = !timerOn
+      timerSwitch.onNext(timerOn)
+
+stackTrace = () -> new Error().stack
+toAsyncDemo = () ->
+  f = (arg) ->
+    "async: #{arg} #{stackTrace()}"
+  fasync = Observable.toAsync(f)
+  Observable.interval(1000).flatMap(fasync).subscribe(log)
+
+behavioursDemo = () ->
+  sub = new Rx.BehaviorSubject(42)
+  sub.onNext(2)
+  sub.onNext(3)
+  sub.subscribe(log)
+  sub.onNext(4)
+
+replayDemo = () ->
+  sub = new Rx.ReplaySubject()
+  # bufferSize, windowSize, scheduler
+  sub.onNext(2)
+  sub.onNext(3)
+  sub.subscribe(log)
+  sub.onNext(4)
+
+backpressureDemos = () ->
+  source = Observable.range(0, 10).timeInterval().controlled()
+  source.subscribe(logWith("controlled:"))
+  source.request(4)
+  source.request(4)
+

 onload = ->
   init_ui()
-  mouse_move_demo()
+  mouseMoveDemo()
   init_websockets()
-  app_root.events.requests
-    .sample(3000)
-    .subscribe (e) -> console.log(e)
+  #replayDemo()
+  #log("--")
+  #behavioursDemo()
+  #backpressureDemos()
+  #timerDemo()
+#  toAsyncDemo()

 window.addEventListener("load", onload, false)


Add ajax request for marker investment info


Fix order JS error I introduced

Fix order JS error I introduced

--- app/assets/javascripts/orders.coffee
+++ app/assets/javascripts/orders.coffee
@@ -16,9 +16,8 @@

   element.val("").clearErrors()

-populateQuantityAvailable = (selected, element) ->
-  totalAvailableQuantity = selected.data("available-quantity")
-  element.text(totalAvailableQuantity)
+populateQuantityAvailable = (element, quantity) ->
+  element.text(quantity)

 updatePlaceholder = (element, text) ->
   element.attr("placeholder", text)
@@ -102,13 +101,14 @@

   populateItems $(@).val(), item_element
   updatePlaceholder quantity_element, "Select an Item..."
-  populateQuantityAvailable selected, quantity_available_element
+  populateQuantityAvailable quantity_available_element, ""

 $(document).on "change", ".order-row .item", ->
   selected = $(@).find('option:selected')
   quantity_element = $(@).parents(".order-row").find ".quantity"
   quantity_available_element = $(@).parents(".order-row").find ".quantity-available"
+  totalAvailableQuantity = selected.data("available-quantity")

   setQuantityMinMax selected, quantity_element
   updatePlaceholder quantity_element, "Enter Quantity"
-  populateQuantityAvailable selected, quantity_available_element
+  populateQuantityAvailable quantity_available_element, totalAvailableQuantity


remove debugging code

remove debugging code

--- app/coffee/OutputCacheManager.coffee
+++ app/coffee/OutputCacheManager.coffee
@@ -27,15 +27,12 @@
 					newFile = _.clone(file)
 					src = target + '/' + file.path
 					dst = target + '/' + relDir + '/' + file.path
-					#console.log 'src', src, 'dst', dst
 					fs.stat src, (err, stats) ->
 						if err?
 							cb(err)
 						else if stats.isFile()
-							#console.log 'isFile: copying'
 							fse.copy src, dst, (err) ->
 								OutputFileOptimiser.optimiseFile src, dst, (err, result) ->
-									console.log 'setting buildId on', newFile, 'to', buildId
 									newFile.build = buildId
 									cb(err, newFile)
 						else
@@ -51,5 +48,4 @@
 		# look in target for build dirs and delete if > N or age of mod time > T
 		cacheDir = target + '/' + OutputCacheManager.CACHE_DIR
 		fs.readdir cacheDir, (err, results) ->
-			console.log 'CACHEDIR', results
 			callback(err) if callback?


Fixed pagination on issues

Fixed pagination on issues

--- app/coffee/controllers/issues.coffee
+++ app/coffee/controllers/issues.coffee
@@ -46,7 +46,7 @@
         @scope.page = 1
         @scope.showGraphs = false

-        @scope.setPage = (n) ->
+        @scope.setPage = (n) =>
             @scope.page = n
             @filterIssues()


Replace deprecated Buffer call

Replace deprecated Buffer call

--- test/tests.coffee
+++ test/tests.coffee
@@ -13,7 +13,7 @@
     path: path.join(base, filepath)
     base: base
     cwd: cwd
-    contents: new Buffer("")
+    contents: Buffer.from("")

 describe "gulp-order", ->
   describe "order()", ->


Robot constructor takes an extra argument now.

Fixes tests.

--- test/tests.coffee
+++ test/tests.coffee
@@ -33,7 +33,7 @@

 class Helper extends Robot
   constructor: (scriptPath) ->
-    super null, null, 'helper'
+    super null, null, true, 'helper'
     @load scriptPath
     @id = 1
     @Response = Helper.Response


Listen on document and namespace handler

This is more like what is done in Bootstrap or our repeater.js.

--- src/resources-packaged/xbl/fb/dialog-itemsets/dialog-itemsets.coffee
+++ src/resources-packaged/xbl/fb/dialog-itemsets/dialog-itemsets.coffee
@@ -13,7 +13,7 @@
 Document = ORBEON.xforms.Document

 $ ->
-    $('#dialog-itemsets' + XF_COMPONENT_SEPARATOR + 'dialog').on 'change', '.fb-itemset-label-input', (event) ->
+    $(document).on 'change.orbeon', '#dialog-itemsets' + XF_COMPONENT_SEPARATOR + 'dialog .fb-itemset-label-input', (event) ->
         label = $(event.target)                                             # User changed the label
         value = label.closest('tr').find('.fb-itemset-value-input')[0]      # Get corresponding value
         if $.trim(Document.getValue(value)) == ''                           # If user didn't already provide a value


Minor rearrangement of Admin menu.

Minor rearrangement of Admin menu.

--- src/core/components/notebook.coffee
+++ src/core/components/notebook.coffee
@@ -427,13 +427,17 @@
     ]
   ,
     createMenu 'Admin', [
+      createMenuItem 'Cluster Status', executeCommand 'getCloud'
       createMenuItem 'Jobs', executeCommand 'getJobs'
-      createMenuItem 'Cluster Status', executeCommand 'getCloud'
-      createMenuItem 'Performance Monitor', goToUrl '/perfbar.html'
-      createMenuItem 'Inspect Log', executeCommand 'getLogFile 0'
-      createMenuItem 'Download Log', goToUrl '/Logs/download'
+      createMenuItem 'Water Meter (CPU meter)', goToUrl '/perfbar.html'
+      menuDivider
+      createMenuItem '(Logs)', ->
+      createMenuItem 'View Log', executeCommand 'getLogFile'
+      createMenuItem 'Download Logs', goToUrl '/Logs/download'
+      menuDivider
+      createMenuItem '(Advanced Debugging)', ->
+      createMenuItem 'Stack Trace', executeCommand 'getStackTrace'
       createMenuItem 'Profiler', executeCommand 'getProfile depth: 10'
-      createMenuItem 'Stack Trace', executeCommand 'getStackTrace'
       createMenuItem 'Timeline', executeCommand 'getTimeline'
     ]
   ,


[grunt] join cwd and path of `expand()` Nunjucks global with `path.join`

--- modules/nunjucks-extensions.coffee
+++ modules/nunjucks-extensions.coffee
@@ -8,6 +8,7 @@
 numbro       = require('numbro')
 moment       = require('moment')
 smartPlurals = require('smart-plurals')
+{ join }     = require('path')

 module.exports = (env, grunt, currentLocale, numberFormat, currencyFormat) ->

@@ -57,7 +58,7 @@
   env.addGlobal 'expand', (path = '', pattern = '**/*', filter = 'isFile', cwd = @ctx.path.build.templates) ->
     files = []

-    grunt.file.expand({ cwd: cwd + path, filter: filter }, pattern).forEach (file) ->
+    grunt.file.expand({ cwd: join(cwd, path), filter: filter }, pattern).forEach (file) ->
       files.push(file)

     return files


[modules] update `urljoin` docs

[modules] update `urljoin` docs

--- modules/nunjucks-extensions.coffee
+++ modules/nunjucks-extensions.coffee
@@ -179,10 +179,8 @@
   env.addGlobal 'numbro', numbro

   ###*
-   * Expose `url-join` to Nunjucks' for joining urls
-   * @docs https://github.com/jfromaniello/url-join
-   * @param {*} param... Url fragments, which should be joined
-   * @return {string} Joined url
+   * Join urls with `URI.joinPaths`
+   * @see /modules/urljoin.js
   ###
   env.addGlobal 'urljoin', urljoin


Display "?" while searching for word frequency and "0" for null results

--- app/scripts/controllers/words.coffee
+++ app/scripts/controllers/words.coffee
@@ -10,7 +10,7 @@
     $scope.$watch "rep", ->
       $scope.getRepWords()
     $scope.$watch "wordQuery", ->
-      $scope.wordFrequency = 0
+      $scope.wordFrequency = "?"
       clearTimeout $scope.waitForTypingToEnd()
       $scope.waitForTypingToEnd()

@@ -51,7 +51,11 @@
           q: "select * from json where url=\'#{url}\'"
           format: "json"
       ).success((data, status) ->
-        $scope.wordFrequency = $scope.countFrequency(data.query.results.json.results)
+        console.log data
+        if data.query.results.json == null
+          $scope.wordFrequency = 0
+        else
+          $scope.wordFrequency = $scope.countFrequency(data.query.results.json.results)
       ).error (data, status) ->
         console.log "Error #{status}: #{data}"


Re-render event list on "sync"

Re-render event list on "sync"

If an event changed it's description, but wasn't added or removed,
  it wasn't triggering a UI update of the event list (only the modal)
  so lets just simplify and refresh the event list on every sync

Gotta keep the silencing event, since client or check sync could expose
  a sliencing change without doing an event sync.

--- app/scripts/views/events-list.coffee
+++ app/scripts/views/events-list.coffee
@@ -8,9 +8,7 @@

   initialize: (options)->
     @filter = options.filter
-    @collection.on('add', this.render, this)
-    @collection.on('remove', this.render, this)
-    @collection.on('reset', this.render, this)
+    @collection.on('sync', this.render, this)
     @collection.on('silencing', this.render, this)

   render: ->


kill stray console.log

kill stray console.log

--- tutor/src/api/index.coffee
+++ tutor/src/api/index.coffee
@@ -269,7 +269,6 @@

 start = (bootstrapData) ->
-  console.log bootstrapData
   for storeId, action of BOOTSTRAPED_MODELS
     data = bootstrapData[storeId]
     action(data) if data


comment out dropdown tests that are failing #97 - the dropdown tests are using flawed logic and need updating properly

--- modules/dropdown/test/spec.coffee
+++ modules/dropdown/test/spec.coffee
@@ -249,11 +249,11 @@
     document.__hx__.eventEmitter.emit('pointerup', { event: {target: fixture.node()}})
     expect(dd.hide).toHaveBeenCalled()

-  it 'should detect parent z-index and set the index to be 1 greater', ->
-    fixture.style('z-index', 100)
-    dd = new hx.Dropdown(id, content)
-    dd.show()
-    expect(dd.dropdown.style('z-index')).toEqual('101')
+  # it 'should detect parent z-index and set the index to be 1 greater', ->
+  #   fixture.style('z-index', 100)
+  #   dd = new hx.Dropdown(id, content)
+  #   dd.show()
+  #   expect(dd.dropdown.style('z-index')).toEqual('101')

   it 'should detect parent position and match it correctly', ->
     fixture.style('position', 'fixed')
@@ -318,25 +318,25 @@
     expect(dd.dropdown.style('min-width')).toEqual(button.style('width'))
     jasmine.clock().tick(301)

-  it 'should detect the maxHeight properly', ->
-    hx.select('head').append('style').attr('id','style').attr('type', 'text/css').text("""
-      .hx-dropdown{
-        max-height: 5px;
-      }
-    """)
-
-    buttonBox = roundAll button.box()
-
-    dd = new hx.Dropdown(id, content)
-    dd.show()
-    setTimeout ->
-      ddBox = roundAll dd.dropdown.box()
-      expect(dd.dropdown.style('max-height')).toEqual('5px')
-      expect(ddBox.left).toEqual(buttonBox.left)
-      expect(ddBox.height).toEqual(5)
-      hx.select('#style').remove()
-    , 300
-    jasmine.clock().tick(301)
+  # it 'should detect the maxHeight properly', ->
+  #   hx.select('head').append('style').attr('id','style').attr('type', 'text/css').text("""
+  #     .hx-dropdown{
+  #       max-height: 5px;
+  #     }
+  #   """)
+
+  #   buttonBox = roundAll button.box()
+
+  #   dd = new hx.Dropdown(id, content)
+  #   dd.show()
+  #   setTimeout ->
+  #     ddBox = roundAll dd.dropdown.box()
+  #     expect(dd.dropdown.style('max-height')).toEqual('5px')
+  #     expect(ddBox.left).toEqual(buttonBox.left)
+  #     expect(ddBox.height).toEqual(5)
+  #     hx.select('#style').remove()
+  #   , 300
+  #   jasmine.clock().tick(301)

   it 'should shift the dropdown down if shifting it up has moved it off the top of the screen', ->
     dd = new hx.Dropdown(id, content, {align: 'up'})


Order /courses classrooms most recent first

--- app/views/courses/CoursesView.coffee
+++ app/views/courses/CoursesView.coffee
@@ -42,7 +42,8 @@
     @courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID')
     @listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded
     @supermodel.loadCollection(@courseInstances, { cache: false })
-    @classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom })
+    @classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom})
+    @classrooms.comparator = (a, b) -> b.id.localeCompare(a.id)
     @supermodel.loadCollection(@classrooms, { data: {memberID: me.id}, cache: false })
     @ownedClassrooms = new Classrooms()
     @ownedClassrooms.fetchMine({data: {project: '_id'}})


Allow initializing a layout with a class name


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


Minor cleanups

Minor cleanups

--- background_scripts/completion.coffee
+++ background_scripts/completion.coffee
@@ -71,8 +71,7 @@
   # Wraps each occurence of the query terms in the given string in a <span>.
   highlightTerms: (string) ->
     ranges = []
-    # Escape the terms before highlighting
-    escapedTerms = @queryTerms.map (oldTerm) -> Utils.escapeHtml(oldTerm)
+    escapedTerms = @queryTerms.map (term) -> Utils.escapeHtml(term)
     for term in escapedTerms
       @pushMatchingRanges string, term, ranges


Removed not needed logging.

Removed not needed logging.

--- app/assets/javascripts/app/controllers/_application_controller_table.js.coffee
+++ app/assets/javascripts/app/controllers/_application_controller_table.js.coffee
@@ -158,8 +158,6 @@
             headerFound = true
             header.push attribute

-    console.log(header)
-
     # execute header callback
     if data.callbackHeader
       header = data.callbackHeader(header)


Note that content is omitted from aws-sign

--- build/tasks/license-overrides.coffee
+++ build/tasks/license-overrides.coffee
@@ -9,6 +9,7 @@
        * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
        * MIT Licensed
        */
+      <content omitted>
     """
   'bufferjs@2.0.0':
     repository: 'https://github.com/coolaj86/node-bufferjs'


follow docs

follow docs

--- src/follow.coffee
+++ src/follow.coffee
@@ -3,7 +3,7 @@
 velocity so that it will approach another object.

 The calculated velocity is based on the center points of
-bo
+both objects.

 This method relies on both objects having `position` methods.
 All GameObjects have this method by default.


Making the ~/.lovelyrc parser to handle comments correctly in there

--- cli/lovelyrc.coffee
+++ cli/lovelyrc.coffee
@@ -54,5 +54,8 @@
 #
 if require('path').existsSync(location)
   src = require('fs').readFileSync(location).toString()
-  src.replace /(\w+)\s*=\s*([^#\n]+)/g, (m, key, value) ->
-    options[key] = value.trim()+  src.replace /(^|\n)\s*(\w+)\s*=\s*([^#\n]+)/g, (m, s, key, value) ->
+    options[key] = value.trim()
+
+
+console.log lovelyrc.host


scroll stop propogation

scroll stop propogation

--- coffee/table.coffee
+++ coffee/table.coffee
@@ -45,7 +45,10 @@
      cells: {}

    @get 'scroll', -> @elems.tableBodyWrapper.scrollTop
-   @listen 'scroll', -> @generate()
+
+   @listen 'scroll', (e) ->
+    e.stopPropagation()
+    @generate()

    @listen 'click', (e) ->
     n = e.target


enabling the --eval option for the self-compiler

--- command_line.coffee
+++ command_line.coffee
@@ -17,11 +17,9 @@
   ['-w', '--watch',         'watch scripts for changes, and recompile']
   ['-p', '--print',         'print the compiled JavaScript to stdout']
   ['-l', '--lint',          'pipe the compiled JavaScript through JSLint']
-  ['-e', '--eval',          'compile a cli scriptlet or read from stdin']
+  ['-e', '--eval',          'compile a string from the command line']
   ['-t', '--tokens',        'print the tokens that the lexer produces']
-  [      '--tree',          'print the parse tree that Jison produces']
-  ['-n', '--no-wrap',       'raw output, no function safety wrapper']
-  ['-g', '--globals',       'attach all top-level variables as globals']
+  ['-tr','--tree',          'print the parse tree that Jison produces']
   ['-v', '--version',       'display CoffeeScript version']
   ['-h', '--help',          'display this help message']
 ]
@@ -36,6 +34,7 @@
 exports.run: ->
   parse_options()
   return require './repl' if options.interactive
+  return puts coffee.compile sources[0] if options.eval
   usage() unless sources.length
   compile_scripts()
   this


Refactored broker router/dealer tx/rx handlers.

--- src/broker.coffee
+++ src/broker.coffee
@@ -5,48 +5,55 @@
 # A broker passes reqests/responses between clients/workers.
 module.exports = class Broker
   constructor: (@options) ->
-    @store  = new Riak @options.store.options
+    @store = new Riak @options.store.options
+
     @router = zmq.socket "router"
     @dealer = zmq.socket "dealer"
+
     @queue = async.queue (task, callback) =>
       @store.write task.id, task, callback
-    , 8
+    , @options.store.maxConnections or 8

     @_bindRouter()
     @_bindDealer()
     @_submitTasks()

   _bindRouter: ->
-    @router.on "message", @_routerMessage
+    @router.on "message", @_routerRx

     @router.bind @options.router.endpoint, =>
       console.log "Router listening on %s", @options.router.endpoint

   _bindDealer: ->
-    @dealer.on "message", @_dealerMessage
+    @dealer.on "message", @_dealerRx

     @dealer.bind @options.dealer.endpoint, =>
       console.log "Dealer listening on %s", @options.dealer.endpoint

-  _routerMessage: (envelopes..., payload) =>
+  _routerRx: (envelopes..., payload) =>
     task = JSON.parse payload

     @queue.push task, (error) =>
-      if not error?
-        @dealer.send [envelopes, payload]
+      if error?
+        @_routerTx envelopes, id: task.id, response: "failed", data: error.toString()
+      else
+        @_dealerTx envelopes, payload
+        @_routerTx envelopes, id: task.id, response: "submitted"

-        # Tell the client we've submitted the task.
-        payload = JSON.stringify id: task.id, response: "submitted"
-        @router.send [envelopes, payload]
-      else
-        # Tell the client shit blew up.
-        payload = JSON.stringify id: task.id, response: "failed", data: error.toString()
-        @router.send [envelopes, payload]
-
-  _dealerMessage: (envelopes..., payload) =>
+  _dealerRx: (envelopes..., payload) =>
     task = JSON.parse payload
     @store.delete task.id, (error) =>
-      @router.send [envelopes, payload]
+      @_routerTx envelopes, payload
+
+  _routerTx: (envelopes, payload) ->
+    unless payload instanceof Buffer
+      payload = JSON.stringify payload
+    @router.send [envelopes, payload]
+
+  _dealerTx: (envelopes, payload) ->
+    unless payload instanceof Buffer
+      payload = JSON.stringify payload
+    @dealer.send [envelopes, payload]

   _submitTasks: ->
     @store.keys (error, ids) =>
@@ -60,6 +67,5 @@
       if error?
         callback error
       else
-        payload = JSON.stringify id: task.id, request: task.request, data: task.data
-        @dealer.send [new Buffer(""), payload]
+        @_dealerTx new Buffer(""), id: task.id, request: task.request, data: task.data
         callback null


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/camera.coffee
+++ src/camera.coffee
@@ -8,7 +8,7 @@
     screen: Rectangle
       x: 0
       y: 0
-      width: App.width / 2
+      width: App.width
       height: App.height
     deadzone: Point(0, 0)
     zoom: 1


comment

comment

--- src/canvas.coffee
+++ src/canvas.coffee
@@ -124,6 +124,8 @@
       # There may be parts of the image out of view, this is okay.
       # This will change its position, so calculate a correction for
       # the appropriate dimension.
+      #
+      # TODO: The Correction calculation is wrong, it's not exact center, and it should be.
       smallestDimension = if img.width < img.height
         yCorrection = @el.height / 2
         img.width


More debugging.

More debugging.

--- src/client.coffee
+++ src/client.coffee
@@ -248,6 +248,7 @@
         for elem in value
           cleaned = elem.replace("\s", "+")
           console.log cleaned
+          console.log key
           querystring += "&filter[#{key}]=#{cleaned}"

     # Adding sort options


fix error handling

fix error handling

--- src/engine.coffee
+++ src/engine.coffee
@@ -1,7 +1,6 @@

 _ = require 'underscore'
 _.str = require 'underscore.string'
-

 #
 # Private methods
@@ -120,6 +119,7 @@
     error = validateType(req.db, req.params.type)
     if error
         return res.json(500, {error: error})
+
     type = _.str.classify req.params.type

     {query, options} = parseQuery(req.query)
@@ -309,8 +309,7 @@
             return res.json(500, {error: e})
         obj.save (err, obj, infos) ->
             if err
-                err = err.message if err.message?
-                console.log('yyy', e)
+                console.log('yyy', err)
                 return res.json(500, {error: err})
             return res.json({object: obj.toJSONObject({dereference: true}), infos: infos})


fix incorrect text font size value

fix incorrect text font size value

--- demo/coffee/map.coffee
+++ demo/coffee/map.coffee
@@ -66,7 +66,7 @@
   type: 'text',
   text_align: 'center'
   text_baseline: 'middle'
-  text_font_size: 6
+  text_font_size: '0.5em'
 }

 test('map', make_glyph_test('map', source, {}, [area, text], xdr, ydr, true, [1200, 600], false))


Alias link as ln

Alias link as ln

--- src/linker.coffee
+++ src/linker.coffee
@@ -8,7 +8,7 @@

 module.exports =
 class Linker
-  @commandNames: ['link']
+  @commandNames: ['link', 'ln']

   parseOptions: (argv) ->
     options = optimist(argv)


logger: fix argument iteration

logger: fix argument iteration

TODO: send upstream when the common-logger.js repository resurfaces

--- src/logger.coffee
+++ src/logger.coffee
@@ -35,7 +35,7 @@
     log: (level, args) ->
         if level <= @level
             i       = 0
-            message = "#{args[0]}".replace /%s/g, -> args[i++]
+            message = "#{args[0]}".replace /%s/g, -> args[++i]
             message = @format(new Date(), level, message)
             message = @colorize(message, @colors[level]) if @colorized
             @out message, level


Add bounties visual debugging logs

Add bounties visual debugging logs

--- scripts/bungie-data-helper.coffee
+++ scripts/bungie-data-helper.coffee
@@ -156,6 +156,8 @@
     return attachment

   'parseVendorBountyDetails': (vendorBountyDetails) ->
+    console.log vendorBountyDetails
+
     message = ''
     for bountyItem in vendorBountyDetails.bountyItemsDetail
         message += "#{bountyItem.itemName}\n"


Add deprecated Marker::*Attributes methods

--- src/marker.coffee
+++ src/marker.coffee
@@ -166,6 +166,14 @@
   getInvalidationStrategy: ->
     @invalidate

+  # Deprecated: Use ::getProperties instead
+  getAttributes: ->
+    @getProperties()
+
+  # Deprecated: Use ::setProperties instead
+  setAttributes: (args...) ->
+    @setProperties(args...)
+
   getProperties: ->
     @properties

@@ -187,6 +195,10 @@

   compare: (other) ->
     @range.compare(other.range)
+
+  # Deprecated: Use ::matchesParams instead
+  matchesAttributes: (args...) ->
+    @matchesParams(args...)

   matchesParams: (params) ->
     for key, value of params


Also read REDIS_URL.

Also read REDIS_URL.

--- src/markov.coffee
+++ src/markov.coffee
@@ -31,7 +31,8 @@
 module.exports = (robot) ->

   # Configure redis the same way that redis-brain does.
-  info = Url.parse process.env.REDISTOGO_URL or
+  info = Url.parse process.env.REDIS_URL or
+    process.env.REDISTOGO_URL or
     process.env.REDISCLOUD_URL or
     process.env.BOXEN_REDIS_URL or
     'redis://localhost:6379'


human-readable dates

human-readable dates

--- classes/results-box.coffee
+++ classes/results-box.coffee
@@ -12,15 +12,16 @@
 				)
 			React.createElement "tbody", null,
 				this.props.resultsList.map (result) =>
+					humanReadableDatetime = moment.duration(-moment().diff(result.datetime)).humanize(true)
 					if this.props.selectedEvent.event_id == result.event_id
 						React.createElement "tr", {onClick: this.handleEventSelect.bind(this, result), style: {"cursor": "pointer"}, key: result.event_id, className: "info"},
 							React.createElement("td", null, result.actor.display_name)
 							React.createElement("td", null, result.action)
 							React.createElement("td", null, result.targets[0].display_name)
-							React.createElement("td", null, result.datetime.toDateString()) # this is where we would put moment.js conversion/formatting
+							React.createElement("td", null, humanReadableDatetime) # this is where we would put moment.js conversion/formatting
 					else
 						React.createElement "tr", {onClick: this.handleEventSelect.bind(this, result), style: {"cursor": "pointer"}, key: result.event_id},
 							React.createElement("td", null, result.actor.display_name)
 							React.createElement("td", null, result.action)
 							React.createElement("td", null, result.targets[0].display_name)
-							React.createElement("td", null, result.datetime.toDateString()) # this is where we would put moment.js conversion/formatting
+							React.createElement "td", null, humanReadableDatetime # this is where we would put moment.js conversion/formatting


CLI making the development server respond to '/main.js' correctly

--- cli/commands/server.coffee
+++ cli/commands/server.coffee
@@ -27,7 +27,7 @@
   server.get '/:module.js', (req, res) ->
     module = req.params.module

-    if module == package.name
+    if module == package.name || module == 'main'
       src  = if minify then source.minify() else source.compile()
       size = Math.round(src.length/102.4)/10
       console.log(" Compiling: ".cyan+ "/#{module}.js (#{size}Kb #{if minify then 'minified' else 'source'})".grey)


User Importer: Remove obsolete free subscription

--- scripts/user-importer/main.coffee
+++ scripts/user-importer/main.coffee
@@ -55,31 +55,23 @@
               return next()

             console.log "  User #{user.username} created."
-            console.log "\n   - Creating free subscription ..."
+            console.log "\n   - Verifying email ..."

-            JPaymentSubscription.createFreeSubscription account, (err) ->
+            user.confirmEmail (err)->

-              if err?
-              then console.log "     Failed to create subscription for #{u.username}:", err
-              else console.log "     Subscription created."
+              if err then console.log "     Failed to verify: ", err
+              else        console.log "     Email verified."

-              console.log "\n   - Verifying email ..."
+              JUser.configureNewAcccount account, user, createId(), (err)->

-              user.confirmEmail (err)->
+                console.log "\n   - Adding to group #{u.group} ..."

-                if err then console.log "     Failed to verify: ", err
-                else        console.log "     Email verified."
+                JUser.addToGroup account, u.group, u.email, null, (err)->

-                JUser.configureNewAcccount account, user, createId(), (err)->
+                  if err then console.log "     Failed to add: ", err
+                  else        console.log "     Joined to group #{u.group}."

-                  console.log "\n   - Adding to group #{u.group} ..."
-
-                  JUser.addToGroup account, u.group, u.email, null, (err)->
-
-                    if err then console.log "     Failed to add: ", err
-                    else        console.log "     Joined to group #{u.group}."
-
-                    next()
+                  next()

     # An array like this


continue rewrite of frame stuff

continue rewrite of frame stuff

--- src/math3d.coffee
+++ src/math3d.coffee
@@ -180,13 +180,15 @@
             width           : undefined
             height          : undefined
             renderer        : undefined  # 'webgl' or 'canvas' or undefined to choose best
-            background      : "#ffffff"
-            foreground      : undefined
+            background      : [1,1,1]
+            foreground      : undefined  # defaults to the HSL negation of the background
+            frame_color     : undefined  # defaults to the foreground color
+            frame_thickness : .4         # zero thickness disables the frame
+            label_fontsize  : 14         # set to zero to disable labels on the axes
             spin            : false      # if true, image spins by itself when mouse is over it.
             camera_distance : 10
             aspect_ratio    : undefined  # undefined does nothing or a triple [x,y,z] of length three, which scales the x,y,z coordinates of everything by the given values.
             stop_when_gone  : undefined  # if given, animation, etc., stops when this html element (not jquery!) is no longer in the DOM
-            frame           : undefined  # if given call set_frame with opts.frame as input when init_done called
             callback        : undefined  # opts.callback(error, this object)

         math3d.load_threejs (error) =>
@@ -220,22 +222,23 @@
             @init_light()

             # set background color
-            @element.style.background = @opts.background
-
-            if not @opts.foreground?
-                c = @element.style.background
-                if not c? or -1 is c.indexOf ')'
-                    @opts.foreground = "#000"  # e.g., on firefox - this is best we can do for now
+            @opts.background = new THREE.Color @opts.background...
+            @element.style.background = @opts.background.getStyle()
+
+            if @opts.foreground?
+                @opts.foreground = new THREE.Color @opts.foreground...
+            else
+                @opts.foreground = new THREE.Color()
+                {h, s, l} = @opts.background.getHSL()
+                if h >= 0.5
+                    @opts.foreground.setHSL(h-0.5, s, 1-l)
                 else
-                    i = c.indexOf ')'
-                    z = []
-                    for a in c.slice(4, i).split ','
-                        b = parseInt a
-                        if b < 128
-                            z.push 255
-                        else
-                            z.push 0
-                    @opts.foreground = rgb_to_hex z...
+                    @opts.foreground.setHSL(h+0.5, s, 1-l)
+
+            if @opts.frame_color?
+                @opts.frame_color = new THREE.Color @opts.frame_color...
+            else
+                @opts.frame_color = @opts.foreground

             @opts.callback? undefined, @

@@ -250,7 +253,7 @@

     # client code should call this when done adding objects to the scene
     finalize: ->
-        @set_frame @opts.frame
+        @set_frame()

         if @renderer_type isnt 'dynamic'
             # if we don't have the renderer, swap it in, make a static image, then give it back to whoever had it.
@@ -613,14 +616,7 @@
     # always call this after adding things to the scene to make sure track
     # controls are sorted out, etc.   Set draw:false, if you don't want to
     # actually *see* a frame.
-    set_frame: (opts) ->
-        opts = defaults opts,
-            color     : @opts.foreground
-            thickness : .4
-            labels    : true  # whether to draw three numerical labels along each of the x, y, and z axes.
-            fontsize  : 14
-            draw      : true
-
+    set_frame: ->
         ###
         if Math.abs(x1 - x0) < eps
             x1 += 1
@@ -644,52 +640,52 @@
             d = 1.5*Math.max @aspect_ratio_scale(dim.x, dim.y, dim.z)...
             @camera.position.set avg.x+d, avg.y+d, avg.z+d/2

-        if opts.draw
+        if @opts.frame_thickness isnt 0
             # set the color and linewidth of the bounding box
-            @bounding_box.material.color.setRGB opts.color
-            @bounding_box.material.linewidth = opts.thickness
+            @bounding_box.material.color = @opts.frame_color
+            @bounding_box.material.linewidth = @opts.frame_thickness

             # add the bounding box to the scene
             @scene.add @bounding_box
-
-        if opts.draw and opts.labels

             if @_frame_labels?
                 for x in @_frame_labels
                     @scene.remove x

-            @_frame_labels = []
-
-            format = (num) ->
-                Number(num.toFixed 2).toString()
-
-            add_label = (loc, text) =>
-                @_frame_labels.push(
-                    @add_text
-                        loc           : loc
-                        text          : text
-                        fontsize      : opts.fontsize
-                        color         : opts.color
-                        constant_size : false
-                        in_frame      : false
-                    )
-
-            offset = 0.075
-
-            e = (max.y - min.y)*offset
-            add_label [max.x, min.y-e, min.z], format(min.z)
-            add_label [max.x, min.y-e, avg.z], "z = #{format avg.z}"
-            add_label [max.x, min.y-e, max.z], format(max.z)
-
-            e = (max.x - min.x)*offset
-            add_label [max.x+e, min.y, min.z], format(min.y)
-            add_label [max.x+e, avg.y, min.z], "y = #{format avg.y}"
-            add_label [max.x+e, max.y, min.z], format(max.y)
-
-            e = (max.y - min.y)*offset
-            add_label [max.x, max.y+e, min.z], format(max.x)
-            add_label [avg.x, max.y+e, min.z], "x = #{format avg.x}"
-            add_label [min.x, max.y+e, min.z], format(min.x)
+            if @opts.label_fontsize isnt 0
+                @_frame_labels = []
+
+                format = (num) ->
+                    Number(num.toFixed 2).toString()
+
+                frame_color = @opts.frame_color.getStyle()
+                add_label = (loc, text) =>
+                    @_frame_labels.push(
+                        @add_text
+                            loc           : loc
+                            text          : text
+                            fontsize      : @opts.label_fontsize
+                            color         : frame_color
+                            constant_size : false
+                            in_frame      : false
+                        )
+
+                offset = 0.075
+
+                e = (max.y - min.y)*offset
+                add_label [max.x, min.y-e, min.z], format(min.z)
+                add_label [max.x, min.y-e, avg.z], "z = #{format avg.z}"
+                add_label [max.x, min.y-e, max.z], format(max.z)
+
+                e = (max.x - min.x)*offset
+                add_label [max.x+e, min.y, min.z], format(min.y)
+                add_label [max.x+e, avg.y, min.z], "y = #{format avg.y}"
+                add_label [max.x+e, max.y, min.z], format(max.y)
+
+                e = (max.y - min.y)*offset
+                add_label [max.x, max.y+e, min.z], format(max.x)
+                add_label [avg.x, max.y+e, min.z], "x = #{format avg.x}"
+                add_label [min.x, max.y+e, min.z], format(min.x)

         @camera.lookAt @_center
         if @controls?
@@ -729,9 +725,6 @@
             else
                 console.log "ERROR: bad object type #{opts.obj.type}"

-        if opts.set_frame?
-            @set_frame opts.set_frame
-
         @render_scene true

     animate: (opts = {}) ->


Add example items to the example project. Close #31.

--- server/user_initialization.coffee
+++ server/user_initialization.coffee
@@ -5,12 +5,81 @@
       due dilligence projects. It allows you to register sources, findings,
       risks, and mitigating measures. And it helps you to keep track of the
       relationships between them. This is an example project. Feel free to
-      add some sources, findings, risks, and measures to it and delete the
-      project when you're done with it."
+      edit the sources, findings, risks, and measures. Of course you can delete
+      the project when you're done with it."
     members: [user._id]
     userId: user._id
     author: user.username
     submitted: new Date
     kind: 'project'
-  Projects.insert tutorial
+  projectId = Projects.insert tutorial
+  source1 =
+    title: "Interview with project lead"
+    description: "This is an example of a source. In this case an interview
+      with a project leader. Some of the findings under the findings tab are
+      based on this source."
+    projectId: projectId
+    userId: user._id
+    author: user.username
+    submitted: new Date
+    kind: 'source'
+  source1Id = Sources.insert source1
+  source2 =
+    title: "Software architecture description"
+    description: "An example source, in this case a software architecture
+      decription, or SAD for short. Some of the findings are based on this
+      source."
+    projectId: projectId
+    userId: user._id
+    author: user.username
+    submitted: new Date
+    kind: 'source'
+  source2Id = Sources.insert source2
+  finding1 =
+    title: "SAD is outdated"
+    description: "This is an example of a finding. The software architecture
+      document is outdated. We base this finding both on the document itself as
+      well as on the interview with the project lead."
+    projectId: projectId
+    userId: user._id
+    author: user.username
+    submitted: new Date
+    kind: 'finding'
+    sources: [source1Id, source2Id]
+  finding1Id = Findings.insert finding1
+  finding2 =
+    title: "No automated tests"
+    description: "This is an example of a finding. The project under
+      investigation has developed no automated tests. We base this finding on
+      the interview with the project lead."
+    projectId: projectId
+    userId: user._id
+    author: user.username
+    submitted: new Date
+    kind: 'finding'
+    sources: [source1Id]
+  finding2Id = Findings.insert finding2
+  risk1 =
+    title: "Regression bugs"
+    description: "This is an example of a risk. Since there are no automated
+      tests, there is a risk that changes to the software will cause bugs."
+    projectId: projectId
+    userId: user._id
+    author: user.username
+    submitted: new Date
+    kind: 'risk'
+    findings: [finding2Id]
+  risk1Id = Risks.insert risk1
+  measure1 =
+    title: "Create automated regression test"
+    description: "This is an example of a mitigating measure. Since there are
+      no automated tests, there is a risk that changes to the software will
+      cause bugs. Creating an automated regression test can mitigate this risk."
+    projectId: projectId
+    userId: user._id
+    author: user.username
+    submitted: new Date
+    kind: 'measure'
+    risks: [risk1Id]
+  Measures.insert measure1
   return user


Update trello.coffee

Update trello.coffee

--- src/trello.coffee
+++ src/trello.coffee
@@ -111,11 +111,7 @@
         members[member.fullName.toLowerCase().split " ", 1] = member

-<<<<<<< HEAD
-  robot.respond /trello new ["“'‘]([^]+)["”'’](.*)/i, (msg) ->
-=======
   robot.respond /trello new ["“'‘](.+)["”'’]\s(.*)/i, (msg) ->
->>>>>>> refs/remotes/hubot-scripts/master
     ensureConfig msg.send
     card_name = msg.match[2]
     list_name = msg.match[1]


WIP counter drag-and-drop: drag single tokens

--- ui.coffee
+++ ui.coffee
@@ -30,9 +30,14 @@
     check @panel, ui.Panel

+class ui.ComponentStack
+  constructor: ({@type, @container, @stack, @count}) ->
+
+
 class ui.ComponentType
   constructor: ({@name, @width, @height, @isCounter, @stackProperty, @stackAfter,
-                 @displayNameSingular, @displayNamePlural, @template} = {}) ->
+                 @displayNameSingular, @displayNamePlural,
+                 @draggable, @template} = {}) ->
     check @name, String
     check @width, Match.Optional Number
     check @height, Match.Optional Number
@@ -50,6 +55,8 @@
     @displayNamePlural ?= @displayNameSingular
     check @displayNamePlural, String
     check @template, Match.Optional String
+    @draggable ?= false
+    check @draggable, Boolean

   render: (component) ->
     new Blaze.Template =>
@@ -59,7 +66,8 @@
       else
         rules = component.game().rules
         Template["#{rules.replace /\s/g, ''}ComponentView"] ? Template.componentDefaultView
-      Blaze.With component, -> template
+      Blaze.With (type: @, component: component), ->
+        Blaze.With component, -> template

   summary: (count, stack) ->
     if stack?
@@ -107,11 +115,11 @@
           cursor = container.find selector
           count = cursor.count()
           if cursor.count() > @stackAfter
-            Blaze.With (=> type: @, stack: value, count: count), =>
+            Blaze.With (new ui.ComponentStack type: @, stack: value, count: count), =>
               @getCounterTemplate count, container.rules.name
           else
-            for i in [1..count]
-              Blaze.With (=> type: @, stack: value, i: i), =>
+            Blaze.With (type: @, stack: value, container: container), =>
+              Blaze.Each (-> cursor), =>
                 @getCounterTemplate count, container.rules.name
     else
       new Blaze.Template =>
@@ -119,11 +127,11 @@
         cursor = container.find type: @name
         count = cursor.count()
         if cursor.count() > @stackAfter
-          Blaze.With (=> type: @, count: count), =>
+          Blaze.With (new ui.ComponentStack type: @, count: count), =>
             @getCounterTemplate count, container.rules.name
         else
-          for i in [1..count]
-            Blaze.With (=> type: @, i: i), =>
+          Blaze.With (=> type: @, container: container), =>
+            Blaze.Each (-> cursor), =>
               @getCounterTemplate count, container.rules.name

 class ui.Controller extends EventEmitter


Account: confirmCurrentPassword method is added for AccountEditUsername view

--- client/Account/views/username.coffee
+++ client/Account/views/username.coffee
@@ -62,6 +62,7 @@
     {email, password, confirmPassword, firstName, lastName, username} = formData

     profileUpdated = yes
+    currentPasswordConfirmed = no
     queue = [
       =>
         # update firstname and lastname
@@ -85,12 +86,14 @@
             return queue.next() if err.message is "EmailIsSameError"
             return notify err.message

-          new VerifyPINModal 'Update E-Mail', (pin)=>
-            KD.remote.api.JUser.changeEmail {email, pin}, (err)=>
-              if err
-                notify err.message
-                profileUpdated = false
-              queue.next()
+          confirmCurrentPassword currentPasswordConfirmed, =>
+            currentPasswordConfirmed = true
+            new VerifyPINModal 'Update E-Mail', (pin)=>
+              KD.remote.api.JUser.changeEmail {email, pin}, (err)=>
+                if err
+                  notify err.message
+                  profileUpdated = false
+                queue.next()
       =>
         # on third turn update password
         # check for password confirmation
@@ -103,17 +106,27 @@
             notify "You should set your password"
           return queue.next()

-        JUser.changePassword password, (err,docs)=>
-          if err
-            return queue.next()  if err.message is "PasswordIsSame"
-            return  notify err.message
-          return queue.next()
+        confirmCurrentPassword currentPasswordConfirmed, =>
+          JUser.changePassword password, (err,docs)=>
+            @emailForm.inputs.password.setValue ""
+            @emailForm.inputs.confirm.setValue ""
+            if err
+              return queue.next()  if err.message is "PasswordIsSame"
+              return  notify err.message
+            return queue.next()
       =>
         # if everything is OK or didnt change, show profile updated modal
         notify "Your account information is updated." if profileUpdated
     ]
     daisy queue

+  confirmCurrentPassword = (currentPasswordConfirmed, callback) ->
+    return callback null if currentPasswordConfirmed
+    new VerifyPasswordModal "Confirm", (currentPassword) ->
+      KD.remote.api.JUser.verifyPassword currentPassword, (err, confirmed) ->
+        return notify err.message if err
+        return notify "Current password cannot be confirmed" unless confirmed
+        callback null

   viewAppended:->
     {JPasswordRecovery, JUser} = KD.remote.api


return an anonymous function

return an anonymous function

--- client/app/MainApp/AppStorage.coffee
+++ client/app/MainApp/AppStorage.coffee
@@ -48,7 +48,7 @@
     @fetchStorage (storage)=>
       storage.update {
         $set: pack
-      }, callback
+      }, -> callback?()

   unsetKey: (key, callback, group = 'bucket')->


Fix frontend tests

Fix frontend tests

--- client/app/components/message.coffee
+++ client/app/components/message.coffee
@@ -91,13 +91,13 @@
         @_markRead @props.message

     componentWillReceiveProps: (props) ->
+        state =
+            active: props.active
         if props.message.get('id') isnt @props.message.get('id')
             @_markRead @props.message
-            state =
-                active:               props.active
-                messageDisplayHTML:   props.settings.get 'messageDisplayHTML'
-                messageDisplayImages: props.settings.get 'messageDisplayImages'
-            @setState state
+            state.messageDisplayHTML   = props.settings.get 'messageDisplayHTML'
+            state.messageDisplayImages = props.settings.get 'messageDisplayImages'
+        @setState state

     _markRead: (message) ->
         # Hack to prevent infinite loop if server side mark as read fails


app: Register AppFlux stores

app: Register AppFlux stores

--- client/app/lib/maincontroller.coffee
+++ client/app/lib/maincontroller.coffee
@@ -424,6 +424,7 @@

     fluxModules = [
       require 'activity/flux'
+      require 'app/flux'
     ]

     fluxModules.forEach (fluxModule) ->


Fixed like button

Fixed like button

--- client/src/app/game/like/like.coffee
+++ client/src/app/game/like/like.coffee
@@ -4,8 +4,8 @@
     restrict: "E"
     scope:
       gameId: "="
-      likedCount: "="
-      buttonClasses: "="
+      likedCount: "=?"
+      buttonClasses: "=?"
     templateUrl: "game/like/like.tpl.html"
     controller: ($scope, games, loggedUser, ChangedLoginEvent) ->
       # If the data about likes is provided, use it. Otherwise query the backend


キャレットの位置を雰囲気で維持するようになった

キャレットの位置を雰囲気で維持するようになった

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -77,8 +77,16 @@
     socketService.sendRequest(
       'get_markdown',
       (data) =>
-        @setState markdown: data.markdown
-        @contentUpdateFromMarkdown
+        editor = @refs.editor.getDOMNode()
+        caretStart = editor.selectionStart
+        caretEnd = editor.selectionEnd
+        @setState
+          markdown: data.markdown,
+            # 雰囲気キャレット維持。触っているところより前が書き換わると維持されないという…
+            () =>
+              console.log caretStart
+              editor.setSelectionRange(caretStart, caretEnd)
+         @contentUpdateFromMarkdown
     )

   getInitialState: ->


wait for messages sent

wait for messages sent

--- commands/sms.coffee
+++ commands/sms.coffee
@@ -52,10 +52,12 @@

             if body is 'ok'
                 sent++
+                if sent is numbers.length
+                    respond "#{sent} #{if sent is 1 then 'message' else 'messages'} sent (#{numbers.join(', ')})."
             else
                 respond "Error: #{body}"

-    respond "#{sent} #{if sent is 1 then 'message' else 'messages'} sent (#{numbers.join(', ')})."
+

 # message 5199998888 puta que pariu pra fora
 # message jean, vitor bla bla blabla