Refactor and remove irrelevant and obsolete comments

--- src/main.coffee
+++ src/main.coffee
@@ -1,84 +1,82 @@
+#### Helpers & Setup
+
+# Require our external dependencies, including **Showdown.js**
+# (the JavaScript implementation of Markdown).
+fs       = require 'fs'
+path     = require 'path'
+showdown = require('./../vendor/showdown').Showdown
+jade     = require 'jade'
+dox      = require 'dox'
+gravatar = require 'gravatar'
+_        = require 'underscore'
+walk     = require 'walk'
+{spawn, exec} = require 'child_process'
+
 # Generate the documentation for a source file by reading it in, splitting it
-# up into comment/code sections, highlighting them for the appropriate language,
-# and merging them into an HTML template.
-generateDocumentation = (source, context, callback) ->
-  fs.readFile source, "utf-8", (error, code) ->
-    throw error if error
-    sections = parse source, code
-    highlight source, sections, ->
-      generate_source_html source, context, sections
-      callback()
+# up into comment/code sections, and merging them into an HTML template.
+generateDocumentation = (sourceFile, context, cb) ->
+  fs.readFile sourceFile, "utf-8", (err, code) ->
+    throw err if err
+    sections = parse get_language(sourceFile), code
+    generate_source_html sourceFile, context, sections
+    cb()

 # Given a string of source code, parse out each comment and the code that
 # follows it, and create an individual **section** for it.
-# Sections take the form:
-#
-#     {
-#       docs_text: ...
-#       docs_html: ...
-#       code_text: ...
-#       code_html: ...
-#     }
-#
-parse = (source, code) ->
-  lines    = code.split '\n'
+parse = (language, data) ->
+  lines = data.split '\n'
   sections = []
-  language = get_language source
-  has_code = docs_text = code_text = ''
-
-  in_multi = false
-  multi_accum = ""
+  docs = code = ''
+
+  inMulti = no
+  hasCode = no
+  multiAccum = ''

   save = (docs, code) ->
-    sections.push docs_text: docs, code_text: code
+    sections.push {
+      docs_text: docs
+      docs_html: showdown.makeHtml docs
+      code_text: code
+      code_html: highlight_start + code + highlight_end
+    }
+
   for line in lines
-    if line.match(language.multi_start_matcher) or in_multi
-
-      if has_code
-        save docs_text, code_text
-        has_code = docs_text = code_text = ''
-
-      # Found the start of a multiline comment line, set in_multi to true
-      # and begin accumulating lines untime we reach a line that finishes
-      # the multiline comment
-      in_multi = true
-      multi_accum += line + '\n'
-
-      # If we reached the end of a multiline comment, template the result
-      # and set in_multi to false, reset multi_accum
+
+    # Multi line comment
+    if line.match(language.multi_start_matcher) or inMulti
+
+      ## Start of a new section, save the old section
+      if hasCode
+        save docs, code
+        docs = code = ''
+        hasCode = no
+
+      # Found the start of a multiline comment.
+      # Begin accumulating lines until we reach the end of the comment block.
+      inMulti = yes
+      multiAccum += line + '\n'
+
+      # If we reached the end of a multiline comment,
+      # set inMulti to false and reset multiAccum
       if line.match(language.multi_end_matcher)
-        in_multi = false
-        try
-          parsed = dox.parseComments( multi_accum )[0]
-          docs_text += dox_template(parsed)
-        catch error
-          console.log "Error parsing comments with Dox: #{error}"
-          docs_text = multi_accum
-        multi_accum = ''
-
+        inMulti = no
+        docs = multiAccum
+        multiAccum = ''
+
+    # Single line comment
     else if line.match(language.comment_matcher) and not line.match(language.comment_filter)
-      if has_code
-        save docs_text, code_text
-        has_code = docs_text = code_text = ''
-      docs_text += line.replace(language.comment_matcher, '') + '\n'
+      if hasCode
+        save docs, code
+        hasCode = docs = code = ''
+      docs += line.replace(language.comment_matcher, '') + '\n'
+
+    # Code
     else
-      has_code = yes
-      code_text += line + '\n'
-  save docs_text, code_text
+      hasCode = yes
+      code += line + '\n'
+
+  save docs, code
   sections
-
-# Highlights a single chunk of CoffeeScript code, using **Pygments** over stdio,
-# and runs the text of its corresponding comment through **Markdown**, using
-# [Showdown.js](http://attacklab.net/showdown/).
-#
-# We process the entire file in a single call to Pygments by inserting little
-# marker comments between each section and then splitting the result string
-# wherever our markers occur.
-highlight = (source, sections, callback) ->
-  for section, i in sections
-    section.code_html = highlight_start + section.code_text + highlight_end
-    section.docs_html = showdown.makeHtml section.docs_text
-  callback()

 # Once all of the code is finished highlighting, we can generate the HTML file
 # and write out the documentation. Pass the completed sections into the template
@@ -174,20 +172,6 @@
     console.log "Calculating project stats failed #{err}" if err
     callback stdout

-#### Helpers & Setup
-
-# Require our external dependencies, including **Showdown.js**
-# (the JavaScript implementation of Markdown).
-fs       = require 'fs'
-path     = require 'path'
-showdown = require('./../vendor/showdown').Showdown
-jade     = require 'jade'
-dox      = require 'dox'
-gravatar = require 'gravatar'
-_        = require 'underscore'
-walk     = require 'walk'
-{spawn, exec} = require 'child_process'
-
 # A list of the languages that Docco supports, mapping the file extension to
 # the name of the Pygments lexer and the symbol that indicates a comment. To
 # add another language to Docco's repertoire, add it here.


Simplify loop by using comprehension.

Simplify loop by using comprehension.

--- src/mock.coffee
+++ src/mock.coffee
@@ -50,10 +50,8 @@
   # private

   _find_signature: (method_name, args...) ->
-    # TODO: use list comprehension?
-    for signature in @method_calls
-      if signature.matches(method_name, args...)
-        return signature
+    for signature in @method_calls when signature.matches(method_name, args...)
+      return signature
     undefined

   _define_expected_method: (method_name) ->


Use xml mode when extension is xml

Use xml mode when extension is xml

--- src/app/buffer.coffee
+++ src/app/buffer.coffee
@@ -175,6 +175,7 @@
       when 'html', 'htm' then 'html'
       when 'css' then 'css'
       when 'java' then 'java'
+      when 'xml' then 'xml'
       else 'text'

     @mode = new (require("ace/mode/#{modeName}").Mode)


Use `observeConfig` in Editor

Use `observeConfig` in Editor

--- src/app/editor.coffee
+++ src/app/editor.coffee
@@ -306,13 +306,11 @@
   backwardsScanInRange: (args...) -> @getBuffer().backwardsScanInRange(args...)

   configure: ->
-    @setShowInvisibles(config.editor.showInvisibles)
-    @setInvisibles(config.editor.invisibles)
-    @setFontSize(config.editor.fontSize)
+    @observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles)
+    @observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles)
+    @observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize)

   handleEvents: ->
-    config.on "update.editor#{@id}", => @configure()
-
     @on 'focus', =>
       @hiddenInput.focus()
       false
@@ -670,7 +668,6 @@
     @destroyEditSessions()

     $(window).off ".editor#{@id}"
-    config.off ".editor#{@id}"
     rootView = @rootView()
     rootView?.off ".editor#{@id}"
     if @pane() then @pane().remove() else super


Serialize project when editor window is unloaded

--- src/app/window.coffee
+++ src/app/window.coffee
@@ -77,6 +77,7 @@
 window.unloadEditorWindow = ->
   return if not project and not rootView
   windowState = atom.getWindowState()
+  windowState.set('project', project.serialize())
   windowState.set('syntax', syntax.serialize())
   windowState.set('rootView', rootView.serialize())
   atom.deactivatePackages()


Turn apps back on

Turn apps back on

--- src/apps/index.coffee
+++ src/apps/index.coffee
@@ -4,85 +4,81 @@
 app = module.exports = express()

 # Normal routing
-# console.time('actions')
-# app.use(require('../apps/actions'))
-# console.timeEnd('actions')
+console.time('actions')
+app.use(require('../apps/actions'))
+console.timeEnd('actions')

-# console.time('feed')
-# app.use(require('../apps/feed'))
-# console.timeEnd('feed')
+console.time('home')
+app.use(require('../apps/home'))
+console.timeEnd('home')

-# console.time('home')
-# app.use(require('../apps/home'))
-# console.timeEnd('home')
-
-# console.time('blog')
-# app.use(require('../apps/blog'))
-# console.timeEnd('blog')
+console.time('blog')
+app.use(require('../apps/blog'))
+console.timeEnd('blog')

 console.time('authentication')
 app.use(require('../apps/authentication'))
 console.timeEnd('authentication')

-# console.time('confirmation')
-# app.use(require('../apps/confirmation'))
-# console.timeEnd('confirmation')
+console.time('confirmation')
+app.use(require('../apps/confirmation'))
+console.timeEnd('confirmation')

-# console.time('getting_started')
-# app.use(require('../apps/getting_started'))
-# console.timeEnd('getting_started')
+console.time('getting_started')
+app.use(require('../apps/getting_started'))
+console.timeEnd('getting_started')

-# console.time('tools')
-# app.use(require('../apps/tools'))
-# console.timeEnd('tools')
+console.time('tools')
+app.use(require('../apps/tools'))
+console.timeEnd('tools')

-# console.time('util')
-# app.use(require('../apps/util'))
-# console.timeEnd('util')
+console.time('util')
+app.use(require('../apps/util'))
+console.timeEnd('util')

-# console.time('about')
-# app.use(require('../apps/about'))
-# console.timeEnd('about')
+console.time('about')
+app.use(require('../apps/about'))
+console.timeEnd('about')

 console.time('manage')
 app.use(require('../apps/manage'))
 console.timeEnd('manage')

-# console.time('marklet')
-# app.use(require('../apps/marklet'))
-# console.timeEnd('marklet')
+console.time('marklet')
+app.use(require('../apps/marklet'))
+console.timeEnd('marklet')

-# console.time('import')
-# app.use(require('../apps/import'))
-# console.timeEnd('import')
+console.time('import')
+app.use(require('../apps/import'))
+console.timeEnd('import')

-# console.time('settings')
-# app.use(require('../apps/settings'))
-# console.timeEnd('settings')
+console.time('settings')
+app.use(require('../apps/settings'))
+console.timeEnd('settings')

-# console.time('onboarding')
-# app.use(require('../apps/onboarding'))
-# console.timeEnd('onboarding')
+console.time('onboarding')
+app.use(require('../apps/onboarding'))
+console.timeEnd('onboarding')

-# console.time('graphql')
-# app.use(require('../apps/graphql'))
-# console.timeEnd('graphql')
+console.time('graphql')
+app.use(require('../apps/graphql'))
+console.timeEnd('graphql')

-# console.time('examples')
-# app.use(require('../apps/examples'))
-# console.timeEnd('examples')
+console.time('examples')
+app.use(require('../apps/examples'))
+console.timeEnd('examples')

-# console.time('vanity_redirects')
-# app.use(require('../apps/vanity_redirects'))
-# console.timeEnd('vanity_redirects')
+console.time('vanity_redirects')
+app.use(require('../apps/vanity_redirects'))
+console.timeEnd('vanity_redirects')

-# console.time('deeplink')
-# app.use(require('../apps/deeplink'))
-# console.timeEnd('deeplink')
+console.time('deeplink')
+app.use(require('../apps/deeplink'))
+console.timeEnd('deeplink')

-# console.time('apple-site-association')
-# app.use(require('../apps/apple_site_association'))
-# console.timeEnd('apple-site-association')
+console.time('apple-site-association')
+app.use(require('../apps/apple_site_association'))
+console.timeEnd('apple-site-association')

 # All other SPA / client-side routes
 console.time('app')


Optimize Background by auto-culling layer images.

Noticing significantly reduced lag, especially in the beginning.

--- src/background.coffee
+++ src/background.coffee
@@ -58,6 +58,7 @@
       for zIndex in [bottomZIndex..topZIndex]
         name = nameTemplate { zIndex }
         image = @add.image 0, 0, name, @group
+        image.autoCull = on
         @layers.push { image, zIndex }

     layout: ->


working on NewAnalyzer

working on NewAnalyzer

--- test/NewAnalyzerSpec.coffee
+++ test/NewAnalyzerSpec.coffee
@@ -74,6 +74,30 @@
           subject()
           expect(spy).not.toHaveBeenCalled()

