Remove old formats file

Remove old formats file

--- coffee/install.json.coffee
+++ coffee/install.json.coffee
@@ -1,392 +0,0 @@
-window.JSON_CONTEXT =
-  'default': -> '''
-
install.json

-
-
install.json is a description of how to include and configure (JavaScript, CSS, and HTML)-based projects into websites.

-
-
install.json has two top-level properties, resources and options. Essentially resources are files included into the page and options are configurations for them.

-
-
Here’s a simple example of an install.json file which includes one JS file and has one color option:

-
-
{
-      "resources": {
-        "head": [
-          {
-            "type": "script",
-            "src": "./file.js"
-          }
-        ]
-      },
-      "options": {
-        "properties": {
-          "color": {
-            "title": "Color",
-            "type": "string",
-            "format": "color",
-            "default": "#e90f92"
-          }
-        }
-      }
-    }

-  '''
-
-  'resources': -> '''
-
Resources

-
-
The JavaScript and CSS files you want to be included in the page.

-
Each resource can be placed in either the <head> or <body> of the page by placing it in in the respective key’s array.

-
-
"resources": {
-      "head": [
-        {
-          "type": "script",
-          "src": "./file.js"
-        }
-      ],
-      "body": [
-        {
-          "type": "style",
-          "src": "./file.css"
-        }
-      ]
-    }

-  '''
-
-  'resources.head': -> '''
-
Head Resources

-
-
The JavaScript and CSS files you want to be included in the <head> of the page.

-
-
"head": [
-      {
-        "type": "script",
-        "src": "./file.js"
-      }
-    ]

-  '''
-
-  'resources.body': -> '''
-
Body Resources

-
-
The JavaScript and CSS files you want to be included in the <body> of the page.

-
-
"body": [
-      {
-        "type": "script",
-        "src": "./file.js"
-      }
-    ]

-  '''
-
-  'resources.head.0': -> INSTALL_JSON_CONTEXT['resources.head']()
-  'resources.body.0': -> INSTALL_JSON_CONTEXT['resources.head']()
-
-  'resources.head.0.type': -> '''
-
Resource Type

-
-
For JavaScript files, use:

-
"type": "script"

-
-
For CSS files, use:

-
"type": "style"

-  '''
-
-  'resources.body.0.type': -> INSTALL_JSON_CONTEXT['resources.head.0.type']()
-
-  'resources.head.0.src': (item) -> '''
-
Resource Contents

-
-
To include a resource as a file, use:

-
"src": "./path/to/file.js"

-
-
To include a resource as a string, use:

-
"contents": "/* JS or CSS string */"

-
-

-
-
Advanced: You may also interpolate options values into src paths, for example:

-
-
"src": "./css/{{ options.theme }}.css"

-  '''
-
-  'resources.body.0.src': -> INSTALL_JSON_CONTEXT['resources.head.0.src']()
-  'resources.head.0.contents': -> INSTALL_JSON_CONTEXT['resources.head.0.src']()
-  'resources.body.0.contents': -> INSTALL_JSON_CONTEXT['resources.head.0.src']()
-
-  'options': -> '''
-
Options

-
-
Allows for end-user customization of the installation of a set of resources.

-
Options are passed back to the app JavaScript via an INSTALL_OPTIONS variable, and they can also be interpolated into resource src paths. Options follow JSON Schema for their structure.

-
-

-
-
There are two types of options, primitive options and abstract options.

-
Primitive options are objects with type boolean, number, integer, or string and a variety of string formats including color, selector, and others.

-
-
Abstract options are arrays and objects which can nest other primitive and abstract options recursively.

-
-
Learn more about install.json options

-  '''
-
-  'options.properties': -> '''
-
Option Property

-
-
Define an individual property for end-user customization.

-
The following is an example option which defines a simple text field to capture headerText:

-
-
"headerText": {
-      "order": 1,
-      "title": "Header Text",
-      "description": "Appears above Plugin.",
-      "type": "string",
-      "default": "Welcome!"
-    }

-
-
If the user didn’t change the default text "Welcome!", the developer would get passed back the following:

-
-
{
-      "headerText": "Welcome!
-    }

-  '''
-
-  'options.properties.*': -> INSTALL_JSON_CONTEXT['options.properties']()
-
-  'options.properties.*.order': -> '''
-
Option Order

-
-
Optional

-
-
Whole number used to determine the order options will visually be presented to the user.

-
-
"order": 1

-  '''
-
-  'options.properties.*.properties.*.order': -> INSTALL_JSON_CONTEXT['options.properties.*.order']()
-  'options.properties.*.properties.*.properties.*.order': -> INSTALL_JSON_CONTEXT['options.properties.*.order']()
-  'options.properties.*.properties.*.properties.*.properties.*.order': -> INSTALL_JSON_CONTEXT['options.properties.*.order']()
-
-  'options.properties.*.title': -> '''
-
Option Title

-
-
Optional

-
-
Property title. If blank, the property key will be used.

-
-
"title": "Header Text"

-  '''
-
-  'options.properties.*.properties.*.title': -> INSTALL_JSON_CONTEXT['options.properties.*.title']()
-  'options.properties.*.properties.*.properties.*.title': -> INSTALL_JSON_CONTEXT['options.properties.*.title']()
-  'options.properties.*.properties.*.properties.*.properties.*.title': -> INSTALL_JSON_CONTEXT['options.properties.*.title']()
-
-  'options.properties.*.description': -> '''
-
Option Description

-
-
Optional

-
-
Short description of the property.

-
-
"description": "Appears above Plugin."

-  '''
-
-  'options.properties.*.properties.*.description': -> INSTALL_JSON_CONTEXT['options.properties.*.description']()
-  'options.properties.*.properties.*.properties.*.description': -> INSTALL_JSON_CONTEXT['options.properties.*.description']()
-  'options.properties.*.properties.*.properties.*.properties.*.description': -> INSTALL_JSON_CONTEXT['options.properties.*.description']()
-
-  'options.properties.*.default': -> '''
-
Option Description

-
-
Optional

-
-
The default value to be used for a primitive option.

-
-
"default": "Default value"

-  '''
-
-  'options.properties.*.properties.*.default': -> INSTALL_JSON_CONTEXT['options.properties.*.default']()
-  'options.properties.*.properties.*.properties.*.default': -> INSTALL_JSON_CONTEXT['options.properties.*.default']()
-  'options.properties.*.properties.*.properties.*.properties.*.default': -> INSTALL_JSON_CONTEXT['options.properties.*.default']()
-
-  'options.properties.*.type': -> '''
-
Option Type

-
-
Primitive option types

-
-
Element to be placed on the end-user’s website

-
-
{
-      "type": "string",
-      "format": "element"
-    }

-
-
Location (CSS selector) on the end-user’s website

-
-
{
-      "type": "string",
-      "format": "selector"
-    }

-
-
Image

-
-
{
-      "type": "string",
-      "format": "image"
-    }

-
-
Color

-
-
{
-      "type": "string",
-      "format": "color"
-    }

-
-
String

-
-
{
-      "type": "string"
-    }

-
-
Number

-
-
{
-      "type": "number"
-    }

-
-
Integer

-
-
{
-      "type": "integer"
-    }

-
-
Boolean

-
-
{
-      "type": "boolean"
-    }

-
-
String with fixed set of options

-
-
{
-      "type": "string",
-      "enum": [
-        "option-a",
-        "option-b"
-      ],
-      "enumNames": {
-        "option-a": "Option A",
-        "option-b": "Option B"
-      }
-    }

-
-

-
-
Abstract option types

-
-
Nested set of options

-
-
{
-      "type": "object",
-      "properties": { ... }
-    }

-
-
Array

-
-
{
-      "type": "array",
-      "items": {
-        "type": "[ Any primitive option type or object abstract option type ]"
-      }
-    }

-  '''
-
-  'options.properties.*.format': -> INSTALL_JSON_CONTEXT['options.properties.*.type']()
-  'options.properties.*.properties.*.type': -> INSTALL_JSON_CONTEXT['options.properties.*.type']()
-  'options.properties.*.properties.*.format': -> INSTALL_JSON_CONTEXT['options.properties.*.type']()
-  'options.properties.*.properties.*.properties.*.type': -> INSTALL_JSON_CONTEXT['options.properties.*.type']()
-  'options.properties.*.properties.*.properties.*.format': -> INSTALL_JSON_CONTEXT['options.properties.*.type']()
-  'options.properties.*.properties.*.properties.*.properties.*.type': -> INSTALL_JSON_CONTEXT['options.properties.*.type']()
-  'options.properties.*.properties.*.properties.*.properties.*.format': -> INSTALL_JSON_CONTEXT['options.properties.*.type']()
-
-  'options.properties.*.enum': -> '''
-
Option Enum

-
-
Used to define a list of string option values for a string with fixed options property type.

-
-
{
-      "type": "string",
-      "enum": [
-        "option-a",
-        "option-b"
-      ],
-      "enumNames": {
-        "option-a": "Option A",
-        "option-b": "Option B"
-      }
-    }

-  '''
-
-  'options.properties.*.enum.0': -> INSTALL_JSON_CONTEXT['options.properties.*.enum']()
-  'options.properties.*.properties.*.enum': -> INSTALL_JSON_CONTEXT['options.properties.*.enum']()
-  'options.properties.*.properties.*.enum.0': -> INSTALL_JSON_CONTEXT['options.properties.*.enum']()
-  'options.properties.*.properties.*.properties.*.enum': -> INSTALL_JSON_CONTEXT['options.properties.*.enum']()
-  'options.properties.*.properties.*.properties.*.enum.0': -> INSTALL_JSON_CONTEXT['options.properties.*.enum']()
-  'options.properties.*.properties.*.properties.*.properties.*.enum': -> INSTALL_JSON_CONTEXT['options.properties.*.enum']()
-  'options.properties.*.properties.*.properties.*.properties.*.enum.0': -> INSTALL_JSON_CONTEXT['options.properties.*.enum']()
-
-  'options.properties.*.enumNames': -> '''
-
Option Enum

-
-
Used to define a list of human for a string property type.

-  '''
-
-  'options.properties.*.enumNames.*': -> INSTALL_JSON_CONTEXT['options.properties.*.enumNames']()
-  'options.properties.*.properties.*.enumNames': -> INSTALL_JSON_CONTEXT['options.properties.*.enumNames']()
-  'options.properties.*.properties.*.enumNames.*': -> INSTALL_JSON_CONTEXT['options.properties.*.enumNames']()
-  'options.properties.*.properties.*.properties.*.enumNames': -> INSTALL_JSON_CONTEXT['options.properties.*.enumNames']()
-  'options.properties.*.properties.*.properties.*.enumNames.*': -> INSTALL_JSON_CONTEXT['options.properties.*.enumNames']()
-  'options.properties.*.properties.*.properties.*.properties.*.enumNames': -> INSTALL_JSON_CONTEXT['options.properties.*.enumNames']()
-  'options.properties.*.properties.*.properties.*.properties.*.enumNames.*': -> INSTALL_JSON_CONTEXT['options.properties.*.enumNames']()
-
-  'options.properties.*.showIf': -> '''
-
Option Enum

-
-
Optional

-
-
Conditionally show or hide the option based on the value of another option.

-
-
{
-      "theme": {
-        "title": "Theme",
-        "type": "string",
-        "enum": [
-          "default",
-          "colorful"
-          "minimal"
-        ],
-        "enumNames": {
-          "default": "Default",
-          "colorful": "Colorful",
-          "minimal":  "Minimal"
-       }
-      },
-      "color": {
-        "title": "Color",
-        "showIf": {
-          "theme": "colorful"
-        },
-        "description": "Choose a color for the Colorful theme.",
-        "type": "string",
-        "format": "color",
-        "default": "#e90f92"
-      }
-    }

-  '''
-
-  'options.properties.*.showIf.*': -> INSTALL_JSON_CONTEXT['options.properties.*.showIf']()
-  'options.properties.*.properties.*.showIf': -> INSTALL_JSON_CONTEXT['options.properties.*.showIf']()
-  'options.properties.*.properties.*.showIf.*': -> INSTALL_JSON_CONTEXT['options.properties.*.showIf']()
-  'options.properties.*.properties.*.properties.*.showIf': -> INSTALL_JSON_CONTEXT['options.properties.*.showIf']()
-  'options.properties.*.properties.*.properties.*.showIf.*': -> INSTALL_JSON_CONTEXT['options.properties.*.showIf']()
-  'options.properties.*.properties.*.properties.*.properties.*.showIf': -> INSTALL_JSON_CONTEXT['options.properties.*.showIf']()
-  'options.properties.*.properties.*.properties.*.properties.*.showIf.*': -> INSTALL_JSON_CONTEXT['options.properties.*.showIf']()


Simplify loop.

Simplify loop.

--- test/e2e/sessionDetailSpec.coffee
+++ test/e2e/sessionDetailSpec.coffee
@@ -35,10 +35,7 @@
     # @param retry the number of times to retry (default 8)
     # @return a promise that resolves when either the button
     #   is hidden or the retry limit is reached
