trying out a new organizational scheme, with a quickie 'inherits' helper function

--- nodes.coffee
+++ nodes.coffee
@@ -63,12 +63,25 @@
     (output[key]: val) for key, val of input
     output

+# Merge objects.
+merge: (src, dest) ->
+  dest[key]: val for key, val of src
+  dest
+
 # Delete a key from an object, returning the value.
 del: (obj, key) ->
   val: obj[key]
   delete obj[key]
   val

+# Quickie inheritance convenience wrapper to reduce typing.
+inherit: (parent, props) ->
+  klass: props.constructor
+  delete props.constructor
+  klass extends parent
+  klass.prototype[name]: prop for name, prop of props
+  klass
+
 # # Provide a quick implementation of a children method.
 # children: (klass, attrs...) ->
 #   klass::children: ->
@@ -77,8 +90,8 @@

 # Mark a node as a statement, or a statement only.
 statement: (klass, only) ->
-  klass::statement:       -> true
-  klass::statement_only:  -> true if only
+  klass::is_statement:       -> true
+  klass::is_statement_only:  -> true if only

 # The abstract base class for all CoffeeScript nodes.
@@ -98,9 +111,9 @@
   @options: dup(o || {})
   @indent:  o.indent
   top:      if @top_sensitive() then o.top else del obj 'top'
-  closure:  @statement() and not @statement_only() and not top and
+  closure:  @is_statement() and not @is_statement_only() and not top and
             not o.returns and not this instanceof CommentNode and
-            not @contains (node) -> node.statement_only()
+            not @contains (node) -> node.is_statement_only()
   if closure then @compile_closure(@options) else @compile_node(@options)

 # Statements converted into expressions share scope with their parent
@@ -124,93 +137,153 @@
   false

 # Default implementations of the common node methods.
-Node::unwrap:         -> this
-Node::children:       []
-Node::statement:      -> false
-Node::statement_only: -> false
-Node::top_sensitive:  -> false
+Node::unwrap:             -> this
+Node::children:           []
+Node::is_statement:       -> false
+Node::is_statement_only:  -> false
+Node::top_sensitive:      -> false

 # A collection of nodes, each one representing an expression.
-Expressions: exports.Expressions: (nodes...) ->
-  @expressions: flatten nodes
-  @children: @expressions
-
-Expressions extends Node
+Expressions: exports.Expressions: inherit Node, {
+
+  constructor: (nodes...) ->
+    @expressions: flatten nodes
+    @children: @expressions
+
+  # Wrap up a node as an Expressions, unless it already is.
+  wrap: (nodes...) ->
+    return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
+    new Expressions(nodes...)
+
+  # Tack an expression on to the end of this expression list.
+  push: (node) ->
+    @expressions.push(node)
+    this
+
+  # Tack an expression on to the beginning of this expression list.
+  unshift: (node) ->
+    @expressions.unshift(node)
+    this
+
+  # If this Expressions consists of a single node, pull it back out.
+  unwrap: ->
+    if @expressions.length is 1 then @expressions[0] else this
+
+  # Is this an empty block of code?
+  empty: ->
+    @expressions.length is 0
+
+  # Is the node last in this block of expressions?
+  is_last: (node) ->
+    l: @expressions.length
+    @last_index ||= if @expressions[l - 1] instanceof CommentNode then -2 else -1
+    node is @expressions[l - @last_index]
+
+  compile: (o) ->
+    if o.scope then super(o) else @compile_root(o)
+
+  # Compile each expression in the Expressions body.
+  compile_node: (o) ->
+    (@compile_expression(node, dup(o)) for node in @expressions).join("\n")
+
+  # If this is the top-level Expressions, wrap everything in a safety closure.
+  compile_root: (o) ->
+    o.indent: @indent: indent: if o.no_wrap then '' else TAB
+    o.scope: new Scope(null, this, null)
+    code: if o.globals then @compile_node(o) else @compile_with_declarations(o)
+    code: code.replace(TRAILING_WHITESPACE, '')
+    if o.no_wrap then code else "(function(){\n"+code+"\n})();"
+
+  # Compile the expressions body, with declarations of all inner variables
+  # pushed up to the top.
+  compile_with_declarations: (o) ->
+    code: @compile_node(o)
+    args: @contains (node) -> node instanceof ValueNode and node.arguments()
+    argv: if args and o.scope.check('arguments') then '' else 'var '
+    code: @idt() + argv + "arguments = Array.prototype.slice.call(arguments, 0);\n" + code if args
+    code: @idt() + 'var ' + o.scope.compiled_assignments() + ";\n" + code  if o.scope.has_assignments(this)
+    code: @idt() + 'var ' + o.scope.compiled_declarations() + ";\n" + code if o.scope.has_declarations(this)
+    code
+
+  # Compiles a single expression within the expressions body.
+  compile_expression: (node, o) ->
+    @indent: o.indent
+    stmt:    node.is_statement()
+    # We need to return the result if this is the last node in the expressions body.
+    returns: o.returns and @is_last(node) and not node.is_statement_only()
+    delete o.returns
+    # Return the regular compile of the node, unless we need to return the result.
+    return (if stmt then '' else @idt()) + node.compile(merge(o, {top: true})) + (if stmt then '' else ';') unless returns
+    # If it's a statement, the node knows how to return itself.
+    return node.compile(merge(o, {returns: true})) if node.is_statement()
+    # If it's not part of a constructor, we can just return the value of the expression.
+    return @idt() + 'return ' + node.compile(o) unless o.scope.function?.is_constructor()
+    # It's the last line of a constructor, add a safety check.
+    temp: o.scope.free_variable()
+    @idt() + temp + ' = ' + node.compile(o) + ";\n" + @idt() + "return " + o.scope.function.name + ' === this.constructor ? this : ' + temp + ';'
+}
+
 statement Expressions

-# Wrap up a node as an Expressions, unless it already is.
-Expressions::wrap: (nodes...) ->
-  return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions
-  new Expressions(nodes...)
-
-# Tack an expression on to the end of this expression list.
-Expressions::push: (node) ->
-  @expressions.push(node)
-  this
-
-# Tack an expression on to the beginning of this expression list.
-Expressions::unshift: (node) ->
-  @expressions.unshift(node)
-  this
-
-# If this Expressions consists of a single node, pull it back out.
-Expressions::unwrap: ->
-  if @expressions.length is 1 then @expressions[0] else this
-
-# Is this an empty block of code?
-Expressions::empty: ->
-  @expressions.length is 0
-
-# Is the node last in this block of expressions?
-Expressions::is_last: (node) ->
-  l: @expressions.length
-  @last_index ||= if @expressions[l - 1] instanceof CommentNode then -2 else -1
-  node is @expressions[l - @last_index]
-
-Expressions::compile: (o) ->
-  if o.scope then super(o) else @compile_root(o)
-
-# Compile each expression in the Expressions body.
-Expressions::compile_node: (o) ->
-  (@compile_expression(node, dup(o)) for node in @expressions).join("\n")
-
-# If this is the top-level Expressions, wrap everything in a safety closure.
-Expressions::compile_root: (o) ->
-  o.indent: @indent: indent: if o.no_wrap then '' else TAB
-  o.scope: new Scope(null, this, null)
-  code: if o.globals then @compile_node(o) else @compile_with_declarations(o)
-  code: code.replace(TRAILING_WHITESPACE, '')
-  if o.no_wrap then code else "(function(){\n"+code+"\n})();"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+# Literals are static values that can be passed through directly into
+# JavaScript without translation, eg.: strings, numbers, true, false, null...
+LiteralNode: exports.LiteralNode: (value) ->
+  @value: value
+  @children: [value]
+
+# Break and continue must be treated as statements -- they lose their meaning
+# when wrapped in a closure.
+LiteralNode::is_statement: ->
+  @value is 'break' or @value is 'continue'
+
+LiteralNode::is_statement_only: LiteralNode::is_statement
+
+LiteralNode::compile_node: (o) ->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


obeying coffeescript convention of a single space after every comma

--- nodes.coffee
+++ nodes.coffee
@@ -651,7 +651,7 @@
       clause = "#{@fromVar} <= #{@toVar} ?"
       body   = "var #{vars}; #{clause} #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{clause} #{i} += 1 : #{i} -= 1"
     post   = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
-    "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this,arguments)"
+    "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this, arguments)"

 #### Slice


mathjax config svg output processor to look like http://docs.mathjax.org/en/latest/options/SVG.html

--- lib/mathjax-helper.coffee
+++ lib/mathjax-helper.coffee
@@ -189,7 +189,7 @@
     }
     SVG: {
       font: "Tex"
-      undefinedFamily: ["STIXGeneral", 'Arial Unicode MS', "serif"]
+      undefinedFamily: "STIXGeneral, 'Arial Unicode MS', serif"
       linebreaks:
         automatic: false
         width: "75%"


Bug fix for patchRemove

Bug fix for patchRemove

--- lib/relations/many.coffee
+++ lib/relations/many.coffee
@@ -211,6 +211,8 @@

     # REMOVE SOME
     return callback(new Error('Many.patchRemove: embedded relationships are not supported')) if @isEmbedded()
+    return callback(new Error('One.patchRemove: missing model for remove')) unless relateds
+    relateds = [relateds] unless _.isArray(relateds)
     collection = @_ensureCollection(model)

     # destroy in memory


Minor fix for favoriting

Minor fix for favoriting

--- app/annotation/annotation.cjsx
+++ app/annotation/annotation.cjsx
@@ -32,6 +32,14 @@
       @setState previews: @props.previews
     ), 2000

+  componentWillReceiveProps: ->
+    if @props.guideIsOpen is true
+      @refs.guideIcon.getDOMNode().src = "./assets/guide-icon.svg"
+
+    if @state.favorited is true
+      @setState favorited: false
+      @refs.favoriteIcon.getDOMNode().src = "./assets/fav-icon.svg"
+
   animateImages: ->
     @animate "image-flip", {transform: 'rotateY(180deg)'}, {transform: 'rotateY(0deg)'}, 'ease-in-out', 500

@@ -43,10 +51,6 @@
     else
       @setState zoomImage: false
       @setState zoomImageIndex: null
-
-  componentWillReceiveProps: ->
-    if @props.guideIsOpen is true
-      @refs.guideIcon.getDOMNode().src = "./assets/guide-icon.svg"

   onClickGuide: ->
     if @props.guideIsOpen is false
@@ -60,11 +64,11 @@
   onClickFavorite: ->
     if @state.favorited is false
       @setState favorited: true
-      @props.classification.favorite is true
+      @props.classification.favorite = true
       @refs.favoriteIcon.getDOMNode().src = "./assets/fav-icon-filled.svg"
     else
       @setState favorited: false
-      @props.classification.favorite is false
+      @props.classification.favorite = false
       @refs.favoriteIcon.getDOMNode().src = "./assets/fav-icon.svg"

   render: ->


Do capability discovery before emitting connected

--- src/runtimes/iframe.coffee
+++ src/runtimes/iframe.coffee
@@ -73,13 +73,14 @@
   onLoaded: =>
     @connecting = false
     @connected = true
+
+    # Perform capability discovery
+    @send 'runtime', 'getruntime', null
+
     @emit 'status',
       online: true
       label: 'connected'
     @emit 'connected'
-
-    # Perform capability discovery
-    @send 'runtime', 'getruntime', null

     @flush()


EnvironmentsMachineContainer: fix new machine request handler

--- client/environments/lib/views/scene/environmentmachinecontainer.coffee
+++ client/environments/lib/views/scene/environmentmachinecontainer.coffee
@@ -1,10 +1,11 @@
-EnvironmentContainer = require './environmentcontainer'
+ComputeHelpers         = require 'app/providers/computehelpers'
+EnvironmentContainer   = require './environmentcontainer'
 EnvironmentMachineItem = require './environmentmachineitem'