+      describe 'inner state changes', ->
+        cases = [
+          { value: "\n",
+            state:
+              endOfLine:        true,
+              escaped:          undefined,
+              quote:            undefined,
+              doubleQuote:      undefined,
+              openParenthesis:  undefined,
+              closeParenthesis: undefined
+          },
+          { value: "\n",
+            state:
+              endOfLine:        true,
+              escaped:          undefined,
+              quote:            undefined,
+              doubleQuote:      undefined,
+              openParenthesis:  undefined,
+              closeParenthesis: undefined
+          }
+        ]
+
+
+
     describe 'main function', ->
       spy        =
       crntChar   =


add test for issue #4

add test for issue #4

--- test/macro-tool_test.coffee
+++ test/macro-tool_test.coffee
@@ -280,6 +280,19 @@
           mask: "url(##{maskId})"
           _: [ { circle: { r: 5 } }, { circle:{ r: 4.5 } } ]
         }
+      it 'should add several clear shapes in a row to the same mask', ->
+        # add a rectangle
+        m = new Macro ['AMNAME']
+        m.primitive [ 21, 1, 10, 5, 0, 0, 0 ]
+        # clear out two smaller circles
+        m.primitive [ 1, 0, 3, 3, 0 ]
+        m.primitive [ 1, 0, 2, -3, 0 ]
+        m.shapes.length.should.equal 1
+        m.masks.length.should.equal 1
+        m.masks[0]._.length.should.equal 3
+        m.masks[0]._.should.containDeep [
+          { rect: { width: 10 } }, { circle: { r: 1.5 } }, { circle: { r: 1} }
+        ]

   describe 'getNumber method', ->
     m = new Macro ['MACRONAME']


New example config file for Rails

New example config file for Rails

	modified:   examples/rails.coffee

--- examples/rails.coffee
+++ examples/rails.coffee
@@ -11,8 +11,8 @@
   # passed into the translation function in the order they are matched. The
   # returned URL String from the translation function will be used to expire
   # the cache for that URL.
-  config.url 'app/assets/:filepath', assets_path
-  config.url 'vendor/assets/:filepath', assets_path
+  config.match ':location/assets/javascripts/*', javascript_path
+  config.match ':location/assets/stylesheets/*', stylesheet_path

   # Establish a white list of URL Strings of the paths to cache.
   # If an incoming request URL does not match an include pattern, it will not
@@ -22,5 +22,8 @@
   return

-  assets_path = (filepath) ->
-    return "/assets/#{filepath}"
+javascript_path = (location, filepath) ->
+  return "/assets/#{filepath}?body=1"
+
+stylesheet_path = (location, filepath) ->
+  return "/assets/application.css?body=1"


save pretty printed json

save pretty printed json

--- code/serv.coffee
+++ code/serv.coffee
@@ -155,7 +155,7 @@
   catch e
     return res.send { error: "Invalid JSON" }, 400
   fs.writeFile "/home/#{req.params.box_name}/scraperwiki.json",
-    (JSON.stringify json), 'utf8', (err) ->
+    (JSON.stringify json, null, 2), 'utf8', (err) ->
       if !err
         return res.send { message: "ok" }, 200
       else


window.document.errors isn't guaranteed to exist, I guess

--- server/sandboxes.coffee
+++ server/sandboxes.coffee
@@ -112,16 +112,16 @@
 		window.WebSocket = WebSocket

 		window.onload = =>
-
+
 			# Catch any errors in the client.
-			for documentError in window.document.errors
+			for documentError in window.document.errors ? []
 				if documentError.data?.error?
 					error = new Error "#{
 						documentError.message
 					}\n#{
 						errors.stack documentError.data.error
 					}"
-
+
 			@emit 'ready', error

 	close: ->


Do not start new conversion again if a conversion of the same file is already ongoing.

--- file-converter.coffee
+++ file-converter.coffee
@@ -54,6 +54,9 @@
         getCached: (callback) =>
                 return @cache.getCached @conversionParams, callback

+        getKey: =>
+                return @cache.getDigest @conversionParams
+

 exports.FileCache = class FileCache
         constructor: (@cachedir="/tmp", @cacheLocation) ->
@@ -61,7 +64,7 @@
         generate: (conversionParams) =>
                 return new FileCacheInstance @, conversionParams

-        _getDigest: (conversionParams) ->
+        getDigest: (conversionParams) ->
                 paramList = []
                 for key, value of conversionParams
                         paramList.push("#{key}:#{value}")
@@ -73,7 +76,7 @@
                 return hash.digest("hex")

         _createFilename: (conversionParams) =>
-                digest = @_getDigest conversionParams
+                digest = @getDigest conversionParams
                 filename = "#{digest}.#{conversionParams.suffix}"
                 return filename

@@ -96,6 +99,8 @@
         createCache: (convertedFilename, conversionParams, callback) =>
                 filepath = @_getCacheName conversionParams
                 input = fs.createReadStream convertedFilename
+                input.on "error", (err) =>
+                        console.log "Read stream error: #{err}"
                 output = fs.createWriteStream filepath
                 input.pipe output
                 input.on "end", =>
@@ -108,24 +113,36 @@
                 @converters =
                         mp3: new Mp3Converter()
                         ogg: new VorbisConverter()
+                @_ongoingConversions = {}

-        _conversionDone: (err, cacheInstance, tempname, response) =>
+        _redirectToCachefile: (cacheInstance, response) =>
+
+        _createResponse: (err, cacheInstance, tempname, callback) =>
                 if err
                         fs.exists tempname, (exists) =>
                                 if exists
                                         fs.unlink tempname
-                                response.send "Failed to convert.", 500
-                        return
+                                callback {data: ["Failed to convert.", 500], headers: {}}
+                                return
                 fs.stat tempname, (statErr, stats) =>
                         if statErr
-                                response.send "Failed to read resulting file name.", 500
+                                callback {data: ["Failed to read resulting file name.", 500], headers: {}}
                                 return
                         stream = fs.createReadStream tempname
                         stream.on "end", =>
                                 fs.unlink tempname
+
                         cacheInstance.createCache tempname, (err, location) ->
-                                response.set "location", location
-                                response.send "Go to #{location}", 302
+                                callback {data: ["Go to #{location}", 302], headers: {"location": location}}
+
+        _conversionDone: (err, cacheInstance, tempname) =>
+                @_createResponse err, cacheInstance, tempname, (responseParams) =>
+                        for response in @_ongoingConversions[cacheInstance.getKey()]
+                                for header, value of responseParams.headers
+                                        response.set(header, value)
+                                [content, code] = responseParams.data
+                                response.send content, code
+                        delete @_ongoingConversions[cacheInstance.getKey()]

         convert: (type, filename, response) =>
                 if type not of @converters
@@ -147,14 +164,22 @@
                                 response.set "location", location
                                 response.send "Go to #{location}", 302
                                 return
+
                         fs.stat filename, (statErr, stats) =>
                                 if statErr
                                         response.send "Failed to read original file name.", 500
                                         return

+                                cacheKey = cacheInstance.getKey()
+                                if cacheKey of @_ongoingConversions
+                                        @_ongoingConversions[cacheKey].push response
+                                        return
+                                else
+                                        @_ongoingConversions[cacheKey] = [response]
+
                                 tempname = temp.path {suffix: ".#{suffix}"}
                                 converter.convert filename, @bitrate, tempname, (err) =>
-                                        @_conversionDone err, cacheInstance, tempname, response
+                                        @_conversionDone err, cacheInstance, tempname

 exports.FileConverterView = class FileConverterView


Test that server can handle a relative as well as an absolute url

--- lib/server/server.spec.coffee
+++ lib/server/server.spec.coffee
@@ -1,5 +1,6 @@
 expect = require('chai').expect
 nock = require('nock')
+url = require('url')
 server = require('./server')
 config = require('../config')
 token = require('../token/token')
@@ -126,6 +127,15 @@
 				expect(error).to.be.an.instanceof(Error)
 				done()

+		it 'should accept a full url', (done) ->
+			server.request {
+				method: 'GET'
+				url: url.resolve(config.remoteUrl, URI.ok)
+			}, (error, response) ->
+				expect(error).to.not.exist
+				expect(response.body.status).to.equal(STATUS.ok)
+				done()
+
 	checkRequestTypeWithoutBody = (type) ->
 		return (done) ->
 			lowercaseType = type.toLowerCase()


Removed extra space before buildtask comments

--- completions.cson
+++ completions.cson
@@ -416,13 +416,13 @@
   \t */
   \tprotected $enabled = true;
   \t
-  \t /**
+  \t/**
   \t * @var string $title Shown in the overview on the TaskRunner
   \t * HTML or CLI interface. Should be short and concise, no HTML allowed.
   \t */
   \tprotected $title = \'${2:${1}}\';
   \t
-  \t /**
+  \t/**
   \t * @var string $description Describe the implications the task has,
   \t * and the changes it makes. Accepts HTML formatting.
   \t */


Added requirements for php

Added requirements for php

--- completions.cson
+++ completions.cson
@@ -90,36 +90,74 @@
   'scope': '.text.html.ss'
   'body': '<% else %>\n\t${0}'

-# Template includes
 'Include':
   'prefix': 'include'
   'module': 'silverstripe/framework'
   'scope': '.text.html.ss'
   'body': '<% include ${1} %>${0}'

+'Base Tag':
+  'prefix': 'basetag'
+  'module': 'silverstripe/framework'
+  'scope': '.text.html.ss'
+  'body': '<% base_tag %>${0}'
+
+# Requirements
+'Require themedJS':
+  'prefix': 'requirethemedjs'
+  'module': 'silverstripe/framework'
+  'variations': [
+    {
+      'scope': '.text.html.ss'
+      'body': '<% require themedJS(\'${1}\') %>${0}'
+    }
+    {
+      'scope': '.text.html.php'
+      'body': 'Requirement::themedJS(\'${1}\')${0}'
+    }
+  ]
+
 'Require themedCSS':
   'prefix': 'requirethemedcss'
   'module': 'silverstripe/framework'
-  'scope': '.text.html.ss'
-  'body': '<% require themedCSS(\'${1}\') %>${0}'
+  'variations': [
+    {
+      'scope': '.text.html.ss'
+      'body': '<% require themedCSS(\'${1}\') %>${0}'
+    }
+    {
+      'scope': '.text.html.php'
+      'body': 'Requirement::themedCSS(\'${1}\')${0}'
+    }
+  ]

 'Require Javascript':
   'prefix': 'requirejavascript'
   'module': 'silverstripe/framework'
-  'scope': '.text.html.ss'
-  'body': '<% require javascript(\'${1}\') %>${0}'
+  'variations': [
+    {
+      'scope': '.text.html.ss'
+      'body': '<% require javascript(\'${1}\') %>${0}'
+    }
+    {
+      'scope': '.text.html.php'
+      'body': 'Requirement::javascript(\'${1}\')${0}'
+    }
+  ]

 'Require CSS':
   'prefix': 'requirecss'
   'module': 'silverstripe/framework'
-  'scope': '.text.html.ss'
-  'body': '<% require css(\'${1}\') %>${0}'
-
-'Base Tag':
-  'prefix': 'basetag'
-  'module': 'silverstripe/framework'
-  'scope': '.text.html.ss'
-  'body': '<% base_tag %>${0}'
+  'variations': [
+    {
+      'scope': '.text.html.ss'
+      'body': '<% require css(\'${1}\') %>${0}'
+    }
+    {
+      'scope': '.text.html.php'
+      'body': 'Requirement::css(\'${1}\')${0}'
+    }
+  ]

 # General variables
 'Current Member':


'local.json' file now is creating if not exists

--- lib/tasks/build/bundle.coffee
+++ lib/tasks/build/bundle.coffee
@@ -5,6 +5,11 @@
 pathUtils = require 'path'

 {log} = Neft
+
+DEFAULT_LOCAL_FILE =
+	android:
+		ndkDir: ''
+		sdkDir: ''

 module.exports = (platform, options, callback) ->
 	mode = if options.release then 'release' else 'develop'
@@ -25,8 +30,7 @@
 				return callback err

 			unless fs.existsSync('./local.json')
-				log.error "'local.json' configuration file not found"
-				return
+				fs.writeFileSync './local.json', JSON.stringify(DEFAULT_LOCAL_FILE, null, 4)

 			config =
 				platform: platform


.