-    waitWhileVisible = (button, retry) ->
-      # The silly Javascript idiom to check for an unset variable.
-      if typeof retry is 'undefined'
-        retry = 8
+    waitWhileVisible = (button, retry=8) ->
       if retry
         # Wait a while, then check if the button is visible.
         browser.executeAsyncScript(SLEEP).then ->


Add APIResults::paginated.

Add APIResults::paginated.

Returns the items of the APIResults in chunks according to the specified page size. Provides a default link generation function, and can accept an override.

--- compiler/ContentAPI.coffee
+++ compiler/ContentAPI.coffee
@@ -17,7 +17,7 @@
 W       = require 'when'
 url     = require 'url'
 sdk_ua_string = require './sdk_ua_string'
-
+_       = require 'lodash'
 SDKError = require './SDKError'
 colors = SDKError.colors

@@ -200,6 +200,27 @@
             sorted_list.reverse()
         return new APIResults(sorted_list)

+    paginated: (page_size=10, linkerFn=APIResults.defaultLinker) ->
+        pages = _.chunk(@_items, page_size).map (chunk, i) ->
+            page_number = i + 1
+            return {
+                items   : chunk
+                number  : page_number
+                link    : linkerFn(page_number)
+            }
+        pages.forEach (page, i) ->
+            page.next = pages[i + 1]
+            page.previous = pages[i - 1]
+        return pages
+
+
+APIResults.defaultLinker = (n) ->
+    if n
+        if n > 1
+            return "/#{ n }/"
+        return '/'
+    return null
+


compute client: make status fail after a few minutes... so the client can try again or other code can fail

--- src/smc-hub/compute-client.coffee
+++ src/smc-hub/compute-client.coffee
@@ -37,7 +37,7 @@
 ###

 # obviously don't want to trigger this too quickly, since it may mean file loss.
-AUTOMATIC_FAILOVER_TIME_S = 60*5 *9999  # 9999 makes it effectively infinite -- don't use this until re-implement
+AUTOMATIC_FAILOVER_TIME_S = 60*3   # NOTE: actual failover is actually disabled below; instead this is the timeout for giving up on getting status.

 SERVER_STATUS_TIMEOUT_S = 7  # 7 seconds

@@ -1104,7 +1104,7 @@
                 # triggers failover of project to another node.
                 misc.retry_until_success
                     f           : f
-                    start_delay : 15000
+                    start_delay : 10000
                     max_time    : AUTOMATIC_FAILOVER_TIME_S*1000
                     cb          : (err) =>
                         if err


Add tracking to file listing creation

Add tracking to file listing creation

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -29,6 +29,7 @@
 {FileTypeSelector, NewFileButton} = require('./project_new')
 {SiteName} = require('./customize')
 {file_actions} = require('./project_store')
+{analytics_event} = require('./tracker')

 STUDENT_COURSE_PRICE = require('smc-util/upgrade-spec').upgrades.subscription.student_course.price.month4

@@ -516,10 +517,13 @@
     handle_click: ->
         if @props.file_search.length == 0
             @props.actions.set_active_tab('new')
+            analytics_event('project_files', 'listing_create_button', 'empty')
         else if @props.file_search[@props.file_search.length - 1] == '/'
             @props.create_folder()
+            analytics_event('project_files', 'listing_create_button', 'folder')
         else
             @props.create_file()
+            analytics_event('project_files', 'listing_create_button', 'file')

     # Returns the full file_search text in addition to the default extension if applicable
     full_path_text: ->
@@ -2072,10 +2076,13 @@
     on_create_button_clicked: ->
         if @props.file_search.length == 0
             @props.actions.set_active_tab('new')
+            analytics_event('project_files', 'search_create_button', 'empty')
         else if @props.file_search[@props.file_search.length - 1] == '/'
             @props.create_folder()
+            analytics_event('project_files', 'search_create_button', 'folder')
         else
             @props.create_file()
+            analytics_event('project_files', 'search_create_button', 'file')

     render: ->
         <SplitButton id='new_file_dropdown'


Add file_search to defaultProps

Add file_search to defaultProps

Remove checks for existance of @props.file_search and assume it to be defined.
file_search:'' added to all default props.

Changed to make the code less brittle.

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -344,18 +344,21 @@

     displayName : 'ProjectFiles-NoFiles'

+    getDefaultProps : ->
+        file_search : ''
+
     # Go to the new file tab if there is no file search
     handle_click : ->
-        if not @props.file_search?.length > 0
+        if @props.file_search.length == 0
             @props.actions.set_focused_page('project-new-file')
-        else if @props.file_search?[@props.file_search.length - 1] == '/'
+        else if @props.file_search[@props.file_search.length - 1] == '/'
             @props.create_folder()
         else
             @props.create_file()

     # Returns the full file_search text in addition to the default extension if applicable
     full_path_text : ->
-        if @props.file_search?.lastIndexOf('.') <= @props.file_search?.lastIndexOf('/')
+        if @props.file_search.lastIndexOf('.') <= @props.file_search.lastIndexOf('/')
           ext = "sagews"
         if ext and @props.file_search.slice(-1) isnt '/'
             "#{@props.file_search}.#{ext}"
@@ -364,7 +367,7 @@

     # Text for the large create button
     button_text : ->
-        if not @props.file_search?.length > 0
+        if @props.file_search.length == 0
             "Create or upload files..."
         else
             "Create #{@full_path_text()}"
@@ -378,13 +381,13 @@

     # TODO: Make better help text
     render_help_alert : ->
-        last_folder_index = @props.file_search?.lastIndexOf('/')
-        if @props.file_search? and @props.file_search.indexOf('\\') != -1
+        last_folder_index = @props.file_search.lastIndexOf('/')
+        if @props.file_search.indexOf('\\') != -1
             <Alert style={marginTop: '10px', fontWeight : 'bold'} bsStyle='danger'>
                 Warning: \ is an illegal character
             </Alert>
         # Non-empty search and there is a file divisor ('/')
-        else if @props.file_search?.length > 0 and last_folder_index > 0
+        else if @props.file_search.length > 0 and last_folder_index > 0
             <Alert style={marginTop: '10px'} bsStyle='info'>
                 {@render_help_text(last_folder_index)}
             </Alert>
@@ -432,7 +435,7 @@
                 <hr/>
                 {@render_create_button() if not @props.public_view}
                 {@render_help_alert()}
-                {@render_file_type_selection() if @props.file_search?.length > 0}
+                {@render_file_type_selection() if @props.file_search.length > 0}
             </Col>
             <Col sm=2>
             </Col>
@@ -457,6 +460,9 @@
         actions       : rtypes.object.isRequired
         create_folder : rtypes.func.isRequired
         create_file   : rtypes.func.isRequired
+
+    getDefaultProps : ->
+        file_search : ''

     render_row : (name, size, time, mask, isdir, display_name, public_data, index) ->
         checked = @props.checked_files.has(misc.path_to_file(@props.current_path, name))
@@ -1373,7 +1379,7 @@
         file_search : ''

     render_help_info : ->
-        if @props.file_search?.length > 0 and @props.files_displayed
+        if @props.file_search.length > 0 and @props.files_displayed
             firstFolderPosition = @props.file_search.indexOf('/')
             if @props.file_search == '/'
                 text = "Showing all folders in this directory"
@@ -1404,7 +1410,7 @@
             else
                 @props.actions.open_file(path: new_path)
             @props.actions.set_file_search('')
-        else if not @props.files_displayed and @props.file_search?.length > 0
+        else if not @props.files_displayed and @props.file_search.length > 0
             if @props.file_search[@props.file_search.length - 1] == '/'
                 @props.create_folder()
             else
@@ -1448,7 +1454,7 @@
         </MenuItem>

     on_menu_item_clicked : (ext) ->
-        if not @props.file_search?.length > 0
+        if @props.file_search.length == 0
             # Tell state to render an error in file search
             @props.actions.setState(file_creation_error : "You must enter file name above to create it")
         else
@@ -1456,9 +1462,9 @@

     # Go to new file tab if no file is specified
     on_create_button_clicked : ->
-        if not @props.file_search?.length > 0
+        if @props.file_search.length == 0
             @props.actions.set_focused_page('project-new-file')
-        else if @props.file_search?[@props.file_search.length - 1] == '/'
+        else if @props.file_search[@props.file_search.length - 1] == '/'
             @props.create_folder()
         else
             @props.create_file()
@@ -1511,6 +1517,7 @@

     getDefaultProps : ->
         page_number : 0
+        file_search : ''

     previous_page : ->
         if @props.page_number > 0
@@ -1520,7 +1527,7 @@
         @props.actions.setState(page_number : @props.page_number + 1)

     create_file : (ext) ->
-        if not ext? and @props.file_search?.lastIndexOf('.') <= @props.file_search?.lastIndexOf('/')
+        if not ext? and @props.file_search.lastIndexOf('.') <= @props.file_search.lastIndexOf('/')
             ext = "sagews"
         @props.actions.create_file
             name         : @props.file_search


chore: handle missing istanbul variable

chore: handle missing istanbul variable

--- src/steps/prepare-coverage.coffee
+++ src/steps/prepare-coverage.coffee
@@ -6,7 +6,7 @@
   # @param args [Promise<Array>] The arguments to the script
   # @return [Promise] The arguments to the script
   run: (args) ->
-    coverageVar = coffeeCoverage.findIstanbulVariable()
+    coverageVar = coffeeCoverage.findIstanbulVariable() ? "_$coffeeIstanbul"
     coffeeCoverage.register
       instrumentor: 'istanbul'
       basePath: process.cwd()


Dont print entire browserify error object

--- config/grunt/server.coffee
+++ config/grunt/server.coffee
@@ -20,8 +20,9 @@

 bundle = (watcher) ->
-  return watcher.bundle().on('error', console.error)
-
+  return watcher.bundle().on('error', (err) ->
+    console.error(err.name, err.message)
+  )

 serve = (connect, req, res, next) ->
   watchers = connect.watchers


only show context-menu entry if data-grammar is

