[client][buttonmanager] enable ranking when creating buttons

--- src/frontend/buttonManager.coffee
+++ src/frontend/buttonManager.coffee
@@ -30,12 +30,17 @@
             newButton.click params.callback

     createOuterButton : (name, icon, rank = -1) =>
+        @_jqContainer.accordion 'destroy'
         newButton = $ '<span>', { class : 'headerAcc white bordered shadowed' }
         newButton.text name
         ($ '<i>', { class : "icon-#{icon}" }).prependTo newButton
-        newButton.appendTo @_jqContainer
-        @_jqContainer.accordion 'refresh'
+        buttons   = @_jqContainer.children 'span.headerAcc'
+        if rank is -1 or rank >= buttons.length
+            newButton.appendTo @_jqContainer
+        else
+            ($ buttons[rank]).before newButton
         newButton.after ($ '<ul>', { class : 'white' })
+        @_jqContainer.accordion { active: false, collapsible: true, heightStyle: 'content', icons: off }

     createInnerButton : (outer, name, icon, rank = -1) =>
         return


Fix #3410, #3182: Allow regex to start with space or =

A regex may not follow a specific set of tokens. These were already known before
in the `NOT_REGEX` and `NOT_SPACED_REGEX` arrays. (However, I've refactored them
to be more correct and to add a few missing tokens). In all other cases (except
after a spaced callable) a slash is the start of a regex, and may now start with
a space or an equals sign. It’s really that simple!

A slash after a spaced callable is the only ambigous case. We cannot know if
that's division or function application with a regex as the argument. The
spacing determines which is which:

Space on both sides:
- `a / b/i`  -> `a / b / i`
- `a /= b/i` -> `a /= b / i`

No spaces:
- `a/b/i`    -> `a / b / i`
- `a/=b/i`   -> `a /= b / i`

Space on the right side:
- `a/ b/i`   -> `a / b / i`
- `a/= b/i`  -> `a /= b / i`

Space on the left side:
- `a /b/i`   -> `a(/b/i)`
- `a /=b/i`  -> `a(/=b/i)`

The last case used to compile to `a /= b / i`, but that has been changed to be
consistent with the `/` operator. The last case really looks like a regex, so it
should be parsed as one.

Moreover, you may now also space the `/` and `/=` operators with other
whitespace characters than a space (such as tabs and non-breaking spaces) for
consistency.

Lastly, unclosed regexes are now reported as such, instead of generating some
other confusing error message.

It should perhaps also be noted that apart from escaping (such as `a /\ b/`) you
may now also use parentheses to disambiguate division and regex: `a (/ b/)`. See
https://github.com/jashkenas/coffeescript/issues/3182#issuecomment-26688427.

--- lexer.coffee
+++ lexer.coffee
@@ -258,10 +258,15 @@
       when @chunk[...3] is '///'
         {tokens, index} = @matchWithInterpolations @chunk[3..], HEREGEX, '///', 3
       when match = REGEX.exec @chunk
-        [regex] = match
+        [regex, closed] = match
         index = regex.length
         prev = last @tokens
-        return 0 if prev and (prev[0] in (if prev.spaced then NOT_REGEX else NOT_SPACED_REGEX))
+        if prev
+          if prev.spaced and prev[0] in CALLABLE
+            return 0 if not closed or POSSIBLY_DIVISION.test regex
+          else if prev[0] in NOT_REGEX
+            return 0
+        @error 'missing / (unclosed regex)' unless closed
       else
         return 0

@@ -776,13 +781,13 @@

 # Regex-matching-regexes.
 REGEX = /// ^
-  / (?! [\s=] ) (    # disallow leading whitespace or equals sign
+  / (?!/) (
   ?: [^ [ / \n \\ ]  # every other thing
    | \\.             # anything (but newlines) escaped
    | \[              # character class
        (?: \\. | [^ \] \n \\ ] )*
      ]
-  )+ /
+  )* (/)?
 ///

 REGEX_FLAGS  = /^\w*/
@@ -798,6 +803,8 @@

 REGEX_ILLEGAL = /// ^ ( / | /{3}\s*) (\*) ///

+POSSIBLY_DIVISION   = /// ^ /=?\s ///
+
 # Other regexes.
 MULTILINER          = /\n/g

@@ -841,23 +848,17 @@
 # Boolean tokens.
 BOOL = ['TRUE', 'FALSE']

-# Tokens which a regular expression will never immediately follow, but which
-# a division operator might.
-#
-# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
-#
-# Our list is shorter, due to sans-parentheses method calls.
-NOT_REGEX = ['NUMBER', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '++', '--']
-
-# If the previous token is not spaced, there are more preceding tokens that
-# force a division parse:
-NOT_SPACED_REGEX = NOT_REGEX.concat ')', '}', 'THIS', 'IDENTIFIER', 'STRING', ']'
-
 # Tokens which could legitimately be invoked or indexed. An opening
 # parentheses or bracket following these tokens will be recorded as the start
 # of a function invocation or indexing operation.
-CALLABLE  = ['IDENTIFIER', 'STRING', 'REGEX', ')', ']', '}', '?', '::', '@', 'THIS', 'SUPER']
-INDEXABLE = CALLABLE.concat 'NUMBER', 'BOOL', 'NULL', 'UNDEFINED'
+CALLABLE  = ['IDENTIFIER', ')', ']', '?', '@', 'THIS', 'SUPER']
+INDEXABLE = CALLABLE.concat ['NUMBER', 'STRING', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '}', '::']
+
+# Tokens which a regular expression will never immediately follow (except spaced
+# CALLABLEs in some cases), but which a division operator can.
+#
+# See: http://www-archive.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
+NOT_REGEX = INDEXABLE.concat ['++', '--']

 # Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
 # occurs at the start of a line. We disambiguate these from trailing whens to


"cleaner" way to call delay

"cleaner" way to call delay

--- src/dt-jquery.coffee
+++ src/dt-jquery.coffee
@@ -5,13 +5,14 @@
 #       http://blog.stevenlevithan.com/archives/faster-than-innerhtml

 # delay or invoke job immediately
-delay = (el, job) ->
+delay = (job) ->
     # only when tag is ready
-    if el._jquery?
+    if @_jquery?
         do job
     else
-        el._jquery_delay ?= []
-        el._jquery_delay.push(job)
+        @_jquery_delay ?= []
+        @_jquery_delay.push(job)
+

 jqueryify = (tpl) ->
     tpl.xml._jquery = $()
@@ -24,7 +25,7 @@
                 do job
             delete el._jquery_delay
         # insert into parent
-        delay el.parent, ->
+        delay.call el.parent, ->
             if el.parent is tpl.xml
                 el.parent._jquery = el.parent._jquery.add(el._jquery)
             else
@@ -37,16 +38,16 @@
                 return # dont return emit result (which is either true or false)

     tpl.on 'text', (el, text) ->
-        delay el, ->
+        delay.call el, ->
             el._jquery.text(text)

     tpl.on 'attr', (el, key, value) ->
-        delay el, ->
+        delay.call el, ->
             el._jquery.attr(key, value)

     tpl.on 'attr:remove', (el, key) ->
-        delay el, ->
+        delay.call el, ->
             el._jquery.removeAttr(key)

     tpl.on 'remove', (el) ->


add cell number to fix app tests

add cell number to fix app tests

--- src/notebook.coffee
+++ src/notebook.coffee
@@ -201,13 +201,13 @@
     InputCellComponent = React.createClass
       render: ->
         # TODO handle hiding
-        React.DOM.div {className: 'cell input'}, [
+        React.DOM.div {className: 'cell input', 'data-cell-number': @props.cell.number}, [
           React.DOM.span {className: 'permalink', onClick: @permalink_link_clicked}, 'link'
-          React.DOM.div {className: 'code'}
+          React.DOM.div {className: 'code', ref: 'code'}
         ]
-      componentDidMount: (node) ->
+      componentDidMount: ->
         editor = @props.cell.editor
-        node.appendChild editor.display.wrapper
+        @refs.code.getDOMNode().appendChild editor.display.wrapper
         editor.refresh()

       permalink_link_clicked: -> generate_permalink @props.cell
@@ -250,19 +250,19 @@
       set_component_list: (@component_list) ->
         @setState component_list: @component_list if @state
       getInitialState: -> component_list: @component_list
-      render: -> React.DOM.div {className: 'cell output clean'}, @state.component_list
+      render: -> React.DOM.div {className: 'cell output clean', 'data-cell-number': @props.cell.number}, @state.component_list

     create_output_cell = (notebook) ->
       number = notebook.output_number++

       cell =
         type: 'output'
-        component: OutputCellComponent()
         visible: true
         active: true
         notebook: notebook
         number: number

+      cell.component = OutputCellComponent {cell}
       cell

     run = (input_cell) ->


console not always available

console not always available

--- src/opensong.coffee
+++ src/opensong.coffee
@@ -153,7 +153,7 @@
         when ";"
           dataObject.lines.push {comments: line.substr(1)}
         else
-          console.log "no support for: #{line}"
+          console?.log "no support for: #{line}"
     dataModel

   transposeChord: (chord, amount) ->


refactor buildFrame

refactor buildFrame

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -48,8 +48,29 @@
       return "#{key} { #{innerStr} }"
     ).join("\n")

+  @buildFrame: (container) ->
+    iframe = container.ownerDocument.createElement('iframe')
+    iframe.frameBorder = '0'
+    container.appendChild(iframe)
+    iframeDoc = iframe.contentWindow.document
+    iframe.height = iframe.width = '100%'
+    iframeDoc.open()
+    iframeDoc.write('<!DOCTYPE html>')
+    iframeDoc.close()
+    htmlTag = iframeDoc.querySelector('html')
+    htmlTag.style.height = iframeDoc.body.style.height = '100%'
+    root = iframeDoc.createElement('div')
+    iframeDoc.body.appendChild(root)
+    return root
+
   constructor: (@container, @emitter, @options = {}) ->
-    this.buildFrame()
+    @container.innerHTML = ''
+    @root = Renderer.buildFrame(@container)
+    @root.id = @options.id
+    DOM.addClass(@root, 'editor-container')
+    DOM.addEventListener(@container, 'focus', =>
+      @root.focus()
+    )
     this.addStyles(DEFAULT_STYLES)
     # Ensure user specified styles are added last
     _.defer(_.bind(this.addStyles, this, @options.styles)) if options.styles?
@@ -76,30 +97,5 @@
       DOM.addClass(@container, 'sc-container')
     )

-  buildFrame: ->
-    @container.innerHTML = ''
-    @iframe = @container.ownerDocument.createElement('iframe')
-    @iframe.frameBorder = '0'
-    @container.appendChild(@iframe)
-    doc = this.getDocument()
-    @iframe.height = @iframe.width = '100%'
-    doc.open()
-    doc.write('<!DOCTYPE html>')
-    doc.close()
-    htmlTag = doc.querySelector('html')
-    htmlTag.style.height = doc.body.style.height = '100%'
-    @root = doc.createElement('div')
-    DOM.addClass(@root, 'editor-container')
-    @root.id = @options.id
-    doc.body.appendChild(@root)
-    DOM.addEventListener(@container, 'focus', =>
-      @root.focus()
-    )
-
-  getDocument: ->
-    return null unless @iframe.parentNode?
-    # Firefox does not like us saving a reference to this result so retrieve every time
-    return @iframe.contentWindow?.document
-

 module.exports = Renderer


Use .mapLimit instead of .map to avoid EMFILE err

--- src/renderer.coffee
+++ src/renderer.coffee
@@ -51,7 +51,7 @@
             callback error
       (callback) ->
         # recursively render tree and its plugins
-        async.map Object.keys(tree), (key, callback) ->
+        async.mapLimit Object.keys(tree), 5, (key, callback) ->
           item = tree[key]
           if item instanceof ContentTree
             renderTree item, callback


Team: updated

Team: updated

--- client/landing/About/team.coffee
+++ client/landing/About/team.coffee
@@ -64,6 +64,9 @@
         username  : 'pablostanley'
         title     : 'Illustrator'
       ,
+        username  : 'pedrosanchez'
+        title     : 'Growth Engineer'
+      ,
         username  : 'dicle'
         title     : 'Business Analyst'
       ,
@@ -72,6 +75,12 @@
       ,
         username  : 'zeynep'
         title     : 'Finance Assistant'
+      ,
+        username  : 'selin'
+        title     : 'Data scientist'
+      ,
+        username  : 'juliapracht'
+        title     : 'Recruiting & HR'
       ,
         username  : 'nesli'
         title     : 'Chef de Cuisine'


Team: added nitin to team

Team: added nitin to team

--- client/landing/About/team.coffee
+++ client/landing/About/team.coffee
@@ -60,6 +60,9 @@
       ,
         username  : 'selin'
         title     : 'Data Scientist'
+      ,
+        username  : 'gniting'
+        title     : 'BizDev'
       # ,
       #   username  : 'cenk'
       #   title     : 'Software Engineer'