Former-commit-id: 0ef758ad5356c2666bec711c5da9096441a336ad [formerly 30f987d89b952d46c202104902b671c4223667b0] [formerly f8dd97c4b356f7dc2437ab176fe786f099d89d0d [formerly 9226be0a9ff82a454af112933e843b245a4b6b25]] [formerly e9b0ed3445026b8d10b2523ec7b0260a5370afdd [formerly 28712985248e938eda72ffcccfcb2a22ed9a4aee] [formerly e0dafc3eeb72646a640c830ec25caaa16cea366c [formerly e0dafc3eeb72646a640c830ec25caaa16cea366c [formerly 56671004a102b3410272ca2275fee7d23d4d5ec1]]]]
Former-commit-id: 014d4297e23c37ea75283e403cea01ab773306bd [formerly d89e6100bc9b902a8e6b0d4d77b4eca7f5944c7c] [formerly fc8e267d3012f8c1c1d1ca518887c9d1dcb29b90 [formerly f5cf7fb00ce5f5c19b8edcf403a42adac4219d85]]
Former-commit-id: 29ecce76111da6842b32bf4ced0021a0ba029aa7 [formerly 9f61463265c4ce43d150fe7ec0b281acbd67f5b3]
Former-commit-id: 22d77570bac663e109e44f1957f536f980e07482
Former-commit-id: 7890762b99a8a3522c9042194eb5db9be5fa7fc0

--- @client/franklin.coffee
+++ @client/franklin.coffee
@@ -2111,7 +2111,7 @@
       comments = comments.slice()
       comments.push @discussion.assessment

-    comments.sort (a,b) -> a.created_at < b.created_at
+    comments.sort (a,b) -> a.created_at > b.created_at

     discussion_style =
       width: DECISION_BOARD_WIDTH


Downgrade all request logs to INFO

Downgrade all request logs to INFO

Commit 9056143fe36f1347a1ff985ef8592a1de7d798dd added logic to log
requests with different error levels depending on the status code. The
intention was to make the 5xx and 4xx requests stand out in Stackdriver.
Unfortunately, this also creates a lot of noise in Sentry since we log
the errors separately from the requests.

This commit brings back the former behaviour of logging all requests at
the INFO level. We can revisit this if we integrate the strategy
implemented in filestore of logging once per request.

--- libraries/metrics/http.coffee
+++ libraries/metrics/http.coffee
@@ -49,10 +49,5 @@
 							"content-length": res._headers?["content-length"]
 							statusCode: res.statusCode
 						"response-time": responseTimeMs
-				if res.statusCode >= 500
-					logger.error(info, "%s %s", req.method, reqUrl)
-				else if res.statusCode >= 400 and res.statusCode < 500
-					logger.warn(info, "%s %s", req.method, reqUrl)
-				else
-					logger.info(info, "%s %s", req.method, reqUrl)
+				logger.info(info, "%s %s", req.method, reqUrl)
 		next()


Add missing extensions to spec

Add missing extensions to spec

--- spec/common-spec.coffee
+++ spec/common-spec.coffee
@@ -25,12 +25,16 @@

   describe ".isPathSupported()", ->
     it "returns true for supported path extensions", ->
+      expect(archive.isPathSupported('/a.epub')).toBe true
       expect(archive.isPathSupported('/a.zip')).toBe true
       expect(archive.isPathSupported('/a.jar')).toBe true
       expect(archive.isPathSupported('/a.war')).toBe true
       expect(archive.isPathSupported('/a.tar')).toBe true
       expect(archive.isPathSupported('/a.tgz')).toBe true
       expect(archive.isPathSupported('/a.tar.gz')).toBe true
+      expect(archive.isPathSupported('/a.whl')).toBe true
+      expect(archive.isPathSupported('/a.egg')).toBe true
+      expect(archive.isPathSupported('/a.xpi')).toBe true
       expect(archive.isPathSupported('/a.bar.gz')).toBe false
       expect(archive.isPathSupported('/a.txt')).toBe false
       expect(archive.isPathSupported('/')).toBe false


:lipstick: Move expect up one line

:lipstick: Move expect up one line

--- spec/config-spec.coffee
+++ spec/config-spec.coffee
@@ -43,8 +43,8 @@
           sameObject: {a: 1, b: 2}
           null: null
           undefined: undefined
-
         expect(atom.config.settings.foo).toBeUndefined()
+
         atom.config.set('foo.same', 1)
         atom.config.set('foo.changes', 2)
         atom.config.set('foo.sameArray', [1, 2, 3])


Reorder return values of getSelectedBufferRanges

The implementation does not depend on a specific order, just the test

--- spec/editor-spec.coffee
+++ spec/editor-spec.coffee
@@ -1431,17 +1431,17 @@
             [[10, 0], [10, 0]]
           ]

-    describe ".addSelectionAbove()", ->
+    fdescribe ".addSelectionAbove()", ->
       describe "when the selection is non-empty", ->
         it "selects the same region of the line above current selections if possible", ->
           editor.setSelectedBufferRange([[3, 16], [3, 21]])
           editor.addSelectionForBufferRange([[3, 37], [3, 44]])
           editor.addSelectionAbove()
           expect(editor.getSelectedBufferRanges()).toEqual [
+            [[3, 16], [3, 21]]
+            [[3, 37], [3, 44]]
             [[2, 16], [2, 21]]
             [[2, 37], [2, 40]]
-            [[3, 16], [3, 21]]
-            [[3, 37], [3, 44]]
           ]
           for cursor in editor.getCursors()
             expect(cursor.isVisible()).toBeFalsy()
@@ -1450,8 +1450,8 @@
           editor.setSelectedBufferRange([[6, 31], [6, 38]])
           editor.addSelectionAbove()
           expect(editor.getSelectedBufferRanges()).toEqual [
+            [[6, 31], [6, 38]]
             [[3, 31], [3, 38]]
-            [[6, 31], [6, 38]]
           ]

         it "honors the original selection's range (goal range) when adding across shorter lines", ->
@@ -1460,10 +1460,10 @@
           editor.addSelectionAbove()
           editor.addSelectionAbove()
           expect(editor.getSelectedBufferRanges()).toEqual [
+            [[6, 22], [6, 38]]
+            [[5, 22], [5, 30]]
+            [[4, 22], [4, 29]]
             [[3, 22], [3, 38]]
-            [[4, 22], [4, 29]]
-            [[5, 22], [5, 30]]
-            [[6, 22], [6, 38]]
           ]

       describe "when the selection is empty", ->
@@ -1473,26 +1473,26 @@
           editor.addSelectionAbove()
           editor.addSelectionAbove()
           expect(editor.getSelectedBufferRanges()).toEqual [
+            [[6, 36], [6, 36]]
+            [[5, 30], [5, 30]]
+            [[4, 29], [4, 29]]
             [[3, 36], [3, 36]]
-            [[4, 29], [4, 29]]
-            [[5, 30], [5, 30]]
-            [[6, 36], [6, 36]]
           ]

         it "skips empty lines when the column is non-zero", ->
           editor.setCursorBufferPosition([11, 4])
           editor.addSelectionAbove()
           expect(editor.getSelectedBufferRanges()).toEqual [
+            [[11, 4], [11, 4]]
             [[9, 4], [9, 4]]
-            [[11, 4], [11, 4]]
           ]

         it "does not skip empty lines when the column is zero", ->
           editor.setCursorBufferPosition([10, 0])
           editor.addSelectionAbove()
           expect(editor.getSelectedBufferRanges()).toEqual [
+            [[10, 0], [10, 0]]
             [[9, 0], [9, 0]]
-            [[10, 0], [10, 0]]
           ]

     describe ".splitSelectionsIntoLines()", ->


:lipstick: spec helper's keydownEvent

:lipstick: spec helper's keydownEvent

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -180,17 +180,15 @@
     charCode = key.toUpperCase().charCodeAt(0)
     "U+00" + charCode.toString(16)

-nativeKeydownEvent = require('atom-keymap').keydownEvent
-
 window.keydownEvent = (key, properties={}) ->
-  nativeProperties = {}
-  nativeProperties.ctrl = properties.ctrlKey
-  nativeProperties.alt = properties.altKey
-  nativeProperties.shift = properties.shiftKey
-  nativeProperties.cmd = properties.metaKey
-  nativeProperties.target = properties.target?[0] ? properties.target
-  nativeProperties.which = properties.which
-  originalEvent = nativeKeydownEvent(key, nativeProperties)
+  originalEventProperties = {}
+  originalEventProperties.ctrl = properties.ctrlKey
+  originalEventProperties.alt = properties.altKey
+  originalEventProperties.shift = properties.shiftKey
+  originalEventProperties.cmd = properties.metaKey
+  originalEventProperties.target = properties.target?[0] ? properties.target
+  originalEventProperties.which = properties.which
+  originalEvent = Keymap.keydownEvent(key, originalEventProperties)
   properties = $.extend({originalEvent}, properties)
   $.Event("keydown", properties)


cleanup spec helper

cleanup spec helper

--- spec/spec-helper.coffee
+++ spec/spec-helper.coffee
@@ -13,7 +13,6 @@
   'characterwise'
 ]

-packageName = 'vim-mode-plus'
 class SpecError
   constructor: (@message) ->
     @name = 'SpecError'
@@ -34,50 +33,25 @@
 unmockPlatform = (editorElement) ->
   editorElement.parentNode.removeChild(editorElement)

-dispatchKeyboardEvent = (target, eventArgs...) ->
-  e = document.createEvent('KeyboardEvent')
-  e.initKeyboardEvent(eventArgs...)
-  # 0 is the default, and it's valid ASCII, but it's wrong.
-  if e.keyCode is 0
-    Object.defineProperty(e, 'keyCode', get: -> undefined)
-  target.dispatchEvent e
-
-dispatchTextEvent = (target, eventArgs...) ->
-  e = document.createEvent('TextEvent')
-  e.initTextEvent(eventArgs...)
-  target.dispatchEvent e
-
-keydown = (key, {element, ctrl, shift, alt, meta, raw}={}) ->
-  unless key is 'escape' or raw?
-    key = "U+#{key.charCodeAt(0).toString(16)}"
-  element ?= document.activeElement
-  eventArgs = [
-    false, # bubbles
-    true, # cancelable
-    null, # view
-    key,  # key
-    0,    # location
-    ctrl, alt, shift, meta
-  ]
-
-  canceled = not dispatchKeyboardEvent(element, 'keydown', eventArgs...)
-  # [FIXME] I think I can remove keypress event dispatch.
-  dispatchKeyboardEvent(element, 'keypress', eventArgs...)
-  unless canceled
-    if dispatchTextEvent(element, 'textInput', eventArgs...)
-      element.value += key
-  dispatchKeyboardEvent(element, 'keyup', eventArgs...)
+KeymapManager = null
+buildKeydownEvent = (key, options) ->
+  KeymapManager ?= atom.keymaps.constructor
+  KeymapManager.buildKeydownEvent(key, options)
+
+keydown = (key, options) ->
+  event = buildKeydownEvent(key, options)
+  atom.keymaps.handleKeyboardEvent(event)

 _keystroke = (keys, event) ->
   if keys in ['escape', 'backspace']
-    keydown keys, event
+    keydown(keys, event)
   else
     for key in keys.split('')
       if key.match(/[A-Z]/)
         event.shift = true
       else
         delete event.shift
-      keydown key, event
+      keydown(key, event)

 isPoint = (obj) ->
   if obj instanceof Point
@@ -120,21 +94,15 @@
     when 2 then [file, callback] = args

   waitsForPromise ->
-    atom.packages.activatePackage(packageName)
+    atom.packages.activatePackage('vim-mode-plus')

   waitsForPromise ->
     file = atom.project.resolvePath(file) if file
-    atom.workspace.open(file).then (e) ->
-      editor = e
+    atom.workspace.open(file).then (e) -> editor = e

   runs ->
-    pack = atom.packages.getActivePackage(packageName)
-    main = pack.mainModule
+    main = atom.packages.getActivePackage('vim-mode-plus').mainModule
     vimState = main.getEditorState(editor)
-    {editorElement} = vimState
-    editorElement.addEventListener 'keydown', (e) ->
-      atom.keymaps.handleKeyboardEvent(e)
-
     callback(vimState, new VimEditor(vimState))

 class TextData
@@ -316,8 +284,11 @@
       expect(@editorElement.classList.contains(m)).toBe(false)

   # Public
+  # options
+  # - waitsForFinish
   keystroke: (keys, options={}) =>
-    {element} = options
+    # {element} = options
+    # console.log element
     if options.waitsForFinish
       finished = false
       @vimState.onDidFinishOperation -> finished = true