--- menus/atom-ternjs.cson
+++ menus/atom-ternjs.cson
@@ -1,6 +1,6 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'context-menu':
-  'atom-text-editor': [
+  'atom-text-editor[data-grammar="source js"]': [
     {
       label: 'Atom Ternjs'
       submenu: [


Add descriptions to Scenario spec

Add descriptions to Scenario spec

--- src/Witness/Scripts/specs/Scenario.coffee
+++ src/Witness/Scripts/specs/Scenario.coffee
@@ -4,14 +4,14 @@

 describe "Scenario",
 {
-	given: ->
+	"given a scenario with given, when, then and dispose": ->
 		@scenario = new Witness.Scenario
 			given:   { description: "given", actions: [ (new Witness.Action (=> @givenCalled = true), []) ] }
 			when:    { description: "when", actions: [ (new Witness.Action (=> @whenCalled = true), []) ] }
 			then:    { description: "then", actions: [ new Witness.Assertion (new Witness.Action (=> @thenCalled = true), []) ] }
 			dispose: { description: "dispose", actions: [ (new Witness.Action (=> @disposeCalled = true), []) ] }

-	when: ->
+	"when scenario is run": ->
 		@scenario.run {}, (->), (->)

 	then:
@@ -21,7 +21,7 @@
 		disposeCalled: should.be true
 },
 {
-	given: ->
+	"given a scenario where the `given` action throws an error": ->
 		@scenario = new Witness.Scenario
 			given:   { description: "given", actions: [ (new Witness.Action (-> throw new Error "given failed"), []) ] }
 			when:    { description: "when", actions: [ (new Witness.Action (=> @whenCalled = true), []) ] }
@@ -29,7 +29,7 @@
 			dispose: { description: "dispose", actions: [ (new Witness.Action (=> @disposeCalled = true), []) ] }

-	when: ->
+	"when scenarion is run": ->
 		@scenario.run {}, (->), ((errors) => @errors = errors)

 	then:


Reject dockerError instead of error

Reject dockerError instead of error

--- src/beautifiers/executable.coffee
+++ src/beautifiers/executable.coffee
@@ -409,7 +409,7 @@
       .then(=> @)
       .catch((dockerError) =>
         @debug(dockerError)
-        Promise.reject(error)
+        Promise.reject(dockerError)
       )

   run: (args, options = {}) ->


add additional subjects

add additional subjects

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -22,8 +22,6 @@
 class Classifier extends BaseController
   className: 'classifier'
   template: require '../views/classifier'
-
-  TRAINING_SUBJECT = null

   events:
     'click button[name="play-frames"]'      : 'onClickPlay'
@@ -134,33 +132,8 @@
   constructor: ->
     super

-    @TRAINING_SUBJECT = new Subject
-      location:
-        standard: [
-          "./dev-subjects-images/01_12DEC02_N04066_0001-50-scaled.png",
-          "./dev-subjects-images/01_12DEC02_N04066_0002-50-scaled.png",
-          "./dev-subjects-images/01_12DEC02_N04066_0003-50-scaled.png",
-          "./dev-subjects-images/01_12DEC02_N04066_0004-50-scaled.png"
-        ]
-        inverted: []
-      metadata:
-        id: "01_12DEC02_N04066"
-        cutout:
-          x: [
-            1367.04,
-            1879.0400000000002
-          ]
-          y: [
-            1822.72,
-            2334.7200000000003
-          ]
-
-        asteroids: [
-          {x: 435, y: 39}
-          {x: 432, y: 40}
-          {x: 430, y: 40}
-          {x: 428, y: 42}
-        ]
+    # replace with real subjects + prior asteroid meta data
+    @trainingSubjects = @loadTrainingSubjects()

     @asteroidMarkedInFrame = [ null, null, null, null ]
     @playTimeouts = []
@@ -504,15 +477,18 @@

   removePriorAsteroids: ->
     priorAsteroid.remove() for priorAsteroid in [@el.find('.prior-asteroid')...]
-
+
+  # REQUIRES:
+  # 1) markingSurfaceList
+  # 2) setOfSightings
   showExistingAsteroids: ->
-    return if @TRAINING_SUBJECT is null
+    return if @trainingSubjects is null
     xs = []
     ys = []
     P = null
     x_sum = null
     y_sum = null
-    for priorAsteroid, i in [@TRAINING_SUBJECT.metadata.asteroids...]
+    for priorAsteroid, i in [@trainingSubjects.metadata.asteroids...]
       xs[i] = priorAsteroid.x
       ys[i] = priorAsteroid.y
       x_sum += xs[i]
@@ -561,4 +537,68 @@
     console?.log JSON.stringify @classification
     @classification.send()

+  loadTrainingSubjects: ->
+    @trainingSubjects = new Subject
+      location:
+        standard: [
+          "./dev-subjects-images/01_12DEC02_N04066_0001-45-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0002-45-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0003-45-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0004-45-scaled.png"
+        ]
+        inverted: []
+      metadata:
+        id: "01_12DEC02_N04066"
+        cutout:
+          x: []
+          y: []
+        asteroids: [
+          {x: 460, y: 89}
+          {x: 458, y: 89}
+          {x: 457, y: 88}
+          {x: 466, y: 90}
+        ]
+
+    @trainingSubjects = new Subject
+      location:
+        standard: [
+          "./dev-subjects-images/01_12DEC02_N04066_0001-51-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0002-51-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0003-51-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0004-51-scaled.png"
+        ]
+        inverted: []
+      metadata:
+        id: "01_12DEC02_N04066"
+        cutout:
+          x: []
+          y: []
+        asteroids: [
+          {x: 434, y: 495}
+          {x: 431, y: 496}
+          {x: 429, y: 497}
+          {x: 427, y: 496}
+        ]
+
+    @trainingSubjects = new Subject
+      location:
+        standard: [
+          "./dev-subjects-images/01_12DEC02_N04066_0001-50-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0002-50-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0003-50-scaled.png",
+          "./dev-subjects-images/01_12DEC02_N04066_0004-50-scaled.png"
+        ]
+        inverted: []
+      metadata:
+        id: "01_12DEC02_N04066"
+        cutout:
+          x: [1367.04, 1879.0400000000002]
+          y: [1822.72, 2334.7200000000003]
+        asteroids: [
+          {x: 435, y: 39}
+          {x: 432, y: 40}
+          {x: 430, y: 40}
+          {x: 428, y: 42}
+        ]
+
 module.exports = Classifier


fix typos

fix typos

--- tests/mocha/unit/normalize.coffee
+++ tests/mocha/unit/normalize.coffee
@@ -261,7 +261,7 @@

     attrTest.run('separate multiple styles',
       initial: 'Color'
-      expected: 'Color'
+      expected: 'Color'
     )

     attrTest.run('separate non-standard style',
@@ -271,12 +271,12 @@

     attrTest.run('separate style from non-span',
       initial: 'Color'
-      expected: 'Color'
+      expected: 'Color'
     )

     attrTest.run('separate multiple styles from non-span',
       initial: 'Color'
-      expected: 'Color'
+      expected: 'Color'
     )
   )
 )


Textarea changes.

Textarea changes.

--- src/client/textarea.coffee
+++ src/client/textarea.coffee
@@ -32,7 +32,7 @@
     elem.scrollTop = scrollTop if elem.scrollTop != scrollTop
     [elem.selectionStart, elem.selectionEnd] = newSelection

-  @on 'insert', (pos, text) ->
+  @on 'insert', insert_listener = (pos, text) ->
     transformCursor = (cursor) ->
       if pos < cursor
         cursor + text.length
@@ -42,7 +42,7 @@
     prevvalue = elem.value.replace /\r\n/g, '\n'
     replaceText prevvalue[...pos] + text + prevvalue[pos..], transformCursor

-  @on 'delete', (pos, text) ->
+  @on 'delete', delete_listener = (pos, text) ->
     transformCursor = (cursor) ->
       if pos < cursor
         cursor - Math.min(text.length, cursor - pos)
@@ -67,3 +67,12 @@
     else
       elem.attachEvent 'on'+event, genOp

+  elem.detach_share = =>
+    @removeListener 'insert', insert_listener
+    @removeListener 'delete', delete_listener
+
+    for event in ['textInput', 'keydown', 'keyup', 'select', 'cut', 'paste']
+      if elem.removeEventListener
+        elem.removeEventListener event, genOp, false
+      else
+        elem.detachEvent 'on'+event, genOp


Improved small-tsu substitution

Improved small-tsu substitution

- Added a check to determine which type of small-tsu should be returned
(ッ for KATAKANA, っ for hiragana).

--- src/coffee/wanakana.coffee
+++ src/coffee/wanakana.coffee
@@ -196,7 +196,11 @@
       wanakana._isCharConsonant(chunkLC.charAt(0)) and
       chunk.charAt(0) == chunk.charAt(1)
         chunkSize = 1
-        chunkLC = chunk = "っ"
+        # Return katakana ッ if chunk is uppercase, otherwise return hiragana っ
+        if wanakana._isCharInRange(chunk.charAt(0), wanakana.UPPERCASE_START, wanakana.UPPERCASE_END)
+          chunkLC = chunk = "ッ"
+        else
+          chunkLC = chunk = "っ"

       kanaChar = wanakana.R_to_J[chunkLC]
       # DEBUG


Clinet-side filtering for autocompletion

--- app/js/controllers/account.coffee
+++ app/js/controllers/account.coffee
@@ -1,4 +1,4 @@
-angular.module("app").controller "AccountController", ($scope, $location, $stateParams, Growl, Wallet, Utils, WalletAPI, $modal, Blockchain) ->
+angular.module("app").controller "AccountController", ($scope, $filter, $location, $stateParams, Growl, Wallet, Utils, WalletAPI, $modal, Blockchain) ->

     name = $stateParams.name
     #$scope.accounts = Wallet.receive_accounts
@@ -89,4 +89,4 @@
         console.log(input)
         Wallet.blockchain_list_registered_accounts(input, 20).then (response) ->
             console.log(response)
-            response
+            $filter('filter')(response, input)


BF-111 implemented scaling

BF-111 implemented scaling

--- app/assets/javascripts/view.coffee
+++ app/assets/javascripts/view.coffee
@@ -106,6 +106,8 @@

     # update postion and FPS displays
     position2d = cam2d.getGlobalPos()
+    # without rounding the position becomes really long and blocks the canvas mouse input
+    position2d = [Math.round(position2d[0]),Math.round(position2d[1]),Math.round(position2d[2])]
     @positionStats.html "Flycam2d: #{position2d}
ZoomStep #{cam2d.getZoomStep(0)}
activePlane: #{cam2d.getActivePlane()}"
     @stats.update()

@@ -196,8 +198,8 @@
   resize : ->
     #FIXME: Is really the window's width or rather the DIV's?
     container = $("#render")
-    WIDTH = (container.width()-40)/2
-    HEIGHT = (container.height()-40)/2
+    WIDTH = (container.width()-41)/2
+    HEIGHT = (container.height()-41)/2

     @rendererxy.setSize( WIDTH, HEIGHT )
     @rendereryz.setSize( WIDTH, HEIGHT )
@@ -241,13 +243,14 @@
     cam2d.move [0, 0, 100*z]

   scaleTrianglesPlane : (delta) ->
-    g = @trianglesplanexy
-    if g
-      x = Number(g.scale.x) + Number(delta)
-      if x > 0 and x < 2
-        # why z? keep in mind the plane is rotated 90°
-        g.scale.x = g.scale.z = x
-        cam2d.hasChanged = true
+    @x = 1 unless @x
+    if (@x+delta > 0.5) and (@x+delta < 1.5)
+      @x += Number(delta)
+      WIDTH = HEIGHT = @x * 384
+      container = $("#render")
+      container.width(2 * WIDTH + 40)
+      container.height(2 * HEIGHT + 40)
+      @resize()

   zoomIn : ->
     if cam2d.getZoomStep(cam2d.getActivePlane()) > 0


Remove cover, polymer-gestures knows ghost clicks.

--- app/client/js/react/gesture.coffee
+++ app/client/js/react/gesture.coffee
@@ -17,29 +17,11 @@
   ###
   constructor: (@routingClickHandler) ->

-    ###*
-      When link is tapped and new page is rendered, subsequent click can focus
-      form field. I don't know how to suppress it, preventDefault nor
-      stopPropagation works. Cover seems to be both simple and reliable.
-      TODO: Wait for https://github.com/Polymer/polymer-gestures/pull/73, then
-      remove.
-      @type {Element}
-      @private
-    ###
-    @cover_ = document.createElement 'div'
-    @cover_.style.cssText = 'position: fixed; top: 0; left: 0; background-color: #000; opacity: 0; width: 100%; height: 100%; z-index: 2147483647;'
-
   ###*
     Used for removing touch-hover before iOS callout is shown.
     @type {number}
   ###
   @CALLOUT_DELAY: 750
-
-  ###*
-    Used for removing touch-hover before iOS callout is shown.
-    @type {number}
-  ###
-  @COVER_DELAY: 350

   ###*
     @param {...string} var_args
@@ -85,7 +67,6 @@
     @return {function(): React.ReactComponent}
   ###
   createTouchableComponent: (tag, touchAction) ->
-    cover = @cover_
     gestures = window['PolymerGestures']
     routingClickHandler = @routingClickHandler

@@ -95,7 +76,7 @@
         @transferPropsTo React.DOM[tag] null, @props.children

       componentDidMount: ->
-        # Undefined on server side, so do nothing.
+        # Undefined at server side, so do nothing.
         return if !gestures

         # React doesn't support this non standard attribute, so add it manually.
@@ -135,19 +116,8 @@
       onTap: (e) ->
         @props.onTap e if @props.onTap
         if e.target.tagName == 'A'
-          # Routing can change whole UI, therefore we have to ignore subsequent
-          # click on any shown element to prevent accidental action like focus.
-          @showCoverTemporarily_()
           @delegateAnchorTapToRoutingClickHandler_ e
         return

-      showCoverTemporarily_: ->
-        # Seems to be more reliable then display block/none.
-        document.body.appendChild cover
-        clearTimeout @coverHideTimer
-        @coverHideTimer = setTimeout ->
-          document.body.removeChild cover
-        , Gesture.COVER_DELAY
-
       delegateAnchorTapToRoutingClickHandler_: (e) ->
         routingClickHandler.onCustomClick e


Reload after 5 secs

Reload after 5 secs

--- app/components/make-payment.coffee
+++ app/components/make-payment.coffee
@@ -102,6 +102,11 @@
         xhr.then (result) ->
           Notify.success "Sucessfully processed your payment. Thank You."
           self.send "closeModal"
+          setTimeout ->
+            location.reload()
+          ,
+            5 * 1000
+          location.reload()
         , (error)->
           Notify.error error.jqXHR.responseJSON.message
       .catch @stripeErrorHandler


test update

test update

--- tests/tests/item_selection.coffee
+++ tests/tests/item_selection.coffee
@@ -4,7 +4,7 @@

 module.exports =

-  "item selection by click selects item and hides list": (browser) ->
+  "item selection by click selects item, hides list and highlights text": (browser) ->
     browser
       .setupCombo()
       .openComboList(ns)
@@ -16,19 +16,21 @@
       .assert.selectedText(ns + combo_input, "my special number is 0.621")
       .end()

-  "item selection by enter hides list": (browser) ->
-    browser
-      .setupCombo()
-      .openComboList(ns)
-      .click(ns + combo_input)
-      .setValue(ns + combo_input, browser.Keys.DOWN_ARROW)
-      .setValue(ns + combo_input, browser.Keys.DOWN_ARROW)
-      .assert.cssClassPresent(ns + second_item, 'active')
-      .setValue(ns + combo_input, browser.Keys.ENTER)
-      .waitForElementNotVisible(ns + combo_list, "list should be hidden on item select")
-      .assert.value(ns + combo_input, "my special number is 0.621", "should contain selected text")
-      # .assert.selectedText(ns + combo_input, "my special number is 0.621")
-      .end()
+  "item selection by ENTER/TAB selects active item, hide list and highlights text": (browser) ->
+    browser.setupCombo()
+    for key in ["ENTER", "TAB"]
+      browser
+        .refresh()
+        .openComboList(ns)
+        .click(ns + combo_input)
+        .setValue(ns + combo_input, browser.Keys.DOWN_ARROW)
+        .setValue(ns + combo_input, browser.Keys.DOWN_ARROW)
+        .setValue(ns + combo_input, browser.Keys[key])
+        .waitForElementNotVisible(ns + combo_list, "list should be hidden on item select by #{key}")
+        .assert.cssClassPresent(ns + second_item, 'active')
+        .assert.value(ns + combo_input, "my special number is 0.621", "should contain selected text")
+        # .assert.selectedText(ns + combo_input, "my special number is 0.621")
+    browser.end()

   "disabled item cannot be selected": (browser) ->
     browser
@@ -38,6 +40,7 @@
       .setValue(ns + combo_input, browser.Keys.DOWN_ARROW)
       .assert.cssClassPresent(ns + disabled_item, 'active')
       .setValue(ns + combo_input, browser.Keys.ENTER)
+      .setValue(ns + combo_input, browser.Keys.TAB)
       .click(ns + disabled_item)
       .pause(100)
       .assert.visible(ns + combo_list, "combo list should remain visible on disabled item select")
@@ -53,4 +56,4 @@
       .assert.cssClassPresent(ns + second_item, "active")
       .end()

-require("../testUtils.js").run_only(module, -1)
+# require("../testUtils.js").run_only(module, -1)


rename address -> account

rename address -> account

--- app/js/controllers/accounts.coffee
+++ app/js/controllers/accounts.coffee
@@ -3,7 +3,7 @@
     $scope.accounts = []
     $scope.wallet_file = ""
     $scope.wallet_password = ""
-    non0addr={}
+    non0acct={}

     #TODO ADD a Barrier or something to deal with race condition


Add smokeprint and crashing

Add smokeprint and crashing

--- src/lazerscripts/stage1/army_drone.coffee
+++ src/lazerscripts/stage1/army_drone.coffee
@@ -9,6 +9,12 @@
       @deathDecoy()
       @smallExplosion(juice: @juice, offsetX: 20, offsetY: 20)
       @rotate 30, 60
+      @smokePrint()
+      @moveTo(
+        y: 1000
+        speed: 400
+        easing: 'easeInQuad'
+      )
       @endDecoy()
     )


add landing page text for mammals/reptiles/birds

--- app/partials/field-guide-list.cjsx
+++ app/partials/field-guide-list.cjsx
@@ -8,11 +8,27 @@
 # TODO Integrate within fieldGuideChoices
 categoryText =
   mammals: '''
-    Quisque vehicula in enim non aliquet. Aliquam at urna commodo, vestibulum ligula vel, tincidunt ex. Sed pretium orci dui, et sodales sem pharetra nec. Vivamus viverra pellentesque ipsum eget suscipit. Proin sollicitudin pretium risus nec blandit. Duis hendrerit, arcu nec mattis elementum, purus ex facilisis tellus, at semper enim dui eget mi. Sed aliquam tincidunt orci, sit amet luctus turpis. Ut eget mi hendrerit, iaculis felis volutpat, aliquam quam. Aenean pulvinar a nisl ac varius.
+    Mammals comprise some 5,416 species worldwide, about 1,125 of which live in Africa. Mammals are warm-blooded vertebrates, which have hair and nurse their young on milk produced by the female’s mammary glands. Most mammalian mothers give live birth and have relatively few babies, but they invest heavily in the survival of their young. Around one in four of the world’s mammals are currently threatened with extinction.
+
+    Click on the icons below to browse some of Gorongosa's mammal species:
   '''
   ecology: '''
     Quisque vehicula in enim non aliquet. Aliquam at urna commodo, vestibulum ligula vel, tincidunt ex. Sed pretium orci dui, et sodales sem pharetra nec. Vivamus viverra pellentesque ipsum eget suscipit. Proin sollicitudin pretium risus nec blandit. Duis hendrerit, arcu nec mattis elementum, purus ex facilisis tellus, at semper enim dui eget mi. Sed aliquam tincidunt orci, sit amet luctus turpis. Ut eget mi hendrerit, iaculis felis volutpat, aliquam quam. Aenean pulvinar a nisl ac varius.
   '''
+  birds: '''
+    Birds are warm-blooded, egg-laying vertebrates that have evolved numerous adaptations for flight, including wings, powerful muscles, hollow bones, and, most distinctively, feathers. While all birds have feathers and wings, not all birds can fly, and there are also notable exceptions of birds that have lost (or partially lost) the trait of hollow bones. Birds comprise some 10,000 species worldwide, about 2,000 of which have been recorded in Africa. According to the International Union for Conservation of Nature (IUCN), 12 percent of birds worldwide are known to be threatened or extinct.
+
+    Click on the icons below to browse some of Gorongosa's bird species:
+  '''
+  reptiles: '''
+    Reptiles are cold-blooded, scaly-skinned vertebrates. Most reptiles lay leathery eggs; however, many lizard and snake species give birth to live young. The reptiles’ dry, scaly skin helps them retain water, making it possible for them to survive and thrive in typically inhospitable habitats such as deserts. However, they are absent from Antarctica. Reptiles comprise over 7,000 species worldwide, about 1,000 of which live in Africa. A third of the world’s reptiles are currently threatened with extinction.
+
+    Click on the icons below to browse some of Gorongosa's reptile species:
+  '''
+
+
+
+

 module.exports = React.createClass
   displayName: 'FieldGuideList'


Hack static children for loader to display

--- src/config/controls.coffee
+++ src/config/controls.coffee
@@ -66,18 +66,22 @@
           dict['selected'] = true if index == counter
           index = index + 1
           dict
-        console.log 'active_program', result, result.length()
         result.all()
     }
     {
       _LABEL: "program_loader"
-      _AUTHORITY: (world) -> world.get('BUTTON')
-      _CHILDREN: (world) ->
-        turtle = world.get('current')
-        program = turtle.get('program')
-        result = program.map (signal) ->
-          {_LABEL: signal}
-        result.all()
+      _CHILDREN: [
+        {
+          _LABEL: 'default'
+          _AUTHORITY: (world) -> world.get('BUTTON')
+          _CHILDREN: [{},{_LABEL: 'forward'}]
+        }
+        {
+          _LABEL: 'conflict'
+          _AUTHORITY: (world) -> world.get('BUTTON')
+          _CHILDREN: [{},{_LABEL: 'reverse'}]
+        }
+      ]
     }
   ]
 }