use autocreate

use autocreate

--- client/skr/models/Payment.coffee
+++ client/skr/models/Payment.coffee
@@ -38,7 +38,7 @@
             Skr.Models.Location.all.get(@location_id) if @location_id
         }
         gl_transaction: { model: "GlTransaction", readOnly: true }
-        credit_card:    { model: "CreditCard", inverse: 'payment' }
+        credit_card:    { model: "CreditCard", autoCreate: true, inverse: 'payment' }

     events:
         'change:vendor': 'onSetVendor'


check if testRunner is es module

check if testRunner is es module

--- src/initialize-test-window.coffee
+++ src/initialize-test-window.coffee
@@ -1,4 +1,5 @@
 ipcHelpers = require './ipc-helpers'
+{requireModule} = require './module-utils'

 cloneObject = (object) ->
   clone = {}
@@ -85,7 +86,7 @@
     TextEditor.setClipboard(clipboard)
     TextEditor.viewForItem = (item) -> atom.views.getView(item)

-    testRunner = require(testRunnerPath)
+    testRunner = requireModule(testRunnerPath)
     legacyTestRunner = require(legacyTestRunnerPath)
     buildDefaultApplicationDelegate = -> new ApplicationDelegate()
     buildAtomEnvironment = (params) ->


use new template for login view

use new template for login view

--- src/join-request.coffee
+++ src/join-request.coffee
@@ -25,6 +25,7 @@
   env = process.env
   team = env.HUBOT_SLACK_TEAM or ''
   url = env.HUBOT_BASE_URL or 'http://please-set-HUBOT_BASE_URL/'
+  loginTpl.team = team

   robot.brain.on 'loaded', ->
     robot.brain.data.ingressAgents ?= []
@@ -44,13 +45,13 @@
     if req.session.user?
       res.redirect '/apply'
     else
-      res.render 'login'
+      res.render 'login', loginTpl

   app.get '/apply', validate, (req, res) ->
     user = req.session.user

-    applyTpl.form.fullName.value = user.displayName
-    applyTpl.form.email.value = user.emails[0].value
+    applyTpl.form.fullName.value = user?.displayName
+    applyTpl.form.email.value = user?.emails[0]?.value

     viewData =
       partials:


Opens only one contact us form instead of all when the button is clicked.

--- src/javascripts/contact_us.coffee
+++ src/javascripts/contact_us.coffee
@@ -2,7 +2,7 @@
   $("aside.contact-us .write-to-us").click (event) ->
     event.preventDefault()
     $(@).hide()
-    $("aside.contact-us .write-to-us-form").slideDown(100)
+    $(@).prev("aside.contact-us form").slideDown(100)
     $('html, body').animate
-      scrollTop: $("aside.contact-us form").offset().top - 35
-    , 100
+      scrollTop: $(@).prev("aside.contact-us form").offset().top - 35
+    , 100


DRY up autoshow timeout

DRY up autoshow timeout

--- demo.cjsx
+++ demo.cjsx
@@ -1,7 +1,7 @@
 Demo = require './src/demo'

 api = require './src/api'
-AUTOSHOW = false
+AUTOSHOW = true

 SETTINGS =
   STUBS:
@@ -27,13 +27,12 @@
   mainDiv = document.getElementById('react-root-container')

   buttonA = document.getElementById('launcher')
-  buttonA.addEventListener 'click', ->
+  show = ->
     Demo.open(mainDiv, collectionUUID: settings.COLLECTION_UUID, moduleUUID: settings.MODULE_UUID)
     true
+  buttonA.addEventListener 'click', show
   if AUTOSHOW
-    setTimeout( ->
-      Demo.open(mainDiv, collectionUUID: settings.COLLECTION_UUID, moduleUUID: settings.MODULE_UUID)
-    , 300)
+    setTimeout( show, 300)
   true

 loadApp() or document.addEventListener('readystatechange', loadApp)


fixed pkg path

fixed pkg path

--- src/g/package.coffee
+++ src/g/package.coffee
@@ -32,6 +32,6 @@
     if @g.config.get("debug")
       url = @development[pkg]
     else
-      url = "http://wzrd.in/bundle/#{pkg}latest"
+      url = "http://wzrd.in/bundle/#{pkg}/latest"

     url


components: ListenSignal: simplify callback code

--- components/ListenSignal.coffee
+++ components/ListenSignal.coffee
@@ -30,7 +30,7 @@
   updateListener: () ->
     return unless @object? and @signal?
     @disconnectListener()
-    @listener = @object.connect @signal, Lang.bind @, () =>
+    @listener = @object.connect @signal, () =>
       @outPorts.object.send true
       @outPorts.object.disconnect()


Removed spurious logging

Removed spurious logging

--- src/main/client/grid/grid-directives-pages.coffee
+++ src/main/client/grid/grid-directives-pages.coffee
@@ -6,7 +6,6 @@
       restrict: "A"
       require: '?ngModel'
       link: (scope, element, attrs, ngModel) ->
-        console.log "Called trackerDateInput", ngModel, scope.study?.options?.dateFormat
         return if ! ngModel

         dateFormat = scope.study?.options?.dateFormat || "YYYY-MM-DD"


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/drawable.coffee
+++ src/drawable.coffee
@@ -24,12 +24,12 @@
     zIndex: 0

   if I.sprite?.isString?()