-ComputeController = require 'app/providers/computecontroller'
-ComputeController_UI = require 'app/providers/computecontroller.ui'
+
+
 module.exports = class EnvironmentMachineContainer extends EnvironmentContainer

-  constructor:(options={}, data)->
+  constructor: (options={}, data) ->

     options      =
       title      : 'virtual machines'
@@ -15,4 +16,4 @@
     super options, data

     @on 'PlusButtonClicked', =>
-      ComputeController_UI.showProvidersModal @getData()
+      ComputeHelpers.handleNewMachineRequest stack: @getData()


Comments, moved the viewsByCid declaration

--- coffee/views/collection_view.coffee
+++ coffee/views/collection_view.coffee
@@ -6,26 +6,32 @@
   # and should instantiate a corresponding item view.
   class CollectionView extends View

+    # Configuration options which may be set in derived classes
+
     # Animation duration when adding new items (set to 0 to disable fade in)
     animationDuration: 500
-
-    # Hash which saves all item views by CID
-    viewsByCid: null

     # The list element the item views are actually appended to.
     # If empty, $el is used.
     # Set the selector property in the derived class to use a specific element.
     listSelector: null
+    # The actual element reference which is filled using listSelector
     $list: null

     # Selector for a fallback element which is shown if the collection is empty.
     # Set the selector property in the derived class to use a specific element.
     fallbackSelector: null
+    # The actual element reference which is filled using fallbackSelector
     $fallback: null

     # Selector which identifies child elements belonging to collection
     # All children are seen as belonging to collection if not present
     itemSelector: null
+
+    # View lists
+
+    # Hash which saves all item views by CID
+    viewsByCid: null

     # Track a list of the visible views
     visibleItems: null
@@ -43,6 +49,8 @@
       #console.debug 'CollectionView#initialize', this, @collection, options

       # Default options
+      # These are not in the options property on so derived classes
+      # may override them when calling super
       _(options).defaults
         render: true      # Render the view immediately per default
         renderItems: true # Render all items immediately per default


improve logging for unique_writes

improve logging for unique_writes

--- src/smc-hub/rethink.coffee
+++ src/smc-hub/rethink.coffee
@@ -3048,7 +3048,8 @@
                     rep = json_stable_stringify(opts.query)
                     q = misc_node.sha1(rep)
                     if uniq[table][q]
-                        dbg("trying to rewrite entry in unique_writes table #{uniq[table][q]} times -- skipping: query='#{rep}'")
+                        who = if opts.account_id? then "account_id='#{opts.account_id}'" else "project_id='#{opts.project_id}'"
+                        dbg("(#{who}): trying to rewrite entry in unique_writes table for the #{uniq[table][q]} time -- skipping: query='#{rep}'")
                         uniq[table][q] += 1
                         opts.cb() # CRITICAL -- DO NOT ERROR!  That stops subsequent records being written, leading to data loss
                         return


webapp/about: showing the opened file stats in the table

--- src/smc-webapp/r_help.cjsx
+++ src/smc-webapp/r_help.cjsx
@@ -55,6 +55,7 @@
             accounts_created    : rtypes.object # {RECENT_TIMES.key → number, ...}
             projects_created    : rtypes.object # {RECENT_TIMES.key → number, ...}
             projects_edited     : rtypes.object # {RECENT_TIMES.key → number, ...}
+            files_opened        : rtypes.object

     displayName : 'HelpPage-HelpPageUsageSection'

@@ -83,6 +84,9 @@
         n = @props.projects_edited?[RECENT_TIMES_KEY.active]
         <ProgressBar now={Math.max(n / 3, 60 / 2)} label={"#{n} projects being edited"} />

+    timespan_keys: ->
+        ['last_hour', 'last_day', 'last_week', 'last_month']
+
     recent_usage_stats_rows: ->
         stats = [
             ['Modified projects', @props.projects_edited],
@@ -92,18 +96,34 @@
         for stat in stats

                 {stat[0]}
-
-                    {stat[1]?[RECENT_TIMES_KEY.last_hour]}
-
-
-                    {stat[1]?[RECENT_TIMES_KEY.last_day]}
-
-
-                    {stat[1]?[RECENT_TIMES_KEY.last_week]}
-
-
-                    {stat[1]?[RECENT_TIMES_KEY.last_month]}
-
+                {
+                    for k in @timespan_keys()
+
+                            {stat[1]?[RECENT_TIMES_KEY[k]]}
+
+                }
+

+
+    render_filetype_stats_rows: ->
+        stats = [
+            ['Sage Worksheets',   'sagews'],
+            ['Jupyter Notebooks', 'ipynb'],
+            ['LaTeX Documents',   'tex'],
+            ['Markdown Documents','md']
+        ]
+        if DEBUG then console.log('@props.files_opened', @props.files_opened)
+        for [name, ext] in stats
+

+                {name}
+                {
+                    for timespan in @timespan_keys()
+                        k        = RECENT_TIMES_KEY[timespan]
+                        total    = @props.files_opened?.total?[k]?[ext]    ? 0
+                        distinct = @props.files_opened?.distinct?[k]?[ext] ? 0
+
+                            {total} ({distinct})
+
+                }

     render_recent_usage_stats: ->
@@ -121,16 +141,14 @@

                 {@recent_usage_stats_rows()}
+

+

+                    File activity
+                    total (and unique) opened or edited
+

+                {@render_filetype_stats_rows()}

-
-    render_historical_metrics: ->
-        return  # disabled, due to being broken...
-

-
-                <Icon name='area-chart' fixedWidth />Historical system metrics
-             — CPU usage, running projects and software instances, etc
-

     render_when_updated: ->
         if @props.time
@@ -153,10 +171,8 @@
                 {@render_recent_usage_stats()}
                 <Icon name='line-chart' fixedWidth />{' '}

-                More data...
+                    More data...

-

-                {@render_historical_metrics()}

         </Col>


fixed duplication

fixed duplication

--- src/smc-webapp/r_misc.cjsx
+++ src/smc-webapp/r_misc.cjsx
@@ -34,7 +34,6 @@
 misc        = require('smc-util/misc')
 immutable   = require('immutable')
 underscore  = require('underscore')
-{IS_MOBILE} = require('./feature')

 markdown    = require('./markdown')

@@ -1179,14 +1178,15 @@
         for name, icon of items
             @render_menu_item(name, icon)

+    render_dropdown_button : (bsSize, className) ->
+        <DropdownButton style={marginRight:'2px'} id='file_info_button' bsStyle='info' bsSize={bsSize} title={<Icon name='info-circle' />} className={className}>
+            {@render_menu_items()}
+        </DropdownButton>
+
     render : ->
         <div>
-            <DropdownButton style={marginRight:'2px'} id='file_info_button' bsStyle='info' bsSize='large' title={<Icon name='info-circle' />} className='pull-left visible-xs'>
-                {@render_menu_items()}
-            </DropdownButton>
-            <DropdownButton style={marginRight:'2px'} id='file_info_button' bsStyle='info' title={<Icon name='info-circle' />} className='pull-left hidden-xs'>
-                {@render_menu_items()}
-            </DropdownButton>
+            {@render_dropdown_button('large', 'pull-left visible-xs')}
+            {@render_dropdown_button(null, 'pull-left hidden-xs')}
         </div>

 exports.render_file_info_dropdown = (filename, actions, dom_node, is_public) ->


Allow duration to be configured

Allow duration to be configured

--- components/mixins/transition.coffee
+++ components/mixins/transition.coffee
@@ -1,14 +1,12 @@
-# This mess is probably going to get replaced with something like:
-# http://ricostacruz.com/jquery.transit/
 module.exports =
-  fade: ($el, options) ->
+  fade: ($el, options = {}) ->
     $el.
       attr('data-state', 'fade-out').
       one($.support.transition.end, ->
-        options.out() if options?.out?
+        options.out() if options.out?
         $el.
           attr('data-state', 'fade-in').
           one($.support.transition.end, ->
-            options.in() if options?.in?
-          ).emulateTransitionEnd 250
-      ).emulateTransitionEnd 250
+            options.in() if options.in?
+          ).emulateTransitionEnd (options.duration or 250)
+      ).emulateTransitionEnd (options.duration or 250)


show full authorized name

show full authorized name

--- app/models/organization.coffee
+++ app/models/organization.coffee
@@ -1,7 +1,7 @@
 `import DS from 'ember-data'`
 `import NpiType from './npi-type'`

-Organization = NpiType.extend
+Organization = NpiType.extend
   ein: DS.attr('string'),
   organizationNameLegalBusinessName: DS.attr('string'),
   otherOrganizationName: DS.attr('string'),
@@ -22,7 +22,7 @@
   fullAuthorizedName: (->
     name = (@get('authorizedOfficialNamePrefix') || '') + ' ' + (@get('authorizedOfficialFirstName') || '') + ' ' + (@get('authorizedOfficialMiddleName') || '') + ' ' + (@get('authorizedOfficialLastName') || '') + ' ' + (@get('authorizedOfficialNameSuffix') || '')
     #return the name unless the name is null only whitespace, otherwise null.
-    name unless /\S/.test(name)
+    name if /\S/.test(name)
     ).property('authorizedOfficialNamePrefix', 'authorizedOfficialFirstName', 'authorizedOfficialMiddleName', 'authorizedOfficialLastName', 'authorizedOfficialNameSuffix')


Adjust text in subject set uploader target.

--- app/pages/lab/subject-set.cjsx
+++ app/pages/lab/subject-set.cjsx
@@ -151,7 +151,7 @@

         <UploadDropTarget accept="text/csv, text/tab-separated-values, image/*" multiple onSelect={@handleFileSelection}>
           Manifest files are required.

-          Drag-and-drop manifests and subject images here.

+          Drag-and-drop or click to upload manifests and subject images here.

           Manifests must be .csv or .tsv. The first row should define metadata headers. All other rows should include at least one reference to an image filename in the same directory as the manifest.

           Subject images can be any of: {{ext}{', ' if VALID_SUBJECT_EXTENSIONS[i + 1]?} for ext, i in VALID_SUBJECT_EXTENSIONS}{' '}
           and may not contain {{char}{', ' if INVALID_FILENAME_CHARS[i + 1]?} for char, i in INVALID_FILENAME_CHARS}.


adding animation for row addition and deletion

closes #14

--- source/javascripts/src/view_xlformbuilder.coffee
+++ source/javascripts/src/view_xlformbuilder.coffee
@@ -167,15 +167,25 @@
   reset: ->
     fe = @formEditorEl.empty()
     @survey.forEachRow (row)->
-      fe.append(new XlfRowView(model: row).render().el)
+      # row._slideDown is for add/remove animation
+      $el = new XlfRowView(model: row).render().$el
+      if row._slideDown
+        row._slideDown = false
+        fe.append($el.hide())
+        $el.slideDown 175
+      else
+        fe.append($el)
   clickRemoveRow: (evt)->
     evt.preventDefault()
     if confirm("Are you sure you want to delete this question? This action cannot be undone.")
-      rowId = $(evt.target).data("rowCid")
+      $et = $(evt.target)
+      rowId = $et.data("rowCid")
+      rowEl = $et.parents("li").eq(0)
       matchingRow = @survey.rows.find (row)-> row.cid is rowId
       if matchingRow
         @survey.rows.remove matchingRow
-      @survey.rows.trigger "reset"
+      # this slideUp is for add/remove row animation
+      rowEl.slideUp 175, "swing", ()=> @survey.rows.trigger "reset"
   clickInsertRow: (evt)->
     cid = $(evt.target).parents("li").eq(0).data("row-model-id")
     newIndex = @survey.rows.indexOf(@survey.rows.get(cid)) + 1
@@ -184,7 +194,10 @@
     @addRowAtIndex(_parent: @survey)
   addRowAtIndex: (opts={}, atIndex=false)->
     atIndex = @survey.rows.length  if atIndex is false