@@ -328,17 +299,18 @@

     # keys must be String or Array
     # Not support Object for keys to avoid ambiguity.
-    element ?= @editorElement
+    target = @editorElement
     mocked = null
     keys = [keys] unless _.isArray(keys)

     for k in keys
       if _.isString(k)
-        _keystroke(k, {element})
+        _keystroke(k, {target})
+        continue
       else
         switch
           when k.platform?
-            mockPlatform(element, k.platform)
+            mockPlatform(target, k.platform)
             mocked = true
           when k.char?
             chars =
@@ -353,10 +325,9 @@
             {editor, editorElement} = @vimState.searchInput
             editor.insertText(k.search)
             atom.commands.dispatch(editorElement, 'core:confirm')
-          when k.ctrl? then _keystroke(k.ctrl, {ctrl: true, element})
-          when k.cmd? then _keystroke(k.cmd, {meta: true, element})
-          when k.raw? then _keystroke(k.raw, {raw: true, element})
-    if mocked
-      unmockPlatform(element)
+          when k.ctrl? then _keystroke(k.ctrl, {ctrl: true, target})
+          when k.cmd? then _keystroke(k.cmd, {meta: true, target})
+          when k.raw? then _keystroke(k.raw, {raw: true, target})
+    unmockPlatform(target) if mocked

 module.exports = {getVimState, getView, dispatch, TextData}


Revert "Normalize not supported on older firefox"

This reverts commit 487be80975ab0d36255d66d54cf51929374c8f53.

--- resource/translators/translator.coffee
+++ resource/translators/translator.coffee
@@ -349,13 +349,10 @@
         )
       value = "{#{value}}"

-  field.bibtex = "  #{field.name} = #{value}"
-  field.bibtex = field.bibtex.normalize('NFKC') if @normalize
+  field.bibtex = "  #{field.name} = #{value}".normalize('NFKC')
   @fields.push(field)
   @has[field.name] = true
   return
-
-Reference::normalize = (typeof(''.normalize) == 'function')

 Reference::complete = ->
   @add({name: 'type', value: @itemtype}) if @fields.length == 0


properly handle cases when there is no intersection between 2 clipping paths

(that includes not exporting clipping groups that have an empty clip)