-    I.sprite = Sprite(I.sprite, (sprite) ->
+    I.sprite = Sprite.loadByName(I.sprite, (sprite) ->
       I.width = sprite.width
       I.height = sprite.height
     )
   else if I.spriteName
-    I.sprite = Sprite(I.spriteName, (sprite) ->
+    I.sprite = Sprite.loadByName(I.spriteName, (sprite) ->
       I.width = sprite.width
       I.height = sprite.height
     )


adjust for test checking button classes

adjust for test checking button classes

--- test/components/helpers/task/checks.coffee
+++ test/components/helpers/task/checks.coffee
@@ -103,7 +103,7 @@
     expect(div.querySelector('.footer-buttons').children.length).to.equal(3)
     classes = _.pluck(div.querySelector('.footer-buttons').children, 'className')
     expect(classes).to.deep.equal([
-      '-try-another btn btn-primary', '-refresh-memory btn btn-primary', '-continue btn btn-primary'
+      '-try-another btn btn-primary', '-refresh-memory btn btn-primary', 'async-button -continue btn btn-primary'
     ])
     {div, component, stepId, taskId, state, router, history}


Removed ugly regex for email validation

Removed ugly regex for email validation


Wrong name for class in Cyclomatic Complexity rule

NoTabs -> CyclomaticComplexity

--- src/rules/cyclomatic_complexity.coffee
+++ src/rules/cyclomatic_complexity.coffee
@@ -1,4 +1,4 @@
-module.exports = class NoTabs
+module.exports = class CyclomaticComplexity

     rule:
         name: 'cyclomatic_complexity'


remove unused require

remove unused require

--- static/coffee/renderers/glyph/glyph.coffee
+++ static/coffee/renderers/glyph/glyph.coffee
@@ -1,5 +1,4 @@
 base = require('../../base')
-Collections = base.Collections
 HasParent = base.HasParent
 safebind = base.safebind


Exclude ignored files from search etc.

Exclude ignored files from search etc.

--- files/.atom/config.cson
+++ files/.atom/config.cson
@@ -11,7 +11,6 @@
   "autocomplete-plus":
     confirmCompletion: "tab always, enter when suggestion explicitly selected"
   core:
-    excludeVcsIgnoredPaths: false
     disabledPackages: [
       "autocomplete-snippets"
       "autocomplete-atom-api"


Tweak

Tweak

--- config/browserify/tests.coffee
+++ config/browserify/tests.coffee
@@ -15,5 +15,5 @@
     options:
       ignore: ['../../../option_sets', '../../../backbone-orm', '../../../../backbone-orm']
       shim:
-        'underscore': {path: path.resolve(path.join('.', path.relative('.', require.resolve('lodash')))), exports: '_'}
+        underscore: {path: path.resolve(path.join('.', path.relative('.', require.resolve('lodash')))), exports: '_'}
         'backbone-orm': {path: './backbone-orm.js', exports: 'BackboneORM', depends: {jquery: 'jQuery', underscore: '_', backbone: 'Backbone', stream: 'stream'}}


Rename Mapping's `module` to `exports`

Rename Mapping's `module` to `exports`

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -1368,7 +1368,7 @@
           console.warn "Unsupported data type #{typeof value} in looking up Maketile rule '#{key}'"
           value = undefined
       value
-    recurse @module?.make ? @module?.default
+    recurse @exports?.make ? @exports?.default
     executed
   doInit: ->
     if @settings.verbose and @init?
@@ -1428,10 +1428,10 @@
       #  }
       #  console.log code
       pirates?.settings = @settings
-      #@module = runWithMapping @, -> require filename
-      @module = require filename
+      #@exports = runWithMapping @, -> require filename
+      @exports = require filename
       @walkDeps filename
-      #console.log filename, @module
+      #console.log filename, @exports
     else
       ## But if file has been explicitly loaded (e.g. in browser),
       ## compile manually and simulate module.
@@ -1440,7 +1440,7 @@
         filename: @filename
       }
       #console.log code
-      @module = {}
+      @exports = {}
       ## Mimick NodeJS module's __filename and __dirname variables
       ## [https://nodejs.org/api/modules.html#modules_the_module_scope]
       _filename = path.resolve @filename
@@ -1451,11 +1451,11 @@
       func = new Function \
         'exports', '__filename', '__dirname', 'svgtiler', 'preact', code
       #runWithMapping @, ->
-      func @module, _filename, _dirname, svgtiler,
+      func @exports, _filename, _dirname, svgtiler,
         (if code.includes 'preact' then require 'preact')
     if @settings.verbose
-      console.log "# Module #{@filename} exported {#{Object.keys(@module).join ', '}}"
-    super (@module.map ? @module.default), @constructor.optsFrom @module
+      console.log "# Module #{@filename} exported {#{Object.keys(@exports).join ', '}}"
+    super (@exports.map ? @exports.default), @constructor.optsFrom @exports
   walkDeps: (filename) ->
     deps = new Set
     recurse = (modname) =>


Complete Drawings, XLSXDrawings

Complete Drawings, XLSXDrawings

--- src/svgtiler.coffee
+++ src/svgtiler.coffee
@@ -409,33 +409,43 @@
   @title: "Tab-separated drawing (spreadsheet export)"
   @delimiter: '\t'

-class Drawings extends Inputs
+class Drawings extends Input
   @filenameSeparator = '_'
   constructor: (@drawings) ->
   @load: (datas) ->
-    new @ for data in datas
-      drawing = Drawing.load data
-      filename = path.parse @filename
-      filename.base += @filenameSeparator + data.subname
-      drawing.filename = path.format filename
-      drawing.subname = data.subname
-      drawing
+    new @ (
+      for data in datas
+        drawing = Drawing.load data
+        drawing.subname = data.subname
+        drawing
+    )
   writeSVG: (mappings, filename) ->
-    for drawing in drawings
+    for drawing in @drawings
       drawing.writeSVG mappings,
-        if drawings.length > 1 and filename?
-          filename2 = path.parse filename
+        if @drawings.length > 1
+          filename2 = path.parse filename ? @filename
+          filename2.base = filename2.base[...-filename2.ext.length]
           filename2.base += @constructor.filenameSeparator + drawing.subname
+          filename2.base += '.svg'
           path.format filename2
         else
+          drawing.filename = @filename  ## use Drawing default if not filename?
           filename

-class ExcelDrawings extends Drawings
+class XLSXDrawings extends Drawings
+  @encoding: 'binary'
   @title: "Spreadsheet drawings (Excel/OpenDocument/Lotus)"
   @parse: (data) ->
-    xlsx.read data, type: 'binary'
-    ## xxx need to convert to array of worksheets, each array of arrays
+    workbook = xlsx.read data, type: 'binary'
     ## https://www.npmjs.com/package/xlsx#common-spreadsheet-format
+    @load (
+      for subname in workbook.SheetNames
+        sheet = workbook.Sheets[subname]
+        rows = xlsx.utils.sheet_to_json sheet,
+          header: 1
+        rows.subname = subname
+        rows
+    )

 class Context
   constructor: (@symbols, @i, @j) ->
@@ -454,6 +464,16 @@
   '.ssv': SSVDrawing
   '.csv': CSVDrawing
   '.tsv': TSVDrawing
+  ## Parsable by xlsx package:
+  '.xlsx': XLSXDrawings  ## Excel 2007+ XML Format
+  '.xlsm': XLSXDrawings  ## Excel 2007+ Macro XML Format
+  '.xlsb': XLSXDrawings  ## Excel 2007+ Binary Format
+  '.xls': XLSXDrawings   ## Excel 2.0 or 2003-2004 (SpreadsheetML)
+  '.ods': XLSXDrawings   ## OpenDocument Spreadsheet
+  '.fods': XLSXDrawings  ## Flat OpenDocument Spreadsheet
+  '.dif': XLSXDrawings   ## Data Interchange Format (DIF)
+  '.prn': XLSXDrawings   ## Lotus Formatted Text
+  '.dbf': XLSXDrawings   ## dBASE II/III/IV / Visual FoxPro

 help = ->
   console.log """


Use dot notation for accessing properties. Deep clone FTW.

--- src/spec/utils/order-utils.spec.coffee
+++ src/spec/utils/order-utils.spec.coffee
@@ -123,7 +123,7 @@
     update = @utils.actionsMapReturnInfo(delta, orderChanged)

     action = _.deepClone orderChanged.returnInfo[0]
-    action['action'] = 'addReturnInfo'
+    action.action = 'addReturnInfo'

     expect(update).toEqual [action]

@@ -212,7 +212,7 @@
     update = @utils.actionsMapReturnInfo(delta, orderChanged)

     addAction = _.deepClone orderChanged.returnInfo[1]
-    addAction['action'] = 'addReturnInfo'
+    addAction.action = 'addReturnInfo'

     expectedUpdate =
       [
@@ -231,7 +231,7 @@
     expect(update).toEqual expectedUpdate

-  it "should return required actions for syncing deliveries", ->
+  it 'should return required actions for syncing deliveries', ->

     orderChanged = _.deepClone ORDER

@@ -242,11 +242,11 @@
     update = @utils.actionsMapDeliveries(delta, orderChanged)

     action = JSON.parse(JSON.stringify(orderChanged.shippingInfo.deliveries[0]))
-    action["action"] = "addDelivery"
+    action.action = "addDelivery"

     expect(update).toEqual [action]

-  it "should return required action for syncing parcels (deliveries)", ->
+  it 'should return required action for syncing parcels (deliveries)', ->

     orderChanged = _.deepClone ORDER

@@ -270,7 +270,7 @@
     delta = @utils.diff(@order, orderChanged)
     update = @utils.actionsMapDeliveries(delta, orderChanged)

-    expectedUpdate = JSON.parse(JSON.stringify(parcel))
+    expectedUpdate = _.deepClone parcel
     expectedUpdate.action = 'addParcelToDelivery'
     expectedUpdate.deliveryId = orderChanged.shippingInfo.deliveries[0].id


updating with better logic to throw an error

--- src/flux/toc.coffee
+++ src/flux/toc.coffee
@@ -32,10 +32,12 @@
     get: ->
       if @_toc?.length
         @_toc[0].children
-
+
     getChapterSection: (sectionId) ->
-      if (@_toc and @_sections)
-        @_sections[sectionId]?.chapter_section or throw new Error('BUG: Invalid section')
+      if (@_toc and @_sections and @_sections[sectionId])
+        @_sections[sectionId].chapter_section
+      else
+        throw new Error('BUG: Invalid section')

     getSectionInfo: (sectionId) ->
       if (@_toc and @_sections)


remove colsole.log alias

remove colsole.log alias

--- lib/client/javascripts/nali/nali.js.coffee
+++ lib/client/javascripts/nali/nali.js.coffee
@@ -1,5 +1,3 @@
-window.__ = ( args... ) -> console.log args...
-
 window.Nali =

   _name:      'Nali'


add date to vehicles controller to allow different datasets to be downloaded for different days

--- lib/controllers/vehicles_controller.coffee
+++ lib/controllers/vehicles_controller.coffee
@@ -1,9 +1,15 @@
 Sensor.VehiclesController = SC.ArrayProxy.extend
   content: []
   date: SC.DateTime.create()
+  urlStartParam: ( ->
+    @get('date').toFormattedString('%Y-%m-%d')
+  ).property('date').cacheable()
+  urlEndParam: ( ->
+    @get('date').advance(day:1).toFormattedString('%Y-%m-%d')
+  ).property('date').cacheable()
   loaded: false
-  vehiclesUrl: ( -> "#{Sensor.API_URL}/units?callback=?&oauth_token=#{Sensor.ACCESS_TOKEN}").property()
-  messagesUrl: ( -> "#{Sensor.API_URL}/messages?callback=?&oauth_token=#{Sensor.ACCESS_TOKEN}").property()
+  vehiclesUrl: ( -> "#{Sensor.API_URL}/units?callback=?&oauth_token=#{Sensor.ACCESS_TOKEN}").property('date')
+  messagesUrl: ( -> "#{Sensor.API_URL}/messages?start=#{@get('urlStartParam')}&end=#{@get('urlEndParam')}&callback=?&oauth_token=#{Sensor.ACCESS_TOKEN}").property('date')
   findById: (id) -> @findProperty('id',id)
   findByImei: (imei) -> @findProperty('imei',imei)
   findByNickname: (nickname) -> @findProperty('nickname',nickname)


hehe.. sometimes i like to act dumb

hehe.. sometimes i like to act dumb

--- src/template.coffee
+++ src/template.coffee
@@ -102,14 +102,14 @@
 # i made these function name short ,
 # because its a little bit faster than with long names

-pp = (tag, name) -> # populate tag with specific child tag genertor
-    tag[    name] = -> tag.tag.apply  this, [name].concat arguments...
-    tag["$"+name] = -> tag.$tag.apply this, [name].concat arguments...
+pp = (proto, name) -> # populate tag with specific child tag genertor
+    proto[    name] = -> @tag.apply  this, [name].concat arguments...
+    proto["$"+name] = -> @$tag.apply this, [name].concat arguments...

-ff = (tag, tags) -> # fill with tags
+ff = (proto, tags) -> # fill with tags
     for tagname in tags
-        pp tag, tagname if tagname
-    return tag
+        pp proto, tagname if tagname
+    return

 class Template extends EventEmitter
@@ -122,23 +122,16 @@
         opts.schema = schema[s]?(opts).split(' ')
         opts.end ?= on

-        class ExtendedBuilder
-            constructor: ->
-                ff this, opts.schema
-                Builder.apply this, arguments
-        for name, method of Builder::
-            ExtendedBuilder::[name] = method
+        class ExtendedBuilder extends Builder
+        ff ExtendedBuilder::, opts.schema

         @xml = new ExtendedBuilder opts
         @end = @xml.end
         Tag = @xml.Tag
-        class ExtendedTag
-            constructor: ->
-                ff this, opts.schema
-                Tag.apply this, arguments
+        class ExtendedTag extends Tag
+        ff ExtendedTag::, opts.schema
         @xml.Tag = @xml.opts.Tag = ExtendedTag
-        for name, method of Tag::
-            ExtendedTag::[name] = method
+
         end_tag = Tag::end
         @xml.Tag::end = (args...) ->
             if opts.self_closing is on or opts.self_closing.match @name


Show context menu only in text editor.

Show context menu only in text editor.

--- menus/xml-formatter.cson
+++ menus/xml-formatter.cson
@@ -1,7 +1,7 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'context-menu':
-  'atom-workspace': [
-    { 'label': 'Enable xml-formatter', 'command': 'xml-formatter:indent' }
+  'atom-text-editor': [
+    { 'label': 'Format XML', 'command': 'xml-formatter:indent' }
   ]

 'menu': [


Use empty string rather than .. for initial content

--- src/tip/qtip.coffee
+++ src/tip/qtip.coffee
@@ -17,7 +17,7 @@
   # defaults for the qtip flyout.
   QTIP_DEFAULTS:
     content:
-      text: '..'
+      text: ''
     show:
       ready: false
       delay: 0
@@ -167,5 +167,3 @@

     bg.removeClass('top left right bottom')
     bg.addClass(direction)
-
-


Modify tests for base91

Modify tests for base91


String#isString

String#isString

--- test/string_extensions.coffee
+++ test/string_extensions.coffee
@@ -16,6 +16,11 @@

 test "#humanize", ->
   equals "employee_salary".humanize(), "Employee salary"
+
+test "isString", ->
+  equals "".isString?(), true, "Strings are strings"
+  equals {}.isString?(), undefined, "objects are not strings"
+  equals [].isString?(), undefined, "arrays are not strings"

 test "#parse", ->
   equals "true".parse(), true, "parsing 'true' should equal boolean true"


Fix a view test in Firefox 6.

Fix a view test in Firefox 6.

--- tests/batman/view_test.coffee
+++ tests/batman/view_test.coffee
@@ -8,6 +8,10 @@
 triggerChange = (domNode) ->
   evt = document.createEvent("HTMLEvents")
   evt.initEvent("change", true, true)
+  domNode.dispatchEvent(evt)
+triggerClick = (domNode) ->
+  evt = document.createEvent("MouseEvents")
+  evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
   domNode.dispatchEvent(evt)

 count = 0
@@ -241,8 +245,9 @@

   source = '<form data-event-submit="doSomething"><input type="submit" id="submit" /></form>'
   render source, context, (node) ->
-    $("#submit", node).click()
-    delay =>
+    triggerClick($("#submit", node)[0])
+    delay =>
+      console.log spy
       ok spy.called

 asyncTest 'it should allow mixins to be applied', 1, ->
@@ -420,7 +425,7 @@

 asyncTest 'it should loop over js objects', ->
   source = '

'
-  playerScores =
+  playerScores =
     mario: 5
     link: 5
     crono: 10


decrease column width

decrease column width

--- src/scripts/models/content/page.coffee
+++ src/scripts/models/content/page.coffee
@@ -31,7 +31,10 @@
       # Wrap solutions in a div so "Show/Hide Solutions" work
       $body.find('.solution')
       .wrapInner('
')
-      .prepend('
<button class="btn-link ui-toggle" title="Show/Hide Solution"></button>
')
+      .prepend('''
+

+          <button class="btn-link ui-toggle" title="Show/Hide Solution"></button>
+
''')

       response.content = $body.html()


Don't break on incorrect files

Don't break on incorrect files

--- src/server/templates/handlebars.coffee
+++ src/server/templates/handlebars.coffee
@@ -31,7 +31,7 @@
     fileParts = file.split "/"

     # partials start with an underscore in their name
-    return unless fileParts[1].substr(0, 1) is "_"
+    return unless fileParts[1]?.substr(0, 1) is "_"

     file = file.replace ".hbs", ""
     handlebars.registerPartial file, content


Fix typo.

Fix typo.

--- tests/training2nd_test.coffee
+++ tests/training2nd_test.coffee
@@ -16,28 +16,28 @@
     @room.user.say('miura', '@hubot ご用件を伺いに来ました。').then =>
       expect(@room.messages).to.eql [
         ['miura', '@hubot ご用件を伺いに来ました。']
-        ['hubot', '@miura 実は最近会社でもブログを使っている所が増えていると聞いてね。\nそこで当社もブログを使って、当社の良さを宣伝したいと思っているんだ。\nなので、早速なんだけど、Wordpressがいいらしいので、それをひとつ用意してくれるかね。\n上長に「お客様からWordpressを立ててほしいと頼まれました。」と相談してくだ さい。']
+        ['hubot', '@miura 実は最近会社でもブログを使っている所が増えていると聞いてね。\nそこで当社もブログを使って、当社の良さを宣伝したいと思っているんだ。\nなので、早速なんだけど、Wordpressがいいらしいので、それをひとつ用意してくれるかね。\n上長に「お客様からWordpressを立ててほしいと頼まれました。」と相談してください。']
       ]

   it 'should instruct trainee to change blog from Wordpress to MovableType', ->
     @room.user.say('miura', '@hubot Wordpressを立てたことを報告しに来ました。').then =>
       expect(@room.messages).to.eql [
         ['miura', '@hubot Wordpressを立てたことを報告しに来ました。']
-        ['hubot', '@miura ありがたいのですが、昨日Wordpressでブログを書いてほしいと社員に伝えたら、\nMovableTypeの方がいいと言い出す社員がいましてね。\nそちらのが慣れているから楽だという話なんですよ。\n私としては書いてもらえば、どちらでもいいのでMovableTypeにしてもらえますか。\n上長に「お客様からMovableTypeに変更してほしいと頼まれました。」と相談して ください。']
+        ['hubot', '@miura ありがたいのですが、昨日Wordpressでブログを書いてほしいと社員に伝えたら、\nMovableTypeの方がいいと言い出す社員がいましてね。\nそちらのが慣れているから楽だという話なんですよ。\n私としては書いてもらえば、どちらでもいいのでMovableTypeにしてもらえますか。\n上長に「お客様からMovableTypeに変更してほしいと頼まれました。」と相談してください。']
       ]

   it 'should instruct trainee to rebuild Wordpress', ->
     @room.user.say('miura', '@hubot MovableTypeに変更したことを報告しに来ました。').then =>
       expect(@room.messages).to.eql [
         ['miura', '@hubot MovableTypeに変更したことを報告しに来ました。']
-        ['hubot', '@miura いやー、変えてもらったところを申し訳ないんだけどね。\nやはりWordpressのがよさそうで、Wordpressにしてほしいんだよ。\nMovableTypeがいいと言っていた社員もブログを書くだけでサーバを運用するわけじゃないからn口出さないように言っておいたよ。それでは、よろしく。\n上長に「お客様からWordpressに戻して100万PVに耐えられるようにしてほしいと頼まれました。」と相談してください。']
+        ['hubot', '@miura いやー、変えてもらったところを申し訳ないんだけどね。\nやはりWordpressのがよさそうで、Wordpressにしてほしいんだよ。\nMovableTypeがいいと言っていた社員もブログを書くだけでサーバを運用するわけじゃないから、\n口出さないように言っておいたよ。それでは、よろしく。\n上長に「お客様からWordpressに戻して100万PVに耐えられるようにしてほしいと頼まれました。」と\n相談してください。']
       ]

   it 'should instruct trainee to schedule a backup for Wordpress', ->
     @room.user.say('miura', '@hubot Wordpressに戻したことを報告しに来ました。').then =>
       expect(@room.messages).to.eql [
         ['miura', '@hubot Wordpressに戻したことを報告しに来ました。']
-        ['hubot', '@miura じゃあ、早速使ってみますよ。\nそれと、せっかく書いたのに消えてしまってはいやだから、\nバックアップをとれるようにしておいてください。\n上長に「お客様からバックアップを取るように頼まれました。」と相談してください。']
+        ['hubot', '@miura じゃあ、早速使ってみますよ。\nそれと、せっかく書いたのに消えてしまってはいやだから、\nバックアップをとるようにしておいてください。\n上長に「お客様からバックアップを取るように頼まれました。」と相談してください。']
       ]

   it 'should instruct restoring Wordpress', ->


fix fromBinder bug

fix fromBinder bug

--- src/Bacon.coffee
+++ src/Bacon.coffee
@@ -17,7 +17,7 @@
       try
         for event in value
           reply = sink(event = toEvent(event))
-          unbind() if reply == Bacon.noMore or event.isEnd()
+          unbind() if reply == Bacon.noMore or event.isEnd?()
       catch e
         # make sure the stream ends even if an exception is thrown
         unbind() if _.last(value).isEnd()


Commenting debug block

Commenting debug block

--- tests/www/tests/runner.coffee
+++ tests/www/tests/runner.coffee
@@ -38,10 +38,10 @@
 # starts server
 server.start coverage

-console.log 'testing'
-(require 'child_process').spawn 'curl', [
-  'http://localhost:8080/'
-], stdio: 'inherit'
+# console.log 'testing'
+# (require 'child_process').spawn 'curl', [
+#   'http://localhost:8080/'
+# ], stdio: 'inherit'

 # mounting suites
 # ------------------------------------------------------------------------------


defaultRuleSetType for determining default rulesetType

--- static/coffee/flows/controllers.coffee
+++ static/coffee/flows/controllers.coffee
@@ -82,6 +82,7 @@
   # inject into our gear menu
   $rootScope.gearLinks = []
   $rootScope.ivr = window.ivr
+  $rootScope.ussd = window.ussd

   $scope.getContactFieldName = (ruleset) ->
     return Flow.getContactField(ruleset)
@@ -283,6 +284,14 @@
       return false
     return true

+  $rootScope.defaultRuleSetType = ->
+    if window.ivr
+      rulesetType = 'wait_digit'
+    else if window.ussd
+      rulesetType = 'wait_ussd'
+    else
+      rulesetType = 'wait_message'
+
   $scope.onConnectorDrop = (connection) ->

     $(connection.sourceId).parent().removeClass('reconnecting')
@@ -326,7 +335,7 @@
             label: "Response " + (Flow.flow.rule_sets.length + 1)
             operand: "@step.value"
             webhook_action: null,
-            ruleset_type: if window.ivr then 'wait_digit' else 'wait_message',
+            ruleset_type: $rootScope.defaultRuleSetType(),
             rules: [
               test:
                 test: "true"
@@ -795,6 +804,7 @@
   # let our template know our editor type
   $scope.nodeType = options.nodeType
   $scope.ivr = window.ivr
+  $scope.ussd = window.ussd
   $scope.options = options

   $scope.contactFields = Flow.contactFieldSearch
@@ -854,7 +864,7 @@
       label: "Response " + (Flow.flow.rule_sets.length + 1)
       operand: "@step.value"
       webhook_action: null,
-      ruleset_type: if window.ivr then 'wait_digit' else 'wait_message',
+      ruleset_type: $rootScope.defaultRuleSetType(),
       rules: [
         test:
           test: "true"


Change action buttons to simple functions

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -146,66 +146,47 @@
                 {@render_sort_link("name", "Name")}
             </Col>
         </Row>
-
-ShareButton = rclass
-    propTypes:
-        id       : rtypes.string
-        on_click : rtypes.func.isRequired
-
-    render: ->
-        <Space/><Space/>
-

-                Private
-

-
-
-PublicButton = rclass
-    propTypes:
-        id       : rtypes.string
-        on_click : rtypes.func.isRequired
-
-    render: ->
-        <Space/>
-            <Button
-                bsStyle = 'info'
-                bsSize  = 'xsmall'
-                onClick = {@props.on_click}
-            >
-                <Icon name='bullhorn' /> Public
-            </Button>
-
-
-CopyButton = rclass
-    propTypes:
-        on_click : rtypes.func.isRequired
-
-    render: ->
-        <Space/>
-            <Button
-                bsStyle = 'info'
-                bsSize  = 'xsmall'
-                onClick = {@props.on_click}
-            >
-                <Icon name='files-o' /> Copy
-            </Button>
-
-generate_on_share_click = (full_path, actions) =>
+ShareButton = ({on_click}) ->
+    <Space/>
+        <Button
+            bsSize  = 'xsmall'
+            onClick = {on_click}
+            style   = {color:'rgb(102, 102, 102)'}
+        >
+            <Icon name='share-o' /> Share...
+        </Button>
+
+
+PublicButton = ({on_click}) ->
+    <Space/>
+        <Button
+            bsStyle = 'info'
+            bsSize  = 'xsmall'
+            onClick = {on_click}
+        >
+            <Icon name='bullhorn' /> Public
+        </Button>
+
+
+CopyButton = ({on_click}) ->
+    <Space/>
+        <Button
+            bsStyle = 'info'
+            bsSize  = 'xsmall'
+            onClick = {on_click}
+        >
+            <Icon name='files-o' /> Copy
+        </Button>
+
+
+generate_click_for = (file_action_name, full_path, project_actions) =>
     (e) =>
         e.preventDefault()
         e.stopPropagation()
-        actions.set_all_files_unchecked()
-        actions.set_file_checked(full_path, true)
-        actions.set_file_action('share')
-
-generate_on_copy_click = (full_path, actions) =>
-    (e) =>
-        e.preventDefault()
-        e.stopPropagation()
-        actions.set_file_checked(full_path, true)
-        actions.set_file_action('copy')
+        unless file_actions[file_action_name].allows_multiple_files
+            project_actions.set_all_files_unchecked()
+        project_actions.set_file_checked(full_path, true)
+        project_actions.set_file_action(file_action_name)

 FileRow = rclass
     displayName : 'ProjectFiles-FileRow'
@@ -282,17 +263,15 @@
     render_public_file_info: ->
         if @props.public_view
             <CopyButton
-                on_click = {generate_on_copy_click(@full_path(), @props.actions)}
+                on_click = {generate_click_for('copy', @full_path(), @props.actions)}
             />
         else if @props.is_public
             <PublicButton
-                id       = {@props.name}
-                on_click = {generate_on_share_click(@full_path(), @props.actions)}
+                on_click = {generate_click_for('share', @full_path(), @props.actions)}
             />
         else
             <ShareButton
-                id       = {@props.name}
-                on_click = {generate_on_share_click(@full_path(), @props.actions)}
+                on_click = {generate_click_for('share', @full_path(), @props.actions)}
             />

     full_path: ->
@@ -417,17 +396,15 @@
     render_public_directory_info: ->
         if @props.public_view
             <CopyButton
-                on_click = {generate_on_copy_click(@full_path(), @props.actions)}
+                on_click = {generate_click_for('copy', @full_path(), @props.actions)}
             />
         else if @props.is_public
             <PublicButton
-                id       = {@props.name}
-                on_click = {generate_on_share_click(@full_path(), @props.actions)}
+                on_click = {generate_click_for('share', @full_path(), @props.actions)}
             />
         else
             <ShareButton
-                id       = {@props.name}
-                on_click = {generate_on_share_click(@full_path(), @props.actions)}
+                on_click = {generate_click_for('share', @full_path(), @props.actions)}
             />

     full_path: ->


Changed download behavior for multiple files

--- src/smc-webapp/project_files.cjsx
+++ src/smc-webapp/project_files.cjsx
@@ -1525,44 +1525,66 @@
             if not (single_file_data.is_public and single_file_data.public?.path isnt single_file)
                 @share_click()

-    download_click: ->
+    download_single_click: ->
         @props.actions.download_file
             path : @props.checked_files.first()
             log : true
         @props.actions.set_file_action()

-    render_download_link: (single_item) ->
+    download_multiple_click: ->
+        destination = ReactDOM.findDOMNode(@refs.download_archive).value
+        @props.actions.zip_files
+            src  : @props.checked_files.toArray()
+            dest : misc.path_to_file(@props.current_path, destination)
+        @props.actions.download_file
+            path : destination
+            log : true
+        @props.actions.set_all_files_unchecked()
+        @props.actions.set_file_action()
+
+    render_download_single: (single_item) ->
         target = @props.actions.get_store().get_raw_link(single_item)
-

-            {target}
-

-
-    render_download_alert: ->
-        <Alert bsStyle='warning'>
-
<Icon name='exclamation-triangle' /> Notice

-
Download for multiple files and directories is not yet implemented.

-
For now, create a zip archive or download files one at a time.

-        </Alert>
+

+
Download link

+

+                {target}
+

+

+
+    render_download_multiple: ->
+

+
Download as archive

+            <FormGroup>
+                <FormControl
+                    autoFocus    = {true}
+                    ref          = 'download_archive'
+                    key          = 'download_archive'
+                    type         = 'text'
+                    defaultValue = {account.default_filename('zip')}
+                    placeholder  = 'Result archive...'
+                    onKeyDown    = {@action_key}
+                />
+            </FormGroup>
+

     render_download: ->
         single_item = @props.checked_files.first()
         if @props.checked_files.size isnt 1 or @props.file_map[misc.path_split(single_item).tail]?.isdir
-            download_not_implemented_yet = true
+            download_multiple_files = true

             <Row>
                 <Col sm=5 style={color:'#666'}>
-
Download file to your computer

+
Download file(s) to your computer

                     {@render_selected_files_list()}
                 </Col>
                 <Col sm=7 style={color:'#666'}>
-
Download link

-                    {if download_not_implemented_yet then @render_download_alert() else @render_download_link(single_item)}
+                    {if download_multiple_files then @render_download_multiple() else @render_download_single(single_item)}
                 </Col>
             </Row>
             <Row>
                 <Col sm=12>
                     <ButtonToolbar>
-                        <Button bsStyle='primary' onClick={@download_click} disabled={download_not_implemented_yet}>
+                        <Button bsStyle='primary' onClick={if download_multiple_files then @download_multiple_click else @download_single_click}>
                             <Icon name='cloud-download' /> Download
                         </Button>
                         <Button onClick={@cancel_action}>
@@ -2208,4 +2230,4 @@
             {@render_access_error() if public_view and not error}
             {@render_file_listing(visible_listing, file_map, error, project_state, public_view)}
             {@render_paging_buttons(Math.ceil(listing.length / file_listing_page_size)) if listing?}
-
+


skip partials (aka view files beginning with an underscore) from layout merge

--- src/steroids/GruntDefaults.coffee
+++ src/steroids/GruntDefaults.coffee
@@ -171,15 +171,19 @@
           filePath = path.resolve filePathPart
           buildFilePath = path.resolve filePathPart.replace("app"+path.sep, "dist"+path.sep)

-          controllerName = path.basename(viewDir).replace(path.sep, "") + "Controller"
-          yieldObj =
-            view: grunt.file.read(filePath, "utf8")
-            controller: controllerName
+          # skip "partial" files that begin with underscore
+          if /^_/.test path.basename(filePath)
+            yieldedFile = grunt.file.read(filePath, "utf8")
+          else
+            controllerName = path.basename(viewDir).replace(path.sep, "") + "Controller"
+            yieldObj =
+              view: grunt.file.read(filePath, "utf8")
+              controller: controllerName

-          # put layout+yields together
-          yieldedFile = grunt.utils._.template(
-            applicationLayoutFile.toString()
-          )({ yield: yieldObj })
+            # put layout+yields together
+            yieldedFile = grunt.utils._.template(
+              applicationLayoutFile.toString()
+            )({ yield: yieldObj })

           # write the file
           grunt.file.mkdir path.dirname(buildFilePath)


js state machine

js state machine

--- src/State.coffee
+++ src/State.coffee
@@ -175,3 +175,51 @@

   self

+SM.Machine = function(name, object, options, block) {
+  events = EventCollection()
+  states = StateCollection()
+  callbacks = CallbackCollection()
+
+  this.object       = object;
+  this.machine_name = name;
+
+  this.internal_state = options && options.initial ? options.initial : '';
+  this._add_methods_to_object(name, object);
+
+  if(block){ block(this); }
+  return this;
+};
+
+SM.Machine.prototype = {
+
+  event: function(name, block) {
+    var event = this.events.add(name, this);
+    this._add_event_methods(name, this.object, event);
+    if(block){ block(event); }
+    return event;
+  },
+  before_transition: function(options, block){
+    var callback = this.callbacks.add('before', options, this, block);
+  },
+  after_transition: function(options, block){
+    var callback = this.callbacks.add('after', options, this, block);
+  },
+  state: function(){
+    return this.internal_state;
+  },
+  /* Private method */
+  _add_methods_to_object: function(name, object){
+    object[name]            = this.state();
+    object[name+'_events']  = this.events.all();
+    object[name+'_states']  = this.states.all();
+  },
+  _add_event_methods: function(name, object, event) {
+    object[name] = function(){ return event.fire(arguments); };
+    object['can_'+name] = function(){ return event.can_fire(); };
+  },
+  _set_state: function(state){
+    this.internal_state = state;
+    this.object[this.machine_name] = state;
+  }
+};
+


Allow intercepting of all types of requests

--- src/bg/bg.coffee
+++ src/bg/bg.coffee
@@ -33,7 +33,6 @@
 		chrome.webRequest.onBeforeRequest.addListener(
 			tabListeners[tabId],
 			urls: ['http://*/*', 'https://*/*', 'chrome-extension://*/*'],
-			types: ['script', 'stylesheet'],
 			tabId: tab.id
 			["blocking"]
 		)


append to the vertical element so its context appears global

--- src/packages/editor-stats/src/editor-stats-view.coffee
+++ src/packages/editor-stats/src/editor-stats-view.coffee
@@ -36,8 +36,7 @@
     super

     @command 'core:cancel', @detach
-    @statusBar = @rootView.find '.status-bar'
-    @panes = @rootView.find('#panes')
+    @statusBar = @rootView.find('.status-bar')
     @css 'background', @statusBar.css('background')

     date = new Date(startDate)
@@ -51,9 +50,9 @@
     @rootView.on 'mouseup', @track

   draw: ->
-    w = @statusBar.width()
+    w = @rootView.vertical.width()
     h = @.height()
-    [pt, pl, pb, pr] = [15,10,0,10]
+    [pt, pl, pb, pr] = [15,10,0,25]

     data = d3.entries @eventLog

@@ -114,7 +113,7 @@
       @attach()

   attach: ->
-    @panes.append(@)
+    @rootView.vertical.append(@)
     @draw()

   detach: =>


fix key bindings (resolves #14)

fix key bindings (resolves #14)

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -50,6 +50,7 @@
     'click button[name="asteroid-done"]'  : 'onClickAsteroidDone'
     'click button[name="cancel"]'         : 'onClickCancel'
     'change input[name="current-frame"]'  : 'onChangeFrameSlider'
+    'keydown'                             : 'onKeyDown'

     # state controller events
     'change input[name="classifier-type"]': (e) ->
@@ -89,27 +90,12 @@
     'button[name="cancel"]'         : 'cancel'
     'input[name="selected-artifact"]': 'selectedArtifactRadios'

-    'keydown': (e) ->
-      return if @el.hasClass 'playing'  # disable while playing
-      switch e.which
-        when KEYS.one
-          @showFrame(0)
-        when KEYS.two
-          @showFrame(1)
-        when KEYS.three
-          @showFrame(2)
-        when KEYS.four
-          @showFrame(3)
-        when KEYS.space
-          @onClickPlay()
-
   states:
     whatKind:
       enter: ->
         console.log "STATE: \'whatKind/enter\'"

         @disableMarkingSurfaces()
-
         # this code is incorrect (e is an array) FIX!
         for e in @el.find('input[name="classifier-type"]')
           e.checked = false
@@ -125,15 +111,10 @@
     asteroidTool:
       enter: ->
         console.log "STATE: \'asteroidTool/enter\'"
-
         @activateFrame 0
-
-
         # create new asteroid
         @currAsteroid = new Asteroid
-
         @enableMarkingSurfaces()
-
         @el.find('.asteroid-classifier').show()

       exit: ->
@@ -184,21 +165,20 @@
     this.masterMarkingSurface.el.id = "surface-master"
     @flickerContainer.append @masterMarkingSurface.el
     @masterMarkingSurface.on 'create-tool', (tool) =>
-        if @state is 'asteroidTool'
-
-          # enforce one mark per frame
-          if @asteroidMarkedInFrame[@currFrameIdx]
-            # undo last mark
-            @masterMarkingSurface.marks[@masterMarkingSurface.marks.length-1].destroy()
-            # replace first element to reflect marks on marking-surface
-            # @marks.shift()
-            # return
-          else
-            # new mark
-            @el.find(".asteroid-frame-complete-#{@currFrameIdx+1}").attr 'checked', true
-            @asteroidMarkedInFrame[@currFrameIdx] = true
-
-        tool.controls.controller.setMark(@currFrameIdx)
+      if @state is 'asteroidTool'
+        # enforce one mark per frame
+        if @asteroidMarkedInFrame[@currFrameIdx]
+          # undo last mark
+          @masterMarkingSurface.marks[@masterMarkingSurface.marks.length-1].destroy()
+          # replace first element to reflect marks on marking-surface
+          # @marks.shift()
+          # return
+        else
+          # new mark
+          @el.find(".asteroid-frame-complete-#{@currFrameIdx+1}").attr 'checked', true
+          @asteroidMarkedInFrame[@currFrameIdx] = true
+
+      tool.controls.controller.setMark(@currFrameIdx)

     #create 4-up view surfaces
     @numFrames = 4
@@ -211,8 +191,6 @@
       @markingSurfaceList[i].on 'create-tool', (tool) =>
         tool.controls.controller.setMark(@currFrameIdx)

-
-
     #######################################################
     #  API event bindings
     #######################################################
@@ -278,6 +256,21 @@
   onChangeFrameSlider: =>
     frame = document.getElementById('frame-slider').value
     @activateFrame(frame)
+
+  onKeyDown: (e) =>
+    return if @el.hasClass 'playing'  # disable while playing
+    switch e.which
+      when KEYS.one
+        @activateFrame(0)
+      when KEYS.two
+        @activateFrame(1)
+      when KEYS.three
+        @activateFrame(2)
+      when KEYS.four
+        @activateFrame(3)
+      when KEYS.space
+        @onClickPlay()
+    e.preventDefault()

   onUserChange: (e, user) =>
     Subject.next() unless @classification?
@@ -426,30 +419,20 @@

     # set "asteroid-frame-complete" checkmarks

-
-
-
-
   onClickAsteroidDone: ->
-
     console.log @currAsteroid
-
     @currAsteroid.addMarks(@marks)
     @currAsteroid.displaySummary()
-
     @asteroids.push @currAsteroid
     @marks = []
-

     # reset checkboxes and radio buttons
     @asteroidMarkedInFrame = []
     for i in [1..@numFrames]
       @el.find(".asteroid-frame-complete-#{i}").attr 'checked', false

-
     # reset state for next asteroid
     @setState 'whatKind'
-

   onClickNextFrame: ->
     return if @currFrameIdx is 3
@@ -480,7 +463,6 @@
     @el.addClass 'playing'
     @disableMarkingSurfaces()
     # @markingSurfaceList.disable()   # no marking while playing
-
     # flip the images back and forth once
     last = @classification.subject.location.standard.length - 1
     iterator = [0...last].concat [last...-1]
@@ -500,13 +482,9 @@

   activateFrame: (@active) ->
     @active = modulus +@active, @classification.subject.location.standard.length
-
     # update asteroid tracking
     @setAsteroidFrame(@active)
-
     # update artifact marking
-    #
-
     @showFrame(@active)

   setCurrentFrameIdx: (frame_idx) ->


reset bad-image checkbox on new subject

reset bad-image checkbox on new subject

--- app/controllers/classifier.coffee
+++ app/controllers/classifier.coffee
@@ -193,7 +193,6 @@
     @invert = false
     @cycling = false
     @guideShowing = false
-    @badSet = false
     window.classifier = @
     @recordedClickEvents = []
     @setOfSightings = []
@@ -791,6 +790,8 @@
     @favoriteMessage.html translate "classifier.favorite.add"
     @stopPlayingFrames()
     element.show() for element in [@surfacesContainer, @finishButton, @rightPanel.find('.answers'), @cycleButton]
+    @el.find('[name="bad-set"]').prop "checked", false
+

     if @shouldShowTraining()
       Subject.group = TRAINING_SUBJECT_GROUP


cache classifications to localStorage as they are saved

--- app/controllers/interfaces.coffee
+++ app/controllers/interfaces.coffee
@@ -51,6 +51,14 @@
         User.current.badges = badges
         @archive.checkBadges()

+    cachedSet = JSON.parse(localStorage.getItem("classifications"))
+    cachedSet = {} unless cachedSet
+    max = 0
+    maxId = 0
+    maxId= (if key > max then key else max) for key, value of cachedSet
+    cachedSet["" + (parseInt(maxId)+1)] = @classification.toJSON()
+    localStorage.setItem("classifications", JSON.stringify cachedSet)
+
     @classification.send done

     Subject.next()


Fixed completed bug that George was annoyed about

--- app/controllers/tasks.item.coffee
+++ app/controllers/tasks.item.coffee
@@ -86,13 +86,22 @@
     @task.unbind 'destroy', @release
     super

-  toggleStatus: ->
-    # Does not work in completed list
-    if List.current.id isnt "completed"
+  toggleStatus: =>
+      # Does not work in completed list
       @task.completed = !@task.completed
       @task.save()

-      if Setting.get("completedDuration") is "instant"
+      if List.current.id is "completed"
+
+        # Clones List
+        order = List.find(@task.list).tasks.slice(0)
+
+        # Checks if it hasn't been moved
+        if order.indexOf(@task.id) is -1
+          order.push(@task.id)
+          List.find(@task.list).updateAttribute("tasks", order)
+
+      else if Setting.get("completedDuration") is "instant"
         settings.moveCompleted()
         @el.remove()


Do not update cultivable zone shape on selector initialization

--- app/assets/javascripts/backend/activities.js.coffee
+++ app/assets/javascripts/backend/activities.js.coffee
@@ -1,7 +1,9 @@
 ((E, $) ->
   'use strict'

-  $(document).on "selector:change", "#activity_production_cultivable_zone_id", (event)->
+  $(document).on "selector:change", "#activity_production_cultivable_zone_id", (event, _value, is_initialization)->
+    return if is_initialization
+
     element = $(this)
     id = element.selector('value')
     map = $('#activity_production_support_shape')


Handle keyboard navigation with hidden selections better

--- app/assets/javascripts/keyboard_shortcuts.js.coffee
+++ app/assets/javascripts/keyboard_shortcuts.js.coffee
@@ -4,11 +4,11 @@
     if active.is('.group-dropdown-search, .selector-link')
       switch event.which
         when 40 # select next group with down arrow
-          target = active.parent().next('.group-item').find('.selector-link')
-          target = $('#group-dropdown-items').find('.selector-link').first() if target.length is 0
+          target = active.parent().next('.group-item:visible').find('.selector-link')
+          target = $('#group-dropdown-items').find('.group-item:visible').first().find('.selector-link') if target.length is 0
         when 38 # select prev group with up arrow
-          target = active.parent().prev('.group-item').find('.selector-link')
-          target = $('#group-dropdown-items').find('.selector-link').last() if target.length is 0
+          target = active.parent().prev('.group-item:visible').find('.selector-link')
+          target = $('#group-dropdown-items').find('.group-item:visible').last().find('.selector-link') if target.length is 0
       if target?
         target.focus()
         event.preventDefault()


Cleaned up logic a bit

Cleaned up logic a bit

--- app/assets/javascripts/store/variant_options.coffee
+++ app/assets/javascripts/store/variant_options.coffee
@@ -2,20 +2,23 @@
   options = params['options']
   outOfStockStr = "[품절]"

-  # select a variant option
+  # When the user selects a variant option...
   $(document).on "change", ".variant-option-values", ->
     parent = $(@).parents('.variant-options')
     optionId = $(@).parents(".variant-options").attr("id").split("_")[2]
     optionIndex = $(@).parents(".variant-options").data("index")
+    otherOptionId = $("[data-index=" + (optionIndex + 1) % 2 + "]").attr("id").split("_")[2]
+    otherOptionSelector = $("#option_type_" + otherOptionId)

-    # reset "out of stock" text
-    unless $(@).find("option:selected").hasClass("in-stock")
-      $(".option-value").each ->
+    # disable the add to cart button – we'll reenable it later
+    $('#cart-form button[type=submit]').attr('disabled', true)
+
+    # if this is the "select an option" option, reset availability on the other option and return
+    if $(@)[0].selectedIndex == 0
+      otherOptionSelector.find(".option-value").each ->
         $(@).text($(@).text().replace("#{outOfStockStr} ", ""))
-
-    # if this is the "select an option" option, don't do anything else
-    return if $(@)[0].selectedIndex == 0
-    console.log $(@)[0].selectedIndex
+          .addClass("in-stock")
+      return

     # get list of variants that match this option value
     elemId = $(@).find("option:selected").attr("id").split("-")[1]
@@ -23,15 +26,8 @@
     ids = for key of variants
       key

-    # reset the other option selector by blacking out all choices
-    otherOptionId = $("[data-index=" + (optionIndex + 1) % 2 + "]").attr("id").split("_")[2]
-    otherOptionSelector = $("#option_type_" + otherOptionId)
+    # update in-stock status of the other option, and find the variant_id if both options have been selected
     otherOptionSelector.find(".option-value").removeClass("in-stock")
-    otherOptionSelector.find(".option-value").each ->
-      text = $(@).text().replace("#{outOfStockStr} ", "")
-      $(@).text("#{outOfStockStr} #{text}")
-
-    # ...then re-enable the ones that are available, and find the variant_id if both options have been selected
     variant_id
     variants = options[otherOptionId]
     for key,variant of variants
@@ -39,19 +35,29 @@
         if typeof variant[id] == "object"
           if variant[id].in_stock
             $("#option-#{key}").addClass("in-stock")
-            $("#option-#{key}").text($("#option-#{key}").text().replace("#{outOfStockStr} ", "") )
-          if $("#option-#{key}").val() == $(@).val()
+          # if this option is selected, we have a variant
+          if $("#option-#{key}").val() == $("#option-#{key}").parent().val()
             $('#variant_id').val(variant[id].id)
             $('#cart-form button[type=submit]').attr('disabled', false)
             $("#cart-form .price").text variant[id].price

-    # if this selection conflicts with the previous selection, reset the previous selection
+    # reset "out of stock" display and update it
+    if $(@).find("option:selected").hasClass("in-stock")
+      otherOptionSelector.find(".option-value").each ->
+        text = $(@).text().replace("#{outOfStockStr} ", "")
+        $(@).text("#{text}")
+    else
+      $(".option-value").each ->
+        $(@).text($(@).text().replace("#{outOfStockStr} ", ""))
+
+    otherOptionSelector.find(".option-value").not(".in-stock").each ->
+      text = $(@).text().replace("#{outOfStockStr} ", "")
+      $(@).text("#{outOfStockStr} #{text}")
+
+    # if the user selects an option that is out-of-stock in this combination, reset the other option
     selected = otherOptionSelector.find("option:selected")
     unless selected.hasClass("in-stock")
       selected.parent()[0].selectedIndex = 0

-    # fade out the cart button if selection is not complete
-    unless $(".selected").length == 2
-      $('#cart-form button[type=submit]').attr('disabled', true)
     return


Show previous messages even if it's already in the store

--- assets/javascripts/pollers/message_poller.js.coffee
+++ assets/javascripts/pollers/message_poller.js.coffee
@@ -15,35 +15,38 @@
     @started = false

-  onEachMessage: (index, message)->
-    return true if (@store.recordIsLoaded("message", message.id))
-    messageParams =
-      id: message.id
-      type: message.type
-      body: message.body
-      createdAt: message.created_at
+  onEachMessage: (index, messageAttrs)->
+    if (@store.recordIsLoaded("message", messageAttrs.id))
+      @store.find("message", messageAttrs.id).then (message)=>
+        @room.get("messages").pushObject(message)
+      return

-    messageObj = @store.createRecord("message", messageParams)
-    messageObj.set("room", @room)
-    @afterMessageId = message.id
+    message = @store.createRecord("message", {
+      id: messageAttrs.id,
+      type: messageAttrs.type,
+      body: messageAttrs.body,
+      createdAt: messageAttrs.created_at
+    })

-    if(@store.recordIsLoaded("user", message.user.id))
+    message.set("room", @room)
+    @afterMessageId = messageAttrs.id
+
+    if(@store.recordIsLoaded("user", messageAttrs.user.id))
       successCallback = (user)=>
-        messageObj.set("user", user)
-
-      @store.find("user", message.user.id).then successCallback
+        message.set("user", user)
+      @store.find("user", messageAttrs.user.id).then successCallback
     else
       userParams =
-        id: message.user.id
-        firstName: message.user.first_name
-        lastName: message.user.last_name
-        role: message.user.role
+        id: messageAttrs.user.id
+        firstName: messageAttrs.user.first_name
+        lastName: messageAttrs.user.last_name
+        role: messageAttrs.user.role
         color: App.paintBox.getColor()

       user = @store.createRecord("user", userParams)
       messageObj.set("user", user)
     #TODO push or shift depending on the query
-    @room.get("messages").pushObject(messageObj)
+    @room.get("messages").pushObject(message)

   fetchMessages: ->


remove al-with

remove al-with

--- alight/directives.coffee
+++ alight/directives.coffee
@@ -653,25 +653,3 @@
     scope.$watch name, setter,
         deep: true
         init: true
-
-###
-    al-with="some.long.path"
-    al-with="some.long.path as item"
-###
-dirs.with =
-    priority: 500
-    init: (element, name, scope, env) ->
-        self = dirs.if.init element, name, scope, env
-        self.insertBlock = (value) ->
-            if self.child
-                self.removeBlock()
-            if not f$.isObject value
-                return
-            self.item = f$.clone self.base_element
-            self.insertDom self.top_element, self.item
-            self.child = alight.Scope
-                prototype: value
-                root: scope.$system.root
-                attachParent: scope
-            alight.applyBindings self.child, self.item, { skip_attr:env.skippedAttr() }
-        self


added conditions

added conditions

--- src/model/statsd.coffee
+++ src/model/statsd.coffee
@@ -37,14 +37,19 @@
     var path = "/render?target=transformNull(summarize(stats.counters." + domain + ".Channels." + allowedIds[i] + ".Statuses.*.count,'1week'))&from=-1weeks&format=json";
     var raw = yield fetchData(path);
     var failed = function () {
-      if (raw.data[0][0])
+      if (raw.data[0][0]){
         return raw.data[0][0]
-      return 0;    }
+      }else{
+        return 0;
+      }
+    }

     var successful = function () {
-      if (raw.data1[0][0])
+      if (raw.data1[0][0]){
         return raw.data1[0][0]
-      return 0;
+      } else{
+        return 0;
+      }
     }

     data.push({
@@ -84,14 +89,19 @@
     var raw = yield fetchData(path);
     var i = 0;
     var failed = function () {
-      if (raw.data[0][0])
+      if (raw.data[0][0]){
         return raw.data[0][0]
-      return 0;    }
+      }else{
+        return 0;
+      }
+    }

     var successful = function () {
-      if (raw.data1[0][0])
+      if (raw.data1[0][0]) {
         return raw.data1[0][0]
-      return 0;
+      } else{
+        return 0;
+      }
     }
     data.push({
       _id : {"channelID": channelId },


Remove momentum from Particle, and simplify boundary methods

--- src/core/Particle.coffee
+++ src/core/Particle.coffee
@@ -19,9 +19,6 @@
     @size =
       width: 1
       height: 1
-    @momentum =
-      horizontal: 0
-      vertical: 0
     @reference = WINDOW

     @isVisible = true
@@ -67,11 +64,6 @@

     return

-  addMomentum: (horizontal = 0, vertical = 0) ->
-    @momentum.horizontal += horizontal
-    @momentum.vertical += vertical
-    return
-
   update: ->
     @position.absolute.x = @reference.getX() + @position.relative.x
     @position.absolute.y = @reference.getY() + @position.relative.y
@@ -103,12 +95,12 @@
   isWithinBounds: ->
     return (@isWithinHorizontalBounds() and @isWithinVerticalBounds())

-  isWithinHorizontalBounds: ->
-    aboveLower = @position.relative.x >= 0
-    belowUpper = @position.relative.x <= (@reference.getWidth() - 1)
+  isWithinHorizontalBounds: (x = @position.relative.x) ->
+    aboveLower = x >= 0
+    belowUpper = x <= (@reference.getWidth() - 1)
     return (aboveLower and belowUpper)

-  isWithinVerticalBounds: ->
-    aboveLower = @position.relative.y >= 0
-    belowUpper = @position.relative.y <= (@reference.getHeight() - 1)
+  isWithinVerticalBounds: (y = @position.relative.y) ->
+    aboveLower = y >= 0
+    belowUpper = y <= (@reference.getHeight() - 1)
     return (aboveLower and belowUpper)


refactor(audio.coffee): move out " - " into a `titleArtistSeparator` variable

--- app/scripts/services/audio.coffee
+++ app/scripts/services/audio.coffee
@@ -8,9 +8,10 @@
 		# id, artist, title, filename, shouldRemove, isSynced, hasConflict, url
 		list = []

+		titleArtistSeparator = " - "
 		toFilename = ( artist, title ) ->
 			sanitizeFilename = require "sanitize-filename"
-			sanitizeFilename( artist + " - " + title ) + ".mp3"
+			sanitizeFilename( artist + titleArtistSeparator + title ) + ".mp3"

 		getRemoteList = ( callback ) ->
 			vkApi.request
@@ -39,10 +40,10 @@

 				localList = filenames.map ( filename ) ->
 					withoutExt = filename.slice 0, -".mp3".length
-					parts = withoutExt.split " - "
+					parts = withoutExt.split titleArtistSeparator

 					artist: parts.shift()
-					title: parts.join " – "
+					title: parts.join titleArtistSeparator
 					filename: filename

 				callback localList


Add a selection service. TODO: work out how to use it.

--- public/coffee/services.coffee
+++ public/coffee/services.coffee
@@ -136,3 +136,18 @@
 Services.factory 'Markdown', ($window) ->
   parse: (src) -> $window.markdown?.toHTML(src, 'Maruku') ? src

+Services.factory 'Selection', ($window, $log) ->
+  select: (el) ->
+    $log.info el
+    el.focus()
+    doc = $window.document
+    if $window.getSelection and doc.createRange
+      sel = $window.getSelection()
+      range = doc.createRange()
+      range.selectNodeContents(el)
+      sel.removeAllRanges()
+      sel.addRange(range)
+    else if (doc.body.createTextRange)
+      range = doc.body.createTextRange()
+      range.moveToElementText(el)
+      range.select()


Create default tables on start

Create default tables on start

--- db.coffee
+++ db.coffee
@@ -23,10 +23,28 @@

 db = new sqlite.Database config.database

-db.run "CREATE TABLE IF NOT EXISTS bot (
-	key VARCHAR(255) PRIMARY KEY,
-	value MEDIUMTEXT
-);"
+db.serialize ->
+	db.run "CREATE TABLE IF NOT EXISTS bot (
+		key VARCHAR(255),
+		value MEDIUMTEXT,
+		PRIMARY KEY(key)
+	);"
+	db.run "CREATE TABLE IF NOT EXISTS users (
+		userID INT(10),
+		lastname VARCHAR(255) DEFAULT '',
+		key CHAR(40) DEFAULT NULL,
+		PRIMARY KEY(userID)
+	);"
+	db.run "CREATE TABLE IF NOT EXISTS user_to_permission (
+		userID INT(10),
+		permission VARCHAR(255),
+		PRIMARY KEY(userID, permission),
+		FOREIGN KEY(userID) REFERENCES users(userID)
+	);"
+	do ->
+		stmt = db.prepare "INSERT OR IGNORE INTO bot (key, value) VALUES (?, ?);"
+		stmt.run 'firstStart', Date.now()
+		do stmt.finalize

 process.on 'exit', ->
 	winston.debug 'Closing database'


actually including ic-ajax

actually including ic-ajax

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -2,9 +2,11 @@
 #= require handlebars
 #= require ember
 #= require ember-data
+#= require ic-ajax.js
 #= require_self
 #= require theoj

 # for more details see: http://emberjs.com/guides/application/
+window.ajax = ic.ajax
 window.Theoj = Ember.Application.create()


fix #1400 Attachment link is not saved

fix #1400 Attachment link is not saved

--- app/assets/javascripts/card_markup.js.coffee
+++ app/assets/javascripts/card_markup.js.coffee
@@ -241,7 +241,7 @@
       fields = $("#attachments .fields:last")

     fields.find(".title").val data.name
-    fields.find(".url").val data.url
+    fields.find(".link").val data.url
     fields.find(".description").val data.description
     fields.find(".markup_id").val data.id
     content = fields.find ".content"


stacked bar chart: show target line legend once

Closes #1019

--- app/assets/javascripts/d3/stacked_bar.coffee
+++ app/assets/javascripts/d3/stacked_bar.coffee
@@ -27,12 +27,28 @@
         .attr("transform", "translate(#{margins.left}, #{margins.top})")

       # add legend
+      # remove duplicate target series. Required for backwards compatibility.
+      # When we'll drop the old charts we should use a single serie as target
+      # rather than two
+      target_lines = []
+      series_for_legend = []
+      for s in @model.series.models
+        label = s.get 'label'
+        if s.get 'is_target_line'
+          if _.indexOf(target_lines, label) == -1
+            target_lines.push label
+            series_for_legend.push s
+          # otherwise the target line has already been added
+        else
+          series_for_legend.push s
+
       legend_columns = 2
       legend_margin = @width / legend_columns
+
       legend = @svg.append('svg:g')
         .attr("transform", "translate(10,#{@series_height + 15})")
         .selectAll("svg.legend")
-        .data(@model.series.models)
+        .data(series_for_legend)
         .enter()
         .append("svg:g")
         .attr("class", "legend")


Allow to pass input filter param

Allow to pass input filter param

This allow us to set a different input to filter results

--- app/assets/javascripts/gl_dropdown.js.coffee
+++ app/assets/javascripts/gl_dropdown.js.coffee
@@ -2,7 +2,9 @@
   BLUR_KEYCODES = [27, 40]

   constructor: (@dropdown, @options) ->
-    @input = @dropdown.find(".dropdown-input .dropdown-input-field")
+    {
+      @input
+    } = @options

     # Key events
     timeout = ""
@@ -77,14 +79,30 @@
   PAGE_TWO_CLASS = "is-page-two"
   ACTIVE_CLASS = "is-active"

+  FILTER_INPUT = '.dropdown-input .dropdown-input-field'
+
   constructor: (@el, @options) ->
+    @dropdown = $(@el).parent()
+
+    # Set Defaults
+    {
+      # If no input is passed create a default one
+      @filterInput = @$(FILTER_INPUT)
+    } = @options
+
     self = @
-    @dropdown = $(@el).parent()
+
+    # If selector was passed
+    if _.isString(@filterInput)
+      @filterInput = @$(@filterInput)
+
+
     search_fields = if @options.search then @options.search.fields else [];

     if @options.data
       # If data is an array
       if _.isArray @options.data
+        @fullData = @options.data
         @parseData @options.data
       else
         # Remote data
@@ -100,6 +118,7 @@
     # Init filiterable
     if @options.filterable
       @filter = new GitLabDropdownFilter @dropdown,
+        input: @filterInput
         remote: @options.filterRemote
         query: @options.data
         keys: @options.search.fields
@@ -133,6 +152,9 @@
         if self.options.clicked
           self.options.clicked()

+  $: (selector) ->
+    $(selector, @dropdown)
+
   toggleLoading: ->
     $('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS

@@ -167,7 +189,7 @@
       @remote.execute()

     if @options.filterable
-      @dropdown.find(".dropdown-input-field").focus()
+      @filterInput.focus()

   hidden: =>
     if @options.filterable


forced filter input to process inputs after data has loaded

--- app/assets/javascripts/gl_dropdown.js.coffee
+++ app/assets/javascripts/gl_dropdown.js.coffee
@@ -186,6 +186,8 @@
             @fullData = data

             @parseData @fullData
+
+            @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
         }

     # Init filterable


Don't call observers when setting fns on ctx

--- src/cuffs/context.coffee
+++ src/cuffs/context.coffee
@@ -104,12 +104,16 @@
         set: (name, value)->
             # Set the object referenced by `name` on the context. Allow
             # for dotted notation.
+            #
             # Examples:
             # `name` = 'todo' => @['todo'] = value
             # `name` = 'todo.task' => @['todo']['task'] = value
             # `name` = 'todo.task.priority' => @['todo']['task']['priority'] = value
             #
             # If the nested object does not exist yet, it's created on the fly.
+            #
+            # If `value` is not a function, all observers will be notified.
+
             parts = (part.trim() for part in name.split '.')

             if parts.length == 1
@@ -120,6 +124,10 @@
                     current = current[part] or current[part] = {}
                 current[parts[parts.length-1]] = value

+            # Don't notify observers. Apply calls `get()` which would run
+            # the function without possibly expected arguments.
+            if utils.typeOf(value) == "function"
+                return

             if @hasProp parts[0]
                 return @apply name


Split test

Split test

--- test/generators/relational/self.coffee
+++ test/generators/relational/self.coffee
@@ -20,36 +20,6 @@

   OMIT_KEYS = ['owner_id', '_rev', 'created_at', 'updated_at']

-  class Flat extends Backbone.Model
-    urlRoot: "#{DATABASE_URL}/flats"
-    schema: BASE_SCHEMA
-    sync: SYNC(Flat)
-
-  class Reverse extends Backbone.Model
-    urlRoot: "#{DATABASE_URL}/reverses"
-    schema: _.defaults({
-      owner: -> ['belongsTo', Owner]
-      another_owner: -> ['belongsTo', Owner, as: 'more_reverses']
-    }, BASE_SCHEMA)
-    sync: SYNC(Reverse)
-
-  class ForeignReverse extends Backbone.Model
-    urlRoot: "#{DATABASE_URL}/foreign_reverses"
-    schema: _.defaults({
-      owner: -> ['belongsTo', Owner, foreign_key: 'ownerish_id']
-    }, BASE_SCHEMA)
-    sync: SYNC(ForeignReverse)
-
-  class Owner extends Backbone.Model
-    urlRoot: "#{DATABASE_URL}/owners"
-    schema: _.defaults({
-      flats: -> ['hasMany', Flat]
-      reverses: -> ['hasMany', Reverse]
-      more_reverses: -> ['hasMany', Reverse, as: 'another_owner']
-      foreign_reverses: -> ['hasMany', ForeignReverse]
-    }, BASE_SCHEMA)
-    sync: SYNC(Owner)
-
   class SelfReference extends Backbone.Model
     urlRoot: "#{DATABASE_URL}/self_references"
     schema: _.defaults({
@@ -58,13 +28,11 @@
     }, BASE_SCHEMA)
     sync: SYNC(SelfReference)

-  describe "hasMany (cache: #{options.cache}, query_cache: #{options.query_cache}, embed: #{options.embed})", ->
+  describe "hasMany (cache: #{true}, query_cache: #{options.query_cache}, embed: #{options.embed})", ->

     before (done) -> return done() unless options.before; options.before([Flat, Reverse, ForeignReverse, Owner], done)
     after (done) -> callback(); done()
     beforeEach (done) ->
-      relation = Owner.relation('reverses')
-      delete relation.virtual
       MODELS = {}

       queue = new Queue(1)
@@ -74,7 +42,7 @@
       queue.defer (callback) -> QueryCache.configure({enabled: !!options.query_cache, verbose: false}).reset(callback) # configure query cache

       # destroy all
-      queue.defer (callback) -> Utils.resetSchemas [Flat, Reverse, ForeignReverse, Owner], callback
+      queue.defer (callback) -> Utils.resetSchemas [SelfReference], callback

       # create all
       queue.defer (callback) ->
@@ -83,6 +51,7 @@
         create_queue.defer (callback) -> Fabricator.create(SelfReference, BASE_COUNT, {
           name: Fabricator.uniqueId('self_reference_')
           created_at: Fabricator.date
+          is_base: true
         }, (err, models) -> MODELS.self_references = models; callback(err))
         create_queue.defer (callback) -> Fabricator.create(SelfReference, BASE_COUNT, {
           name: Fabricator.uniqueId('self_reference_target_')
@@ -102,18 +71,18 @@
         for self_reference in MODELS.self_references
           do (self_reference) ->
             save_queue.defer (callback) ->
+              self_reference_inverse = MODELS.self_reference_inverses.pop()
+              self_reference_inverse.set({
+                owner: self_reference
+              })
+              self_reference_inverse.save callback
+            save_queue.defer (callback) ->
               self_references = self_reference.get('self_references') or []
               self_references.push MODELS.self_reference_targets.pop()
               self_reference.set({
                 self_references: self_references
               })
               self_reference.save callback
-            save_queue.defer (callback) ->
-              self_reference_inverse = MODELS.self_reference_inverses.pop()
-              self_reference_inverse.set({
-                owner: self_reference
-              })
-              self_reference_inverse.save callback

         save_queue.await callback

@@ -123,7 +92,7 @@
       related_key = 'self_references'
       related_id_accessor = 'self_reference_ids'

-      SelfReference.cursor({$one: true}).include(related_key).toModels (err, owner) ->
+      SelfReference.cursor({$one: true, is_base: true}).include(related_key).toModels (err, owner) ->
         assert.ok(!err, "No errors: #{err}")
         assert.ok(owner, 'found model')
         owner_id = owner.id


Update boot script

Update boot script

--- boot.coffee
+++ boot.coffee
@@ -12,4 +12,4 @@

   cluster.fork() while cpus--
 else
-  require('./app')
+  require('./app/server')


Small typo fixes.

Small typo fixes.

Found by Intellisense. :)

--- src/natives/main.coffee
+++ src/natives/main.coffee
@@ -802,7 +802,7 @@
             fd = fd_obj.get_field rs, 'Ljava/io/FileDescriptor;fd'
             rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), "Bad file descriptor" if fd is -1
             unless fd is 0
-              bytes_left = fs.fstatSync(file).size - _this.$pos
+              bytes_left = fs.fstatSync(fd).size - _this.$pos
               to_skip = Math.min(n_bytes.toNumber(), bytes_left)
               _this.$pos += to_skip
               return gLong.fromNumber(to_skip)
@@ -914,11 +914,11 @@
                 else
                   fs.open filepath, 'w', (err, fd) ->
                     if err?
-                      except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), e.message
+                      except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), err.message
                     else
                       fs.close fd, (err) ->
                         if err?
-                          except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), e.message
+                          except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), err.message
                         else
                           resume_cb true
         o 'createFileExclusively(Ljava/lang/String;Z)Z', (rs, _this, path) ->  # Apple-java version
@@ -930,11 +930,11 @@
                 else
                   fs.open filepath, 'w', (err, fd) ->
                     if err?
-                      except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), e.message
+                      except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), err.message
                     else
                       fs.close fd, (err) ->
                         if err?
-                          except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), e.message
+                          except_cb -> rs.java_throw rs.get_bs_class('Ljava/io/IOException;'), err.message
                         else
                           resume_cb true
         o 'delete0(Ljava/io/File;)Z', (rs, _this, file) ->


Removed error module file

Removed error module file


- small bugfix for module /node detection

--- src/xhrStore.coffee
+++ src/xhrStore.coffee
@@ -3,16 +3,17 @@
 Q = require "q"
 path = require "path"

-
 if detectEnv.isNode
   XMLHttpRequest = require("xhr2").XMLHttpRequest
+
+if detectEnv.isBrowser
+  XMLHttpRequest = window.XMLHttpRequest
+  #logger = Minilog('xhr-store')
+  #Minilog.enable();
+
+if detectEnv.isModule
   logger = require('minilog')('xhr-store')
   require('minilog').enable()
-
-if detectEnv.isBrowser and !(dectectEnv.isModule)
-  XMLHttpRequest = window.XMLHttpRequest
-  logger = Minilog('xhr-store')
-  Minilog.enable();

 class XHRStore
   constructor:(options)->


fix issue where correcting errors outside a form would not propagate into the form


Removed unneeded g.root element

Removed unneeded g.root element

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -10,12 +10,11 @@
         @debug = true

         # create the root svg element
-        svg = d3.select(element).append('svg').attr('class', 'timeslider')
-        @root = svg.append('g').attr('class', 'root')
+        @svg = d3.select(element).append('svg').attr('class', 'timeslider')

         # default options and other variables for later
-        @options.width  = svg[0][0].clientWidth
-        @options.height = svg[0][0].clientHeight
+        @options.width  = @svg[0][0].clientWidth
+        @options.height = @svg[0][0].clientHeight
         @options.brush ||= {}
         @options.brush.start || = @options.start
         @options.brush.end ||= new Date(new Date(@options.brush.start).setDate(@options.brush.start.getDate() + 3))
@@ -57,7 +56,7 @@
                 .tickFormat(customFormats)
                 .tickSize(@options.height - 13)

-        @root.append('g')
+        @svg.append('g')
             .attr('class', 'axis')
             .call(@axis.x)

@@ -96,7 +95,7 @@
             )
             .extent([@options.brush.start, @options.brush.end])

-        @root.append('g')
+        @svg.append('g')
             .attr('class', 'brush')
             .call(@brush)
             .selectAll('rect')
@@ -104,18 +103,18 @@
                 .attr('y', 0)

         # datasets
-        @root.append('g')
+        @svg.append('g')
             .attr('class', 'datasets')
             .attr('width', @options.width)
             .attr('height', @options.height)
             .attr('transform', "translate(0, #{options.height - 18})")

         @drawDataset = (dataset) =>
-            @root.select('g.datasets')
+            @svg.select('g.datasets')
                 .append('g')
                     .attr('class', 'dataset')
                     .attr('id', "dataset-#{dataset.id}")
-            el = @root.select("g.datasets #dataset-#{dataset.id}")
+            el = @svg.select("g.datasets #dataset-#{dataset.id}")
             @options.datasetIndex = 0 unless @options.datasetIndex?

             @data[dataset.id] = {
@@ -129,7 +128,7 @@
             @updateDataset(dataset.id)

         @updateDataset = (dataset) =>
-            el = @root.select("g.datasets #dataset-#{dataset}")
+            el = @svg.select("g.datasets #dataset-#{dataset}")
             d = @data[dataset]

             # update data
@@ -205,7 +204,7 @@
             .x(@scales.x)
             .scaleExtent([1, Infinity])
             .on('zoom', zoom)
-        @root.call(@options.zoom)
+        @svg.call(@options.zoom)

     # Function pair to allow for easy hiding and showing the time slider
     hide: ->


Remove forever-monitor

Remove forever-monitor

--- src/daemon/runner.coffee
+++ src/daemon/runner.coffee
@@ -1,11 +1,10 @@
 require 'shelljs/global'
 fs = require 'fs'
-forever = require 'forever-monitor'

 # Runs express apps
 module.exports =

-  forever: forever
+  foreverPath: 'node_modules/.bin/forever'

   nodemonPath: 'node_modules/.bin/nodemon'

@@ -14,15 +13,7 @@
   start: (path, port) ->
     console.log "Starting #{path} port: #{port}"
     console.log path, @getCommand(path)
-    @forever.start @getCommand(path).split(' '), @getForeverOptions(path, port)
-
-  getForeverOptions: (path, port) ->
-    sourceDir: path
-    max: 1
-    silent: false
-    outFile: "#{path}/katon.logs"
-    env:
-      PORT: port
+    shout.exec "#{@foreverPath} start --sourceDir #{pwd()} #{@getCommand(path)} --port #{port}"

   getCommand: (path) ->
     if test '-e', "#{path}/.katon"
@@ -32,37 +23,8 @@
       start = pkg.scripts?.start
       main = pkg.main
       if main?
-        @nodemonPath
-        # start.replace 'node', @nodemonPath
+        start.replace 'node', @nodemonPath
       else
         start
     else
-      @staticPath
-
-#   forever: forever
-
-#   foreverPath: 'node_modules/.bin/forever'
-
-#   nodemonPath: 'node_modules/.bin/nodemon'
-
-#   staticPath: 'node_modules/.bin/static'
-
-#   start: (path, port) ->
-#     console.log "Starting #{path} port: #{port}"
-#     console.log path, @getCommand(path)
-#     shout.exec "#{@foreverPath} start --sourceDir #{pwd()} #{@getCommand(path)} --port #{port}"
-
-#   getCommand: (path) ->
-#     if test '-e', "#{path}/.katon"
-#       cat "#{path}/.katon"
-#     else if test '-e', "#{path}/package.json"
-#       pkg = JSON.parse(fs.readFileSync "#{path}/package.json")
-#       start = pkg.scripts?.start
-#       main = pkg.main
-#       if main?
-#         @nodemonPath
-#         # start.replace 'node', @nodemonPath
-#       else
-#         start
-#     else
-#       "#{@staticPath} #{path}"+      "#{@staticPath} #{path}"


better error handling

better error handling

--- app/authorization.coffee
+++ app/authorization.coffee
@@ -13,12 +13,12 @@
       console.log("XHR.responseText", xhr.responseText)
       if xhr.status is 401
         @promptLogin()
-      else if xhr.status is 500
-        @alert('Server Error')
-      else if xhr.status is 403
-        @alert('Bad Request')
       else
-        @alert('Network Error')
+        try
+          errorData = JSON.parse(xhr.responseText)
+          @alert "Network Error: #{xhr.statusText}. #{errorData.message}"
+        catch error
+          @alert "Network Error: #{xhr.statusText}. #{xhr.responseText}"

     @token = @getToken()
     @setupAjax()


fix window.location => window.location.href

--- app/authorization.coffee
+++ app/authorization.coffee
@@ -95,9 +95,9 @@
       if window.device.platform.indexOf('Android')!=-1
         navigator.app.loadUrl @::oauthEndPoint
       else
-        window.location = @::oauthEndPoint
+        window.location.href = @::oauthEndPoint
     else
-      window.location = @::oauthEndPoint
+      window.location.href = @::oauthEndPoint

   @handleOpenURL: =>
     # This is being checked by appResumed since handleOpenURL is fired together with appResumed


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/engine_levels.coffee
+++ src/engine_levels.coffee
@@ -29,4 +29,4 @@
     loadLevel level

   reloadLevel: ->
-    loadLevel
+    loadLevel currentLev


gama: Decision Tree: correct count of example images for 'Odd' question.

This now matches the number of gama-10_x-2_*.jpg files that actually
exist in public/images/examples/

--- app/lib/gama_tree.coffee
+++ app/lib/gama_tree.coffee
@@ -65,7 +65,7 @@
     @help "These are examples of galaxy morphology that are rare, but still important to note when you see them. You can select more than one option if you see multiple features in the same galaxy. Click to see larger images."
     @checkbox 'None', icon: 'no', examples: 2
     @checkbox 'Ring', icon: 'ring', examples: 6
-    @checkbox 'Lens or arc', icon: 'lens', examples: 4
+    @checkbox 'Lens or arc', icon: 'lens', examples: 3
     @checkbox 'Irregular', icon: 'irregular', examples: 6
     @checkbox 'Other', icon: 'other', examples: 3
     @checkbox 'Dust lane', icon: 'dustlane', examples: 6


Fix multiple click on jumpIn buttons breaks home.

--- src/frontend/home.coffee
+++ src/frontend/home.coffee
@@ -38,6 +38,8 @@
         (do @_jqCancel.get).click @hide

     display : (event) =>
+        (do @_jqCancel.get).attr 'disabled', off
+        (do @_jqBtn.get).attr 'disabled', on
         for elem in do (do @_jqDiv.get).siblings
             elemName = $(elem).attr 'id'
             if elemName isnt @name and elemName isnt (do @father._jqFirstStep.get).attr 'id'
@@ -50,6 +52,8 @@
         }, 600

     hide : (event) =>
+        (do @_jqBtn.get).attr 'disabled', off
+        (do @_jqCancel.get).attr 'disabled', on
         do HideAllStates
         do @father.show
         (do @_jqDiv.get).fadeTo 0.99
@@ -91,7 +95,7 @@
         (do @_jqElem.get).addClass 'displayed'
         window.location.hash    = @name
         (do @_jqElem.get).fadeIn 600
-
+
     displayFieldIcon : (field, ok) =>
         jqIcon      = ($ 'body').find "span.stateIcon[rel=#{field}]"
         jqMessage   = ($ 'body').find "span.errorMessage[rel=#{field}]"


Fix bug in ftp publisher.

Fix bug in ftp publisher.

--- src/ftp-publisher.coffee
+++ src/ftp-publisher.coffee
@@ -99,7 +99,7 @@
   c = new Client()
   c.on 'ready', ->
     callback null, c
-  c.on 'error', ->
+  c.on 'error', (err) ->
     callback err
   c.connect info


allow the giphy instance to be instrumented in travis builds as well

--- src/giphy.coffee
+++ src/giphy.coffee
@@ -26,7 +26,6 @@
 #   Pat Sissons[patricksissons@gmail.com]

 DEBUG = process.env.DEBUG
-NODE_ENV = process.env.NODE_ENV

 api = require('giphy-api')({
   https: (process.env.HUBOT_GIPHY_HTTPS is 'true') or false
@@ -169,5 +168,5 @@

   # this allows testing to instrument the giphy instance
   ### istanbul ignore next ###
-  if NODE_ENV == 'development'
+  if process.env.NODE_ENV == 'development' or process.env.TRAVIS
     giphy


Add test to ensure cert bundle only gets loaded once

--- test/node_http_client.spec.coffee
+++ test/node_http_client.spec.coffee
@@ -18,12 +18,21 @@
   http = new AWS.NodeHttpClient()

   describe 'handleRequest', ->
+    it 'loads certificate bundle from disk in SSL request (once)', ->
+      readSpy = spyOn(AWS.util, 'readFileSync').andCallThrough()
+      done = false
+      req = new AWS.HttpRequest 'https://invalid'
+      runs -> http.handleRequest req, null, ->
+        done = true
+        expect(AWS.NodeHttpClient.sslAgent).not.toEqual(null)
+        expect(readSpy.callCount).toEqual(1)
+      waitsFor -> done
+
     it 'emits error event', ->
       done = false
-      endpoint = new AWS.Endpoint('http://invalid')
-      req = endpoint: endpoint
+      req = new AWS.HttpRequest 'http://invalid'
       runs ->
-        stream = http.handleRequest req, null, (err) ->
+        http.handleRequest req, null, (err) ->
           expect(err.code).toEqual 'ENOTFOUND'
           done = true
       waitsFor -> done


include original datapoint

include original datapoint

--- src/graph.coffee
+++ src/graph.coffee
@@ -132,7 +132,7 @@
           value = transform_value value
           value_min = Math.min value, value_min ? value if value?
           value_max = Math.max value, value_max
-          {value, time: moment(timestamp * 1000)}
+          {value, time: moment(timestamp * 1000), original: datapoint}
         {values, name: s.target}

       if params.areaMode is 'stacked'


Agent: testing pointers with switcher

Agent: testing pointers with switcher

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -11,6 +11,8 @@
         @instanceDataId = $(@node).attr('data-id')
         window.oldPieData = window.oldPieData or {}
         window.oldPieData[@instanceDataId] = window.oldPieData[@instanceDataId] or []
+        console.log ('$(@node)',$(@node))
+        console.log ('$(@node).parent()',$(@node).parent())

     onData: (data) ->
         #$(@node).fadeOut().fadeIn()


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -104,28 +104,28 @@
     path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
       color i
     #@path.transition().duration(1500).attrTween 'd', that.pieTween
-    path.transition()
-        .duration(1000)
-        #.attrTween("d", pieTween)
-        .attrTween("d", (d,i) ->
-            #console.log("d",d)
-            #console.log("i",i)
-            i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
-            return (t) ->
-                b = i(t)
-                return arc(b)
-        )
+    #path.transition()
+    #    .duration(1000)
+    #    #.attrTween("d", pieTween)
+    #    .attrTween("d", (d,i) ->
+    #        #console.log("d",d)
+    #        #console.log("i",i)
+    #        i = d3.interpolate({startAngle: 0, endAngle: 0}, {startAngle: d.startAngle, endAngle: d.endAngle})
+    #        return (t) ->
+    #            b = i(t)
+    #            return arc(b)
+    #    )
     #@path.exit().transition().duration(300).attrTween('d', that.removePieTween).remove()
-    path.exit()
-        .transition()
-        .duration(300)
-        .attrTween("d", (d,i) ->
-            i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
-            return (t) ->
-                b = i(t)
-                return arc(b)
-        )
-        .remove()
+    #path.exit()
+    #    .transition()
+    #    .duration(300)
+    #    .attrTween("d", (d,i) ->
+    #        i = d3.interpolate({startAngle: d.startAngle,endAngle: d.endAngle},{startAngle: 2 * Math.PI,endAngle: 2 * Math.PI})
+    #        return (t) ->
+    #            b = i(t)
+    #            return arc(b)
+    #    )
+    #    .remove()
     labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
       p1.startAngle - p2.startAngle
     ))


sort query parameters in tests to correctly match the query being made by restangular

Signed-off-by: Dominik Richter <b7323add517e187efe0283b76511b7ed762bfe13@gmail.com>

--- test/spec/controllers/main.coffee
+++ test/spec/controllers/main.coffee
@@ -105,7 +105,7 @@
   build_request = (site, params = [])->
     # remove all undefined
     p = params.filter (x) -> x?
-    q = ( p.map (x) -> x.join('=') ).join('&')
+    q = ( p.map (x) -> x.join('=') ).sort().join('&')
     if q.length == 0
       site
     else


no need to check for owner when listing domains as we have shared vms

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -308,8 +308,7 @@

         selector =
           hostnameAlias : hostnameAlias
-          users         : { $elemMatch: id: user.getId(), owner: yes }
-
+          users         : $elemMatch: id: user.getId()
         JVM.one selector, {hostnameAlias:1}, (err, vm)->
           return callback err, []  if err or not vm
           JVM.fetchDomains {hostnameAlias: vm.hostnameAlias}, callback