-    @survey.rows.add(new XLF.Row(opts), at: atIndex)
+    # row._slideDown is for add/remove animation
+    nRow = new XLF.Row(opts)
+    nRow._slideDown = true
+    @survey.rows.add(nRow, at: atIndex)
   publishButtonClick: (evt)->
     @onPublish.call(@, arguments)


ThreadPage: Check for unsaved comment when user clicks cancel reply button

--- vue/src/shared/services/comment_service.coffee
+++ vue/src/shared/services/comment_service.coffee
@@ -4,6 +4,7 @@
 import Records from '@/shared/services/records'
 import openModal from '@/shared/helpers/open_modal'
 import Flash from '@/shared/services/flash'
+import RescueUnsavedEditsService from '@/shared/services/rescue_unsaved_edits_service'

 export default new class CommentService
   actions: (comment, vm) ->
@@ -39,13 +40,17 @@
       menu: isOwnComment
       canPerform: -> AbilityService.canRespondToComment(comment)
       perform: ->
-        vm.newComment = Records.comments.build
-          bodyFormat: "html"
-          body: ""
-          discussionId: comment.discussion().id
-          authorId: Session.user().id
-          parentId: comment.id
-        vm.showReplyForm = !vm.showReplyForm
+        if vm.showReplyForm
+          if RescueUnsavedEditsService.okToLeave(vm.newComment)
+            vm.showReplyForm = false
+        else
+          vm.newComment = Records.comments.build
+            bodyFormat: "html"
+            body: ""
+            discussionId: comment.discussion().id
+            authorId: Session.user().id
+            parentId: comment.id
+          vm.showReplyForm = true

     edit_comment:
       name: 'common.action.edit'


JDomain: add createDomain helper to create JDomains from given domainData, update signature

--- workers/social/lib/social/models/domain.coffee
+++ workers/social/lib/social/models/domain.coffee
@@ -37,7 +37,6 @@
         fetchDomains:
           (signature Function)
         createDomain: [
-          (signature Function)
           (signature Object, Function)
         ]
         # getTldList:
@@ -96,6 +95,7 @@
       hostnameAlias : 'sparse'

     schema          :
+
       domain        :
         type        : String
         required    : yes
@@ -219,8 +219,23 @@

         callback new KodingError "CNAME settings are wrong.", "CNAMEMISMATCH"

-
-    callback null, slug
+  createDomain = (domainData, account, callback)->
+
+    model = new JDomain domainData
+    model.save (err)->
+      return callback err  if err
+
+      rel = new Relationship
+        targetId   : model.getId()
+        targetName : 'JDomain'
+        sourceId   : account.getId()
+        sourceName : 'JAccount'
+        as         : 'owner'
+
+      rel.save (err)->
+        return callback err  if err
+
+      callback err, model

   @createDomain: secure (client, options, callback)->


Teamwork: Ref rename.

Teamwork: Ref rename.

--- client/Teamwork/Views/teamworktabview.coffee
+++ client/Teamwork/Views/teamworktabview.coffee
@@ -6,7 +6,7 @@

     @createElements()
     @keysRef  = @workspaceRef.child "keys"
-    @stateRef = @workspaceRef.child "state"
+    @indexRef = @workspaceRef.child "index"

     @recoverSession()  if @isJoinedASession


Modify the help for the force option on upload command

--- src/lib/command.coffee
+++ src/lib/command.coffee
@@ -78,8 +78,10 @@
     )
     .option(
       '-F, --force'
-      ,"If does not exist source in config.json but exist on server,
-       server's file will be deleted"
+      ,"If does not exist source in gas-project.json but exist on server,
+       server's file will be deleted.
+       And if does not exist source on server but in gas-project.json,
+       upload the new file to server"
       )
     .action(require('./commands/upload-command').upload)


Removed filterActive from filter alerts

Removed filterActive from filter alerts

--- app/schemas/models/user.coffee
+++ app/schemas/models/user.coffee
@@ -218,11 +218,6 @@
       items:
         type: visa.type
         enum: visa.enum
-    filterActive:
-      title: 'Filter active'
-      description: 'Whether or not an employer will receive emails based on this filter'
-      type: 'boolean'
-      default: false
   })

   points: {type: 'number'}


feat(serialization): Allows for resources to have properties of their own class' type, and not infinitely recurse even when that field is null.

--- coffee/classes/081_base_resource.tail.coffee
+++ coffee/classes/081_base_resource.tail.coffee
@@ -169,47 +169,46 @@
 		json

 	$_fromJson: ( json ) ->
-		json = json or {} # ensures if json is null that any errors that may/should arise from that situation occur
-
-		json.this = json if @$_useThis # this is to allow expressions to use `this` to refer to the json object itself
-
-		# Actually assign all the JSON properties properly to the resource if possible
-		angular.forEach @$_propertyToJsonMapping, ( jsonExpression, propertyExpression ) =>
-			jsonExpressionGetter = @$parse jsonExpression
-			jsonValue = jsonExpressionGetter json
-
-			propertyExpressionGetter = @$parse propertyExpression
-			propertyExpressionSetter = propertyExpressionGetter.assign
-			propertyExpressionSetter @, jsonValue
-
-		# Assign all the aggregated resources
-		angular.forEach @$_propertyToResourceMapping, ( aggregateResourceDefinition, propertyExpression ) =>
-			# We will first assume we're not going to be attributing these aggregated resources to the given propertyExpression evaluation as an array unless (1), any
-			# of the aggregated resources (given by its corresponding jsonExpression) is an array, or (2), we have multiple aggregated resources
-			# that each correspond to this same propertyExpression evaluation.
-			isPropertyArray = no
-			jsonResources = new Array()
-
-			propertyExpressionGetter = @$parse propertyExpression
-			propertyExpressionSetter = propertyExpressionGetter.assign
-
-			angular.forEach aggregateResourceDefinition, ( resourceClass, jsonExpression ) =>
+		if json?
+			json.this = json if @$_useThis # this is to allow expressions to use `this` to refer to the json object itself
+
+			# Actually assign all the JSON properties properly to the resource if possible
+			angular.forEach @$_propertyToJsonMapping, ( jsonExpression, propertyExpression ) =>
 				jsonExpressionGetter = @$parse jsonExpression
-				jsonResourceObjectOrArray = jsonExpressionGetter json
-
-				if angular.isArray jsonResourceObjectOrArray
-					isPropertyArray = isPropertyArray or yes
-					for jsonResourceDatum in jsonResourceObjectOrArray
-						jsonResources.push if jsonResourceDatum? then new resourceClass().$_fromJson( jsonResourceDatum ) else jsonResourceDatum
+				jsonValue = jsonExpressionGetter json
+
+				propertyExpressionGetter = @$parse propertyExpression
+				propertyExpressionSetter = propertyExpressionGetter.assign
+				propertyExpressionSetter @, jsonValue
+
+			# Assign all the aggregated resources
+			angular.forEach @$_propertyToResourceMapping, ( aggregateResourceDefinition, propertyExpression ) =>
+				# We will first assume we're not going to be attributing these aggregated resources to the given propertyExpression evaluation as an array unless (1), any
+				# of the aggregated resources (given by its corresponding jsonExpression) is an array, or (2), we have multiple aggregated resources
+				# that each correspond to this same propertyExpression evaluation.
+				isPropertyArray = no
+				jsonResources = new Array()
+
+				propertyExpressionGetter = @$parse propertyExpression
+				propertyExpressionSetter = propertyExpressionGetter.assign
+
+				angular.forEach aggregateResourceDefinition, ( resourceClass, jsonExpression ) =>
+					jsonExpressionGetter = @$parse jsonExpression
+					jsonResourceObjectOrArray = jsonExpressionGetter json
+
+					if angular.isArray jsonResourceObjectOrArray
+						isPropertyArray = isPropertyArray or yes
+						for jsonResourceDatum in jsonResourceObjectOrArray
+							jsonResources.push if jsonResourceDatum? then new resourceClass().$_fromJson( jsonResourceDatum ) else jsonResourceDatum
+					else
+						jsonResources.push if jsonResourceObjectOrArray? then new resourceClass().$_fromJson( jsonResourceObjectOrArray ) else jsonResourceObjectOrArray
+
+				if isPropertyArray or jsonResources.length > 1
+					propertyExpressionSetter @, jsonResources
 				else
-					jsonResources.push if jsonResourceObjectOrArray? then new resourceClass().$_fromJson( jsonResourceObjectOrArray ) else jsonResourceObjectOrArray
-
-			if isPropertyArray or jsonResources.length > 1
-				propertyExpressionSetter @, jsonResources
-			else
-				propertyExpressionSetter @, jsonResources[ 0 ]
-
-		angoolar.delete json, 'this' if @$_useThis
+					propertyExpressionSetter @, jsonResources[ 0 ]
+
+			angoolar.delete json, 'this' if @$_useThis

 		@$_init()


Disable fade transition

Disable fade transition

--- components/inquiry_questionnaire/view.coffee
+++ components/inquiry_questionnaire/view.coffee
@@ -1,6 +1,5 @@
 Backbone = require 'backbone'
 State = require '../branching_state/index.coffee'
-{ fade } = require '../mixins/transition.coffee'
 map = require './map.coffee'
 debug = require './debug.coffee'

@@ -13,18 +12,10 @@
     @listenTo @state, 'next', @render

   render: ->
-    pendingView = @state.view user: @user, state: @state, artwork: @artwork
-
     debug @state

-    if ($parent = @$el.parent()).length
-      fade $parent,
-        out: =>
-          @$el.html pendingView.render().$el
-        in: =>
-          @view?.remove()
-          @view = pendingView
-    else
-      @$el.html (@view = pendingView).render().$el
+    @view?.remove()
+    @view = @state.view user: @user, state: @state, artwork: @artwork
+    @$el.html @view.render().$el

     this


move connection:close middleware ahead of other routes

--- app.coffee
+++ app.coffee
@@ -61,14 +61,14 @@
 			logger.err err: exception, "exception in request domain handler"
 	requestDomain.run next

-app.get  "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.getFile
-app.post "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.insertFile
-
 app.use (req, res, next) ->
 	if not appIsOk
 		# when shutting down, close any HTTP keep-alive connections
 		res.set 'Connection', 'close'
 	next()
+
+app.get  "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.getFile
+app.post "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.insertFile

 app.put "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.copyFile
 app.del "/project/:project_id/file/:file_id", keyBuilder.userFileKey, fileController.deleteFile


joyride only when map guide is clicked

joyride only when map guide is clicked

--- app/assets/javascripts/em.js.coffee
+++ app/assets/javascripts/em.js.coffee
@@ -54,8 +54,6 @@
         else
             $('.modal-wrapper').hide()

-    showIntro()
-
     initializeShowMapTipsBtn = ->
         $('.map-tips-trigger').click ->
             startJoyride()


Model#get2

--- app/assets/javascripts/model.coffee
+++ app/assets/javascripts/model.coffee
@@ -48,6 +48,81 @@

 			_.defer -> callback null, output