support ctrl-shift-home/end on Windows

support ctrl-shift-home/end on Windows

fixes issue #2989

--- keymaps/win32.cson
+++ keymaps/win32.cson
@@ -84,6 +84,8 @@
   'ctrl-delete': 'editor:delete-to-end-of-word'
   'ctrl-home': 'core:move-to-top'
   'ctrl-end': 'core:move-to-bottom'
+  'ctrl-shift-home': 'core:select-to-top'
+  'ctrl-shift-end': 'core:select-to-bottom'

   # Sublime Parity
   'ctrl-a': 'core:select-all'


expect 301s

expect 301s

--- verticalcheck.cson
+++ verticalcheck.cson
@@ -13,14 +13,14 @@
 			'metoceanview.com': '192.64.32.82'
 		ping: ['192.64.32.82']
 		http: [
-			'http://fugro.metoceanview.com'
-			'http://metra.metoceanview.com'
-			'http://forecast.metoceanview.com'
-			'http://hindcast.metoceanview.com'
-			'http://route.metoceanview.com'
-			'http://climate.metoceanview.com'
-			'http://route.metoceanview.com'
-			'http://eri.metoceanview.com'
+			'http://fugro.metoceanview.com': 301
+			'http://metra.metoceanview.com': 301
+			'http://forecast.metoceanview.com': 301
+			'http://hindcast.metoceanview.com': 301
+			'http://route.metoceanview.com': 301
+			'http://climate.metoceanview.com': 301
+			'http://route.metoceanview.com': 301
+			'http://eri.metoceanview.com': 301
 			'http://metoceanview.com'
 		]
 	}
@@ -74,8 +74,6 @@
 			'https://tiles.metoceanview.com/wxtiles/wxtiles.v2.min.js'
 			'http://ukcd.metoceanview.com/docs'
 			'http://psfd.metoceanview.com/docs'
-			'https://ukcd.metoceanview.com/docs'
-			'https://psfd.metoceanview.com/docs'
 			'http://graphd.metoceanview.com/docs'
 			'http://hcd.metoceanview.com/docs'
 			'http://maps.metoceanview.com/'


change work dir to prevent "Docset not found"

--- coffee/index.coffee
+++ coffee/index.coffee
@@ -9,6 +9,8 @@

 mainWindow = null

+process.chdir __dirname
+
 app.on 'ready', ->
     require './js/search'
     mainWindow = new browserWindow


Added new 'Minimax Tic-Tac-Toe' level

Added new 'Minimax Tic-Tac-Toe' level

--- app/views/play/MainPlayView.coffee
+++ app/views/play/MainPlayView.coffee
@@ -229,6 +229,13 @@
         image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
         description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu'
       }