--- AI2ASS.coffee
+++ AI2ASS.coffee
@@ -31,6 +31,7 @@
   makeClip = ( clippingPath ) ->
     clip = {
       tempGroup: null
+      isVisible: false

       add: (clippingPath) ->
         # prepare a group to apply the pathfinder effect to
@@ -56,8 +57,17 @@
           app.executeMenuCommand("expandStyle")
           # expanding created a new group
           @tempGroup = doc.selection[0]
+
+          # no intersection between paths means we have an empty clipping area
+          if @tempGroup.pageItems.length == 1
+            @isVisible = true
+          else
+            @isVisible = false
+            @tempGroup.pageItems.removeAll()
+
           # restore previous selection
           doc.selection = prevSelection
+        else @isVisible = true

       copy: -> return makeClip @tempGroup.pageItems[0]
       get: -> return @tempGroup.pageItems[0]
@@ -79,11 +89,7 @@
       if not @pathCnt?
         @pathCnt = countPathItems obj

-
-      #if clip?
-      #  clip = clip.clone()
-
-      unless obj.hidden
+      unless obj.hidden or clip? and !clip.isVisible
         switch obj.typename
           when "Document"
             for layer in obj.layers


forbid connection from receiver to FlintServer while defining useIpc as
false

Signed-off-by: Manson Li <f2a598d7717cc6757bef28b84a9ab02e5efc6048@matchstick.tv>

--- lib/dial/session/ReceiverSession.coffee
+++ lib/dial/session/ReceiverSession.coffee
@@ -62,11 +62,15 @@

             # receiver application register itself
             if data.type is "register"
-                Log.d "app #{@appId} is registered"
-                # application is launched, and start heartbeat
-                app.started()
-                @_onRegister()
-                @_startHeartbeat()
+                if app.getAppInfo().useIpc
+                    Log.d "app #{@appId} is registered"
+                    # application is launched, and start heartbeat
+                    app.started()
+                    @_onRegister()
+                    @_startHeartbeat()
+                else
+                    Log.e "app #{@appId} should not be registered, close it!!!"
+                    app.stop()
             # receiver application unregister itself, fling-service stop it
             else if data.type is "unregister"
                 @sessionManager.clearReceiverSession()


remove git-plus:menu mappings

remove git-plus:menu mappings

--- keymaps/git-plus.cson
+++ keymaps/git-plus.cson
@@ -9,13 +9,11 @@
 # https://atom.io/docs/latest/advanced/keymaps

 '.platform-darwin':
-  'cmd-shift-h': 'git-plus:menu'
   'cmd-shift-c': 'git-plus:commit'
   'cmd-shift-a s': 'git-plus:status'
   'cmd-shift-a a': 'git-plus:add-all-and-commit'

 '.platform-win32, .platform-linux':
-  'ctrl-shift-h': 'git-plus:menu'
   'ctrl-shift-x': 'git-plus:commit'
   'ctrl-shift-a s': 'git-plus:status'
   'ctrl-shift-a a': 'git-plus:add-all-and-commit'


removed 'more text'

removed 'more text'

--- roc_dashboard/widgets/text/text.coffee
+++ roc_dashboard/widgets/text/text.coffee
@@ -1,13 +1,13 @@
 class Dashing.Text extends Dashing.Widget

   @accessor 'title?', ->
-    "#{@get('title')} more text more text".toUpperCase()
+    "#{@get('title')}".toUpperCase()

   @accessor 'artist?', ->
-    "#{@get('artist')} more text more text"
+    "#{@get('artist')}"

   @accessor 'album?', ->
-    "#{@get('album')} more text more text"
+    "#{@get('album')}"

   @accessor 'album_art?', ->
     if @get('music')


Revert "use URL api instead of anchor tags"

This reverts commit b5c2dd8fbc066200bfe19526ccac88cd2b5fbb12.


Removed setLogger. Renamed last logger to console logger. Added docpad-debug.log output if we are running in debug log level.

--- src/lib/docpad.coffee
+++ src/lib/docpad.coffee
@@ -157,20 +157,16 @@
 		@serverExpress = servers.serverExpress
 		@serverHttp = servers.serverHttp

-	# The caterpillar instance bound to docpad
+	# The caterpillar instances bound to docpad
 	loggerInstances: null
-	getLogger: (all=false) ->
-		if all
-			@loggerInstances
+	getLogger: -> @loggerInstances?.logger
+	getLoggers: -> @loggerInstances
+	setLoggers: (loggers) ->
+		if @loggerInstances
+			@warn('Loggers have already been set')
 		else
-			@loggerInstances?.logger
-	setLogger: (value,all=false) ->
-		if all or value.logger?
-			@loggerInstances = value
-		else
-			@loggerInstances ?= {}
-			@loggerInstances.logger = value
-		@
+			@loggerInstances = loggers
+		return loggers

 	# The action runner instance bound to docpad
 	actionRunnerInstance: null
@@ -969,12 +965,27 @@
 				locale = docpad.getLocale()
 				docpad.log('warn', locale.trackError+'\n'+locale.errorFollows, err)

-		# Initialize the logger
-		logger = new (require('caterpillar').Logger)()
-		filter = new (require('caterpillar-filter').Filter)
-		last = human = new (require('caterpillar-human').Human)
-		logger.pipe(filter).pipe(human).pipe(process.stdout)
-		@setLogger({logger,filter,human,last})
+		# Initialize the loggers
+		if (loggers = instanceConfig.loggers)
+			delete instanceConfig.loggers
+		else
+			# Create
+			logger = new (require('caterpillar').Logger)()
+
+			# console
+			(console = logger
+				.pipe(
+					new (require('caterpillar-filter').Filter)
+				).pipe(
+					new (require('caterpillar-human').Human)
+				)
+			).pipe(process.stdout)
+
+			# Apply
+			loggers = {logger,console}
+
+		# Configure
+		@setLoggers(loggers)
 		@setLogLevel(6)

 		# Log to bubbled events
@@ -1279,7 +1290,6 @@
 		@setServer(@config.server)  if @config.server

 		# Extract and apply the logger
-		@setLogger(@config.logger)  if @config.logger
 		@setLogLevel(@config.logLevel)

 		# Resolve any paths
@@ -1795,6 +1805,15 @@
 	# Set Log Level
 	setLogLevel: (level) ->
 		@getLogger().setConfig({level})
+		if level is 7
+			loggers = @getLoggers()
+			loggers.debug ?= loggers.logger
+				.pipe(
+					new (require('caterpillar-human').Human)
+				)
+				.pipe(
+					require('fs').createWriteStream(process.cwd()+'/docpad-debug.log')
+				)
 		@

 	# Are we debugging?
@@ -2862,11 +2881,11 @@
 		# Progress
 		if @getLogLevel() is 6 and 'development' in @getEnvironments()
 			opts.progress ?= require('progressbar').create()
-			docpad.getLogger(true).last.unpipe(process.stdout)
+			docpad.getLoggers().console.unpipe(process.stdout)
 		finish = (err) ->
 			opts.progress?.finish()
 			opts.progress = null
-			docpad.getLogger(true).last.pipe(process.stdout)
+			docpad.getLoggers().console.pipe(process.stdout)
 			return next(err)

 		# Re-load and re-render only what is necessary


Added a test that covered requests with params but no bodies (i.e. all params are uri or header params).

--- test/unit/rest_xml_service.spec.coffee
+++ test/unit/rest_xml_service.spec.coffee
@@ -47,8 +47,18 @@
     buildRequest = (params) ->
       svc.buildRequest('sampleOperation', params)

-    describe 'body', ->
+    describe 'empty bodies', ->

-      it 'defaults to an empty body when there is no payload', ->
+      it 'defaults body to null when there are no inputs', ->
+        operation.i = null
         expect(buildRequest().body).toEqual(null)

+      it 'defaults body to null when all inputs are uri or header values', ->
+        operation.u = '/{Bucket}'
+        operation.i = {Bucket:{l:'uri',r:1},ACL:{l:'header',n:'x-amz-acl'}}
+        params = { Bucket:'abc', ACL:'canned-acl' }
+        req = buildRequest(params)
+        expect(req.body).toEqual(null)
+        expect(req.uri).toEqual('/abc')
+        expect(req.headers['x-amz-acl']).toEqual('canned-acl')
+


fix bug for opinion views if there's a problem finding a particular user

--- @client/opinion_views.coffee
+++ @client/opinion_views.coffee
@@ -922,6 +922,9 @@

   pass = (u) ->
     user = fetch(u)
+
+    return false if !user.tags
+
     val_for_user = user.tags[attr_key]
     is_array = Array.isArray(val_for_user)


use new pdfjs getDestination method in pdfRenderer

gives faster lookup of link destinations in pdf file

--- services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
+++ services/web/public/coffee/ide/pdfng/directives/pdfRenderer.coffee
@@ -60,22 +60,10 @@
 					pdfDocument.getDestinations()

 			getDestination: (dest) ->
-				# There is a direct method for this in pdf.js but it is not
-				# available in pdf.js-1.0.712. Use the following workaround of
-				# getting all the destinations and returning only the one we
-				# want.
-				@destinations = @document.then (pdfDocument) ->
-					pdfDocument.getDestinations()
-				return @destinations.then (all) ->
-					all[dest]
+				@document.then (pdfDocument) ->
+					pdfDocument.getDestination(dest)
 				, (error) =>
 					@errorCallback?(error)
-				# When we upgrade we can switch to using the following direct
-				# code.
-				# @document.then (pdfDocument) ->
-				# 	pdfDocument.getDestination(dest)
-				# , (error) ->
-				# 	console.log 'ERROR', error

 			getPageIndex: (ref) ->
 				@document.then (pdfDocument) =>


- fixes

- fixes


fix: first in select is now null

fix: first in select is now null


ignore message body parsing error

ignore message body parsing error

--- api/services/file.coffee
+++ api/services/file.coffee
@@ -26,7 +26,7 @@
 					try
 						prop = JSON.parse matchingRecord.body
 					catch error
-						return
+						sails.log.debug error
 					sails.config.file.opts.adapter(sails.config.file.opts)
 						.read matchingRecord.file, (err, data) ->
 							if err


make table occupy proper height (quick fix, not keeping register just yet)

--- src/mkts-table.coffee
+++ src/mkts-table.coffee
@@ -62,6 +62,7 @@
     fails:                [] ### recoverable errors / fails warnings ###
     fieldcells:           {} ### field extents in terms of cells, by field designations ###
     cellfields:           {} ### which cells belong to what fields, by cellkeys ###
+    table_dimensions:     {} ### width and height of enclosing `\minipage`, in terms of (unitwidth,unitheight) ###
     cell_dimensions:      {}
     fieldborders:         {} ### field borders, as TikZ styles by edges ###
     field_dimensions:     {} ### field extents in terms of (unitwidth,unitheight), by field designations ###
@@ -290,6 +291,7 @@
   @_compute_field_dimensions    me
   @_compute_border_dimensions   me
   @_compute_pod_dimensions      me
+  @_compute_table_height        me
   #.........................................................................................................
   ### Preparatory ###
   yield from @_walk_opening_events                      me
@@ -329,9 +331,9 @@
   yield texr 'µ2', "\\mktsColorframebox{red}{% debugging framebox" if me.debug
   ### NOTE only height of minipage is important; TikZ will happily draw outside of minipage when told ###
   ### TAINT calculate proper height so text will keep register ###
-  yield texr 'µ2', "\\begin{minipage}[t][45mm][t]{\\linewidth}"
   yield texr 'µ3', "\\newdimen\\mktsTableUnitwidth\\setlength{\\mktsTableUnitwidth}{#{me.unitwidth}}"
   yield texr 'µ4', "\\newdimen\\mktsTableUnitheight\\setlength{\\mktsTableUnitheight}{#{me.unitheight}}"
+  yield texr 'µ2', "\\begin{minipage}[t][#{me.table_dimensions.height}\\mktsTableUnitheight][t]{\\linewidth}"
   yield texr 'µ5', "\\begin{tikzpicture}[ overlay, yshift = 0mm, yscale = -1, line cap = rect ]"
   yield texr 'µ6', "\\tikzset{x=#{me.unitwidth}};\\tikzset{y=#{me.unitheight}};"
   yield return
@@ -597,6 +599,12 @@
   return null

 #-----------------------------------------------------------------------------------------------------------
+@_compute_table_height = ( me ) ->
+  me.table_dimensions.height  = @_bottom_from_rownr me, me.grid.height
+  me.table_dimensions.width   = null ### not used ATM, all tables are nominally as wide as column ###
+  return null
+
+#-----------------------------------------------------------------------------------------------------------
 @_left_from_colnr = ( me, colnr ) ->
   ### TAINT should precompute ###
   @_ensure_cellwidths me


Added more Artisan permissions for Articles.

--- server/articles/article_handler.coffee
+++ server/articles/article_handler.coffee
@@ -7,7 +7,7 @@
   jsonSchema: require '../../app/schemas/models/article'

   hasAccess: (req) ->
-    req.method is 'GET' or req.user?.isAdmin()
+    req.method is 'GET' or req.user?.isAdmin() or req.user?.isArtisan()

   hasAccessToDocument: (req, document, method=null) ->
     return true if req.method is 'GET' or method is 'get' or req.user?.isAdmin() or req.user?.isArtisan()


fixed url generation tests

fixed url generation tests

--- server/coffee/test/service_test.coffee
+++ server/coffee/test/service_test.coffee
@@ -1,51 +1,33 @@
 assert = require("assert")
-
-helpers = require("./helpers")
-testify = require("../src/testify")
+Testify = require "testify"

 api = require("./sample_api.coffee")
 Patchboard = require("../patchboard")
 service = new Patchboard.Service(api)

-testify "URL generation with no arguments", ->
-  assert.equal(
-    service.generate_url("resource_collection"),
-    "http://patchboarded.com/resources"
-  )
+Testify.test "Patchboard.Service", (context) ->

-testify "URL generation with named arguments", ->
-  assert.equal(
-    service.generate_url("resource_instance", {id: "someidvalue"}),
-    "http://patchboarded.com/resources/someidvalue"
-  )
-  assert.equal(
-    service.generate_url("attachment", {id: "someidvalue", attachment_id: "othervalue"}),
-    "http://patchboarded.com/resources/someidvalue/attachments/othervalue"
-  )
+  context.test "URL generation with no arguments", ->
+    assert.equal(
+      service.generate_url("organizations"),
+      "http://gh-knockoff.com/organizations"
+    )

-testify "URL generation with positional arguments", ->
-  assert.equal(
-    service.generate_url("resource_instance", "someidvalue"),
-    "http://patchboarded.com/resources/someidvalue"
-  )
+  context.test "URL generation with named arguments", ->
+    assert.equal(
+      service.generate_url("organization", {id: "someidvalue"}),
+      "http://gh-knockoff.com/organizations/someidvalue"
+    )

-  assert.equal(
-    service.generate_url("attachment", "someidvalue", "othervalue"),
-    "http://patchboarded.com/resources/someidvalue/attachments/othervalue"
-  )
+  context.test "URL generation with positional arguments", ->
+    assert.equal(
+      service.generate_url("organization", "someidvalue"),
+      "http://gh-knockoff.com/organizations/someidvalue"
+    )

-api.service_url = "http://patchboarded.com/"
-service = new Patchboard.Service(api)
+  context.test "URL normalization", ->
+    parsed = service.parse_url("http://gh-knockoff.com//some/path")
+    assert.equal(parsed.path, "/some/path")

-testify "URL generation with no arguments", ->
-  assert.equal(
-    service.generate_url("resource_collection"),
-    "http://patchboarded.com/resources"
-  )
-
-testify "URL normalization", ->
-  parsed = service.parse_url("http://patchboarded.com//some/path")
-  assert.equal(parsed.path, "/some/path")
-
-# TODO: test failure conditions (too many or few arguments, incorrect names, etc.)
+  # TODO: test failure conditions (too many or few arguments, incorrect names, etc.)


fix firefox pushState 640K limit problem

--- src/pjax.coffee
+++ src/pjax.coffee
@@ -71,7 +71,10 @@
           @request.abort()
           @request = null

-        @el.html state.html
+        page = @getCache state.url
+        return unless page
+
+        @el.html page.html
         @el[0].offsetHeight
         @pageTitle state.name
         #@requestPage state
@@ -137,7 +140,8 @@
         name: @_i18n('loading')
         html: ''

-    state = $.extend {}, page
+    state = $.extend {}, page,
+      html: ''
     @trigger 'pushstate', [state]
     title = @pageTitle state.name
     history.pushState state, title, state.url
@@ -155,8 +159,8 @@
     if opts.norefresh and page
       $page = @el.children().first()
       pageId = $page.attr 'id'
+      $(document).trigger 'pjaxload#' + pageId, [$page, page] if pageId
       @trigger 'pjaxload', [$page, page]
-      $(document).trigger 'pjaxload#' + pageId, [$page, page] if pageId
     else
       @requestPage page

@@ -177,9 +181,11 @@
         page.html = xhr.responseText
         @loadPage page
         Pjax.clearCache page.url
-      success: (result) =>
+      success: (result, status, xhr) =>
         page.html = $.trim result
         page.name = ''
+        if pageUrl = xhr.getResponseHeader 'X-PJAX-URL'
+          page.url = simple.url(pageUrl).toString('relative')
         @loadPage page

   loadPage: (page) ->
@@ -207,7 +213,8 @@
     @url = simple.url page.url
     @setCache page

-    state = $.extend {}, page
+    state = $.extend {}, page,
+      html: ''
     @trigger 'replacestate', [state]
     title = @pageTitle state.name
     history.replaceState state, title, state.url
@@ -215,8 +222,8 @@
     @el.height ''

     pageId = $page.attr 'id'
+    $(document).trigger 'pjaxload#' + pageId, [$page, page] if pageId
     @trigger 'pjaxload', [$page, page]
-    $(document).trigger 'pjaxload#' + pageId, [$page, page] if pageId

   unload: ->
     page = @setCache() if @url
@@ -224,7 +231,8 @@
       return false

     if page
-      state = $.extend {}, page
+      state = $.extend {}, page,
+        html: ''
       @trigger 'replacestate', [state]
       title = @pageTitle state.name
       history.replaceState state, title, state.url
@@ -248,8 +256,8 @@
     Pjax.pageCache[page.url] = page
     page

-  getCache: ->
-    Pjax.pageCache[@url.toString('relative')]
+  getCache: (url = @url.toString('relative')) ->
+    Pjax.pageCache[url]

   @clearCache: (url) ->
     if url


fix offsets due to renderer changes

fix offsets due to renderer changes

--- tests/mocha/unit/modules/cursor.coffee
+++ tests/mocha/unit/modules/cursor.coffee
@@ -1,6 +1,8 @@
 describe('Cursor', ->
   describe('basic operations', ->
     cursorManager = editor = null
+    offsetLeft = 15
+    offsetTop = 10
     before((done) ->
       initial = '
BoldItalic

Large
'
       $('#test-container').html(ScribeNormalizer.normalizeHtml(initial))
@@ -22,51 +24,51 @@
     it('should set at middle of leaf', ->
       cursorManager.setCursor('id', 2, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
-      expect(cursor.style.left).to.equal('32px')
-      expect(parseInt(cursor.style.top)).to.be.within(-1, 1)
+      expect(parseInt(cursor.style.left) - offsetLeft).to.equal(17)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(-1, 1)
     )

     it('should set at leaf boundary', ->
       cursorManager.setCursor('id', 4, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
-      expect(parseInt(cursor.style.left)).to.be.within(43, 44)
-      expect(parseInt(cursor.style.top)).to.be.within(-1, 1)
+      expect(parseInt(cursor.style.left) - offsetLeft).to.be.within(28, 29)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(-1, 1)
     )

     it('should set at newline', ->
       cursorManager.setCursor('id', 11, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
-      expect(cursor.style.left).to.equal('15px')
-      expect(parseInt(cursor.style.top)).to.be.within(14, 16)
+      expect(parseInt(cursor.style.left) - offsetLeft).to.equal(0)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(14, 16)
     )

     it('should set at beginning', ->
       cursorManager.setCursor('id', 0, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
-      expect(cursor.style.left).to.equal('15px')
-      expect(parseInt(cursor.style.top)).to.be.within(-1, 1)
+      expect(parseInt(cursor.style.left) - offsetLeft).to.equal(0)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(-1, 1)
     )

     it('should set at end', ->
       cursorManager.setCursor('id', 17, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('61px')
-      expect(parseInt(cursor.style.top)).to.be.within(29, 31)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(29, 31)
     )

     it('should set at beginning of large font', ->
       cursorManager.setCursor('id', 12, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
-      expect(cursor.style.left).to.equal('15px')
-      expect(parseInt(cursor.style.top)).to.be.within(29, 31)
+      expect(parseInt(cursor.style.left) - offsetLeft).to.equal(0)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(29, 31)
       expect(parseInt(cursor.style.height)).to.be.within(18, 22)
     )

     it('should set in middle of large font', ->
       cursorManager.setCursor('id', 14, 'Test', 'red')
       cursor = cursorManager.container.querySelector('.cursor')
-      expect(cursor.style.left).to.equal('35px')
-      expect(parseInt(cursor.style.top)).to.be.within(29, 31)
+      expect(parseInt(cursor.style.left) - offsetLeft).to.equal(20)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(29, 31)
       expect(parseInt(cursor.style.height)).to.be.within(18, 22)
     )

@@ -82,8 +84,8 @@
       cursor = cursorManager.container.querySelector('.cursor')
       expect(cursor.style.left).to.equal('24px')
       editor.insertAt(0, 'A', { bold: true })
-      expect(parseInt(cursor.style.left)).to.be.within(33, 34)
-      expect(parseInt(cursor.style.top)).to.be.within(-1, 1)
+      expect(parseInt(cursor.style.left) - offsetLeft).to.be.within(18, 19)
+      expect(parseInt(cursor.style.top) - offsetTop).to.be.within(-1, 1)
     )

     it('should append after external edit', ->


Add bolus arrival bar.

Add bolus arrival bar.

--- app/javascripts/intensityChart.coffee
+++ app/javascripts/intensityChart.coffee
@@ -2,81 +2,6 @@
   intensityChart = ng.module 'qiprofile.intensitychart', []

   intensityChart.factory 'IntensityChart', ->
-    # Highlights the bolus arrival tick mark. The bolus arrival is
-    # only highlighted if it occurs after the first series.
-    #
-    # @param session the session object
-    # @param chart the intensity chart
-    highlightBolusArrival = (session, chart) ->
-      # If the bolus arrival index is zero, then it does not have
-      # have a tick mark, so bail.
-      if not session.bolusArrivalIndex
-        return
-      # Select the SVG element.
-      svg = d3.select(chart.container)
-      # The x axis element.
-      xAxis = svg.select('.nv-x')
-
-      # The D3 CSS3 bolus tick selector. The tick marks are SVG
-      # g elements with the tick class. There is a tick mark for
-      # all but the first and last series. Therefore, the tick
-      # offset is one less than the bolus arrival index.
-      offset = session.bolusArrivalIndex - 1
-      # The D3 CSS3 nth-of-type selector argument of zero returns
-      # null. The work-around for this possible D3 bug by using
-      # the first-of-type selector argument in that case.
-      if offset
-        bolusTickSel = "g.tick:nth-of-type(#{ offset })"
-      else
-        bolusTickSel = "g.tick:first-of-type"
-      # The bolus tick element.
-      bolusTick = xAxis.select(bolusTickSel)
-      # The bolus tick child line element.
-      bolusTickLine = bolusTick.select('line')
-      highlightNode = bolusTickLine.node().cloneNode()
-      highlight = d3.select(highlightNode)
-      # Set the bolus CSS class.
-      highlight.classed('qi-bolus-arrival', true)
-
-      # Insert the highlight SVG element after the tick line. The
-      # highlight will display centered over the tick line. Note
-      # that D3 insert differs from jQuery insert. The D3 arguments
-      # are a function which returns a DOM element and the selector
-      # before which the node is inserted. In our case, the node to
-      # insert is the highlight node and the before selector is the
-      # bolus tick selector.
-      highlightNodeFunc = -> highlightNode
-      bolusTick.insert(highlightNodeFunc, 'line')
-
-      # The chart legend.
-      legend = svg.select('.nv-legend')
-      # The legend group holds the legend elements.
-      legendGroup = legend.select(':first-child')
-      # Add a bolus legend group before the standard legends.
-      bolusGroup = legendGroup.insert('svg:g', ':first-child')
-      bolusGroup.attr('transform', 'translate(-25, 5)')
-      # The legend text.
-      text = 'Bolus Arrival'
-      # The SVG text size in em units.
-      textScale = 0.32
-      # The legend length.
-      textWidth = textScale * text.length
-
-      # A rectangle of the same color as the bolus arrival timeline
-      # bar is the background for the legend text.
-      bolusLegendBar = bolusGroup.append('svg:rect')
-      bolusLegendBar.attr('width', "#{ textWidth + 1 }em")
-      bolusLegendBar.attr('height', '1.1em')
-      bolusLegendBar.attr('x', "#{ -textWidth - 1.1 + textScale }em")
-      bolusLegendBar.attr('y', "-#{ textScale + .2 }em")
-      bolusLegendBar.attr('fill', 'Gainsboro')
-
-      # The bolus legend text is painted on top of the rectangle.
-      bolusLegend = bolusGroup.append('svg:text')
-      bolusLegend.attr('class', 'qi-bolus-legend')
-      bolusLegend.attr('dy', "#{ textScale }em")
-      bolusLegend.text(text)
-
     # The Y axis value formatter function. If the given intensity
     # value is integral, then this function returns the integer.
     # Otherwise, the value is truncated to two decimal places.
@@ -152,29 +77,47 @@
       registrationDataSeries = (registration, index) ->
         # Return the data series format object.
         key: dataSeriesKey(index)
+        type: 'line'
+        yAxis: 1
         color: REG_COLORS[index % REG_COLORS.length]
         values: coordinates(registration.volumes.images)

       # The scan data series configuration.
       scanDataSeries =
         key: 'Scan'
+        type: 'line'
+        yAxis: 1
         color: SCAN_COLOR
         values: coordinates(scan.volumes.images)
       # The registration data series.
       regDataSeries = (registrationDataSeries(reg, i) for reg, i in scan.registrations)
+
       # Combine the scan and registration data series.
       data = [scanDataSeries].concat(regDataSeries)

-      # TODO - adapt highlightBolusArrival for ngnvd3 and enable
-      #   as the onready callback.
-      #   But try out a line/bar combo chart first.
-      highlightBolusArrival: (chart) ->
-        highlightBolusArrival(scan.session, chart)
+      # Add the bolus arrival bar.
+      bolusArvNdx = scan.session.bolusArrivalIndex
+      if bolusArvNdx?
+        # Extend the bar from the axis to the top.
+        dsIntensities = (_.map(dataSeries.values, 'y') for dataSeries in data)
+        dsMaxIntensities = (_.max(values) for values in dsIntensities)
+        maxIntensity = _.max(dsMaxIntensities)
+        yValues = new Array(scan.volumes.images.length).fill(0)
+        yValues[bolusArvNdx] = maxIntensity
+        values = ({x: i + 1, y: y} for y, i in yValues)
+        bolusDataSeries =
+          key: 'Bolus Arrival'
+          type: 'bar'
+          yAxis: 1
+          color: 'SandyBrown'
+          values: values
+          showMaxMin: false
+        data.push(bolusDataSeries)

       # Return the chart configuration.
       options:
         chart:
-          type: 'lineChart'
+          type: 'multiChart'
           forceY: [0]
           yAxis:
             tickFormat: formatIntensity
@@ -185,4 +128,7 @@
           tooltip:
             headerFormatter: (volumeNumber) ->
               "Volume #{ volumeNumber }"
+            # Note: due to a svd3 but, valueFormatter doesn't work on a
+            # multichart, although headerFormatter does.
+            #valueFormatter: d3.format('.2f')
       data: data


tokenize search

tokenize search

--- app/scripts/services/highlight.coffee
+++ app/scripts/services/highlight.coffee
@@ -26,4 +26,5 @@
         otherHighlight && @_id == otherHighlight._id

       matches: (query) ->
-        _.includes @_searchText, query
+        _.all query.split(' '), (token) =>
+          _.includes @_searchText, token


progress, but still not right according to the spec

--- src/openpgp/ocfb.iced
+++ src/openpgp/ocfb.iced
@@ -95,8 +95,8 @@
     wa = WordArray.from_buffer buf[0...@block_size]
     wa.xor @FRE, {n_words : (Math.min wa.words.length, @FRE.words.length) }
     buf = wa.to_buffer()
-    @FR = buf
     @out_bufs.push buf
+    @FR = new Buffer buf

   #-------------

@@ -125,12 +125,13 @@
     #     data that were prefixed to the plaintext.  This produces C[BS+1]
     #     and C[BS+2], the next two octets of ciphertext.
     b = @FRE.to_buffer()
-    @out_bufs.push new Buffer((b.readUInt8(i) ^ prefixrandom.readUInt8(@block_size+i)) for i in [0...2])
+    canary = new Buffer((b.readUInt8(i) ^ prefixrandom.readUInt8(@block_size+i)) for i in [0...2])
+    @out_bufs.push canary

     # 7.  (The resync step) FR is loaded with C3-C10.
+    offset = if @resync then 2 else 0
     ct = @compact()
-    offset = if @resync then 2 else 0
-    @FR = ct[offset...(offset+@block_size)]
+    ct.copy(@FR,0,offset,offset+@block_size)

     # 8.  FR is encrypted to produce FRE.
     @_enc()
@@ -141,13 +142,19 @@
     sb = new SlicerBuffer plaintext

     if @resync
-      @emit_sb sb
+      @_emit_sb sb
     else
       # 9. FRE is xored with the first 8 octets of the given plaintext, now
       #    That we have finished encrypting the 10 octets of prefixed data.
       #    This produces C11-C18, the next 8 octets of ciphertext.
       buf = Buffer.concat [ new Buffer([0,0]), sb.read_buffer(@block_size-2) ]
-      @_emit_buf buf
+      wa = WordArray.from_buffer buf
+      wa.xor @FRE, {}
+      buf = wa.to_buffer()[2...]
+      console.log @out_bufs
+      @out_bufs.push buf
+      buf.copy(@FR, 2, 0)
+      console.log @out_bufs

     while sb.rem()
       @_enc()
@@ -222,8 +229,8 @@
     @blockSize = 16

   encryptBlock : (words) ->
-    for w, i in words
-      words[i] = w ^ 0x11223344
+    #for w, i in words
+    #  words[i] = w ^ 0x11223344
     #t = words[0]
     #words[0] = words[1]
     #words[1] = words[2]
@@ -240,7 +247,7 @@
   ct = encrypt { block_cipher_class, key, prefixrandom, plaintext }
   console.log ct
   pt = decrypt {block_cipher_class, key, prefixrandom, ciphertext : ct }
-  console.log pt.toString('utf8')
+  console.log pt # .toString('utf8')

 test()


Focus input. Close window. (Trying to fix Windows problems.)

--- app/scripts/popup.coffee
+++ app/scripts/popup.coffee
@@ -57,21 +57,23 @@
         , 1

         # `@hide()` suddenly caused the popup to stay open.
-        return
+        return window.close()

     # Init `typeahead`.
-    ($ '#typeahead').typeahead
-        source: source
-        matcher: matcher
-        sorter: sorter
-        highlighter: highlighter
-        items: 10
+    ($ '#typeahead')
+        .typeahead
+            source: source
+            matcher: matcher
+            sorter: sorter
+            highlighter: highlighter
+            items: 10
+        .focus()

     # Do not `submit` form,
     # but reset input to empty string.
-    ($ 'form').on 'submit', ->
+    ($ 'form').on 'submit', (event) ->
         ($ '#typeahead').val ''
-        false
+        event.stopPropagation()

 # Go go go
 window.tabahead window.jQuery, window.fuzzy, window.chrome, window.setTimeout unless window.__karma__?


Ensure an array can be passed to a property setter defined with chain().

--- src/properties.coffee
+++ src/properties.coffee
@@ -53,7 +53,7 @@
           throw "Control class #{@className()} tried to chain to an undefined getter/setter function #{functionNames[i]}."
         params = functionParams[i]
         if i == length - 1 and value isnt undefined
-          params = params.concat value  # Invoke last function as setter
+          params = params.concat [ value ]  # Invoke last function as setter
         result = fn.apply result, params
       if value is undefined
         result  # Chain was invoked as getter.


TeamworkChatPane: remove promise implementation

--- client/Teamwork/Views/teamworkchatpane.coffee
+++ client/Teamwork/Views/teamworkchatpane.coffee
@@ -118,26 +118,20 @@

     KD.remote.api.JAccount.someWithRelationship query, {}, (err, accounts) =>

-      promises = usernames.map (username) =>
-
-        new Promise (resolve, reject) =>
-
-          account = null
-          user    = account for account in accounts when account.profile.nickname is username
-
-          if user
-            @workspace.createUserList() # TODO: fatihacet - find a better way for invite
-
-            @workspace.userList.once "UserInvited", =>
-              @botReply getMessage "invited", username
-
-            @workspace.userList.sendInviteTo account
-          else
-            @botReply getMessage "inviteFailed", username
-
-          resolve()
-
-      Promise.all(promises)
+      usernames.forEach (username) =>
+
+        account = null
+        user    = account for account in accounts when account.profile.nickname is username
+
+        if user
+          @workspace.createUserList() # TODO: fatihacet - find a better way for invite
+
+          @workspace.userList.once "UserInvited", =>
+            @botReply getMessage "invited", username
+
+          @workspace.userList.sendInviteTo account
+        else
+          @botReply getMessage "inviteFailed", username

   replyForWatch: (usernames) ->
     username = usernames.first


CPUs utilization for VMs.

CPUs utilization for VMs.

--- src/spec.coffee
+++ src/spec.coffee
@@ -267,7 +267,7 @@
             }
             VM_guest_metrics: {
               update: (metrics, UUID) ->
-                return if UUID isnt refsToUUIDs[@generator.metrics]
+                return if UUID isnt refsToUUIDs[@generator.guest_metrics]

                 @field.size = +metrics.memory.total
                 @field.usage = +metrics.memory.used
@@ -276,7 +276,20 @@

           power_state: get('power_state')

-          CPUs: [] # TODO
+          # TODO: initialize this value with `VCPUs_at_startup`.
+          # TODO: Should we use a map like the XAPI?
+          # FIXME: use the RRDs to get this information.
+          CPUs: @dynamic [], {
+            VM_metrics: {
+              update: (metrics, UUID) ->
+                return if UUID isnt refsToUUIDs[@generator.metrics]
+
+                @field = for _, utilisation of metrics.VCPUs_utilisation
+                  {
+                    usage: utilisation
+                  }
+            }
+          }

           $CPU_usage: ->
             n = @value.CPUs.length


really bad boo boo

really bad boo boo

--- caboose-model/lib/query.coffee
+++ caboose-model/lib/query.coffee
@@ -3,7 +3,7 @@
 module.exports = class Query
   constructor: (@model, @query) ->
     @query ?= {}
-    @query._id = new Query.ObjectID(@query._id) if @query._id? and typeof @query._id is 'string' and /[0-9a-z]{24}/i.test(@query._id)
+    @query._id = new @model.ObjectID(@query._id) if @query._id? and typeof @query._id is 'string' and /[0-9a-z]{24}/i.test(@query._id)
     @options = {}

   __error__: (promise, err) ->


Refactor setting up watch map as a function

--- client/ide/lib/collaborationcontroller.coffee
+++ client/ide/lib/collaborationcontroller.coffee
@@ -201,6 +201,7 @@

     @setCollaborativeReferences()
     @addParticipant whoami()
+    @setWatchMap()
     @registerCollaborationSessionId()

     if @amIHost
@@ -210,11 +211,15 @@
     @rtm.isReady = yes
     @emit 'RTMIsReady'

-    unless @myWatchMap.values().length
-      @listChatParticipants (accounts) =>
-        accounts.forEach (account) =>
-          {nickname} = account.profile
-          @myWatchMap.set nickname, nickname
+
+  setWatchMap: ->
+
+    return  if @myWatchMap.values().length
+
+    @listChatParticipants (accounts) =>
+      accounts.forEach (account) =>
+        {nickname} = account.profile
+        @myWatchMap.set nickname, nickname

   activateRealtimeManagerForHost: ->


Update preview pane url and emit location changed event.

--- client/ide/workspace/panes/previewpane.coffee
+++ client/ide/workspace/panes/previewpane.coffee
@@ -15,3 +15,7 @@
     delete viewerOptions.params.path  unless viewerOptions.params.path

     @addSubView @previewer = new PreviewerView viewerOptions
+
+    @previewer.on 'ViewerLocationChanged', (newLocation) =>
+      @setOption 'url', newLocation
+      @emit 'LocationChanged', newLocation


Add reportAdClick

Add reportAdClick

--- src/teak.coffee
+++ src/teak.coffee
@@ -269,6 +269,9 @@
         @uiCallbackHandler(callback, carrotResponse)

+  reportAdClick: (adId) ->
+    @ajaxPost("#{@scheme}://parsnip.gocarrot.com/ad_click", {creative_id: adId, user_id: @udid});
+
   acceptRequest: (requestId, callback) ->
     @ajaxPost("#{@scheme}://posts.gocarrot.com/requests/#{requestId}/clicks", {clicking_user_id: @udid, sig: "s"},
       (jqXHR) =>


Allow any origin for parent frame

Allow any origin for parent frame

--- src/script/app.coffee
+++ src/script/app.coffee
@@ -40,7 +40,7 @@
 ###
 Converts events sent by the component to messages to the parent window.
 ###
-origin = 'http://' + window.location.host
+origin = '*'
 $(window).on "sendAttachment.vtex", (e, attachmentName, attachmentData) ->
   parent.postMessage({"event": "sendAttachment.vtex", "arguments": [attachmentName, attachmentData]}, origin)


Drop some TODO note not to forget about it later

--- src/vent.coffee
+++ src/vent.coffee
@@ -244,7 +244,7 @@
                 ch.consume(queue_name, listener)
             ])
             w.all(steps)
-
+            # TODO: add channle bindings to restart whole subscription if any of it fails

     _parse_event: (event) ->
         assert _.isString(event), "event string required"
@@ -319,8 +319,6 @@
             channel.ack(msg)
         )

-
-

 module.exports = (setup, options) ->
     setup = {url: setup} if _.isString(setup)


separate the interactive view and the opinion view toggler

--- @client/opinion_views.coffee
+++ @client/opinion_views.coffee
@@ -591,9 +591,12 @@

-clear_all = ->
+clear_all = (local_state) ->
   opinion_views = fetch 'opinion_views'
   opinion_views_ui = fetch 'opinion_views_ui'
+
+  opinion_views_ui.last_interacted_with = local_state.key or local_state
+  save opinion_views_ui

   to_remove = []
   for k,v of opinion_views.active_views
@@ -613,8 +616,8 @@
   date_state.start = date_state.end = date_state.active = null
   save date_state

-reset_to_all = ->
-  clear_all()
+reset_to_all = (local_state) ->
+  clear_all(local_state)
   is_admin = fetch('/current_user').is_admin
   show_others = !customization('hide_opinions') || is_admin
   opinion_views_ui = fetch 'opinion_views_ui'
@@ -643,19 +646,23 @@
   view_is_set

+
+
 OpinionViews = ReactiveComponent
   displayName: 'OpinionViews'

   render : ->
-    @local.minimized ?= true

     return SPAN null if !fetch('/subdomain').name
-
+    local_state = fetch @props.ui_key or @local.key
+
+    local_state.minimized ?= true

     has_other_filters = get_participant_attributes().length > 0
     opinion_views = fetch 'opinion_views'
     opinion_views_ui = fetch 'opinion_views_ui'
+

     is_admin = fetch('/current_user').is_admin
@@ -666,7 +673,7 @@
       {
         key: 'all'
         label: translator 'opinion_views.view_buttons_all', 'All opinions'
-        callback: clear_all
+        callback: -> clear_all(local_state)
         disabled: !show_others
       }
       {
@@ -674,7 +681,7 @@
         label: translator 'opinion_views.view_buttons_you', 'Just you'
         disabled: @props.disable_switching
         callback: ->
-          clear_all()
+          clear_all(local_state)
           toggle_opinion_filter just_you_filter
       }
       {
@@ -684,19 +691,19 @@
         callback: (item, previous_state) =>
           if previous_state == 'custom'
             if user_has_set_a_view()
-              @local.minimized = !@local.minimized
-              save @local
+              local_state.minimized = !local_state.minimized
+              save local_state
             else
-              reset_to_all()
+              reset_to_all(local_state)

           else
-            @local.minimized = false
-            clear_all()
+            local_state.minimized = false
+            clear_all(local_state)
       }
     ]

     reset_to_default_view = (force_all) ->
-      clear_all()
+      clear_all(local_state)

       dfault = customization('opinion_views_default')

@@ -764,10 +771,10 @@
           ToggleButtons view_buttons, opinion_views_ui,
             minWidth: 290

-          if opinion_views_ui.active == 'custom'
+          if opinion_views_ui.active == 'custom' && opinion_views_ui.last_interacted_with == local_state.key
             triangle_left = (@local.view_state_left or 60) + 35

-            if @local.minimized
+            if local_state.minimized
               if user_has_set_a_view()
                 DIV
                   className: 'custom_view_triangle'
@@ -778,14 +785,14 @@
                     height: 0
                     borderLeft: '12px solid transparent'
                     borderRight: '12px solid transparent'
-                    borderTop: '7px solid #2478CC'
+                    borderTop: "7px solid #{focus_blue}"
             else
               DIV
                 className: 'custom_view_triangle'
                 style:
                   left: triangle_left
-                  bottom: if browser.is_mobile then -27 else -25
-                dangerouslySetInnerHTML: __html: """<svg width="25px" height="13px" viewBox="0 0 25 13"><g id="Page-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Artboard" transform="translate(-1086.000000, -586.000000)" fill="#FFFFFF" stroke="#979797"><polyline id="Path" points="1087 599 1098.5 586 1110 599"></polyline></g></g></svg>"""
+                  bottom: if browser.is_mobile then -29 else -27
+                dangerouslySetInnerHTML: __html: """<svg width="25px" height="13px" viewBox="0 0 25 13"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="Artboard" transform="translate(-1086.000000, -586.000000)" fill="#FFFFFF" stroke="rgb(182,182,182)"><polyline id="Path" points="1087 599 1098.5 586 1110 599"></polyline></g></g></svg>"""

           SPAN
@@ -794,55 +801,7 @@
               left: '100%'
             @MinimizeExpandButton()

-      if opinion_views_ui.active == 'custom'
-        needs_expansion = @props.additional_width && @props.style?.width
-
-        width = 0
-        if needs_expansion
-          if has_other_filters
-            width = if ONE_COL() then Math.min(720, @props.style.width) else @props.style.width + @props.additional_width
-          else
-            width = Math.min(720, @props.style.width + @props.additional_width)
-
-        DIV
-          style:
-            width: if width then width
-            position: 'relative'
-            right: if needs_expansion then width - @props.style.width
-
-
-          if @local.minimized
-
-            DIV
-              style:
-                marginTop: 12
-
-              NonInteractiveOpinionViews
-                more_views_positioning: @props.more_views_positioning
-
-          else
-            DIV
-              style:
-                marginTop: 18
-
-              DIV
-                style:
-                  border: '1px solid #B6B6B6'
-                  borderRadius: 8
-                  width: 'fit-content'
-                  maxWidth: if width then width
-
-                  margin: if @props.more_views_positioning == 'centered' then 'auto'
-                  float: if @props.more_views_positioning == 'right' then 'right'
-
-
-                DIV
-                  style:
-                    padding: '0px 24px'
-
-                  InteractiveOpinionViews()
-
-          DIV style: clear: 'both'
+

   MinimizeExpandButton: ->
@@ -868,10 +827,13 @@
           translator 'opinion_views.minimize_minimize', 'minimize'

   shadow_view_state_offset: ->
-    left = document.querySelector('[data-view-state="custom"]')?.offsetLeft
-
+    ww = WINDOW_WIDTH()
+    return if @local.view_state_left? && ww == @left_set_for_window_width
+
+    left = @refs.custom?.offsetLeft
     if left != @local.view_state_left
       @local.view_state_left = left
+      @left_set_for_window_width = ww
       save @local

   componentDidUpdate: -> @shadow_view_state_offset()
@@ -986,6 +948,70 @@
     options: attribute.options

   toggle_group view, true
+
+
+styles += """
+.opinion_view_interaction_wrapper {
+  max-height: 0;
+  overflow-y: hidden;
+  transition: max-height 0.5s;
+}
+.opinion_view_interaction_wrapper.showing_custom {
+  max-height: 1200px;
+}
+
+"""
+
+window.OpinionViewInteractionWrapper = ReactiveComponent
+  displayName: 'OpinionViewInteractionWrapper'
+
+  render: ->
+    opinion_views_ui = fetch 'opinion_views_ui'
+    local_state = fetch @props.ui_key or @local
+
+
+    DIV
+      className: "opinion_view_interaction_wrapper #{if opinion_views_ui.active == 'custom' && opinion_views_ui.last_interacted_with == local_state.key then 'showing_custom' else ''}"
+
+      if opinion_views_ui.active == 'custom'
+        width = @props.width
+
+        DIV
+          style:
+            position: 'relative'
+
+          if local_state.minimized || opinion_views_ui.last_interacted_with != local_state.key
+
+            DIV
+              style:
+                marginTop: 12
+                width: if width then width
+
+              NonInteractiveOpinionViews
+                more_views_positioning: @props.more_views_positioning
+
+          else
+            DIV
+              style:
+                display: 'flex'
+                justifyContent: if @props.more_views_positioning == 'centered' then 'center' else 'flex-end'
+
+              DIV
+                style:
+                  border: '1px solid #B6B6B6'
+                  borderRadius: 8
+                  width: 'fit-content'
+                  maxWidth: if width then width
+
+
+                DIV
+                  style:
+                    padding: '0px 24px'
+
+                  InteractiveOpinionViews()
+
+
+

 InteractiveOpinionViews = ReactiveComponent
@@ -1428,7 +1454,7 @@
                 onClick: ->
                   mini.toggle()
                   if !user_has_set_a_view()
-                    reset_to_all()
+                    reset_to_all(local_state)

                 'x'

@@ -1702,9 +1728,9 @@
   }

   .minimized_view {
-    color: #1059a2;
+    color: #{focus_blue};
     /* border: 1px solid #2478cc; */
-    background-color: #e4edf7;
+    background-color: #e9edfb;
     width: fit-content;
     position: relative;
     display: inline-block;
@@ -1840,6 +1866,7 @@
       do (item) =>
         key = item.key or item.label
         LI
+          ref: key
           key: key
           className: if view_state.active == key then 'active'
           'data-view-state': key


turn on debugging again

turn on debugging again

--- assets/javascripts/application.coffee
+++ assets/javascripts/application.coffee
@@ -8,7 +8,7 @@
 console.log("Yeah! The dashboard has started!")

 Dashing.on 'ready', ->
-  Dashing.debugMode = false
+  Dashing.debugMode = true
   Dashing.widget_margins ||= [5, 5]
   Dashing.widget_base_dimensions ||= [300, 360]
   Dashing.numColumns ||= 5


adjust atom font size

adjust atom font size

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -11,7 +11,7 @@
   editor:
     backUpBeforeSaving: true
     fontFamily: "Inconsolata"
-    fontSize: 13
+    fontSize: 15
     invisibles: {}
     lineHeight: 1.4
     showInvisibles: true


Updated Atom package configurations

Updated Atom package configurations

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -24,7 +24,7 @@
   "linter-eslint":
     fixOnSave: true
   "linter-ui-default":
-    panelHeight: 69
+    panelHeight: 109
   metrics:
     userId: "352f3ca5a6c75d15bba3a83230f85e64aca3a111"
   "one-dark-ui": {}
@@ -34,6 +34,7 @@
       "change*"
     ]
     keymapPToPutWithAutoIndent: true
+    keymapYToYankToLastCharacterOfLine: true
   welcome:
     showOnStartup: false
 ".bibtex.text":


atom config change

atom config change

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,34 +1,35 @@
-'editor':
-  'showIndentGuide': true
-  'fontFamily': 'Menlo'
-  'fontSize': 15
-  'invisibles': {}
-'core':
-  'ignoredNames': [
-    '.bundle'
-    '.git'
-    'log'
-    'repositories'
-    'tmp'
-    'vendor'
-  ]
-  'themes': [
-    'unity-ui'
-    'glacier-syntax'
-  ]
-  'hideGitIgnoredFiles': true
-  'disabledPackages': [
-    'background-tips'
-    'exception-reporting'
-    'metrics'
-  ]
-'spell-check':
-  'grammars': [
-    'text.plain'
-    'source.gfm'
-    'text.git-commit'
-  ]
-'welcome':
-  'showOnStartup': false
-'release-notes':
-  'viewedVersion': '0.89.0'
+'global':
+  'editor':
+    'showIndentGuide': true
+    'fontFamily': 'Menlo'
+    'fontSize': 15
+    'invisibles': {}
+  'core':
+    'ignoredNames': [
+      '.bundle'
+      '.git'
+      'log'
+      'repositories'
+      'tmp'
+      'vendor'
+    ]
+    'themes': [
+      'unity-ui'
+      'glacier-syntax'
+    ]
+    'hideGitIgnoredFiles': true
+    'disabledPackages': [
+      'background-tips'
+      'exception-reporting'
+      'metrics'
+    ]
+  'spell-check':
+    'grammars': [
+      'text.plain'
+      'source.gfm'
+      'text.git-commit'
+    ]
+  'welcome':
+    'showOnStartup': false
+  'release-notes':
+    'viewedVersion': '0.89.0'


new config format

new config format

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,39 +1,39 @@
-'editor':
-  'fontFamily': 'Menlo'
-  'fontSize': 15
-  'showIndentGuide': true
-  'invisibles': {}
-'core':
-  'disabledPackages': [
-    'go-to-line'
-    'autosave'
-    'autocomplete-plus'
-    'highlight-selected'
-    'exception-reporting'
-    'metrics'
-  ]
-  'themes': [
-    'atom-dark-ui'
-    'solarized-dark-syntax'
-  ]
-  'useReactEditor': true
-'spell-check':
-  'grammars': [
-    'source.gfm'
-    'text.git-commit'
-    'text.plain'
-  ]
-'markdown-preview':
-  'grammars': [
-    'source.gfm'
-    'source.litcoffee'
-    'text.plain'
-    'text.plain.null-grammar'
-  ]
-'tabs-to-spaces':
-  'onSave': 'untabify'
-'welcome':
-  'showOnStartup': false
-'release-notes':
-  'viewedVersion': '0.95.0'
-'emmet': {}
+'global':
+  'editor':
+    'fontFamily': 'Menlo'
+    'fontSize': 15
+    'showIndentGuide': true
+    'invisibles': {}
+  'core':
+    'disabledPackages': [
+      'go-to-line'
+      'autosave'
+      'autocomplete-plus'
+      'highlight-selected'
+      'exception-reporting'
+      'metrics'
+    ]
+    'themes': [
+      'atom-dark-ui'
+      'solarized-dark-syntax'
+    ]
+    'useReactEditor': true
+  'spell-check':
+    'grammars': [
+      'source.gfm'
+      'text.git-commit'
+      'text.plain'
+    ]
+  'markdown-preview':
+    'grammars': [
+      'source.gfm'
+      'source.litcoffee'
+      'text.plain'
+      'text.plain.null-grammar'
+    ]
+  'tabs-to-spaces':
+    'onSave': 'untabify'
+  'welcome':
+    'showOnStartup': false
+  'release-notes':
+    'viewedVersion': '0.95.0'


Terminal: while restoring a session show the opened terminal pane if it is already existed

--- client/Terminal/AppView.coffee
+++ client/Terminal/AppView.coffee
@@ -250,8 +250,13 @@

   createNewTab: (options = {}) ->

-    {shouldShow} = options
+    {shouldShow, session} = options
     {hostnameAlias: vmName, region} = options.vm
+
+    # before creating new tab check for its existence first and then show that pane if it is already opened
+    pane = @findPane session
+
+    return @tabView.showPane pane  if pane

     defaultOptions =
       testPath    : "webterm-tab"
@@ -321,6 +326,16 @@
       storage.setValue 'savedSessions', sessions
       storage.setValue 'activeIndex', activeIndex

+  findPane: (session) ->
+    foundPane = null
+    @tabView.panes.forEach (pane) =>
+      { terminalView } = pane.getOptions()
+      return unless terminalView
+      sessionId = terminalView.sessionId ? terminalView.getOption 'session'
+      return foundPane = pane  if session is sessionId
+
+    return foundPane
+
   removeSession: ({vmName, sessionId}) ->
     @updateSessions()
     {vmController:{terminalKites}} = KD.singletons


Reference required module with a closer relative path, that doesn't assume pre-compilation.

--- src/all-headers.coffee
+++ src/all-headers.coffee
@@ -2,7 +2,7 @@

 # For now, only the "Transaction Descriptor" header (s2.2.5.3.2) is supported.

-WritableTrackingBuffer = require('../lib/tracking-buffer/tracking-buffer').WritableTrackingBuffer
+WritableTrackingBuffer = require('./tracking-buffer/tracking-buffer').WritableTrackingBuffer

 TYPE =
   QUERY_NOTIFICATIONS: 1


converted metrics to coffee-script

converted metrics to coffee-script

--- src/api/metrics.coffee
+++ src/api/metrics.coffee
@@ -11,70 +11,66 @@
 # getGlobalLoadTimeMetrics() function for generating aggregated global Metrics #
 ########################################################################

-exports.getGlobalLoadTimeMetrics = `function *() {
-
-  var filtersObject = this.request.query;
-  var userRequesting = this.authenticated;
-
-  results = yield metrics.fetchGlobalLoadTimeMetrics(userRequesting, filtersObject);
+exports.getGlobalLoadTimeMetrics =  ->
+  filtersObject = this.request.query
+  userRequesting = this.authenticated
+  results = yield metrics.fetchGlobalLoadTimeMetrics(userRequesting, filtersObject)
   this.body = []
-
-  for (var i = 0; i < results.length; i++) {
-        this.body.push({
-          load: results[i].load,
-          avgResp: results[i].avgResp,
-          timestamp : moment.utc([results[i]._id.year, results[i]._id.month - 1, results[i]._id.day,results[i]._id.hour]).format()//, 'YYYY-MM-DD H+00:00'
-      });
-    }
-
-}`
+  i = 0
+  while i < results.length
+    this.body.push
+      load: results[i].load
+      avgResp: results[i].avgResp
+      timestamp: moment.utc([
+        results[i]._id.year
+        results[i]._id.month - 1
+        results[i]._id.day
+        results[i]._id.hour
+      ]).format()
+    i++
+  return

 ################################################################################################
 # getGlobalStatusMetrics() function for generating aggregated Transaction Status Metrics #
 ################################################################################################

-exports.getGlobalStatusMetrics = `function *() {
-
-  var filtersObject = this.request.query;
-  var userRequesting = this.authenticated;
-
-  results = yield metrics.fetchGlobalStatusMetrics(userRequesting, filtersObject);
+exports.getGlobalStatusMetrics = ->
+  filtersObject = this.request.query
+  userRequesting = this.authenticated
+  results = yield metrics.fetchGlobalStatusMetrics(userRequesting, filtersObject)
   this.body = results
-
-}`
+  return

 ##########################################################################
 # getChannelMetrics() function for generating aggregated channel Metrics #
 ##########################################################################

-exports.getChannelMetrics = `function *(time, channelId) {
-
-  var filtersObject = this.request.query;
-  var userRequesting = this.authenticated;
-
-  results = yield metrics.fetchChannelMetrics(time, channelId,userRequesting,filtersObject);
-
-  if (time == 'status') {
-    this.body = results;
-  } else {
-    this.body = [];
-   //format the message to show what the console expects
-    for (var i = 0; i < results.length; i++) {
-      if (!results[i]._id.minute) {
+exports.getChannelMetrics = (time, channelId) ->
+  filtersObject = this.request.query
+  userRequesting = this.authenticated
+  results = yield metrics.fetchChannelMetrics(time, channelId, userRequesting, filtersObject)
+  if time == 'status'
+    this.body = results
+  else
+    this.body = []
+    #format the message to show what the console expects
+    i = 0
+    while i < results.length
+      if !results[i]._id.minute
         results[i]._id.minute = '00'
-      }
-      if (!results[i]._id.hour) {
+      if !results[i]._id.hour
         results[i]._id.hour = '00'
-        }
-    if (!results[i]._id.day) {
-      results[i]._id.day = '1'
-    }
-
-    this.body.push({
-      load: results[i].load,
-      avgResp: results[i].avgResp,
-      timestamp: moment.utc([results[i]._id.year, results[i]._id.month - 1, results[i]._id.day,results[i]._id.hour, results[i]._id.minute]).format() //, 'YYYY-MM-DD H:mm+00:00'
-    });
-  }
-  }
-}`;
+      if !results[i]._id.day
+        results[i]._id.day = '1'
+      this.body.push
+        load: results[i].load
+        avgResp: results[i].avgResp
+        timestamp: moment.utc([
+          results[i]._id.year
+          results[i]._id.month - 1
+          results[i]._id.day
+          results[i]._id.hour
+          results[i]._id.minute
+        ]).format()
+      i++
+  return


Allowed either callback or return values for authorizePublish().

--- src/ProjectUtils.coffee
+++ src/ProjectUtils.coffee
@@ -153,9 +153,12 @@
     unless @userId then return []
     try
       ProjectUtils.assertAuthorization(projectId, @userId)
-      callback?.call(@)
-      return true
+      if callback?
+        return callback.call(@)
+      else
+        return true
     catch e
       Logger.error('Error in publications', e, e.stack)
       @error(e)
-      return false
+      return false unless callback?
+


+ findById(id, filter, stCode) method

+ findById(id, filter, stCode) method

--- src/abstract-api.coffee
+++ src/abstract-api.coffee
@@ -63,6 +63,13 @@
       debug 'find filter=%s', aFilter
       result.query filter: aFilter
     result.expect stCode
+  findById: (aId, aFilter, stCode = 200)->
+    result = @get encodeURIComponent aId
+    if isObject aFilter
+      aFilter = JSON.stringify aFilter
+      debug 'find filter=%s', aFilter
+      result.query filter: aFilter
+    result.expect stCode
   findOne: (aFilter, stCode = 200)->
     result = @get 'findOne'
     if isObject aFilter


added utf-8 support

added utf-8 support

--- src/addons/Fs/Fs.coffee
+++ src/addons/Fs/Fs.coffee
@@ -4,7 +4,7 @@
 Addon =
     fs:
         readFile: (path, cb) ->
-            fs.readFile(path, (err, data)  ->
+            fs.readFile(path, 'utf8', (err, data)  ->
                 cb(err, data)
             )


type: foud -> found

type: foud -> found

--- src/api/channels.coffee
+++ src/api/channels.coffee
@@ -92,7 +92,7 @@
 				this.body = "Access denied to:'" + channel_name + "'.";
 				this.status = 'forbidden';
 			} else {
-				// Channel not foud! So inform the user
+				// Channel not found! So inform the user
 				this.body = "We could not find a channel with this name:'" + channel_name + "'.";
 				this.status = 'not found';
 			}