+	get2 : (coordinates, callback) ->
+
+		output = new Float32Array(~~(coordinates.length / 3))
+
+		linear = (p0, p1, d) ->
+			p0 * (1 - d) + p1 * d
+
+		bilinear = (p0, p1, p2, p3, d0, d1) ->
+			p0 * (1 - d0) * (1 - d1) +
+			p1 * d0 * (1 - d1) +
+			p2 * (1 - d0) * d1 +
+			p3 * d0 * d1
+
+		trilinear = (p0, p1, p2, p3, p4, p5, p6, p7, d0, d1, d2) ->
+			p0 * (1 - d0) * (1 - d1) * (1 - d2) +
+			p1 * d0 * (1 - d1) * (1 - d2) +
+			p2 * (1 - d0) * d1 * (1 - d2) +
+			p3 * (1 - d0) * (1 - d1) * d2 +
+			p4 * d0 * (1 - d1) * d2 +
+			p5 * (1 - d0) * d1 * d2 +
+			p6 * d0 * d1 * (1 - d2) +
+			p7 * d0 * d1 * d2
+
+		for i in [0...coordinates.length] by 3
+
+			x = coordinates[i]
+			y = coordinates[i + 1]
+			z = coordinates[i + 2]
+
+			x0 = x >> 0; x1 = x0 + 1; xd = x - x0
+			y0 = y >> 0; y1 = y0 + 1;	yd = y - y0
+			z0 = z >> 0; z1 = z0 + 1; zd = z - z0
+
+			output[i / 3] = if x0 == x
+				if y0 == y
+					if z0 == z
+						value(x, y, z)
+					else
+						#linear z
+						linear(value(x, y, z0), value(x, y, z1), zd)
+				else
+					if z0 == z
+						#linear y
+						linear(value(x, y0, z), value(x, y1, z), yd)
+					else
+						#bilinear y,z
+						bilinear(value(x, y0, z0), value(x, y1, z0), value(x, y0, z1), value(x, y1, z1), yd, zd)
+			else
+				if y0 == y
+					if z0 == z
+						#linear x
+						linear(value(x0, y, z), value(x1, y, z), xd)
+					else
+						#bilinear x,z
+						bilinear(value(x0, y, z0), value(x1, y, z0), value(x0, y, z1), value(x1, y, z1), xd, zd)
+				else
+					if z0 == z
+						#bilinear x,y
+						bilinear(value(x0, y0, z), value(x1, y0, z), value(x0, y1, z), value(x1, y1, z), xd, yd)
+					else
+						#trilinear x,y,z
+						trilinear(
+							value(x0, y0, z0),
+							value(x1, y0, z0),
+							value(x0, y1, z0),
+							value(x1, y1, z0),
+							value(x0, y0, z1),
+							value(x1, y0, z1),
+							value(x0, y1, z1),
+							value(x1, y1, z1),
+							xd, yd, zd
+						)
+
+		callback(null, output)
+
 	get : (position, direction, callback) ->

 		@lazyInitialize (err) =>


HOME : fix signup click

HOME : fix signup click

--- app/static/scripts/home.coffee
+++ app/static/scripts/home.coffee
@@ -84,7 +84,7 @@
 init = () =>
     quick              = ($ '#quick')
     signin             = ($ '#signin')
-    signup             = ($ '.signup a')
+    signup             = ($ '.signup a').first()
     cancel             = ($ '.btn-cancel')
     tabs               = ($ '.tabs > div')
     rememberMe         = ($ '.rememberMe')


Remove debug log

Remove debug log