+      {
+        name: 'Minimax Tic-Tac-Toe'
+        difficulty: 4
+        id: 'minimax-tic-tac-toe'
+        image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
+        description: 'Learn how to make a game AI with the Minimax algorithm. - by Alexandru Caciulescu'
+      }
     ]

     playerCreated = [


Correctly cycle through subjects

Correctly cycle through subjects

--- app/views/settings/subject.coffee
+++ app/views/settings/subject.coffee
@@ -18,14 +18,18 @@
     if @model.get('data_source').isExternal()
       prevSubject = @model.get('data_source').data.previous(_.last(@model.get('selected_ids')))
     else
-      prevSubject = @model.get('data_source').source.get('data_source').data.previous(_.last(@model.get('selected_ids')))
+      source = @model.collection.find (tool) =>
+        tool.get('channel') is @model.get('data_source').get('source')
+      prevSubject = source.get('data_source').data.previous(_.last(@model.get('selected_ids')))
     @model.set 'selected_ids', [prevSubject]

   onSelectNextSubject: =>
     if @model.get('data_source').isExternal()
       nextSubject = @model.get('data_source').data.next(_.last(@model.get('selected_ids')))
     else
-      nextSubject = @model.get('data_source').source.get('data_source').data.next(_.last(@model.get('selected_ids')))
+      source = @model.collection.find (tool) =>
+        tool.get('channel') is @model.get('data_source').get('source')
+      nextSubject = source.get('data_source').data.previous(_.last(@model.get('selected_ids')))
     @model.set 'selected_ids', [nextSubject]

 module.exports = SubjectSettings


Update FuzzyProvider to use disableForSelector

--- lib/fuzzy-provider.coffee
+++ lib/fuzzy-provider.coffee
@@ -16,7 +16,7 @@
     @buildWordList()
     @selector = '*'
     builtinProviderBlacklist = atom.config.get('autocomplete-plus.builtinProviderBlacklist')
-    @blacklist = builtinProviderBlacklist if builtinProviderBlacklist? and builtinProviderBlacklist.length
+    @disableForSelector = builtinProviderBlacklist if builtinProviderBlacklist? and builtinProviderBlacklist.length

   updateCurrentEditor: (currentPaneItem) =>
     return unless currentPaneItem?


Shim process stdout and stderr

Shim process stdout and stderr

--- src/initialize-test-window.coffee
+++ src/initialize-test-window.coffee
@@ -30,6 +30,10 @@
   require('module').globalPaths.push(exportsPath)
   process.env.NODE_PATH = exportsPath # Set NODE_PATH env variable since tasks may need it.

+  # Shim process stdout and stderr so that they can be logged out to console.
+  process.stdout = remote.process.stdout
+  process.stderr = remote.process.stderr
+
   document.title = "Spec Suite"

   legacyTestRunner = require(getWindowLoadSettings().legacyTestRunnerPath)


use only load-program

use only load-program

--- src/layout/mixins/programs.coffee
+++ src/layout/mixins/programs.coffee
@@ -58,6 +58,7 @@
     perform: (world, key) ->
       contents = sprite.get('actions').get(key)
       return load_program(key) if _.isArray contents
+
       world.call 'perform_instruction', contents

     perform_instruction: (world, contents) ->
@@ -71,7 +72,7 @@
       # if this is my sprite to handle
       if collision_subject.get('obstruction')
         world.call 'reset_index'
-        sprite.put 'running', 'interrupt'
+        load_program 'interrupt'
       else
         sprite.call 'commit', coordinates


Refresh repo after custom commands

Refresh repo after custom commands

--- lib/models/git-run.coffee
+++ lib/models/git-run.coffee
@@ -5,11 +5,10 @@
 notifier = require '../notifier'
 OutputViewManager = require '../output-view-manager'

-runCommand = (args, workingDirectory) ->
+runCommand = (repo, args) ->
   view = OutputViewManager.create()
-  promise = git.cmd(args, cwd: workingDirectory, {color: true})
-  promise
-  .then (data) ->
+  promise = git.cmd(args, cwd: repo.getWorkingDirectory(), {color: true})
+  promise.then (data) ->
     msg = "git #{args.join(' ')} was successful"
     notifier.addSuccess(msg)
     if data?.length > 0
@@ -17,12 +16,14 @@
     else
       view.reset()
     view.finish()
+    git.refresh repo
   .catch (msg) =>
     if msg?.length > 0
       view.setContent msg
     else
       view.reset()
     view.finish()
+    git.refresh repo
   return promise

 class InputView extends View
@@ -45,17 +46,12 @@
     @disposables.add atom.commands.add 'atom-text-editor', 'core:confirm', (e) =>
       @disposables.dispose()
       @panel?.destroy()
-      args = @commandEditor.getText().split(' ')
-      # TODO: remove this?
-      if args[0] is 1 then args.shift()
-      runCommand args, @repo.getWorkingDirectory()
-      .then =>
+      runCommand(@repo, @commandEditor.getText().split(' ')).then =>
         @currentPane.activate()
         git.refresh @repo

-module.exports = (repo, args) ->
-  if args
-    args = args.split(' ')
-    runCommand args, repo.getWorkingDirectory()
+module.exports = (repo, args=[]) ->
+  if args.length > 0
+    runCommand repo, args.split(' ')
   else
     new InputView(repo)


fixed missing .length bug

fixed missing .length bug

--- src/lib/parser/mist-parser.coffee
+++ src/lib/parser/mist-parser.coffee
@@ -34,7 +34,7 @@

 conditionals =
   ifeq: (args, v...)->
-    (return false if val isnt v[0]) for val, i in val when i > 1
+    (return false if val isnt v[0]) for val, i in v when i > 1
     return true
   ifneq: (args, v...)-> !(conditionals.ifeq.apply null, v)
   ifdef: (args, v...)->


Style update

Style update

--- lib/transforms/ifs.coffee
+++ lib/transforms/ifs.coffee
@@ -53,19 +53,20 @@
   ###

   handleBlankIfs: (node) ->
-    noBody = ->
-      ! node.consequent
-
-    emptyBodyBlock = ->
-      cons = node.consequent
-      result =
-        cons and
-        cons.type is 'BlockStatement' and
-        nonComments(cons.body).length is 0
-
-    if (noBody() or emptyBodyBlock()) and !node.alternate
+    if noBody(node) or emptyBodyBlock(node) and !node.alternate
       node.alternate =
         type: 'BlockStatement'
         body: []

     node
+
+noBody = (node) ->
+  ! node.consequent
+
+emptyBodyBlock = (node) ->
+  cons = node.consequent
+  result =
+    cons and
+    cons.type is 'BlockStatement' and
+    nonComments(cons.body).length is 0
+


:art: Rewrite conditional

:art: Rewrite conditional

--- lib/user-utilities.coffee
+++ lib/user-utilities.coffee
@@ -57,8 +57,10 @@
   linuxVersionText: ->
     new Promise (resolve, reject) ->
       @linuxVersionInfo().then (info) ->
-        return "#{os.platform()} #{os.release()}" unless info.DistroName and info.DistroVersion
-        "#{info.DistroName} #{info.DistroVersion}"
+        if info.DistroName and info.DistroVersion
+          "#{info.DistroName} #{info.DistroVersion}"
+        else
+          "#{os.platform()} #{os.release()}"

   linuxVersionInfo: ->
     new Promise (resolve, reject) ->


Rename ::violationTooltip to ::tooltip in ViolationView

--- lib/violation-view.coffee
+++ lib/violation-view.coffee
@@ -40,7 +40,7 @@

   prepareTooltip: ->
     HTML = @violation.getHTML()
-    @violationTooltip
+    @tooltip
       title: HTML || @violation.message
       html: HTML?
       container: @lintView
@@ -99,7 +99,7 @@
           @scheduleDeferredShowHighlight()
       else
         @hideHighlight()
-        @violationTooltip('hide')
+        @tooltip('hide')

   isVisibleMarkerChange: (event) ->
     editorFirstVisibleRow = @editorView.getFirstVisibleScreenRow()
@@ -112,7 +112,7 @@
       if @isValid
         @toggleTooltipWithCursorPosition()
       else
-        @violationTooltip('hide')
+        @tooltip('hide')

   showHighlight: ->
     @updateHighlight()
@@ -154,9 +154,9 @@

     if cursorPosition.row is @screenStartPosition.row &&
        cursorPosition.column is @screenStartPosition.column
-      @violationTooltip('show')
+      @tooltip('show')
     else
-      @violationTooltip('hide')
+      @tooltip('hide')

   getCurrentBufferStartPosition: ->
     @editor.bufferPositionForScreenPosition(@screenStartPosition)
@@ -164,7 +164,7 @@
   getCurrentScreenRange: ->
     new Range(@screenStartPosition, @screenEndPosition)

-  violationTooltip: (option) ->
+  tooltip: (option) ->
     violationView = this
     @each ->
       $this = $(this)
@@ -179,4 +179,4 @@

   beforeRemove: ->
     @marker?.destroy()
-    @violationTooltip('destroy')
+    @tooltip('destroy')


Define views for Room management

Define views for Room management

--- assets/javascripts/views.js.coffee
+++ assets/javascripts/views.js.coffee
@@ -10,6 +10,14 @@
   layoutName: "settings"
   classNames: ["settings"]

-App.RoomsView = Ember.View.extend
+App.RoomsIndexView = Ember.View.extend
   layoutName: "settings"
   classNames: ["settings"]
+
+App.RoomsNewView = Ember.View.extend
+  layoutName: "settings"
+  classNames: ["settings"]
+
+App.RoomEditView = Ember.View.extend
+  layoutName: "settings"
+  classNames: ["settings"]


Add comments to ipkg grammar

Add comments to ipkg grammar

ipgk Files support idris style comments. Enable those in the grammar.

--- grammars/language-ipkg.cson
+++ grammars/language-ipkg.cson
@@ -3,6 +3,17 @@
 fileTypes: ['ipkg']
 patterns:
   [
+    {
+      name: 'comment.line.ipkg'
+      match: '(--).*$\n?'
+      comment: 'Line comment'
+    }
+    {
+      name: 'comment.block.ipkg'
+      begin: '\\{-'
+      end: '-\\}'
+      comment: 'Block comment'
+    }
     {
       name: 'keyword.control.ipkg'
       match: '\\b(package|opts|modules|sourcedir|makefile|objs|executable|main|libs|pkgs|tests)\\b'


Only click on first menu in stat admin if admin menu present

--- app/assets/javascripts/gricer_backend_jquery.js.coffee
+++ app/assets/javascripts/gricer_backend_jquery.js.coffee
@@ -322,7 +322,7 @@
   return false

 jQuery ->
-  $('#gricer-menu a:first').click();
+  $('#gricer-menu a:first')?.click();

   if $.datepicker != undefined
     dates = $('#gricer-from-field, #gricer-thru-field').datepicker


Filter now updates itself on page received rather than cards received

--- app/assets/javascripts/lib/components/filter.js.coffee
+++ app/assets/javascripts/lib/components/filter.js.coffee
@@ -23,7 +23,7 @@
     # Subscribe
     listen: ->

-      $(@config.LISTENER).on ':cards/received', (e, data) =>
+      $(@config.LISTENER).on ':page/received', (e, data) =>
         @_update(data)

       $(@config.LISTENER).on ':filter/reset', =>


Deleted the popstate hack to deal with the chrome bug (bug resolved)

--- app/assets/javascripts/lib/extends/pushstate.js.coffee
+++ app/assets/javascripts/lib/extends/pushstate.js.coffee
@@ -8,9 +8,6 @@
     state: {}

     constructor: (args = {}) ->
-      # Ignore initial popstate in chrome
-      # https://code.google.com/p/chromium/issues/detail?id=63040
-      @popStateFired = false
       @currentUrl = @getUrl()

       $.extend @config, args
@@ -53,9 +50,7 @@

     # WebKit fires a popstate event on document load
     _handlePopState: () ->
-      if !@popStateFired
-        @popStateFired = true
-        if @getUrl() is @currentUrl then return
+      if @getUrl() is @currentUrl then return
       @setUrl(@getUrl())

     _supportsHistory: ->


test the case where we have no results

test the case where we have no results

--- test/hubot-confluence_test.coffee
+++ test/hubot-confluence_test.coffee
@@ -3,6 +3,7 @@
 sinon = require 'sinon'
 nock = require 'nock'
 chai.use require 'sinon-chai'
+require 'coffee-script/register'

 Robot = require 'hubot/src/robot'
 TextMessage = require('hubot/src/message').TextMessage
@@ -156,6 +157,26 @@

     adapter.receive(new TextMessage chat_user, "how do I foo")

+  it 'test trigger search no results', (done) ->
+    body =
+      results: [
+      ]
+    base = "https://#{test_host}:#{test_port}"
+    path = "/wiki/rest/api/content/search"
+    params = "cql=type%3Dpage%20and%20space%3Dbar%20and%20title~%22foo%22"
+    full = "#{path}?#{params}"
+    nock(base).get(full).reply(200, JSON.stringify({}))
+    path = "/wiki/rest/api/content/search"
+    params = "cql=type%3Dpage%20and%20space%3Dbar%20and%20text~%22foo%22"
+    full = "#{path}?#{params}"
+    nock(base).get(full).reply(200, JSON.stringify({}))
+    adapter.on "send", (envelope, strings) ->
+      expect(strings[0]).to.string "No results"
+      done()
+
+    adapter.receive(new TextMessage chat_user, "how do I foo")
+
+
 describe 'test-require', ->
   it 'requries hubot-confluence', ->
     require('../src/hubot-confluence')


'Updating for version 0.12.5dev7'

'Updating for version 0.12.5dev7'

--- bokehjs/src/coffee/version.coffee
+++ bokehjs/src/coffee/version.coffee
@@ -1 +1 @@
-export version = '0.12.5dev6'
+export version = '0.12.5dev7'


fixing comment

fixing comment

Former-commit-id: f6fdb809d8d0a737235902fe14762f6b61b3e5de [formerly a1f8fc7d5f71e452600db9d6576eb0aba5ef380c] [formerly 851471e69ffa4a123ea7025f888824056ff28719 [formerly 60340439e64200c7640bbb1ada0a7eb1f65a92ef]] [formerly 59e7a6ff886a93c2289fd471d2960d2556c998bb [formerly dd6bcc157ef0ad68b75994561dca71a5d85fb0fb] [formerly 0034901a3ff6eb094bfef33d3f3b756f7c09bad6 [formerly 0034901a3ff6eb094bfef33d3f3b756f7c09bad6 [formerly 8d230b9bb810fe3b3f68530e368a30b27ed29fe8]]]]
Former-commit-id: d0e971f4241b4d760a2073352a4f36a0880087d4 [formerly 757e646490a8c2c39e4f20aa73e6c427263203f0] [formerly 31802890fb8e11ebff2e6a1c6ea4a6c8e934ef75 [formerly c4f0361e4e88a67737b5e24a5f0a17f106239507]]
Former-commit-id: b09539622df6acd2afd38c7d5d20b91e88019f18 [formerly 19088791e6dbf358db6a37bd4d3d9315b91a35dc]
Former-commit-id: 8edc6861fc8a98135e875875a504db3dfb446f1c
Former-commit-id: 8509868fcf6584d55a318b2490b7a4fbb272941c

--- test/tests/lurker_scenario.coffee
+++ test/tests/lurker_scenario.coffee
@@ -252,7 +252,7 @@
           @click(".dashboard-profile-activity-action a[data-id='#{entity_id}']")
           casper.wait 5000, ->
             @HTMLCapture 'body',
-              caption: 'User profile page, different sizes'
+              caption: 'Position page, accessed from profile'

             assert_position_open test


several other obvious disables -- see https://github.com/sagemathinc/cocalc/pull/3003

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -971,7 +971,7 @@
             </div>

     render_action_button: (name) ->
-        disabled = (name in ["move","compress","rename"] and @props.current_path?.startsWith(".snapshots"))
+        disabled = (name in ["move","compress","rename","delete","share","duplicate"] and @props.current_path?.startsWith(".snapshots"))
         obj = file_actions[name]
         get_basename = =>
             misc.path_split(@props.checked_files?.first()).tail


Item Continuous wheel improved

Item Continuous wheel improved

--- impl/css/level0/item.coffee
+++ impl/css/level0/item.coffee
@@ -63,16 +63,12 @@
 SIGNALS_ARGS =
 	'pointerWheel': do ->
 		REQUIRED_CHECKS = 200
-		REQUIRED_DELTA_CHECKS = 1000
 		NORMALIZED_VALUE = 120

 		checks = 0
 		lastX = lastY = 0

-		xDeltaChecks = 1
-		yDeltaChecks = 1
-		averageX = NORMALIZED_VALUE
-		averageY = NORMALIZED_VALUE
+		isSlowContinuous = false

 		getDeltas = (e) ->
 			e.preventDefault()
@@ -80,15 +76,6 @@
 			x = e.wheelDeltaX || 0
 			y = e.wheelDelta || -e.detail

-			if x isnt 0 and xDeltaChecks < REQUIRED_DELTA_CHECKS
-				averageX = (averageX * xDeltaChecks + Math.abs(x)) / ++xDeltaChecks
-
-			if y isnt 0 and yDeltaChecks < REQUIRED_DELTA_CHECKS
-				averageY = (averageY * yDeltaChecks + Math.abs(y)) / ++yDeltaChecks
-
-			x *= NORMALIZED_VALUE / averageX
-			y *= NORMALIZED_VALUE / averageY
-
 			x: x
 			y: y

@@ -100,7 +87,23 @@

 			r

-		continuousWheel = getDeltas
+		continuousWheel = (e) ->
+			deltas = getDeltas e
+
+			# MAGIC!
+			# It looks that Chrome on MacBook never gives values in range (-3, 3) as
+			# it does Firefox which always sends lower values
+			if not isSlowContinuous
+				delta = deltas.x or deltas.y or 3
+
+				if (delta > 0 and delta < 3) or (delta < 0 and delta > -3)
+					isSlowContinuous = true
+
+			if isSlowContinuous
+				deltas.x *= 25
+				deltas.y *= 25
+
+			deltas

 		(e) ->
 			x = e.wheelDeltaX || 0


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_selector.coffee
+++ src/engine_selector.coffee
@@ -16,6 +16,27 @@

   ###*
   Get the game object matching the given selector that is closest to the given position.
+
+    player = engine.add
+    x: 0
+    y: 0
+
+  enemy1 = engine.add
+    enemy: true
+    x: 10
+    y: 0
+
+  enemy2 = engine.add
+    enemy: true
+    x: 0
+    y: 15
+
+  player2 = engine.add
+    x: 0
+    y: 10
+
+  equals engine.closest(".enemy", player.position()), enemy1
+  equals engine.closest(".enemy", player2.position()), enemy2

   @param {String} selector
   @param {Point} position


There may be no piece of the other list has more pieces

--- src/trix/models/piece_list.coffee
+++ src/trix/models/piece_list.coffee
@@ -20,7 +20,7 @@
     return if @isEqualTo(pieceList)

     piecesAreEqual = (index, otherIndex) =>
-      @getPieceAtIndex(index).isEqualTo(pieceList.getPieceAtIndex(otherIndex ? index))
+      @getPieceAtIndex(index)?.isEqualTo(pieceList.getPieceAtIndex(otherIndex ? index))

     leftIndex = 0
     leftIndex++ while piecesAreEqual(leftIndex)


Show duplicate schools in position details.

Because schools as units were stored in a backbone
collection, duplicates were automatically removed.
But the same unit can provide different levels
of schooling, requiring the duplicates to remain.

--- src/views/position-details.coffee
+++ src/views/position-details.coffee
@@ -1,6 +1,7 @@
 define [
     'underscore',
     'jquery',
+    'backbone',
     'cs!app/models',
     'cs!app/map-view',
     'cs!app/views/base',
@@ -8,6 +9,7 @@
 ], (
     _,
     $,
+    Backbone,
     models,
     MapView,
     base,
@@ -44,8 +46,8 @@
                 'maternity_clinic_district',
                 'income_support_district',
                 'lower_comprehensive_school_district_fi',
+                'upper_comprehensive_school_district_fi',
                 'lower_comprehensive_school_district_sv',
-                'upper_comprehensive_school_district_fi',
                 'upper_comprehensive_school_district_sv',
                 'rescue_area',
                 'rescue_district',
@@ -120,7 +122,6 @@
             app.commands.execute 'setRadiusFilter', 750

         onRender: ->
-            @renderAdminDivs()
             @routeRegion.show new RouteView
                 model: @model
                 route: @route
@@ -128,18 +129,25 @@
                 routingParameters: @routingParameters
                 selectedUnits: null
                 selectedPosition: @selectedPosition
+            @renderAdminDivs()
         renderAdminDivs: ->
             divsWithUnits = @divList.filter (x) -> x.has('unit')
             emergencyDiv = @divList.find (x) ->
                 x.get('type') == 'emergency_care_district'
             if divsWithUnits.length > 0
-                units = new models.UnitList(
+                units = new Backbone.Collection(
                     divsWithUnits.map (x) ->
+                        # Ugly hack to allow duplicate
+                        # units in listing.
                         unit = new models.Unit x.get('unit')
-                        unit.set 'area', x
+                        unitData = unit.attributes
+                        storedId = unitData.id
+                        delete unitData.id
+                        unitData.storedId = storedId
+                        unitData.area = x
                         if x.get('type') == 'health_station_district'
-                            unit.set 'emergencyUnitId', emergencyDiv.getEmergencyCareUnit()
-                        unit
+                            unitData.emergencyUnitId = emergencyDiv.getEmergencyCareUnit()
+                        new Backbone.Model(unitData)
                 )
                 @areaServices.show new UnitListView
                     collection: units
@@ -201,8 +209,11 @@
         tagName: 'li'
         template: 'unit-list-item'
         serializeData: ->
-            data = super()
-            data
+            unless @model.get('storedId')
+                return super()
+            data = @model.toJSON()
+            data.id = @model.storedId
+            (new models.Unit(data)).toJSON()
         handleInnerClick: (ev) =>
             ev?.stopPropagation()
         handleClick: (ev) =>


Removed default cursor in web IO

Removed default cursor in web IO

--- src/io/web/renderer.coffee
+++ src/io/web/renderer.coffee
@@ -21,9 +21,7 @@

 		@tileSize = 32

-		@cursor =
-			x: 2
-			y: 2
+		@cursor = null

 		@logBox = document.getElementById 'log'


Less indirection

Less indirection

--- src/layout/controls.coffee
+++ src/layout/controls.coffee
@@ -1,10 +1,9 @@
 {my} = require '../my'
 {make} = require '../render/make'

-control_names = ["step", "run", "stop", "reset"]
 exports.controls = make.buttons(
   'control',
-  control_names,
+  ["step", "run", "stop", "reset"],
   my.control,
   (world, args) -> world.send world.get('value')
 )


Router: Call route callback if a callback exists.

--- src/lib/core/Router.coffee
+++ src/lib/core/Router.coffee
@@ -122,7 +122,7 @@
         token = token.replace /:/g, '' # replace the `:` for correct token name
         params[token] = values[index]  # match token with values

-      cb.call this, params, query
+      cb.call this, params, query if cb

   ###*


Remove OverlayManager from the LinesComponent

--- src/lines-component.coffee
+++ src/lines-component.coffee
@@ -4,7 +4,6 @@

 CursorsComponent = require './cursors-component'
 HighlightsComponent = require './highlights-component'
-OverlayManager = require './overlay-manager'

 DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
 AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
@@ -39,13 +38,6 @@
       insertionPoint = document.createElement('content')
       insertionPoint.setAttribute('select', '.overlayer')
       @domNode.appendChild(insertionPoint)
-
-      insertionPoint = document.createElement('content')
-      insertionPoint.setAttribute('select', 'atom-overlay')
-      @overlayManager = new OverlayManager(@presenter, @hostElement)
-      @domNode.appendChild(insertionPoint)
-    else
-      @overlayManager = new OverlayManager(@presenter, @domNode)

   updateSync: (state) ->
     @newState = state.content
@@ -81,8 +73,6 @@

     @cursorsComponent.updateSync(state)
     @highlightsComponent.updateSync(state)
-
-    @overlayManager?.render(state)

     @oldState.indentGuidesVisible = @newState.indentGuidesVisible
     @oldState.scrollWidth = @newState.scrollWidth


test for analysys of nonstable rules

test for analysys of nonstable rules

--- tests/test_analyze_pattern.coffee
+++ tests/test_analyze_pattern.coffee
@@ -64,3 +64,15 @@

     r1 = Cells.analyze r.cells, single_rot
     assert.deepEqual [r1.dx, r1.dy], [1,1]
+
+  it "must correctly detect patterns in rules with nonstable vacuum", ->
+    critters = Rules.from_list [15,14,13,3,11,5,6,1,7,9,10,2,12,4,8,0]
+
+    pattern = Cells.from_rle "$bo$2bo$2bo$bo" #The common )-spaceship
+
+    result = Cells.analyze pattern, critters
+    assert.equal result.dx, 2
+    assert.equal result.dy, 0
+
+    assert.equal result.period, 4
+    assert.equal result.cells.length, 4


Added event listeners cleanup

Added event listeners cleanup

--- src/io.coffee
+++ src/io.coffee
@@ -21,19 +21,24 @@
   _createFileSource: ->
     return @_tmpFilename if @_tmpFilename
     filename = tmpFilename()
-    promise = if Buffer.isBuffer @source
+    done = if Buffer.isBuffer @source
       nodefn.call fs.writeFile, filename, @source
     else if @source instanceof Stream
+      {resolve, reject, promise} = When.defer()
       deferred = When.defer()
       stream = fs.createWriteStream(filename)
-        .once('error', deferred.reject)
-        .once('close', deferred.resolve)
-        .once('finish', deferred.resolve)
+        .once('error', reject)
+        .once('close', resolve)
+        .once('finish', resolve)
       @source.pipe stream
-      deferred.promise
+      promise.tap ->
+        # Cleanup
+        stream.removeListener 'error', reject
+        stream.removeListener 'close', resolve
+        stream.removeListener 'finish', resolve
     else
       When.reject new Error 'Mailformed source'
-    @_tmpFilename = promise.then -> filename
+    @_tmpFilename = done.then -> filename

   _cleanup: (varname) ->
     return unless varname and promise = @[varname]
@@ -72,11 +77,14 @@
     bindCallback promise, next

   stream: (next) ->
-    done = false
-    cleanup = =>
-      return if done
-      done = true
-      @_cleanup '_tmpOutname'
+    _cleanup = @_cleanup.bind @, '_tmpOutname'
+    cleanup = ->
+      return if cleanup.done
+      cleanup.done = true
+      @removeListener 'error', cleanup
+      @removeListener 'close', cleanup
+      @removeListener 'end', cleanup
+      _cleanup()
     promise = @_writeTmp().then (outname) ->
       fs.createReadStream(outname)
         .once('error', cleanup)


 Merge branch 'master.groups.removeinvitationcode' into 'master.groups'

Master.Groups.Removeinvitationcode

--- client/Dashboard/views/groupsinvitationcodelistitemview.coffee
+++ client/Dashboard/views/groupsinvitationcodelistitemview.coffee
@@ -15,6 +15,11 @@
       style       : 'solid green'
       title       : 'Share'
       callback    : @bound 'showShareModal'
+
+    @deleteButton = new KDButtonView
+      style       : 'solid red'
+      title       : 'Delete'
+      callback    : @bound 'deleteInvitation'

     @statusText    = new KDCustomHTMLView
       partial     : ''
@@ -109,6 +114,31 @@
     @editButton.hide()
     @shareButton.hide()

+  deleteInvitation:->
+    {group, code} = @getData()
+    KD.remote.api.JInvitation.byCode code, (err, invitation) =>
+      return KD.showError err if err
+
+      modal                  = new KDModalViewWithForms
+        cssClass             : 'invitation-remove-modal'
+        title                : "Remove Invitation Code #{code}"
+        overlay              : yes
+        tabs                 :
+          forms              :
+            Remove           :
+              buttons        :
+                Confirm      :
+                  itemClass  : KDButtonView
+                  style      : 'modal-clean-green'
+                  loader     :
+                    color    : '#ffffff'
+                    diameter : 12
+                  callback   : =>
+                    invitation.remove (err) =>
+                      return KD.showError err if err
+                      modal.destroy()
+                      @destroy()
+
   viewAppended: JView::viewAppended

   pistachio:->
@@ -116,7 +146,7 @@
     codeSuffix = if memo then " (#{memo})" else ''
     """

-
{{> @shareButton}} {{> @editButton}}

+
{{> @deleteButton}} {{> @shareButton}} {{> @editButton}}

       {{> @statusText}}

#{code}#{codeSuffix}


FinderTreeController: Let the publish app works from app folder context menu

--- client/Finder/filetree/controllers/findertreecontroller.coffee
+++ client/Finder/filetree/controllers/findertreecontroller.coffee
@@ -384,18 +384,25 @@

       callback? err

-
-  # runApp:(nodeView, callback)->
-
-  #   folder = nodeView.getData()
-  #   folder.emit "fs.job.started"
-  #   kodingAppsController = KD.getSingleton 'kodingAppsController'
-
-  #   manifest = KodingAppsController.getManifestFromPath folder.path
-
-  #   KD.getSingleton("appManager").open manifest.name, =>
-  #     folder.emit "fs.job.finished"
-  #     callback?()
+  publishApp:(nodeView)->
+
+    folder = nodeView.getData()
+    folder.emit "fs.job.started"
+
+    KodingAppsController.createJApp path: folder.path, (err, app)->
+      folder.emit "fs.job.finished"
+
+      if err or not app
+        warn err
+        return new KDNotificationView
+          title : "Failed to publish"
+
+      new KDNotificationView
+        title: "Published successfully!"
+
+      KD.singletons
+        .router.handleRoute "/Apps/#{app.manifest.authorNick}/#{app.name}"
+

   cloneRepo: (nodeView) ->
     folder   = nodeView.getData()
@@ -403,29 +410,6 @@
       vmName : folder.vmName
       path   : folder.path
     modal.on "RepoClonedSuccessfully", => @notify "Repo cloned successfully.", "success"
-
-  # publishApp:(nodeView)->
-
-  #   folder = nodeView.getData()
-
-  #   folder.emit "fs.job.started"
-  #   KD.getSingleton('kodingAppsController').publishApp folder.path, (err)=>
-  #     folder.emit "fs.job.finished"
-  #     unless err
-  #       @notify "App published!", "success"
-  #     else
-  #       @notify "Publish failed!", "error", err
-  #       message = err.message or err
-  #       modal = new KDModalView
-  #         title        : "Publish failed!"
-  #         overlay      : yes
-  #         cssClass     : "new-kdmodal"
-  #         content      : "
#{message}
"
-  #         buttons      :
-  #           "Close"    :
-  #             style    : "modal-clean-gray"
-  #             callback : (event)->
-  #               modal.destroy()

   # makeNewApp:(nodeView)->
   #   KD.getSingleton('kodingAppsController').makeNewApp()
@@ -477,8 +461,8 @@
   cmOpenFile:      (nodeView, contextMenuItem)-> @openFile nodeView
   cmPreviewFile:   (nodeView, contextMenuItem)-> @previewFile nodeView
   # cmMakeNewApp:    (nodeView, contextMenuItem)-> @makeNewApp nodeView
-  # cmPublish:       (nodeView, contextMenuItem)-> @publishApp nodeView
   cmCompile:       (nodeView, contextMenuItem)-> @compileApp nodeView
+  cmPublish:       (nodeView, contextMenuItem)-> @publishApp nodeView
   cmOpenFileWithApp: (nodeView, contextMenuItem)-> @openFileWithApp  nodeView, contextMenuItem
   cmCloneRepo:     (nodeView, contextMenuItem)-> @cloneRepo nodeView
   cmDropboxChooser:(nodeView, contextMenuItem)-> @chooseFromDropbox nodeView


wtf > remove some eases.

wtf > remove some eases.

--- wtf/src/app.coffee
+++ wtf/src/app.coffee
@@ -89,10 +89,7 @@
     'linear',
     'quad',
     'cubic',
-    'sin',
-    'exp',
-    'circle',
-    'bounce'
+    'exp'
 ]
 treeCount = 0
 randomTree = (x, y) ->


Add more dependencies.

Add more dependencies.

--- .atom/packages.cson
+++ .atom/packages.cson
@@ -1,4 +1,5 @@
 packages: [
+  "atom-beautify"
   "atom-ide-ui"
   "atom-python-virtualenv"
   "autocomplete-python"
@@ -7,6 +8,7 @@
   "busy-signal"
   "editorconfig"
   "file-icons"
+  "highlight-selected"
   "hyperclick"
   "hyperclick-robot-framework"
   "ide-flowtype"
@@ -19,6 +21,7 @@
   "language-docker"
   "language-gitignore"
   "language-gradle"
+  "language-ini"
   "language-restructuredtext"
   "language-robot-framework"
   "language-twig"


improved syntax highlighting and indent of keywords

--- client/codemirror-apl-mode.coffee
+++ client/codemirror-apl-mode.coffee
@@ -47,7 +47,7 @@

 CodeMirror.defineMIME 'text/apl', 'apl'
 CodeMirror.defineMode 'apl', (config) -> # https://codemirror.net/doc/manual.html#modeapi
-  startState: -> isHeader: 1, stack: '', dfnDepth: 0
+  startState: -> isHeader: 1, stack: '', dfnDepth: 0, kwStack: []
   token: (stream, state) ->
     if state.isHeader
       delete state.isHeader; stream.match /[^⍝\n\r]*/; s = stream.current()
@@ -83,7 +83,23 @@
       else if /[\+\-×÷⌈⌊\|⍳\?\*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢→]/.test c then 'apl-fn'
       else if state.dfnDepth && /[⍺⍵∇:]/.test c then "apl-dfn apl-dfn#{state.dfnDepth}"
       else if c == '∇' then state.isHeader = 1; 'apl-trad'
-      else if c == ':' then (if stream.match(/\w*/)?[0]?.toLowerCase() in keywords then 'apl-kw' else 'apl-err')
+      else if c == ':'
+        kw = stream.match(/\w*/)?[0]?.toLowerCase()
+        ok = 0
+        if kw in keywords
+          if kw in ['class', 'for', 'hold', 'if', 'interface', 'namespace',
+                    'property', 'repeat', 'section', 'select', 'trap', 'while', 'with']
+            state.kwStack.push kw; ok = 1
+          else if /^end/.test kw
+            kw0 = kw[3..]; i = state.kwStack.lastIndexOf kw0; ok = i == state.kwStack.length - 1
+            i >= 0 && state.kwStack.splice i
+          else if kw in ['else', 'elseif', 'andif', 'orif']
+            ok = state.kwStack[-1..][0] == 'if'
+          else if kw == 'in'
+            ok = state.kwStack[-1..][0] == 'for'
+          else
+            ok = 1
+        ok && 'apl-kw' || 'apl-err'
       else if c == '⎕' then (if stream.match(/[áa-z0-9]*/i)?[0].toLowerCase() in quadNames then 'apl-quad' else 'apl-err')
       else if c == '⍞' then 'apl-quad'
       else if c == '#' then 'apl-ns'
@@ -94,4 +110,11 @@
         else 'apl-glb'
       else 'apl-err'

-  indent: (state, textAfter) -> config.indentUnit * (state.dfnDepth - /^\s*\}/.test textAfter)
+  electricInput: /:(?:else|end|andif|orif)$/
+
+  indent: (state, textAfter) ->
+    config.indentUnit *
+      if state.dfnDepth
+        state.dfnDepth - /^\s*\}/.test textAfter
+      else
+        state.kwStack.length - /^\s*:(?:end|else|andif|orif)/i.test textAfter


IDE: fixed whitespace problems

IDE: fixed whitespace problems

--- client/ide/RealTimeManager.coffee
+++ client/ide/RealTimeManager.coffee
@@ -158,7 +158,7 @@
     return  if @isDisposed
     @emit 'TextInsertedIntoString', string, e

-  textDeleted:(string, e)=>
+  textDeleted:(string, e) ->
     return  if @isDisposed
     @emit 'TextDeletedFromString', string, e

@@ -181,15 +181,15 @@
   unbindMapListeners: (map) ->
     map.removeEventListener gapi.drive.realtime.EventType.VALUE_CHANGED, @binder map, @mapValueChanged

-  listValueAdded: (list, v)->
+  listValueAdded: (list, v) ->
     return  if @isDisposed
     @emit 'ValuesAddedToList', list, v

-  listValueRemoved: (list, v)->
+  listValueRemoved: (list, v) ->
     return  if @isDisposed
     @emit 'ValuesRemovedFromList', list, v

-  listValueSet: (list, e)->
+  listValueSet: (list, e) ->
     return  if @isDisposed
     @emit 'ListValuesSet', list, e

@@ -207,7 +207,7 @@
   # values holds the event handlers of the collaborative objects
   values = {}

-  binder: (collaborativeObj, callback)->
+  binder: (collaborativeObj, callback) ->
     # all kind of collaborativeObjs have id
     throw new Error "id is not set" if not collaborativeObj.id


Don't include value attribute if option isn't set

--- client/lanes/views/Helpers.coffee
+++ client/lanes/views/Helpers.coffee
@@ -57,8 +57,9 @@
         impl.grid_widths(widths);

     text_field: (name,options={})->
+        value = if options.value then " value=#{options.value}" else ''
         impl.field(name, options,
-            "<input type='#{options.type || 'text'}' name='#{name}' value='#{options.value}'>"
+            "<input type='#{options.type || 'text'}' name='#{name}'#{value}>"
             "

"
         )


refactor room_list helpers

refactor room_list helpers

--- client/room_list/room_list.coffee
+++ client/room_list/room_list.coffee
@@ -1,19 +1,20 @@
-Template.room_list.room = ->
-  query = Session.get 'search-query'
-  filter = new RegExp query, 'i'
-  Rooms.find { displayName: filter }, { sort: { displayName: 1 } }
+Template.room_list.helpers
+  room: ->
+    query = Session.get 'search-query'
+    filter = new RegExp query, 'i'
+    Rooms.find { displayName: filter }, { sort: { displayName: 1 } }

 Template.room_list.rendered = ->
   @$('[data-toggle="tooltip"]').tooltip()

-Template.room_summary.thumbnail = ->
-  switch Session.get 'view'
-    when 'view-screen'
-      @presentationVideo
-    when 'view-camera'
-      @presenterVideo
-    when 'view-galicaster'
-      @screen
-
-Template.room_summary.zoom = ->
-  Session.get 'zoom'
+Template.room_summary.helpers
+  thumbnail: ->
+    switch Session.get 'view'
+      when 'view-screen'
+        @presentationVideo
+      when 'view-camera'
+        @presenterVideo
+      when 'view-galicaster'
+        @screen
+  zoom: ->
+    Session.get 'zoom'


Remove post to slack utility function call

Signed-off-by: Sonmez Kartal <d2d15c3d37396a5f2a09f8b42cf5f27d43a3fbde@koding.com>

--- client/test/lib/utils/fail.coffee
+++ client/test/lib/utils/fail.coffee
@@ -35,61 +35,41 @@
             s3path = "https://kodingdev-test-data.s3.amazonaws.com/#{filename}"
             console.log ' ✔ Test screenshot uploaded to', s3path

+            { CI, WERCKER_STARTED_BY, WERCKER_GIT_BRANCH, WERCKER_BUILD_URL } = process.env
+
             try

-              { CI, WERCKER_STARTED_BY, WERCKER_GIT_BRANCH, WERCKER_BUILD_URL } = process.env
+              logString = ''

-              if CI
+              @client.api.getLog 'browser', (logs) =>

-                helpers = require '../helpers/helpers.js'
+                try

-                { selector, ms, _stackTrace } = this
-                message     = "#{defaultMsg.replace('%s', selector).replace('%d', ms)} - expected: #{expected} but got: #{actual}"
-                information = "Test *#{test.name}* in *#{test.module}* suite failed on #{WERCKER_STARTED_BY}'s *#{WERCKER_GIT_BRANCH}* branch. Build url is #{WERCKER_BUILD_URL}"
+                  logString += "#{log.level} #{log.message}\n"  for log in logs

-                helpers.postToSlack
-                  text    : "#{information} ```#{message}``` ```#{_stackTrace}``` #{s3path}"
-                  channel : 'failed-tests'
+                  s3 = new AWS.S3 { params:
+                    Key    : "console.log-#{test.module}-#{test.name}-#{Date.now()}.log"
+                    Bucket : 'kodingdev-test-data'
+                  }

-              try
-
-                logString = ''
-
-                @client.api.getLog 'browser', (logs) =>
-
-                  try
-
-                    logString += "#{log.level} #{log.message}\n"  for log in logs
-
-                    s3 = new AWS.S3 { params:
-                      Key    : "console.log-#{test.module}-#{test.name}-#{Date.now()}.log"
-                      Bucket : 'kodingdev-test-data'
-                    }
-
-                    if logString.length
-                      s3.upload { Body: logString }, (err, res) =>
-                        NW_ORG_FAIL.call this, result, actual, expected, defaultMsg
-                        msg = if err then ' ✖ Unable to write console log to S3.' else " ✔ Console log saved to S3. #{res.Location}"
-                        console.log msg
-                    else
-                      console.log ' ✖ There was no browser log available...'
+                  if logString.length
+                    s3.upload { Body: logString }, (err, res) =>
                       NW_ORG_FAIL.call this, result, actual, expected, defaultMsg
-
-                  catch e
-
-                    console.log ' ✖ Failed to upload browser logs to s3.', e
+                      msg = if err then ' ✖ Unable to write console log to S3.' else " ✔ Console log saved to S3. #{res.Location}"
+                      console.log msg
+                  else
+                    console.log ' ✖ There was no browser log available...'
                     NW_ORG_FAIL.call this, result, actual, expected, defaultMsg

-              catch e
+                catch e

-                console.log ' ✖ Failed to get browser logs.', e
-                NW_ORG_FAIL.call this, result, actual, expected, defaultMsg
+                  console.log ' ✖ Failed to upload browser logs to s3.', e
+                  NW_ORG_FAIL.call this, result, actual, expected, defaultMsg

             catch e

-              console.log ' ✖ Failed to post a Slack message.', e
+              console.log ' ✖ Failed to get browser logs.', e
               NW_ORG_FAIL.call this, result, actual, expected, defaultMsg
-

       catch e


Increase Casper timeout

Increase Casper timeout

--- client/tests/casper/common.coffee
+++ client/tests/casper/common.coffee
@@ -59,7 +59,7 @@
         casper.options.verbose = true
         casper.options.logLevel = 'debug'
     casper.options.waitTimeout = 15000
-    casper.options.timeout = 30000
+    casper.options.timeout = 120000
     casper.options.viewportSize = {width: 1024, height: 768}
     casper.on 'exit', (res) ->
         if res isnt 0 or dev


Added page test for redirects

Added page test for redirects

--- test/javascripts/page_test.coffee
+++ test/javascripts/page_test.coffee
@@ -65,3 +65,22 @@
       assert loadPageStub.calledOnce
       assert loadPageStub.calledWith null, "placeholder for an XHR", true, afterRefreshCallback, ['a', 'b']
       loadPageStub.restore()
+
+    it 'updates window push state when response is a redirect', ->
+      replaceStateStub = stub(Turbolinks, "replaceState")
+
+      mockXHR = {
+        getResponseHeader: (header) ->
+          if header == 'Content-Type'
+            return "text/html"
+          else if header == 'X-XHR-Redirected-To'
+            return "http://www.test.com/redirect"
+          ""
+        status: 302
+        responseText: "
redirected
"
+      }
+      Page.refresh
+        response: mockXHR,
+        onlyKeys: ['a']
+
+      replaceStateStub.calledWith mockXHR.getResponseHeader('X-XHR-Redirected-To')


test: Add unit tests for setup token support

--- test/unit/AnvilConnectKeys.coffee
+++ test/unit/AnvilConnectKeys.coffee
@@ -3,6 +3,7 @@
 fs = require 'fs'
 path = require 'path'
 sinon = require 'sinon'
+crypto = require 'crypto'
 mkdirp = require 'mkdirp'
 childProcess = require 'child_process'
 pemjwk = require 'pem-jwk'
@@ -336,3 +337,165 @@
           alg: 'RS256'
           n: 3
           e: 4
+
+  describe 'generate setup token (static)', ->
+
+    describe 'with sufficient access rights', ->
+      {token} = {}
+
+      before ->
+        sinon.stub mkdirp, 'sync'
+        sinon.stub fs, 'writeFileSync'
+        sinon.stub crypto, 'randomBytes', -> 'SETUP TOKEN'
+        token = AnvilConnectKeys.generateSetupToken '/test/setup.token'
+
+      after ->
+        mkdirp.sync.restore()
+        fs.writeFileSync.restore()
+        crypto.randomBytes.restore()
+
+      it 'should create the key folder if needed', ->
+        mkdirp.sync.should.have.been.calledWith '/test'
+
+      it 'should generate a 256-byte random token', ->
+        crypto.randomBytes.should.have.been.calledWith 256
+
+      it 'should save the token', ->
+        fs.writeFileSync.should.have.been.calledWith(
+          '/test/setup.token', 'SETUP TOKEN', 'utf8'
+        )
+
+      it 'should return the token', ->
+        token.should.equal 'SETUP TOKEN'
+
+    describe 'with insufficient access rights', ->
+      {err} = {}
+
+      before ->
+        sinon.stub(mkdirp, 'sync').throws()
+        sinon.stub fs, 'writeFileSync'
+        sinon.stub crypto, 'randomBytes', -> 'SETUP TOKEN'
+        try
+          token = AnvilConnectKeys.generateSetupToken '/test/setup.token'
+        catch e
+          err = e
+
+      after ->
+        mkdirp.sync.restore()
+        fs.writeFileSync.restore()
+        crypto.randomBytes.restore()
+
+      it 'should throw an error', ->
+        err.should.be.an.instanceof Error
+
+  describe 'load setup token (static)', ->
+
+    describe 'with sufficient access rights', ->
+      {token} = {}
+
+      before ->
+        sinon.stub fs, 'readFileSync', -> 'SETUP TOKEN'
+        token = AnvilConnectKeys.loadSetupToken '/test/setup.token'
+
+      after ->
+        fs.readFileSync.restore()
+
+      it 'should load the token', ->
+        fs.readFileSync.should.have.been.calledWith '/test/setup.token', 'utf8'
+
+      it 'should return the token', ->
+        token.should.equal 'SETUP TOKEN'
+
+    describe 'with insufficient access rights', ->
+      {err} = {}
+
+      before ->
+        sinon.stub(mkdirp, 'sync').throws()
+        sinon.stub fs, 'readFileSync'
+        try
+          token = AnvilConnectKeys.readFileSync '/test/setup.token'
+        catch e
+          err = e
+
+      after ->
+        mkdirp.sync.restore()
+        fs.readFileSync.restore()
+
+      it 'should throw an error', ->
+        err.should.be.an.instanceof Error
+
+  describe 'generate setup token (instance)', ->
+
+    describe 'with no errors during token generation', ->
+      {token} = {}
+
+      before ->
+        sinon.stub AnvilConnectKeys, 'generateSetupToken', -> 'SETUP TOKEN'
+        connectKeys = new AnvilConnectKeys '/test'
+        token = connectKeys.generateSetupToken()
+
+      after ->
+        AnvilConnectKeys.generateSetupToken.restore()
+
+      it 'should generate a token', ->
+        AnvilConnectKeys.generateSetupToken.should.have.been.calledWith(
+          '/test/keys/setup.token'
+        )
+
+      it 'should return the token', ->
+        token.should.equal 'SETUP TOKEN'
+
+    describe 'with errors during token generation', ->
+      {err} = {}
+
+      before ->
+        sinon.stub(AnvilConnectKeys, 'generateSetupToken').throws()
+        connectKeys = new AnvilConnectKeys '/test'
+        try
+          token = connectKeys.generateSetupToken()
+        catch e
+          err = e
+
+      after ->
+        AnvilConnectKeys.generateSetupToken.restore()
+
+      it 'should not trap the error', ->
+        err.should.be.an.instanceof Error
+
+  describe 'load setup token (instance)', ->
+
+    describe 'with sufficient access rights', ->
+      {token} = {}
+
+      before ->
+        sinon.stub AnvilConnectKeys, 'loadSetupToken', -> 'SETUP TOKEN'
+        connectKeys = new AnvilConnectKeys '/test'
+        token = connectKeys.loadSetupToken()
+
+      after ->
+        AnvilConnectKeys.loadSetupToken.restore()
+
+      it 'should load the token', ->
+        AnvilConnectKeys.loadSetupToken.should.have.been.calledWith(
+          '/test/keys/setup.token'
+        )
+
+      it 'should return the token', ->
+        token.should.equal 'SETUP TOKEN'
+
+    describe 'with errors during token loading', ->
+      {err} = {}
+
+      before ->
+        sinon.stub(AnvilConnectKeys, 'loadSetupToken').throws()
+        connectKeys = new AnvilConnectKeys '/test'
+        try
+          token = connectKeys.loadSetupToken()
+        catch e
+          err = e
+
+      after ->
+        AnvilConnectKeys.loadSetupToken.restore()
+
+      it 'should not trap the error', ->
+        err.should.be.an.instanceof Error


new config file structure

new config file structure

--- testApp/config/application.coffee
+++ testApp/config/application.coffee
@@ -1,5 +1,3 @@
-Steroids = require "steroids"
-
 Steroids.config.name = "Steroids"
 Steroids.config.location = "views/layers/index.html"

@@ -41,6 +39,4 @@
 #Steroids.config.tabBar.selectedTabTintColor = ""
 #Steroids.config.tabBar.selectedTabBackgroundImage = ""

-#Steroids.config.theme = "black"
-
-module.exports = Steroids.config+#Steroids.config.theme = "black"


fix(authWebsite): pass correct arguments to `window.open`

--- src/vk.coffee
+++ src/vk.coffee
@@ -28,7 +28,7 @@
             windowStyle: windowStyle
             redirectUrl: "close.html"

-          popup = window.open authUrl, "vk-api-auth", "width:700,height=600"
+          popup = window.open authUrl, "vk-api-auth", "width=700,height=600"

           intervalHandler = window.setInterval =>
             if popup.closed


neo4j: /-/cache/latest returns time in ISO format

--- server/lib/server/graphdecorator.coffee
+++ server/lib/server/graphdecorator.coffee
@@ -37,12 +37,16 @@
   @extractSingleActivityOverview:(datum)->

     overview =
-      createdAt : [datum.meta.createdAt]
+      createdAt : [@convertToISO(datum.meta.createdAt)]
       ids       : [datum.id]
       type      : datum.name
       count     : 1

     return overview
+
+  @convertToISO: (time)->
+    t = new Date(time)
+    return t.toISOString()

   @extractSingleActivity:(datum)->

@@ -55,12 +59,16 @@
       type       : datum.name
       originId   : datum.originId
       originType : datum.originType
-      createdAt  : datum.meta.createdAt
-      modifiedAt : datum.meta.modifiedAt
+      createdAt  : @convertToISO datum.meta.createdAt
+      modifiedAt : @convertToISO datum.meta.modifiedAt
       sorts :
         repliesCount  : repliesCount
         likesCount    : likesCount
         followerCount : followerCount
+
+    snapshotMeta            = datum.meta
+    snapshotMeta.createdAt  = @convertToISO datum.meta.createdAt
+    snapshotMeta.modifiedAt = @convertToISO datum.meta.modifiedAt

     snapshot =
       _id               : datum._id
@@ -70,7 +78,7 @@
       slug_             : datum.slug_
       originId          : datum.originId
       originType        : datum.originType
-      meta              : datum.meta
+      meta              : snapshotMeta
       body              : datum.body
       attachments       : datum.attachments
       repliesCount      : repliesCount


Updated tests to af9cfe (cache off by default).

--- test.coffee
+++ test.coffee
@@ -66,13 +66,13 @@
     template: 'h1 "harcoded: " + obj.foo'
     run: ->
       obj = {foo: 'bar'}
+      @compiled = ck.compile(@template, hardcode: {obj})
       @expected = '
harcoded: bar
'
-      @result = render(@template, hardcode: {obj})
+      @result = @compiled()
       @success = @result is @expected
       if @success
         obj.foo = 'baz'
-        @expected = '
harcoded: bar
'
-        @result = render(@template, hardcode: {obj})
+        @result = @compiled()
         @success = @result is @expected

   'Local vars, hard-coded (functions)':


Remove direct references to console methods, wrap them with functions

--- lib/core/kd.coffee
+++ lib/core/kd.coffee
@@ -59,12 +59,12 @@
     inst.on 'KDObjectWillBeDestroyed', => delete instancesToBeTested[key]

   noop: ->
-  log: console.log.bind console
-  warn: console.warn.bind console
-  error: console.error.bind console
-  info: console.info.bind console
-  time: console.time.bind console
-  timeEnd: console.timeEnd.bind console
+  log: -> console.log.apply console, arguments
+  warn: -> console.warn.apply console, arguments
+  error: -> console.error.apply console, arguments
+  info: -> console.info.apply console, arguments
+  time: -> console.time.apply console, arguments
+  timeEnd: -> console.timeEnd.apply console, arguments

   debugStates: debugStates
   instances: instances


Remove testing related functions on kd

Remove testing related functions on kd

--- lib/core/kd.coffee
+++ lib/core/kd.coffee
@@ -54,13 +54,6 @@

   getAllKDInstances: -> instances

-  getInstanceForTesting: (key) -> instancesToBeTested[key]
-
-  registerInstanceForTesting: (inst) ->
-    key = inst.getOption 'testPath'
-    instancesToBeTested[key] = inst
-    inst.on 'KDObjectWillBeDestroyed', => delete instancesToBeTested[key]
-
   noop: ->
   log: -> console.log.apply console, arguments
   warn: -> console.warn.apply console, arguments


paypal: fix return url

paypal: fix return url

--- servers/lib/server/paypal_return.coffee
+++ servers/lib/server/paypal_return.coffee
@@ -40,4 +40,4 @@
       return templateFn err  if err

       params = { token, accountId : account._id }
-      post "/payments/paypal/return", params, (err)-> templateFn err, res
+      post "/api/social/payments/paypal/return", params, (err)-> templateFn err, res


Fix spelling and grammar

Fix spelling and grammar

--- lib/helpers.coffee
+++ lib/helpers.coffee
@@ -47,7 +47,7 @@
     return rootDir

   ###*
-   * Checks to see if a config file exists in the projects root directory if a root directory exists
+   * Checks to see if a config file exists in the project's root directory if a root directory exists
    * @param {string|null} dir - The current project root or null if a project doesn't exist
    * @param {string} configExt - The Sass-lint config extension
    * @return {string|null} The path to the config file if located in the project root, null if it doesn't exist
@@ -67,11 +67,11 @@
     return rootDir

   ###*
-   * Looks for an returns the path to a projects config file or null if it can't be found or doesn't exist
+   * Looks for and returns the path to a projects config file or null if it can't be found or doesn't exist
    * @param {Object} editor - An editor instance
    * @param {string} filePath - The currently active editor file path
    * @param {string} configExt - The Sass-lint config extension
-   * @param {boolean} noRootConfigDisable - The user specified option to disable linter-sass-lint if no config
+   * @param {boolean} noRootConfDisable - The user specified option to disable linter-sass-lint if no config
    *                 is found in the root of the project
    * @return {string|null} The path to the config file or null if not found
    ###


fix product detail css for small windows

--- public/views/product_detail_view.coffee
+++ public/views/product_detail_view.coffee
@@ -67,7 +67,8 @@
       }
       .epages-shop-overlay-box {
         float: left;
-        width: 500px;
+        min-width: 400px;
+        max-width: 490px;
       }
       .epages-shop-overlay-product-availability-OnStock { color: green; }
       .epages-shop-overlay-product-availability-WarnStock { color: orange; }