--- scripts/arrivals.coffee
+++ scripts/arrivals.coffee
@@ -55,8 +55,6 @@

   robot.listen(
     (msg) ->
-      if msg.room is 'C5F138J1K'
-        console.log msg
       msg.room is 'C5F138J1K' and msg.message?.rawMessage?.files?.length > 0
     (res) ->
       message = res.message.message


fix mentions being appended to the wrong element

--- scripts/mentions.coffee
+++ scripts/mentions.coffee
@@ -19,4 +19,4 @@
       screenName: message.find('.gg-messageSenderId').text()
       displayName: message.find('.gg-messageSender').text()
       timestamp: message.find('.gg-messageTimestamp').text()
-    $('#mentions').append html+    $('#mentions .ui-menuContent').append html


Temporary comment global proxy conf

Temporary comment global proxy conf

--- actions/proxy.coffee
+++ actions/proxy.coffee
@@ -97,32 +97,33 @@
   # There is no proxy to configure
   return next() unless ctx.config.proxy.http_proxy
   @name 'Proxy # Profile'
-  {system, http_proxy, https_proxy} = ctx.config.proxy
-  modified = 0
-  write = (file, callback) ->
-    ctx.write [
-      match: /.*http_proxy.*/
-      replace: "#{if system and http_proxy then '' else '#'}export http_proxy=#{http_proxy}"
-      destination: file
-      append: true
-    ,
-      match: /.*https_proxy.*/
-      replace: "#{if system and https_proxy then '' else '#'}export https_proxy=#{https_proxy}"
-      destination: file
-      append: true
-    ], (err, written) ->
-      modified++ if written
-      callback()
-  # System wide
-  write ctx.config.proxy.system_file, (err) ->
-    return next err if err
-    each(ctx.config.users)
-    .on 'item', (user, next) ->
-      return next() unless user.home
-      file = path.resolve user.home, '.bash_profile'
-      write file, next
-    .on 'both', (err) ->
-      next err, if modified then ctx.OK else ctx.PASS
+  return next null, 'DISABLED'
+  # {system, http_proxy, https_proxy} = ctx.config.proxy
+  # modified = 0
+  # write = (file, callback) ->
+  #   ctx.write [
+  #     match: /.*http_proxy.*/
+  #     replace: "{if system and http_proxy then '' else '#'}export http_proxy=#{http_proxy}"
+  #     destination: file
+  #     append: true
+  #   ,
+  #     match: /.*https_proxy.*/
+  #     replace: "{if system and https_proxy then '' else '#'}export https_proxy=#{https_proxy}"
+  #     destination: file
+  #     append: true
+  #   ], (err, written) ->
+  #     modified++ if written
+  #     callback()
+  # # System wide
+  # write ctx.config.proxy.system_file, (err) ->
+  #   return next err if err
+  #   each(ctx.config.users)
+  #   .on 'item', (user, next) ->
+  #     return next() unless user.home
+  #     file = path.resolve user.home, '.bash_profile'
+  #     write file, next
+  #   .on 'both', (err) ->
+  #     next err, if modified then ctx.OK else ctx.PASS


Checking for solid entities for projectile collisions

--- src/projectiles.coffee
+++ src/projectiles.coffee
@@ -6,15 +6,15 @@
 class Kona.Projectile extends Kona.Entity
   constructor: (opts={}) ->
     super(opts)
-    @speed = 7
+    @speed = opts.speed || 10

   update: ->
-    super
+    super()
     @position.x += @speed * @direction.dx
     if @leftCollisions() || @rightCollisions()
       for name, list of @neighborEntities()
         for ent in list
-          if @leftCollision(ent) || @rightCollision(ent)
+          if (@leftCollision(ent) || @rightCollision(ent)) && ent.solid
             ent.destroy() if _.contains(@destructibles, name)
             @destroy()


Atom reformatted config.cson

Atom reformatted config.cson

--- editors/atom/config.cson
+++ editors/atom/config.cson
@@ -1,55 +1,55 @@
 "*":
-  "exception-reporting":
-    userId: "d8ccd476-7728-7965-237e-8697ea1c9e22"
-  welcome:
-    showOnStartup: false
-  metrics:
-    userId: "f8484a553ab176923d240d2fcca1cc4aa18f1f9a"
-  editor:
-    fontFamily: "Hack"
-    tabLength: 4
-    invisibles: {}
+  "":
+    coffee:
+      source:
+        editor:
+          tabLength: 2
+  "atom-beautify":
+    _analyticsUserId: "ac214814-5908-408b-808f-94f635e0f891"
+  "atom-terminal": {}
+  "autocomplete-plus": {}
   core:
+    disabledPackages: [
+      "linter-pep8"
+    ]
     projectHome: "~/Projects"
     themes: [
       "one-dark-ui"
       "monokai"
     ]
-    disabledPackages: [
-      "linter-pep8"
-    ]
-  minimap:
-    v4Preview: true
+  editor:
+    fontFamily: "Hack"
+    invisibles: {}
+    tabLength: 4
+  "exception-reporting":
+    userId: "d8ccd476-7728-7965-237e-8697ea1c9e22"
+  "fuzzy-finder": {}
+  "git-diff": {}
+  "go-plus":
+    goPath: "~/Projects/go"
   linter: {}
   "linter-flake8":
     ignoreErrorCodes: [
       "E501"
       "N802"
     ]
-  "autocomplete-plus": {}
-  "atom-terminal": {}
+  "linter-scalac":
+    scalacExecutablePath: "/usr/lib/scala/bin/"
+  "markdown-preview": {}
+  "merge-conflicts": {}
+  metrics:
+    userId: "f8484a553ab176923d240d2fcca1cc4aa18f1f9a"
+  minimap:
+    v4Preview: true
+  "one-dark-ui": {}
+  "open-on-bitbucket":
+    cloudUrlOverride: "git-staging@staging.bitbucket.org"
   tabs:
     usePreviewTabs: true
   "tree-view": {}
-  "go-plus":
-    goPath: "~/Projects/go"
-  "atom-beautify":
-    _analyticsUserId: "ac214814-5908-408b-808f-94f635e0f891"
-  "merge-conflicts": {}
   "vim-mode": {}
-  "linter-scalac":
-    scalacExecutablePath: "/usr/lib/scala/bin/"
-  "one-dark-ui": {}
-  "fuzzy-finder": {}
-  "git-diff": {}
-  "markdown-preview": {}
-  "":
-    coffee:
-      source:
-        editor:
-          tabLength: 2
-  "open-on-bitbucket":
-    cloudUrlOverride: "git-staging@staging.bitbucket.org"
+  welcome:
+    showOnStartup: false
 ".basic.html.text":
   editor:
     tabLength: 2


Prevent double consecutive intercom messages.

--- packages/retronator-hq/scenes/intercom.coffee
+++ packages/retronator-hq/scenes/intercom.coffee
@@ -45,7 +45,16 @@
       # Looks OK, say something funny!
       @_playMessage()

-      @_scheduleNextMessage()
+      # Wait for message to play.
+      Meteor.setTimeout =>
+        Tracker.autorun (computation) =>
+          return if LOI.adventure.interface.busy()
+          computation.stop()
+
+          # Continue to next.
+          @_scheduleNextMessage()
+      ,
+        0
     ,
       options.delay


Fix ANSI-to-HTML conversion

Fix ANSI-to-HTML conversion

--- src/chromeExtension/components/000-root.cjsx
+++ src/chromeExtension/components/000-root.cjsx
@@ -1,3 +1,4 @@
+_                 = require '../../vendor/lodash'
 React             = require 'react'
 Login             = require './010-login'
 LargeMessage      = require './900-largeMessage'
@@ -65,9 +66,10 @@
     return null if not spans
     return null if not spans.length
     return spans.map (span) =>
+      if _.isString span
+        return span
       <span style={span.style}>
-        {span.txt}
-        {@_renderRecordSpans spans.children}
+        {@_renderRecordSpans span.children}
       </span>


Remove log message that breaks XML output of checkstyle (and probably JSLint)

--- src/configfinder.coffee
+++ src/configfinder.coffee
@@ -33,7 +33,7 @@
     try
         JSON.parse(fs.readFileSync(filename).toString())
     catch e
-        console.error "Could not load JSON file '%s': %s", filename, e
+        process.stderr.write "Could not load JSON file '#{filename}': #{e}"
         null

 # Tries to find a configuration file in either project directory (if file is
@@ -50,5 +50,4 @@
     envs = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE
     home = path.normalize(path.join(envs, "coffeelint.json"))
     if fs.existsSync(home)
-        console.log 'loaded', home
         return loadJSON(home)


Remove epoxy dependency

Remove epoxy dependency

--- dist/DropDown.coffee
+++ dist/DropDown.coffee
@@ -1,7 +1,6 @@
 define (require, exports, module)->
   Backbone = require "backbone"
   MixinBackbone = require "backbone-mixin"
-  require "epoxy"

   View = MixinBackbone Backbone.Epoxy.View

@@ -21,11 +20,30 @@
     className: "dropdown_item"
     tagName: "li"

-    bindings:
-      ":el": "text: text, classes: {active: active}"
-
     events:
       "click": "onClick"
+
+    initialize: ->
+      @listenTo @model,
+        "change:text": @onTextChange
+        "change:active": @onActiveChange
+      @updateText()
+      @updateActive()
+
+    updateText: ->
+      @$el.text @model.get "text"
+
+    updateActive: ->
+      if @model.get "active"
+        @$el.addClass "active"
+      else
+        @$el.removeClass "active"
+
+    onTextChange: ->
+      @updateText()
+
+    onActiveChange: ->
+      @updateActive()

     onClick: ->
       @model.set active:true
@@ -45,16 +63,18 @@
       menu: "[data-js-menu]"
       button: "[data-js-button]"

-    bindings:
-      "@ui.menu": "collection: $collection"
-
     events:
       "click": "onClick"

     initialize: ->
+      window.aaa = @
       @collection = new DropDownCollection
-      @listenTo @collection, "change:active", @onChangeCollectionActive
+      @listenTo @collection,
+        "change:active": @onChangeCollectionActive
+        "add": @onAddCollection
+        "remove": @onRemoveCollection
       @currentActiveModel = null
+      @views = {}

     bindToInput: (@$input)->

@@ -68,7 +88,14 @@
     onClick: ->
       @$el.toggleClass "open"

-    onChangeCollectionActive: (model,value)->
+    onAddCollection: (model)->
+      @views[model.cid] = itemView = new @itemView {model}
+      @ui.menu.append itemView.$el
+
+    onRemoveCollection: (model)->
+      @views[model.cid].remove()
+
+    onChangeCollectionActive: (model, value)->
       return unless value
       @currentActiveModel?.set active:false
       @currentActiveModel = model


Updated to look for purchase_id [WAIT-31]

--- src/core/javascripts/controllers/total.js.coffee
+++ src/core/javascripts/controllers/total.js.coffee
@@ -14,7 +14,7 @@
   $rootScope.connection_started.then =>
     $scope.bb.payment_status = null

-    id = if $scope.bb.total then $scope.bb.total.long_id else QueryStringService('booking_id')
+    id = if $scope.bb.total then $scope.bb.total.long_id else QueryStringService('purchase_id')

     if id
       PurchaseService.query({url_root: $scope.bb.api_url, purchase_id: id}).then (total) ->


Resolve text reference in returnValue array

--- src/util/referencer.coffee
+++ src/util/referencer.coffee
@@ -248,8 +248,8 @@
       if data.doc.comment
         data.doc.comment = @resolveTextReferences(data.doc.comment, entity, path)

-      if data.doc.returnValue?.desc
-        data.doc.returnValue.desc = @resolveTextReferences(data.doc.returnValue.desc, entity, path)
+      for returnValue in data.doc.returnValue ? []
+        returnValue.desc = @resolveTextReferences(returnValue.desc, entity, path)

       if data.doc.throwValue
         for throws, index in data.doc.throwValue


pre-1.3.2

pre-1.3.2

 - Implement `addFile()` method
 - Fix potential bug in `link()` method
 - Fix potential bugs in `load`, `write` and `upload` methods
 - `opts.meta` now always object

--- files.coffee
+++ files.coffee
@@ -2,8 +2,9 @@
   ###
   @description Require "fs-extra" npm package
   ###
-  fs = Npm.require "fs-extra"
+  fs      = Npm.require "fs-extra"
   request = Npm.require "request"
+  util    = Npm.require "util"

   ###
   @var {object} bound - Meteor.bindEnvironment aka Fiber wrapper
@@ -356,7 +357,7 @@
   @description Write buffer to FS and add to Meteor.Files Collection
   @returns {Files} - Return this
   ###
-  write: if Meteor.isServer then (buffer, opts, callback) ->
+  write: if Meteor.isServer then (buffer, opts = {}, callback) ->
     console.info "Meteor.Files Debugger: [write(buffer, #{opts}, callback)]" if @debug
     check opts, Match.Optional Object
     check callback, Match.Optional Function
@@ -366,23 +367,27 @@
       fileName      = if opts.name or opts.fileName then opts.name or opts.fileName else randFileName
       extension     = fileName.split('.').pop()
       path          = "#{@storagePath}/#{randFileName}.#{extension}"
+
+      opts.type = 'application/*' if not opts.type
+      opts.meta = {} if not opts.meta
+      opts.size = buffer.length if not opts.size

       result =
         name:       fileName
         extension:  extension
         path:       path
         meta:       opts.meta
-        type:       if opts.type then opts.type else 'application/*'
-        size:       if opts.size then opts.size else buffer.length
+        type:       opts.type
+        size:       opts.size
+        isVideo: if opts.type then opts.type.toLowerCase().indexOf("video") > -1 else false
+        isAudio: if opts.type then opts.type.toLowerCase().indexOf("audio") > -1 else false
+        isImage: if opts.type then opts.type.toLowerCase().indexOf("image") > -1 else false
         versions:
           original:
             path: path
-            type: if opts.type then opts.type else 'application/*'
-            size: if opts.size then opts.size else buffer.length
+            type: opts.type
+            size: opts.size
             extension: extension
-        isVideo: if opts.type then opts.type.toLowerCase().indexOf("video") > -1 else false
-        isAudio: if opts.type then opts.type.toLowerCase().indexOf("audio") > -1 else false
-        isImage: if opts.type then opts.type.toLowerCase().indexOf("image") > -1 else false
         _prefix: @_prefix
         _collectionName: @collectionName
         _storagePath:    @storagePath
@@ -413,7 +418,7 @@
   @description Download file, write stream to FS and add to Meteor.Files Collection
   @returns {Files} - Return this
   ###
-  load: if Meteor.isServer then (url, opts, callback) ->
+  load: if Meteor.isServer then (url, opts = {}, callback) ->
     console.info "Meteor.Files Debugger: [load(#{url}, #{opts}, callback)]" if @debug
     check url, String
     check opts, Match.Optional Object
@@ -425,6 +430,7 @@
       fileName      = if opts.name or opts.fileName then opts.name or opts.fileName else randFileName
       extension     = fileName.split('.').pop()
       path          = "#{@storagePath}/#{randFileName}.#{extension}"
+      opts.meta     = {} if not opts.meta

       request.get(url).on('error', (error)->
         throw new Meteor.Error 500, "Error on [load(#{url}, #{opts})]; Error:" + JSON.stringify error
@@ -437,15 +443,15 @@
             meta:       opts.meta
             type:       response.headers['content-type']
             size:       response.headers['content-length']
+            isVideo: response.headers['content-type'].toLowerCase().indexOf("video") > -1
+            isAudio: response.headers['content-type'].toLowerCase().indexOf("audio") > -1
+            isImage: response.headers['content-type'].toLowerCase().indexOf("image") > -1
             versions:
               original:
                 path: path
                 type: response.headers['content-type']
                 size: response.headers['content-length']
                 extension: extension
-            isVideo: response.headers['content-type'].toLowerCase().indexOf("video") > -1
-            isAudio: response.headers['content-type'].toLowerCase().indexOf("audio") > -1
-            isImage: response.headers['content-type'].toLowerCase().indexOf("image") > -1
             _prefix: self._prefix
             _collectionName: self.collectionName
             _storagePath:    self.storagePath
@@ -471,44 +477,63 @@
   @description Add file from FS to Meteor.Files
   @returns {Files} - Return this
   ###
-  # addFile: if Meteor.isServer then (path, opts, callback) ->
-  #   console.info "[addFile(#{path})]" if @debug
-  #   check path, String
-  #   check opts, Match.Optional Object
-  #   check callback, Match.Optional Function
-
-  #   try
-  #     fs.statSync path
-  #     buffer    = fs.readFileSync path
-  #     pathParts = path.split '/'
-  #     fileName  = pathParts[pathParts.length - 1]
-  #     ext       = fileName.split('.').pop()
-  #     result    =
-  #       name:       fileName
-  #       extension:  ext
-  #       path:       path
-  #       meta:       opts.meta
-  #       type:       if opts.type then opts.type else 'application/*'
-  #       size:       if opts.size then opts.size else buffer.length
-  #       isVideo:    if opts.type then opts.type.toLowerCase().indexOf("video") > -1 else false
-  #       isAudio:    if opts.type then opts.type.toLowerCase().indexOf("audio") > -1 else false
-  #       isImage:    if opts.type then opts.type.toLowerCase().indexOf("image") > -1 else false
-  #       _prefix:    @_prefix
-  #       _collectionName: @collectionName
-  #       _storagePath:    path.replace "/#{fileName}", ''
-  #       _downloadRoute:  @downloadRoute
-
-  #     result._id = @collection.insert _.clone result
-  #     console.info "The file #{fileName} (binary) was added to #{@collectionName}" if @debug
-
-  #     callback and callback null, result
-  #     return result
-
-  #   catch error
-  #     callback and callback error, undefined
-  #     return error
-  # else
-  #   undefined
+  addFile: if Meteor.isServer then (path, opts, callback) ->
+    console.info "[addFile(#{path})]" if @debug
+
+    throw new Meteor.Error 403, "Can not run [addFile()] on public collection" if @public
+    check path, String
+    check opts, Match.Optional Object
+    check callback, Match.Optional Function
+
+    try
+      stats     = fs.statSync path
+
+      if stat.isFile()
+        fileSize  = util.inspect(stats).size
+        pathParts = path.split '/'
+        fileName  = pathParts[pathParts.length - 1]
+        ext       = fileName.split('.').pop()
+
+        opts.type = 'application/*' if not opts.type
+        opts.meta = {} if not opts.meta
+        opts.size = fileSize if not opts.size
+
+        result    =
+          name:       fileName
+          extension:  ext
+          path:       path
+          meta:       opts.meta
+          type:       opts.type
+          size:       opts.size
+          isVideo:    opts.type.toLowerCase().indexOf("video") > -1
+          isAudio:    opts.type.toLowerCase().indexOf("audio") > -1
+          isImage:    opts.type.toLowerCase().indexOf("image") > -1
+          versions:
+            original:
+              path: path
+              type: opts.type
+              size: opts.size
+              extension: ext
+          _prefix:         @_prefix
+          _collectionName: @collectionName
+          _storagePath:    path.replace "/#{fileName}", ''
+          _downloadRoute:  @downloadRoute
+
+        result._id = @collection.insert _.clone result
+        console.info "The file #{fileName} (binary) was added to #{@collectionName}" if @debug
+
+        callback and callback null, result
+        return result
+      else
+        error = new Meteor.Error 400, "[Files.addFile(#{path})]: File does not exist"
+        callback and callback error
+        return error
+
+    catch error
+      callback and callback error
+      return error
+  else
+    undefined

   ###
   @isomorphic
@@ -914,7 +939,7 @@
     if _.isString fileRef
       version = fileRef
       fileRef = undefined
-    return undefined if not fileRef or @currentFile
+    return undefined if not fileRef or not @currentFile

     if fileRef and _.isObject fileRef
       if @public


Fix mime-type in base64 uploads

Fix mime-type in base64 uploads

 - Fix mime-type in base64 uploads, thanks to @FinnFrotscher

--- files.coffee
+++ files.coffee
@@ -1552,7 +1552,7 @@
           _file = @config.file.split ','
           @fileData =
             size: Math.floor (_file[1].replace(/\=/g, '')).length / 4 * 3
-            type: _file[0]
+            type: _file[0].split(';')[0]
             name: @config.fileName
             meta: @config.meta
           @config.file = _file[1]


Update debug messages

Update debug messages

--- files.coffee
+++ files.coffee
@@ -728,7 +728,6 @@
       extension: extension

     result._id = fileId
-    console.info "[FilesCollection] [write]: #{fileName} -> #{@collectionName}" if @debug

     stream = fs.createWriteStream opts.path, {flags: 'w', mode: @permissions}
     stream.end buffer, (error) -> bound ->
@@ -738,8 +737,10 @@
         self.collection.insert _.clone(result), (error) ->
           if error
             callback and callback error
+            console.warn "[FilesCollection] [write] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug
           else
             callback and callback null, result
+            console.info "[FilesCollection] [write]: #{fileName} -> #{self.collectionName}" if self.debug
     return @
   else
     undefined
@@ -797,11 +798,11 @@

       self.collection.insert _.clone(result), (error) ->
         if error
+          callback and callback error
           console.warn "[FilesCollection] [load] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug
-          callback and callback error
         else
+          callback and callback null, result
           console.info "[FilesCollection] [load] [insert] #{fileName} -> #{self.collectionName}" if self.debug
-          callback and callback null, result
     ).pipe fs.createWriteStream(opts.path, {flags: 'w', mode: @permissions})

     return @
@@ -861,11 +862,11 @@

         self.collection.insert _.clone(result), (error) ->
           if error
+            callback and callback error
             console.warn "[FilesCollection] [addFile] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug
-            callback and callback error
           else
-            console.info "[FilesCollection] [addFile] [insert]: #{fileName} -> #{self.collectionName}" if self.debug
             callback and callback null, result
+            console.info "[FilesCollection] [addFile]: #{fileName} -> #{self.collectionName}" if self.debug
       else
         callback and callback new Meteor.Error 400, "[FilesCollection] [addFile(#{path})]: File does not exist"


Node Modules Koding: upload snapshots to s3 when a leak occurs

--- node_modules_koding/memoryleakwatcher/lib/index.coffee
+++ node_modules_koding/memoryleakwatcher/lib/index.coffee
@@ -1,27 +1,54 @@
+AWS        = require 'aws-sdk'
 memwatch   = require 'memwatch'
 v8profiler = require 'v8-profiler'

-state              = off
 snapshotInterval   = null
-latestHeapSnapshot = null
+latestHeapSnapshot = {}
+
+state      = off
+bucket     = 'koding-nodejs-runtime-heapsnapshots'
+AWS_KEY    = null
+AWS_SECRET = null
+
+handleSnapshots = (info) ->
+
+  leakedHeapSnapshot = { content: v8profiler.takeSnapshot(), date: Date.now() }
+  console.log 'Memory leakage detected ', info
+  console.log 'Snapshot headers: Leaked snapshot:', leakedHeapSnapshot.content.getHeader(), 'Latest snapshot before the leak:', latestHeapSnapshot.content.getHeader()
+  console.log 'Leaked snapshot compared to the latest snapshot: ', latestHeapSnapshot.content.compare leakedHeapSnapshot.content
+
+  leakedHeapSnapshot.key = "leakedHeapSnapshot.#{leakedHeapSnapshot.date}"
+  latestHeapSnapshot.key = "latestHeapSnapshot.#{latestHeapSnapshot.date}"
+
+  s3Upload [leakedHeapSnapshot, latestHeapSnapshot]

-#TODO upload snapshots to s3
-handleSnapshots = (info) ->
+s3Upload = (snapshots) ->

-  leakedHeapSnapshot = v8profiler.takeSnapshot()
-  console.log 'Memory leakage detected ', info
-  console.log 'Snapshot headers: Leaked snapshot:', leakedHeapSnapshot.getHeader(), 'Latest snapshot before the leak:', latestHeapSnapshot.getHeader()
-  console.log 'Leaked snapshot compared to the latest snapshot: ', latestHeapSnapshot.compare leakedHeapSnapshot
+  s3Bucket = new AWS.S3 { params: { Bucket: bucket } }
+  s3Bucket.createBucket ->

+    snapshots.forEach (snapshot) ->
+      { key, content } = snapshot
+
+      content.export (err, result) ->
+        return console.log err  if err
+        params = { Key: key, Body: result }
+
+        s3Bucket.upload params, (err) ->
+          return console.log err  if err
+          console.log "#{key} uploaded to s3 bucket #{bucket}"

 start = ->

   state = on

-  # take heap snapshot every second
+  # take an initial snapshot
+  latestHeapSnapshot = { content: v8profiler.takeSnapshot(), date: Date.now() }
+
+  # then take a heap snapshot every second
   snapshotInterval = setInterval ->
-    latestHeapSnapshot = v8profiler.takeSnapshot()
+    latestHeapSnapshot = { content: v8profiler.takeSnapshot(), date: Date.now() }
   , 1000

   memwatch.on 'leak', handleSnapshots
@@ -39,11 +66,13 @@
   console.log "Stopped watching possible memory leakage for the process with id #{process.pid}"

-setup = ->
+setup = (options = {}) ->
   console.log "To start/stop watching memory profiling send SIGUSR2 to PID #{process.pid}"

+  { AWS_KEY, AWS_SECRET } = options
+  AWS.config.update { accessKeyId: AWS_KEY, secretAccessKey: AWS_SECRET }
+
   process.on 'SIGUSR2', ->
-
     if state is off
     then start()
     else stop()


added a missing require()

added a missing require()

--- src/vocabulary/quad.coffee
+++ src/vocabulary/quad.coffee
@@ -1,4 +1,5 @@
 {format} = require './format'
+{APLArray} = require '../array'

 @['get_⎕'] = ->
   if typeof window?.prompt is 'function'


Partial fix for #299

Partial fix for #299

--- files.coffee
+++ files.coffee
@@ -1416,10 +1416,11 @@
             callback and callback error
             console.warn "[FilesCollection] [addFile] [insert] Error: #{fileName} -> #{self.collectionName}", error if self.debug
           else
-            callback and callback null, result
+            fileRef = self.collection.findOne result._id
+            callback and callback null, fileRef
             if proceedAfterUpload is true
-              self.onAfterUpload and self.onAfterUpload.call self, result
-              self.emit 'afterUpload', result
+              self.onAfterUpload and self.onAfterUpload.call self, fileRef
+              self.emit 'afterUpload', fileRef
             console.info "[FilesCollection] [addFile]: #{fileName} -> #{self.collectionName}" if self.debug
           return
       else


Fix comment.

Fix comment.

--- airbrake-shim.coffee
+++ airbrake-shim.coffee
@@ -1,4 +1,4 @@
-# this == global
+# this == window
 global = this

 # Airbrake shim that stores exceptions until Airbrake notifier is loaded.


Use process.env values on first inserting settings.

--- packages/rocketchat-lib/settings/server/methods.coffee
+++ packages/rocketchat-lib/settings/server/methods.coffee
@@ -10,7 +10,9 @@

 	# console.log '[functions] RocketChat.settings.add -> '.green, 'arguments:', arguments

-	if Meteor.settings?[_id]?
+	if process?.env?[_id]?
+		value = process.env[_id]
+	else if Meteor.settings?[_id]?
 		value = Meteor.settings[_id]

 	updateSettings =


Change the default value of API_Embed to true

--- packages/rocketchat-lib/settings/server/startup.coffee
+++ packages/rocketchat-lib/settings/server/startup.coffee
@@ -21,7 +21,7 @@

 	RocketChat.settings.addGroup 'API'
 	RocketChat.settings.add 'API_Analytics', '', { type: 'string', group: 'API', public: true }
-	RocketChat.settings.add 'API_Embed', '', { type: 'boolean', group: 'API', public: true }
+	RocketChat.settings.add 'API_Embed', true, { type: 'boolean', group: 'API', public: true }

 	RocketChat.settings.addGroup 'SMTP'
 	RocketChat.settings.add 'SMTP_Host', '', { type: 'string', group: 'SMTP' }


remove unnecessary GA coffeescript

remove unnecessary GA coffeescript

--- app/assets/javascripts/google_analytics.js.coffee
+++ app/assets/javascripts/google_analytics.js.coffee
@@ -1,44 +0,0 @@
-# thanks to Jonathon Wolfe at http://reed.github.io/turbolinks-compatibility/google_analytics.html
-
-class @GoogleAnalytics
-
-  @load: ->
-    # Google Analytics depends on a global _gaq array. window is the global scope.
-    window._gaq = []
-    window._gaq.push ["_setAccount", GoogleAnalytics.analyticsId()]
-
-    # Create a script element and insert it in the DOM
-    ga = document.createElement("script")
-    ga.type = "text/javascript"
-    ga.async = true
-    ga.src = ((if "https:" is document.location.protocol then "https://ssl" else "http://www")) + ".google-analytics.com/ga.js"
-    firstScript = document.getElementsByTagName("script")[0]
-    firstScript.parentNode.insertBefore ga, firstScript
-
-    # If Turbolinks is supported, set up a callback to track pageviews on page:change.
-    # If it isn't supported, just track the pageview now.
-    if typeof Turbolinks isnt 'undefined' and Turbolinks.supported
-      document.addEventListener "page:change", (->
-        GoogleAnalytics.trackPageview()
-      ), true
-    else
-      GoogleAnalytics.trackPageview()
-
-  @trackPageview: (url) ->
-    unless GoogleAnalytics.isLocalRequest()
-      if url
-        window._gaq.push ["_trackPageview", url]
-      else
-        window._gaq.push ["_trackPageview"]
-      window._gaq.push ["_trackPageLoadTime"]
-
-  @isLocalRequest: ->
-    GoogleAnalytics.documentDomainIncludes "local"
-
-  @documentDomainIncludes: (str) ->
-    document.domain.indexOf(str) isnt -1
-
-  @analyticsId: ->
-    ''
-
-GoogleAnalytics.load()


fix replace all typo

fix replace all typo

--- app/assets/javascripts/lib/mark-button-mixin.cjsx
+++ app/assets/javascripts/lib/mark-button-mixin.cjsx
@@ -87,7 +87,7 @@
       when 'transcribe-finished'
         # console.log 'TRANSCRIBING MARK WITH ID: ', @props #.mark.child_subject_id
         # console.log 'STATE: ', @state
-        location.replace "/#/transcribe/#{@props.subject_id}?scrollX=#{window.scrollX}&scrollY=#{window.scrollY}&page=#{@props.subjectsubjectCurrentPage}&mark_key=#{@props.taskKey}"
+        location.replace "/#/transcribe/#{@props.subject_id}?scrollX=#{window.scrollX}&scrollY=#{window.scrollY}&page=#{@props.subjectCurrentPage}&mark_key=#{@props.taskKey}"
         @forceUpdate()
         # @setState locked: true
         console.log 'All done. Nothing left to do here.'


Fixed the typo error

Fixed the typo error

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -65,7 +65,7 @@

       done: (e, data) ->
         content = build_content_object $uploadForm, data.files[0], data.result
-        fomrs_for_submit = []
+        forms_for_submit = []
         callback_url = $uploadForm.data('callback-url')
         if callback_url
           content[$uploadForm.data('callback-param')] = content.url


Fixing selectors.

Fixing selectors.

--- app/assets/javascripts/s3_direct_upload.js.coffee
+++ app/assets/javascripts/s3_direct_upload.js.coffee
@@ -45,8 +45,8 @@
           unless settings.types.test(file.type) || settings.types.test(file.name)
             return alert("File does not match required filetype.")
         if settings.file_count_span
-          count = parseInt($(file_count_span).html())
-          $(file_count_span).html(count + 1)
+          count = parseInt($('.span-update').html())
+          $('.span-update').html(count + 1)

         unless settings.before_add and not settings.before_add(file)
           current_files.push data


fix js problem

fix js problem

--- app/assets/javascripts/social-share-button.coffee
+++ app/assets/javascripts/social-share-button.coffee
@@ -16,6 +16,7 @@
     via = encodeURIComponent($parent.data("via") || '')
     desc = encodeURIComponent($parent.data("desc") || ' ')
     popup = encodeURIComponent($parent.data("popup") || 'false')
+    videos = encodeURIComponent($parent.data("videos") || '')

     if url.length == 0
       url = encodeURIComponent(location.href)


try to have comment field visible on mobile

--- app/assets/javascripts/views/users_view.js.coffee
+++ app/assets/javascripts/views/users_view.js.coffee
@@ -31,4 +31,12 @@

       showRow $this.closest('.coworker') unless event.target is $this.get()

-      return false+      return false
+
+    @.$('#users-list').on 'focus', '.kudos-comment', ->
+      width = $(window).width()
+      $('#dashboard').fadeOut() if width <= 768
+
+    @.$('#users-list').on 'blur', '.kudos-comment', ->
+      width = $(window).width()
+      $('#dashboard').fadeIn() if width <= 768


[feature] MoodToTracksRequest

[feature] MoodToTracksRequest

--- app/scripts/helpers/mood_to_tracks_request.coffee
+++ app/scripts/helpers/mood_to_tracks_request.coffee
@@ -4,19 +4,255 @@

   class MoodToTracksRequest

+    mapRequest:
+
+      default:
+        a:
+          vocalinstrumental: 'instrumental'
+          fuzzytags: 'easylistening'
+          speed: 'low'
+
+      cloudy:
+        a:
+          vocalinstrumental: 'instrumental'
+          fuzzytags: 'ambient'
+          speed: 'medium'
+
+      overcast:
+        a:
+          fuzzytags: 'gothicmetal+gothicrock+gothic+goth'
+          speed: 'low'
+        b:
+          vocalinstrumental: 'instrumental'
+          fuzzytags: 'ambient'
+          speed: 'low'
+
+      snow:
+        a:
+          vocalinstrumental: 'instrumental'
+          fuzzytags: 'piano'
+          speed: 'low'
+        b:
+          fuzzytags: 'acousticguitar+minor'
+          speed: 'low'
+
+      snow_wind:
+        a:
+          vocalinstrumental: 'instrumental'
+          fuzzytags: 'piano'
+          speed: 'veryhigh'
+
+      rain:
+        a:
+          fuzzytags: 'blues+minor'
+
+      rain_wind:
+        a:
+          fuzzytags: 'bluesrock+minor'
+          speed: 'high'
+
+      frost:
+        a:
+          vocalinstrumental: 'instrumental'
+          fuzzytags: 'epic+orchestral'
+
+      cold:
+        a:
+          fuzzytags: 'cold'
+
+      hot:
+        a:
+          vocalinstrumental: 'vocal'
+          fuzzytags: 'ska+major'
+          speed: 'high'
+
+      storm:
+        a:
+          fuzzytags: 'drumnbass'
+          speed: 'medium'
+        b:
+          fuzzytags: 'metal+rock'
+          speed: 'medium'
+
+      hurricane:
+        a:
+          fuzzytags: 'metal+rock'
+          speed: 'high'
+
+      blind:
+        a:
+          fuzzytags: 'synthesizer+ambient'
+
+    mapVisualOrder: [
+      'pre'
+      'snow'
+      'rain'
+      'wind'
+      'clouds'
+      'temperature_feeling'
+      'atmosphere'
+    ]
+
+    mapVisual:
+      pre: [
+        {
+          key: 'wind'
+          condition:
+            type: 'or'
+            keys: [
+              'gale'
+              'storm'
+            ]
+        }
+      ]
+
+      snow: [
+        {
+          key: 'snow'
+          condition:
+            type: 'or'
+            keys: [
+              'snow'
+              'heavy_snow'
+            ]
+        }
+        {
+          key: 'snow_wind'
+          condition:
+            type: 'and'
+            keys: [
+              ':snow'
+              ':wind'
+            ]
+        }
+      ]
+
+      rain: [
+        {
+          key: 'rain'
+          condition:
+            type: 'or'
+            keys: [
+              'drizzle'
+              'light_rain'
+              'rain'
+              'shower'
+            ]
+        }
+        {
+          key: 'rain_wind'
+          condition:
+            type: 'and'
+            keys: [
+              ':rain'
+              ':wind'
+            ]
+        }
+      ]
+
+      wind: [
+        {
+          key: 'hurricane'
+        }
+      ]
+
+      clouds: [
+        {
+          key: 'overcast'
+        }
+        {
+          key: 'cloudy'
+        }
+      ]
+
+      temperature_feeling: [
+        {
+          key: 'hot'
+        }
+        {
+          key: 'cold'
+        }
+        {
+          key: 'frost'
+        }
+      ]
+
+      atmosphere: [
+        {
+          key: 'blind'
+          condition:
+            type: 'or'
+            keys: [
+              'mist'
+              'smoke'
+              'haze'
+              'overcast'
+              'dust'
+              'fog'
+            ]
+        }
+      ]
+
     constructor: ->
       @initialize.apply @, arguments

     initialize: (items = {}) ->
-      @items = items
+      @items = _.clone items
       @result = {}
       @

     convert: ->
-      @setDummyAll()
+      # @setDummyAll()
+      @setFromMap()
       @

-    setDummyAll: ->
-      @result =
-        acousticelectric: 'acoustic'
-        gender: 'female'
+    # setDummyAll: ->
+    #   @setResult 'hot'
+
+    setFromMap: ->
+
+      keyMatches = (key, items) =>
+        if /^:/.test key
+          orig_key = key.match(/^:(.*)$/)[1]
+          _.include @found, orig_key
+        else
+          _.include items, key
+
+      ruleApplies = (rule, items) =>
+        result = false
+        if rule.condition?.type == 'and'
+          result = _.all rule.condition.keys, (key) =>
+            keyMatches key, items
+          symbolic = _.all rule.condition.keys, (key) =>
+            /^:/.test key
+          if result and symbolic
+            _.each rule.condition.keys, (key) =>
+              orig_key = key.match(/^:(.*)$/)[1]
+              @found = _(@found).without orig_key
+        else if rule.condition?.type == 'or'
+          result = _.any rule.condition.keys, (key) =>
+            keyMatches key, items
+        else
+          result = _.include items, rule.key
+        result
+
+      ruleApply = (rule, items) =>
+        if ruleApplies rule, items
+          @found.push rule.key
+
+      @found = []
+
+      _.each @mapVisualOrder, (category) =>
+        _.each @mapVisual[category], (rule) =>
+          ruleApply rule, @items, @found
+
+      key = _.first @found
+
+      @setResult key
+
+    setResult: (key = 'default', sub_key = undefined) ->
+      map = _.clone @mapRequest
+      key = 'default'  unless map[key]?
+      sub_key ||= _.chain(map[key]).keys().shuffle().first().value()
+      result = map[key][sub_key]
+      @result = result


add linum to teacher-student-view (#6395)

Co-authored-by: smallst_wsl <6c42689ff77371401bf43b7d56b93ac1ca3e92d3@163.com>

--- app/views/teachers/TeacherStudentView.coco.coffee
+++ app/views/teachers/TeacherStudentView.coco.coffee
@@ -124,6 +124,7 @@
       codeElem = $(@).first().children().first()
       lang = mode for mode of aceUtils.aceEditModes when codeElem?.hasClass('lang-' + mode)
       aceEditor = aceUtils.initializeACE(@, lang or classLang)
+      aceEditor.renderer.setShowGutter true
       aceEditors.push aceEditor

   updateSolutions: ->


Remove broken require.

Remove broken require.

--- application/chrome/content/wesabe/xml/Text.coffee
+++ application/chrome/content/wesabe/xml/Text.coffee
@@ -1,4 +1,3 @@
-{_inspectString} = require 'util'
 Colorizer        = require 'util/Colorizer'

 class Text
@@ -27,4 +26,5 @@
      .reset()
      .toString()

+
 module.exports = Text


Removed debugging prints.

Removed debugging prints.

--- picoCTF-web/web/coffee/management-problems.coffee
+++ picoCTF-web/web/coffee/management-problems.coffee
@@ -45,7 +45,6 @@
       memo), {}
     activeStates[name].active = true
     @setState activeStates
-    console.log activeStates

   render: ->
     activeState = @state
@@ -367,7 +366,6 @@
       # We shouldn't do anything.

   onSortChange: (name, ascending) ->
-    console.log name, ascending
     @setState update @state,
       activeSort: $set: {name: name, ascending: ascending}

@@ -394,7 +392,6 @@
       sortedProblems.reverse()

   render: ->
-    console.log @state.activeSort
     filteredProblems = @filterProblems @props.problems
     <Row className="pad">
       <Col xs={3} md={3}>


Adjust use of async.filter to take into account error argument.

caolan/async#1028 added support for errors in filter, which changed the
functions API.

--- src/core/content.coffee
+++ src/core/content.coffee
@@ -181,8 +181,8 @@
             env.logger.verbose "ignoring #{ filename.relative } (matches: #{ pattern })"
             include = false
             break
-        callback include
-      , (result) -> callback null, result
+        callback null, include
+      , callback
     else
       callback null, filenames


Refactor UserStore updateFromServer.

Refactor UserStore updateFromServer.

--- app/client/js/users/store.coffee
+++ app/client/js/users/store.coffee
@@ -146,28 +146,29 @@
     console.log 'updateFromServer' if goog.DEBUG
     @user = authUser
     localUserStoreJson = @toJson()
-    @mergeSongs localUserStoreJson, serverUserStoreJson
+    # serverUserStoreJson can be null for new user.
+    if serverUserStoreJson
+      @mergeSongs localUserStoreJson.songs, serverUserStoreJson.songs
     @fromJson localUserStoreJson
     @serverNotify()

   ###*
-    @param {Object} localUserStoreJson
-    @param {Object} serverUserStoreJson
+    @param {Object} localSongs
+    @param {Object} serverSongs
   ###
-  mergeSongs: (localUserStoreJson, serverUserStoreJson) ->
-    # serverUserStoreJson can be null for new user.
-    return if !serverUserStoreJson
-    serverSongs = serverUserStoreJson.songs
+  mergeSongs: (localSongs, serverSongs) ->
+    # TODO: It is really needed?
     return if !serverSongs
     for serverSongId, serverSong of serverSongs
-      localSong = localUserStoreJson.songs[serverSongId]
+      localSong = localSongs[serverSongId]
       if !localSong
-        localUserStoreJson.songs[serverSongId] = serverSong
+        localSongs[serverSongId] = serverSong
         continue
       @mergeSong localSong, serverSong
     return

   ###*
+    For now, server song always override local one.
     @param {Object} localSong
     @param {Object} serverSong
   ###


testing

testing


Indentation

Indentation

--- server/bootstrap.coffee
+++ server/bootstrap.coffee
@@ -47,10 +47,10 @@
       profile:
         person: personId
     Persons.update
-        _id: personId
-      ,
-        $set:
-          'user.id': userId
+      _id: personId
+    ,
+      $set:
+        'user.id': userId

     #Hannah Arendt
     personId = Persons.insert
@@ -86,10 +86,10 @@
       profile:
         person: personId
     Persons.update
-        _id: personId
-      ,
-        $set:
-          'user.id': userId
+      _id: personId
+    ,
+      $set:
+        'user.id': userId

     console.log "Created users 'carl-sagan', 'hannah-arendt' with password '#{ INITIAL_PASSWORD }'"


Use callbacks, dummy.

Use callbacks, dummy.

--- src/github-pages.coffee
+++ src/github-pages.coffee
@@ -42,14 +42,11 @@
   else if command in LATEST_COMMANDS
     "#{base}/builds/latest"

-github_pages_info = (githubot, repo, command) ->
-  console.log("Fetching info on repo '#{repo}'")
+github_pages_info = (githubot, repo, command, cb) ->
   githubot.handleErrors (response) ->
     console.log "Error fetching info about GitHub Pages site for #{repo}: #{reponse.statusCode} #{response.error}"
-  pages_info = null
-  githubot.get endpoint(repo, command), (info) ->
-    pages_info = info
-  pages_info
+
+  githubot.get endpoint(repo, command), {}, cb

 fetch_from_info = (info, command) ->
   if command in INFO_KEYS
@@ -58,7 +55,7 @@
     info

 formatted_build_text = (build) ->
-  "Page build #{build.status}@#{build.commit.substr(0, 7)} Triggered by #{build.pusher} at #{timeago(build.created_at)}."
+  "Page build #{build.status} @ #{build.commit.substr(0, 7)}. Triggered by #{build.pusher.login} #{timeago(build.created_at)}."

 module.exports = (robot) ->
   github = require("githubot")(robot)
@@ -70,13 +67,18 @@
       msg.send "Sorry, what was that? '#{command}' isn't a recognized command."
       return

-    info = github_pages_info github, repo, command
-    unless info?
-      msg.send "No info found for '#{repo}'. Make sure you're authenticated and are the owner of the repo."
+    cb = null
+    if command in INFO_COMMANDS
+      cb = (info) ->
+        msg.send "No info found for '#{repo}'." unless info?
+        msg.send JSON.stringify fetch_from_info(info, command)
+    else if command in BUILD_COMMANDS
+      cb = (info) ->
+        msg.send "No info found for '#{repo}'." unless info?
+        msg.send (formatted_build_text(build) for build in info).join("\n")
+    else if command in LATEST_COMMANDS
+      cb = (info) ->
+        msg.send "No info found for '#{repo}'." unless info?
+        msg.send formatted_build_text info

-    if command in INFO_COMMANDS
-      msg.send JSON.stringify fetch_from_info(info, command)
-    else if command in BUILD_COMMANDS
-      msg.send (formatted_build_text(build) for build in info).join("\n")
-    else if command in LATEST_COMMANDS
-      msg.send formatted_build_text info
+    github_pages_info github, repo, command, cb


save data to brain

save data to brain

--- src/join-request.coffee
+++ src/join-request.coffee
@@ -12,6 +12,9 @@
   app = robot.router
   env = process.env
   team = env.HUBOT_SLACK_TEAM or ''
+
+  robot.brain.on 'loaded', ->
+    robot.brain.data.ingressAgents ?= []

   app.engine 'html', cons.hogan
   app.set 'view engine', 'html'
@@ -63,3 +66,10 @@
         imagePath: filename

       res.render 'thanks', viewData
+
+      user.agentName = req.body.agentName.slice 0, 32
+      user.community = req.body.comments.slice 0, 140
+      user.comments = req.body.comments.slice 0, 140
+
+      robot.brain.data.ingressAgents.push user
+      robot.brain.save()


Add isCorePath helper

Add isCorePath helper

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -13,6 +13,7 @@
   ranges: {}
   registered: false
   resourcePath: null
+  resourcePathWithTrailingSlash: null

 # isAbsolute is inlined from fs-plus so that fs-plus itself can be required
 # from this cache.
@@ -23,6 +24,9 @@
   isAbsolute = (pathToCheck) ->
     pathToCheck and pathToCheck[0] is '/'

+isCorePath = (pathToCheck) ->
+  pathToCheck.indexOf(cache.resourcePathWithTrailingSlash) is 0
+
 loadDependencies = (modulePath, rootPath, rootMetadata, moduleCache) ->
   fs = require 'fs-plus'

@@ -98,14 +102,15 @@
   return if relativePath[relativePath.length - 1] is '/'

   resolvedPath = path.resolve(path.dirname(parentModule.filename), relativePath)
-  if resolvedPath.indexOf(cache.resourcePath) is 0
-    extension = path.extname(resolvedPath)
-    if extension
-      return resolvedPath if cache.extensions[extension]?.has(resolvedPath)
-    else
-      for extension, paths of cache.extensions
-        resolvedPathWithExtension = "#{resolvedPath}#{extension}"
-        return resolvedPathWithExtension if paths.has(resolvedPathWithExtension)
+  return unless isCorePath(resolvedPath)
+
+  extension = path.extname(resolvedPath)
+  if extension
+    return resolvedPath if cache.extensions[extension]?.has(resolvedPath)
+  else
+    for extension, paths of cache.extensions
+      resolvedPathWithExtension = "#{resolvedPath}#{extension}"
+      return resolvedPathWithExtension if paths.has(resolvedPathWithExtension)

   return

@@ -131,7 +136,7 @@
   return unless candidates?

   for version, resolvedPath of candidates
-    if Module._cache.hasOwnProperty(resolvedPath) or resolvedPath.indexOf(cache.resourcePath) is 0
+    if Module._cache.hasOwnProperty(resolvedPath) or isCorePath(resolvedPath)
       return resolvedPath if satisfies(version, range)

   return
@@ -214,6 +219,7 @@

   cache.registered = true
   cache.resourcePath = resourcePath
+  cache.resourcePathWithTrailingSlash = "#{resourcePath}#{path.sep}"
   registerBuiltins(devMode)

   return


Remove semicolon

Remove semicolon

--- src/module-cache.coffee
+++ src/module-cache.coffee
@@ -127,7 +127,7 @@

   originalFindPath = Module._findPath
   Module._findPath = (request, paths) ->
-    cacheKey = JSON.stringify({request, paths});
+    cacheKey = JSON.stringify({request, paths})
     cache.findPathCount++ unless Module._pathCache[cacheKey]
     startTime = Date.now()
     foundPath = originalFindPath.apply(global, arguments)


fix multiselect reset

fix multiselect reset

--- src/multi-select.coffee
+++ src/multi-select.coffee
@@ -92,6 +92,7 @@
       $scope.reset = ->
         $scope.selectedItems = []
         $scope.focus = true
+        search('')

       $scope.onkeys = (event)->
         switch event.keyCode


Fix syncing bug.

Fix syncing bug.

This fixes a second dumb error I made in #1456.

--- background_scripts/sync.coffee
+++ background_scripts/sync.coffee
@@ -62,7 +62,8 @@
   # No need to propagate updates to the rest of vimium, that's already been done.
   set: (key, value) ->
     if @shouldSyncKey key
-      @storage.set key: value
+      setting = {}; setting[key] = value
+      @storage.set setting

   # Only called synchronously from within vimium, never on a callback.
   clear: (key) ->


bugfix: make the errors typeof Error

bugfix: make the errors typeof Error

--- examples/auth/app.coffee
+++ examples/auth/app.coffee
@@ -42,16 +42,16 @@
         ## and loggings to point you with your nose into ;-)
         booted: ()->
             if process.getuid() isnt 0
-                @logger.error 'FATAL: you are not root! run sudo floyd start'
+                @logger.error new Error 'FATAL: you are not root! run sudo floyd start'

         ##
         started: ->
-
+
             if _secret = floyd.tools.files.exists './private/secret.txt'
-                @logger.error 'FATAL: the file ./private/secret.txt is readable'
+                @logger.error new Error 'FATAL: the file ./private/secret.txt is readable'

             if _users = floyd.tools.files.exists './private/users.data'
-                @logger.error 'FATAL: the file ./private/users.data is readable', if _secret then 'too!' else ''
+                @logger.error new Error 'FATAL: the file ./private/users.data is readable', if _secret then 'too!' else ''

             if !_secret && !_users
                 @logger.info 'SUCCESS: userdata and secret are protected propperly.'


Removed unused variable

Removed unused variable

--- app/js/loader.coffee
+++ app/js/loader.coffee
@@ -5,8 +5,6 @@
 iframe = document.createElement('iframe')
 div = document.createElement('div')
 flScript = document.createElement('script')
-
-iframeLoaded = false

 iframe.style.display = 'block'
 iframe.style.border = '0px solid transparent'


Fixes

Fixes

--- app/locale/bg.coffee
+++ app/locale/bg.coffee
@@ -317,9 +317,9 @@
     tip_talk_is_cheap: "Приказките са вятър и мъгла. Покажи ми кода. - Линус Торвалдс"
     tip_first_language: "Най-пагубното нещо, което можеш да научиш е първият ти език за програмиране. - Alan Kay"
     tip_hardware_problem: "Въпр.: Колко програмиста са нужни, за да сменят електрическа крушка?  Отг.: Николко, това е хардуерен проблем."
-    tip_hofstadters_law: "Закон на Хофщадтер: Всяка работа продължава повече от колкото се очаква, дори дори когато законът на Хофщадтер е взет в предвид."
+    tip_hofstadters_law: "Закон на Хофщадтер: Всяка работа продължава повече от колкото се очаква, дори когато законът на Хофщадтер е взет в предвид."
     tip_premature_optimization: "Прибързаната оптимизиция е коренът на всяко зло. - Donald Knuth"
-    tip_brute_force: "Когато се съмняваш използвай груба сила. - Ken Thompson"
+    tip_brute_force: "Когато се съмняваш, използвай груба сила. - Ken Thompson"
     tip_extrapolation: "Има само два типа хора: тези, които могат да екстраполират от непълни данни..."
     tip_superpower: "Програмирането е най-голямото ни доближаване до суперсилата."
     tip_control_destiny: "При истинският отворен код всеки има правото да контролира собствената си съдба. - Линус Торвалдс"
@@ -330,10 +330,10 @@
     tip_lines_of_code: "Да мериш работата на програмиста по брой редове е все едно да мериш построяването на самолет по теглото му. — Бил Гейтс"
     tip_source_code: "Искам да променя света, но едва ли ще ми дадат сорсовете."
     tip_javascript_java: "Java е за JavaScript това, което са компютрите за компотите. - Chris Heilmann (перифразирано)"
-    tip_move_forward: "каквото и да правиш, продължавай напред. - Мартин Лутър Кинг Мл."
+    tip_move_forward: "Каквото и да правиш, продължавай напред. - Мартин Лутър Кинг Мл."
     tip_google: "Имаш проблем, който не можеш да решиш? Гугълни го!"
     tip_adding_evil: "Добавяме щипка зло."
-    tip_hate_computers: "Какво да ви кажа за хората, които си мислят че мразят компютрите. Всъщност те мразят скапаните програмисти. - Лари Нивън"
+    tip_hate_computers: "Какво да ви кажа за хората, които си мислят, че мразят компютрите. Всъщност те мразят скапаните програмисти. - Лари Нивън"
     tip_open_source_contribute: "И Вие можете да помогнете за подобряването на CodeCombat!"
     tip_recurse: "Итерациите са човещина, рекурсиите са божествени. - L. Peter Deutsch"
     tip_free_your_mind: "Трябва да им позволиш да си отидат Нео. Страхът, съмнението и липсата на вяра. Освободи съзнанието си. - Морфеус"


Change name of office hours to webinars

Change name of office hours to webinars

--- app/locale/en.coffee
+++ app/locale/en.coffee
@@ -1721,10 +1721,9 @@
     teacher_quest_less: "See fewer quests"
     refresh_to_update: "(refresh the page to see updates)"
     view_project_gallery: "View Project Gallery"
-    office_hours: "Hour of Code Office Hours"
+    office_hours: "Hour of Code Webinars"
     office_hours_detail: "Learn how to keep up with with your students as they create games and embark on their coding journey! Come and attend our"
-    office_hours_link: "Hour of Code Office Hours"
-    office_hours_detail_2: "sessions."
+    office_hours_link: "Hour of Code Webinar"
     office_hours_detail_2: "sessions."

   share_licenses:


Update sk.coffee (#4129)

Update sk.coffee (#4129)

Only 2 changes

--- app/locale/sk.coffee
+++ app/locale/sk.coffee
@@ -740,7 +740,7 @@
     required_parameters: "Požadované  parametere"
     optional_parameters: "Voliteľné  parametere"
     returns: "Vracia"
-    granted_by: "Poskytnutné od"
+    granted_by: "Zaručuje"

   save_load:
     granularity_saved_games: "Uložené"
@@ -748,7 +748,7 @@

   options:
     general_options: "Všeobecné nastavenia" # Check out the Options tab in the Game Menu while playing a level
-    volume_label: "Halsitosť"
+    volume_label: "Hlasitosť"
     music_label: "Hudba"
     music_description: "Vypnúť/zapnúť hudbu na pozadí."
     editor_config_title: "Konfigurácia editora"