mirror code

mirror code

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -625,6 +625,7 @@

     src_slug = args[0]
     dst_slug = args[1]
+    is_mirror = args[2] is 'mirror'

     # check arguments
     exit "must specify site src_slug" unless src_slug
@@ -711,7 +712,7 @@
           data = src_object.val()
           src_id = data._id
           delete data._id
-          data.seo = {}
+          data.seo = {} unless is_mirror
           data.created = Date.now()
           data.last_modified = Date.now()
           data.site_id = dst_site.get('_id').val()
@@ -738,6 +739,7 @@
           data = JSON.parse data
           dst_site.get('regions').set data

+        dst_site.get('id_mapping').set ids if is_mirror
         next null, {db, dst_objects, ids, dst_site}

       # update object references


Hotfix

Hotfix

--- lib/cli.coffee
+++ lib/cli.coffee
@@ -131,7 +131,7 @@
                                 else
                                     idsToDelete.push taskProgress._id

-                            next null idsToDelete
+                            next null, idsToDelete

                         async.mapLimit teams, 5, findDuplicateTeamScore, (err, results) ->
                             if err?


cleaned up dns server

cleaned up dns server

--- lib/dns.coffee
+++ lib/dns.coffee
@@ -164,14 +164,35 @@
     )

     @server.send(res, 0, res.length, remote_info.port, remote_info.address)
-
+
+  fail_dns: (remote_info, req) ->
+    res = DnsResponse.pack(
+      header:
+        qid: req.header.qid
+        flags:
+          qr: 1
+          rd: 1
+          ra: 1
+          rcode: 3
+        qcount: 1
+        acount: 1
+        auth_count: 0
+        addl_count: 0
+      question:
+        domain: req.question.domain
+        qtype: req.question.qtype
+        qclass: req.question.qclass
+    )
+
+    @server.send(res, 0, res.length, remote_info.port, remote_info.address)
+
   on_message: (msg, remote_info) ->
     req = @parse(msg)
     console.log "#{REQUEST_TYPES[req.question.qtype]} #{req.question.domain}"

-    return @respond_with('127.0.0.1', remote_info, req) if req.question.qtype in [REQUEST_TYPES.A, REQUEST_TYPES.AAAA, REQUEST_TYPES.CNAME] and req.question.domain is 'foo.dev'
     return @respond_with('127.0.0.1', remote_info, req) if req.question.qtype in [REQUEST_TYPES.A, REQUEST_TYPES.AAAA, REQUEST_TYPES.CNAME] and @axle.match(req.question.domain)?
-    @forward_dns(remote_info, msg)
+    @fail_dns(remote_info, req)
+    # @forward_dns(remote_info, msg)

   start: ->
     @server.bind(53)


implement various shapes for fallback glyph


Revert the fix for Issue 79, Error using IE8

Building the main page upon pageinit is needed to enable IE8 to work.
However, pageinit doesn't seem to get triggered.
Instead, we go back to using document ready event,
which is always triggered.

--- src/main.coffee
+++ src/main.coffee
@@ -15,8 +15,8 @@
 	'load_spinner'
 	'login_bar'
 ], -> (($) ->
-
-	$(document).bind 'pageinit', ->
+
+	$ ->

 		# We will apply a load spinner to the body.  The load spinner will
 		# indicate that data is being transferred across the network.


Voila!

Voila!

--- src/main.coffee
+++ src/main.coffee
@@ -15,7 +15,7 @@
     create: ->

         window.game.stage.backgroundColor = '#000000'
-        window.game.world.setBounds(0, 0, 3000, 3000)
+        window.game.world.setBounds(0, 0, 3000, 5000)

         window.collect = window.game.add.audio('collect')
         window.hurt = window.game.add.audio('hurt')
@@ -30,7 +30,7 @@
         window.bullets = []

         for y in [0..25]
-            for i in [0..8]
+            for i in [0..9 - Phaser.Math.clampBottom(Math.floor(y / 10) , 0)]
                 sh = rand(-40, 40)
                 sh2 = rand(-10, 10)
                 window.enemies.push(new Enemy(window.game, rand(0, 3000), y * 200 + sh, y * 10 + 30 + sh2))
@@ -131,7 +131,7 @@
         @sprite.addChild(@rightEye)

-        @title = game.add.text(window.w / 2, 50, "Game", {
+        @title = game.add.text(window.w / 2, 50, "See", {
             font: "64px Visitor",
             fill: "#ffffff",
             align: "center"


Properly deal with missing `end` in events.

--- src/main.coffee
+++ src/main.coffee
@@ -61,13 +61,15 @@
         locate:
           map: make_fun (doc) ->
             return unless doc.type? and doc.type is 'event'
-            return unless doc.start? and doc.end?
+            return unless doc.start?
             start = new Date doc.start
-            end = new Date doc.end
-            while start < end
-              emit start.toJSON(), null
-              start = new Date start.valueOf()+7*24*3600*1000
-            emit end.toJSON(), null
+            emit start.toJSON(), null
+            if doc.end?
+              end = new Date doc.end
+              while start < end
+                start = new Date start.valueOf()+7*24*3600*1000
+                emit start.toJSON(), null
+              emit end.toJSON(), null

     p._rev = doc._rev if doc?
     db.put p
@@ -110,7 +112,10 @@
         return next false

       doc.start = moment(event.start).format()
-      doc.end = moment(event.end).format()
+      if event.end?
+        doc.end = moment(event.end).format()
+      else
+        delete doc.end
       doc.allDay = event.allDay

       db.put doc, (err,doc) ->
@@ -164,9 +169,10 @@
     doc =
       type: 'event'
       start: moment(start).format()
-      end: moment(end).format()
       allDay: allDay
       title: 'New Event'
+
+    doc.end = moment(end).format() if end

     db.post doc, (err,response) ->
       if err or not response.ok


coffee configurator

coffee configurator

--- js/main.coffee
+++ js/main.coffee
@@ -1,19 +1,11 @@
-do ->
-  count = 0;
-  window.mPlus = (x) ->
-    count += x
-    count
-  window.mMinus = (x) ->
-    count -= x
-    count
-  this
+do =>
+  createConfigurator = ->
+    obj = {}
+    createArguments = (x, y)->
+      createArguments.result = obj
+      obj[x] = y
+      arguments.callee

-console.log mPlus 0
-console.log mPlus 2
-console.log mPlus 10
-console.log mPlus 3
+  _ = createConfigurator()
+  console.log _("age", 25)("name", "sergii").result

-console.log mMinus 5
-console.log mPlus 3
-console.log mMinus 2
-console.log mPlus 9


made rerun better

made rerun better

--- script/rerun.coffee
+++ script/rerun.coffee
@@ -1,6 +1,7 @@
 msgpack = require 'msgpack'
 redis = require 'redis'
 Manager = require '../lib/manager'
+_ = require '../lib/util'

 # Print usage message and die
 usage = ->
@@ -11,6 +12,8 @@
 argv = require('optimist').argv
 slice = argv.s ? process.env['SLICE'] ? 2
 port = argv.p ? process.env['REDIS_PORT'] ? 6379
+seed = argv.seed ? process.env['SEED'] ? null
+
 usage() unless argv.w

 # Create redis connection
@@ -18,7 +21,6 @@
 r.select slice

 # Globals
-seed = null
 to_delete = []
 manager = null

@@ -62,10 +64,9 @@
 # Delete all the collected intermediate keys, then call `rerun`.
 delete_keys = ->
   if to_delete.length
-    r.del to_delete..., (err) ->
-      throw err if err
-      console.log "Deleted #{to_delete.length/4} keys"
-      rerun()
+    chunks = _(to_delete).in_groups_of(10000)
+    each chunks, rerun, (chunk, next) ->
+      r.del chunk..., next
   else
     console.log "No keys to delete"
     rerun()
@@ -84,7 +85,7 @@
           r.scard "sources:#{key}", (err, sources) ->
             throw err if err
             if sources || (prefix == 'one_shot_cashout')
-              seed = key unless targets
+              seed ?= key unless targets
               to_delete.push key
               to_delete.push 'lock:'+key
               to_delete.push 'sources:'+key


Migrate p13n schema.

Migrate p13n schema.

Changed key: city.

--- src/p13n.coffee
+++ src/p13n.coffee
@@ -24,7 +24,7 @@

     SUPPORTED_LANGUAGES = appSettings.supported_languages
     LOCALSTORAGE_KEY = 'servicemap_p13n'
-    CURRENT_VERSION = 1
+    CURRENT_VERSION = 2
     LANGUAGE_NAMES =
         fi: 'suomi'
         sv: 'svenska'
@@ -91,10 +91,20 @@
                 bicycle_parked: true
                 bicycle_with: false

+    migrateCityFromV1ToV2 = (source) ->
+        city = source.city
+        source.city = _.clone DEFAULTS.city
+        if not city of source.city
+            return
+        source.city[city] = true
+
     deepExtend = (target, source, allowedValues) ->
         for prop of target
             if prop not of source
                 continue
+            if prop == 'city' and typeof source.city == 'string'
+                migrateCityFromV1ToV2 source
+
             sourceIsObject = !!source[prop] and typeof source[prop] == 'object'
             targetIsObject = !!target[prop] and typeof target[prop] == 'object'
             if targetIsObject != sourceIsObject


run: nikita kv

run: nikita kv

--- lib/run.coffee
+++ lib/run.coffee
@@ -149,11 +149,13 @@
   params = command: params if typeof params is 'string'
   params.end ?= true
   services = @config.services
+  engine = require('nikita/src/core/kv/engines/memory')()
   each @contexts
   .parallel true
   .call (context, callback) ->
     return callback() if params.hosts and multimatch(context.config.host, params.hosts).length is 0
     error = false
+    context.kv.engine engine: engine
     context.log.cli host: context.config.host, pad: host: 20, header: 60
     context.log.md basename: context.config.shortname
     context.ssh.open context.config.ssh, host: context.config.ip or context.config.host unless params.command is 'prepare'


Rewrite labels in context-menu and menu

Rewrite labels in context-menu and menu

--- menus/coffee-refactor.cson
+++ menus/coffee-refactor.cson
@@ -1,16 +1,15 @@
 # See https://atom.io/docs/latest/creating-a-package#menus for more details
 'context-menu':
   '.overlayer':
-      'Rename coffee-refactor': 'coffee-refactor:rename'
+      'Rename Coffee Refactor': 'coffee-refactor:rename'

 'menu': [
-  {
-    'label': 'Packages'
+  'label': 'Packages'
+  'submenu': [
+    'label': 'Coffee Refactor'
     'submenu': [
-      'label': 'coffee-refactor'
-      'submenu': [
-        { 'label': 'Rename', 'command': 'coffee-refactor:rename' }
-      ]
+      'label': 'Rename'
+      'command': 'coffee-refactor:rename'
     ]
-  }
+  ]
 ]


introducing new raw, command escaping

introducing new raw, command escaping

--- src/MKTS.coffee
+++ src/MKTS.coffee
@@ -896,9 +896,9 @@
     .pipe @$_flatten_tokens                 state
     .pipe @$_reinject_html_blocks           state
     .pipe @$_rewrite_markdownit_tokens      state
-    .pipe @$_replace_text                   state, @_unescape_command_fences_A
-    .pipe @$_preprocess_commands            state
-    .pipe @$_replace_text                   state, @_unescape_command_fences_B
+    # .pipe @$_replace_text                   state, @_unescape_command_fences_A
+    # .pipe @$_preprocess_commands            state
+    # .pipe @$_replace_text                   state, @_unescape_command_fences_B
     # .pipe @$_remove_postdef_dispensables    state
     .pipe @$_process_end_command            state
     .pipe R
@@ -909,7 +909,7 @@
     ### TAINT what to do with useful data appearing environment? ###
     ### TAINT environment becomes important for footnotes ###
     environment = {}
-    md_source   = @_escape_command_fences md_source
+    # md_source   = @_escape_command_fences md_source
     tokens      = md_parser.parse md_source, environment
     # @set_meta R, 'environment', environment
     confluence.write token for token in tokens


arxiv: noise reduction (remove abstracts, will add function for calling abstracts later)

--- scripts/arxiv.coffee
+++ scripts/arxiv.coffee
@@ -24,7 +24,7 @@
                 if keyWords.every(inText)
                     text.push '*' + title + '*'
                     text.push link
-                    text.push description
+                    # text.push description
                     text.push ''
                     gotNone = false
             if gotNone


:memo: doc dimension getter/setter

:memo: doc dimension getter/setter

--- src/atom.coffee
+++ src/atom.coffee
@@ -13,6 +13,8 @@
 Subscriber = require './subscriber'

 # Public: Atom global for dealing with packages, themes, menus, and the window.
+#
+# An instance of this class is always available as the `atom` global.
 module.exports =
 class Atom
   _.extend @prototype, Subscriber
@@ -43,12 +45,26 @@
   getCurrentWindow: ->
     remote.getCurrentWindow()

+  # Public: Get the dimensions of this window.
+  #
+  # Returns an object with x, y, width, and height keys.
   getDimensions: ->
     browserWindow = @getCurrentWindow()
     [x, y] = browserWindow.getPosition()
     [width, height] = browserWindow.getSize()
     {x, y, width, height}

+  # Public: Set the dimensions of the window.
+  #
+  # * dimensions:
+  #    + x:
+  #      The new x coordinate.
+  #    + y:
+  #      The new y coordinate.
+  #    + width:
+  #      The new width.
+  #    + height:
+  #      The new height.
   setDimensions: ({x, y, width, height}) ->
     browserWindow = @getCurrentWindow()
     browserWindow.setSize(width, height)


Making Nitin dangerous

Making Nitin dangerous

--- client/Business/AppView.coffee
+++ client/Business/AppView.coffee
@@ -64,7 +64,7 @@

Scale as you grow

-              Fully scalable environments with customizable stacks gives you
+              Fully scalable environments with customizable stacks give you
               the ability to scale as you get bigger in size. Size matters.


Check if error message exists before using it

--- src/pane.coffee
+++ src/pane.coffee
@@ -677,7 +677,7 @@
     true

   handleSaveError: (error) ->
-    if error.code is 'EISDIR' or error.message.endsWith('is a directory')
+    if error.code is 'EISDIR' or error.message?.endsWith('is a directory')
       atom.notifications.addWarning("Unable to save file: #{error.message}")
     else if error.code is 'EACCES' and error.path?
       atom.notifications.addWarning("Unable to save file: Permission denied '#{error.path}'")


Add deprecated Pane::removeItem

Add deprecated Pane::removeItem

--- src/pane.coffee
+++ src/pane.coffee
@@ -92,6 +92,9 @@
   serializeParams: ->
     model: @model.serialize()

+  # Deprecated: Use ::destroyItem
+  removeItem: (item) -> @destroyItem(item)
+
   # Deprecated: Use ::activateItem
   showItem: (item) -> @activateItem(item)


[WIP] Fix specs for negative numbers.

[WIP] Fix specs for negative numbers.


Slight alterations to make it more Coffee-idiomatic

--- src/poco.coffee
+++ src/poco.coffee
@@ -8,7 +8,7 @@
 #   None
 #
 # Commands:
-#   hubot poco suburb/postal code - Displays a formatted response from poco.co.za. Limited to 5 results.
+#   hubot poco <suburb|postal code> - Displays a formatted response from poco.co.za. Limited to 5 results.
 #
 # Author:
 #   johanmeiring
@@ -38,22 +38,22 @@
       if result_array.length > 0
         results = ''
         for i in [0..Math.min(result_array.length-1, 4)] by 1
-          if result_array[i].suburb != ''
+          unless result_array[i].suburb == ''
             results = "#{results}Suburb: #{result_array[i].suburb}; "
-          if result_array[i].area != ''
+          unless result_array[i].area == ''
             results = "#{results}Area: #{result_array[i].area}; "
-          if result_array[i].postal != ''
+          unless result_array[i].postal == ''
             results = "#{results}Postal Code: #{result_array[i].postal}; "
-          if result_array[i].street != ''
+          unless result_array[i].street == ''
             results = "#{results}Street Code: #{result_array[i].street}; "
           results = "#{results}\n"
         callback results
       else
         callback "No results were found for #{location} :("
     else
-      msg.reply "Sorry, I couldn't search for that location."
+      msg.reply 'Sorry, I couldn\'t search for that location.'

-  msg.http("http://poco.co.za/api/locations/")
+  msg.http('http://poco.co.za/api/locations/')
   .query(
     search: location
   ).get() processResult


Display error messages from the socket

Display error messages from the socket

--- src/quip.coffee
+++ src/quip.coffee
@@ -96,6 +96,8 @@
       when "heartbeat"
         @robot.logger.info "Got heartbeat"
         @lastBeatSeen = Date.now()
+      when "error"
+        @robot.logger.error packet.message
       when "message"
         user = @robot.brain.userForId packet.user.id, name: packet.user.name, room: packet.thread.id
         message = new TextMessage user, packet.message.text, packet.message.id


Change `user.name` with `user.id`

Change `user.name` with `user.id`

The ID is a more reliable key.

--- src/rbac.coffee
+++ src/rbac.coffee
@@ -89,7 +89,7 @@

     robot.respond /auth me/i, id: "auth.me", (res) ->
         # TODO: List blacklist?
-        subject = res.message.user.name
+        subject = res.message.user.id
         if not _subjects.has(subject) or _subjects.get(subject).size is 0
             res.reply "You are not assigned to any roles."
         else
@@ -163,7 +163,7 @@

     robot.listenerMiddleware (context, next, done) ->
         lid = context.listener.options.id
-        subject = context.response.message.user.name
+        subject = context.response.message.user.id

         terminate = false
         _blockRequest = ->
@@ -174,7 +174,7 @@
             return false

         # Skip if there are no subjects / policies or if it is a power user
-        if _subjects.size is 0 or _policies.size is 0 or _isPower(context.response.message.user.name)
+        if _subjects.size is 0 or _policies.size is 0 or _isPower(subject)
             robot.logger.debug "hubot-rbac: Skipping checks..."
             next()
             return false


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


fix app test indentation

fix app test indentation


delete api key if use id/password.

delete api key if use id/password.

--- coffee/services/redmine.coffee
+++ coffee/services/redmine.coffee
@@ -102,6 +102,7 @@
     if auth.apiKey? and auth.apiKey.length > 0
       @$http.defaults.headers.common["X-Redmine-API-Key"] = auth.apiKey
     else
+      delete @$http.defaults.headers.common["X-Redmine-API-Key"]
       @$http.defaults.headers.common['Authorization'] = 'Basic ' + @Base64.encode(auth.id + ':' + auth.pass)
     return config


add vlan info for PIF's

add vlan info for PIF's

--- src/spec.coffee
+++ src/spec.coffee
@@ -618,6 +618,8 @@

       $network: -> @genval.network

+      vlan: -> @genval.VLAN
+
       # TODO: What is it?
       #
       # Could it mean “is this a physical interface?”.


trigger tab change when controller becomes active

--- src/tabs.coffee
+++ src/tabs.coffee
@@ -30,5 +30,7 @@
   connect: (tabName, controller) ->
     @bind 'change', (name) ->
       controller.active() if name == tabName
+	controller.bind "active", () =>
+	      @change tabName

-module?.exports = Spine.Tabs+module?.exports = Spine.Tabs


Set size normal.  Fix potential null ref error in button pressing.

--- src/gurk.coffee
+++ src/gurk.coffee
@@ -225,7 +225,7 @@
   music : null

   constructor: ->
-    @setSize("small")
+    @setSize("normal")
     # Get all the images in here
     #Screen.SCALE = 4
     console.log("Preloading Images at scale: #{Screen.SCALE}")
@@ -418,6 +418,15 @@
     @setView(splashView)
     console.log("Set splash view")
     @playMusic(Data.splashMusic)
+
+
+    # Scene/Camera
+#    @scene = new Scene @game
+#    @tileMap = new TileMap("town")
+#    @scene.addObject(@tileMap)
+#    @tileMapView = new TileMapView @screen.canvas, @tileMap
+#    @scene.addView @tileMapView
+#    @scene.start()
 #    touchstart = (e) =>
 #      @buttonGrid.clicked(document.getElementById("controlID").relMouseCoords(e))
 #    document.getElementById("controlID").addEventListener("touchstart", touchstart, false);
@@ -536,7 +545,7 @@
       @showView()

   buttonPressed: (text) =>
-    @view.command(text)
+    @view?.command(text)

   showAlert: (icon, title, text, result) =>
     alert = new AlertView(this, icon, title, text, result)


Fixed bug in guts.coffee to ensure comparator is a function

--- src/guts.coffee
+++ src/guts.coffee
@@ -265,7 +265,7 @@
       if typeof comparator is 'string'
         comparator_string = comparator
         comparator = (model) => model.get comparator_string
-      if typeof comparator is not 'function'
+      if typeof comparator isnt 'function'
         throw "Guts : error : BaseCollectionView only understands function or string comparators"
       index = @collection.sortedIndex model, comparator
     else


Comment logging

Comment logging

--- lib/app.coffee
+++ lib/app.coffee
@@ -138,11 +138,11 @@
     }
     response.write '\n'

-    logger.info 'Client opened event stream'
+    # logger.info 'Client opened event stream'
     realtime.connections.push response

     request.on 'close', ->
-        logger.info 'Client closed event stream'
+        # logger.info 'Client closed event stream'
         ndx = realtime.connections.indexOf response
         if ndx > -1
             realtime.connections.splice ndx, 1
@@ -155,13 +155,11 @@
     name = obj.name
     obj = _.omit obj, 'name'
     message = JSON.stringify obj
-    logger.info "Event #{name} - #{message}"
-    logger.info "Sending event to #{realtime.connections.length} clients"
+    # logger.info "Event #{name} - #{message}"
+    # logger.info "Sending event to #{realtime.connections.length} clients"

     for connection in realtime.connections
-        connection.write "id: #{now.getTime()}\n"
-        connection.write "event: #{name}\n"
-        connection.write "data: #{message}\n\n"
+        connection.write "id: #{now.getTime()}\nevent: #{name}\ndata: #{message}\n\n"

 app.use (err, request, response, next) ->


Fix getServerPort method

Fix getServerPort method

--- lib/app.coffee
+++ lib/app.coffee
@@ -6,7 +6,7 @@
     response.send 'Hello, world!'

 getServerPort = ->
-    parseInt(process.env.SERVER_PORT, 10) | 3000
+    parseInt(process.env.SERVER_PORT, 10) or 3000

 exports.run = ->
     server = app.listen getServerPort(), ->


JName::fetchModels: to support arrays in given name list

Which enables following:

	JName.fetchModels ["gokmen", "dicle"], callback

--- workers/social/lib/social/models/name.coffee
+++ workers/social/lib/social/models/name.coffee
@@ -75,8 +75,9 @@
   stripTemplate:->
     stripTemplate Base.constructors[@constructorName], @name

-  @fetchModels =do->
-    fetchByNameObject =(nameObj, callback)->
+  @fetchModels = do->
+
+    fetchByNameObject = (nameObj, callback)->
       models = []
       queue = nameObj.slugs.map (slug, i)->->
         konstructor = Base.constructors[slug.constructorName]
@@ -94,19 +95,38 @@
         callback null, { models, name: nameObj }

     fetchModels = (name, callback)->
+
       if 'string' is typeof name
+
         JName.one {name}, (err, nameObj)->
           if err then callback err
           else if nameObj?
             fetchByNameObject nameObj, callback
           else
             callback null
+
+      else if Array.isArray name
+
+        JName.some {name: $in: name}, limit: 30, (err, nameObjects)->
+          return callback err  if err?
+
+          if nameObjects?
+            models = []
+            queue = nameObjects.map (nameObj, i)->->
+              fetchByNameObject nameObj, (err, model)->
+                models[i] = err ? model
+                queue.fin()
+            dash queue, ->
+              callback null, models
+          else
+            callback null
+
       else
         fetchByNameObject name, callback

   fetchModels:(callback)-> @fetchModels this, callback

-  @release =(name, callback=->)->
+  @release = (name, callback=->)->
     @remove {name}, callback

   @validateName =(candidate)->


Refactored SelectionModel into an abstract IdSet and renamed all methods and variables to work with adding/removing IDs.

--- src/tree.coffee
+++ src/tree.coffee
@@ -2,7 +2,6 @@
 TemplateClass = Template[templateName]
 selectEventName = 'select'
 checkEventName = 'check'
-hasSemanticUi = Package['nooitaf:semantic-ui']

 TemplateClass.created = ->
   data = @data
@@ -17,8 +16,8 @@
   @items = items

   @model = data.model ? new TreeModel(collection: @collection)
-  @selection = new SelectionModel(settings)
-  @check = new SelectionModel(_.extend({}, settings, {multiSelect: true}))
+  @selection = new IdSet({exclusive: !settings.multiSelect})
+  @check = new IdSet({exclusive: false})
   # Allow modifying the underlying logic of the tree model.
   _.extend(@model, data.settings)

@@ -112,92 +111,37 @@
 # SELECTION
 ####################################################################################################

-# A generic class for representing the selection of items.
-class SelectionModel
-
-  constructor: (args) ->
-    args = _.extend({
-      multiSelect: true
-    }, args)
-    @selectedIds = new ReactiveVar([])
-    @selectedIdsMap = {}
-    @multiSelect = args.multiSelect
-
-  setSelectedIds: (ids) ->
-    selectedIds = @getSelectedIds()
-    toDeselectIds = _.difference(selectedIds, ids)
-    toSelectIds = _.difference(ids, selectedIds)
-    @removeSelection(toDeselectIds)
-    @addSelection(toSelectIds)
-    {deselectedIds: toDeselectIds, selectedIds: toSelectIds}
-
-  getSelectedIds: -> @selectedIds.get()
-
-  deselectAll: ->
-    selectedIds = @getSelectedIds()
-    @removeSelection(selectedIds)
-    selectedIds
-
-  toggleSelection: (ids) ->
-    selectedIds = @getSelectedIds()
-    toDeselectIds = _.intersection(selectedIds, ids)
-    toSelectIds = _.difference(ids, selectedIds)
-    _.extend(@removeSelection(toDeselectIds), @addSelection(toSelectIds))
-
-  addSelection: (ids) ->
-    selectedIds = @getSelectedIds()
-    toSelectIds = _.difference(ids, selectedIds)
-    newSelectedIds = _.union(selectedIds, toSelectIds)
-    if toSelectIds.length > 0
-      if @multiSelect == false
-        @deselectAll()
-        if newSelectedIds.length > 1
-          newSelectedIds = toSelectIds = [ids[0]]
-      @selectedIds.set(newSelectedIds)
-    _.each toSelectIds, (id) => @selectedIdsMap[id] = true
-    {selectedIds: toSelectIds, deselectedIds: []}
-
-  removeSelection: (ids) ->
-    selectedIds = @getSelectedIds()
-    toDeselectIds = _.intersection(selectedIds, ids)
-    newSelectedIds = _.difference(selectedIds, toDeselectIds)
-    @selectedIds.set(newSelectedIds)
-    _.each toDeselectIds, (id) => delete @selectedIdsMap[id]
-    {selectedIds: [], deselectedIds: toDeselectIds}
-
-  isSelected: (id) -> !!@selectedIdsMap[id]
-
 setSelectedIds = (domNode, ids) ->
   return unless isSelectable(domNode)
-  result = getTemplate(domNode).selection.setSelectedIds(ids)
+  result = getTemplate(domNode).selection.setIds(ids)
   handleSelectionResult(domNode, result)

-getSelectedIds = (domNode) -> getTemplate(domNode).selection.getSelectedIds()
+getSelectedIds = (domNode) -> getTemplate(domNode).selection.getIds()

 deselectAll = (domNode) ->
   return unless isSelectable(domNode)
-  selectedIds = getTemplate(domNode).selection.deselectAll()
-  handleSelectionResult(domNode, {selectedIds: selectedIds, deselectedIds: []})
+  selectedIds = getTemplate(domNode).selection.removeAll()
+  handleSelectionResult(domNode, {added: selectedIds, removed: []})

 toggleSelection = (domNode, ids) ->
   return unless isSelectable(domNode)
-  result = getTemplate(domNode).selection.toggleSelection(ids)
+  result = getTemplate(domNode).selection.toggle(ids)
   handleSelectionResult(domNode, result)

 addSelection = (domNode, ids) ->
   return unless isSelectable(domNode)
-  result = getTemplate(domNode).selection.addSelection(ids)
+  result = getTemplate(domNode).selection.add(ids)
   handleSelectionResult(domNode, result)

 removeSelection = (domNode, ids) ->
   return unless isSelectable(domNode)
-  result = getTemplate(domNode).selection.removeSelection(ids)
+  result = getTemplate(domNode).selection.remove(ids)
   handleSelectionResult(domNode, result)

 handleSelectionResult = (domNode, result) ->
   $tree = getTreeElement(domNode)
-  _.each result.selectedIds, (id) -> _selectNode($tree, id)
-  _.each result.deselectedIds, (id) -> _deselectNode($tree, id)
+  _.each result.added, (id) -> _selectNode($tree, id)
+  _.each result.removed, (id) -> _deselectNode($tree, id)
   $tree.trigger(selectEventName, result)

 selectNode = (domNode, id) -> addSelection(domNode, [id])
@@ -218,7 +162,7 @@

 handleSelectionEvent = (e, template) ->
   $tree = template.$tree
-  multiSelect = template.selection.multiSelect
+  multiSelect = !template.selection.exclusive
   selectedNode = e.node
   return unless selectedNode
   selectedId = selectedNode.id
@@ -230,7 +174,7 @@
 handleClickEvent = (e, template) ->
   return unless isSelectable(template)
   $tree = template.$tree
-  multiSelect = template.selection.multiSelect
+  multiSelect = !template.selection.exclusive
   selectedNode = e.node
   selectedId = selectedNode.id
   # Disable single selection behaviour from taking effect.
@@ -261,7 +205,7 @@
     $title.before($checkbox)
     # TODO(aramk) Remove this reference and call off() on node removal.
     checkboxes.push($checkbox)
-    $checkbox.prop('checked', template.check.isSelected(node.id))
+    $checkbox.prop('checked', template.check.contains(node.id))
     $checkbox.on 'click', (e) -> e.stopPropagation()
     $checkbox.on 'change', ->
       isChecked = $checkbox.is(':checked')
@@ -275,39 +219,35 @@

 setCheckedIds = (domNode, ids) ->
   return unless isCheckable(domNode)
-  result = getTemplate(domNode).check.setSelectedIds(ids)
+  result = getTemplate(domNode).check.setIds(ids)
   handleCheckResult(domNode, result)

-getCheckedIds = (domNode) -> getTemplate(domNode).check.getCheckedIds()
+getCheckedIds = (domNode) -> getTemplate(domNode).check.getIds()

 uncheckAll = (domNode) ->
   return unless isCheckable(domNode)
-  selectedIds = getTemplate(domNode).check.deselectAll()
-  handleCheckResult(domNode, {selectedIds: selectedIds, deselectedIds: []})
+  selectedIds = getTemplate(domNode).check.removeAll()
+  handleCheckResult(domNode, {added: selectedIds, removed: []})

 toggleChecked = (domNode, ids) ->
   return unless isCheckable(domNode)
-  result = getTemplate(domNode).check.toggleSelection(ids)
+  result = getTemplate(domNode).check.toggle(ids)
   handleCheckResult(domNode, result)

 addChecked = (domNode, ids) ->
   return unless isCheckable(domNode)
-  result = getTemplate(domNode).check.addSelection(ids)
+  result = getTemplate(domNode).check.add(ids)
   handleCheckResult(domNode, result)

 removeChecked = (domNode, ids) ->
   return unless isCheckable(domNode)
-  result = getTemplate(domNode).check.removeSelection(ids)
+  result = getTemplate(domNode).check.remove(ids)
   handleCheckResult(domNode, result)

 handleCheckResult = (domNode, result) ->
   $tree = getTreeElement(domNode)
-  result.checkedIds ?= result.selectedIds
-  result.uncheckedIds ?= result.deselectedIds
-  delete result.selectedIds
-  delete result.deselectedIds
-  _.each result.checkedIds, (id) -> _checkNode($tree, id)
-  _.each result.uncheckedIds, (id) -> _uncheckNode($tree, id)
+  _.each result.added, (id) -> _checkNode($tree, id)
+  _.each result.removed, (id) -> _uncheckNode($tree, id)
   $tree.trigger(checkEventName, result)

 checkNode = (domNode, id) -> addChecked(domNode, [id])
@@ -452,6 +392,61 @@
   compareDocs: (docA, docB) ->
     if docA.name < docB.name then -1 else 1

+
+class IdSet
+
+  constructor: (args) ->
+    args = _.extend({
+      exclusive: false
+    }, args)
+    @ids = new ReactiveVar([])
+    @idsMap = {}
+    @exclusive = args.exclusive
+
+  setIds: (ids) ->
+    existingIds = @getIds()
+    toRemove = _.difference(existingIds, ids)
+    toAdd = _.difference(ids, existingIds)
+    @remove(toRemove)
+    @add(toAdd)
+    {added: toAdd, removed: toRemove}
+
+  getIds: -> @ids.get()
+
+  add: (ids) ->
+    existingIds = @getIds()
+    toAdd = _.difference(ids, existingIds)
+    newIds = _.union(existingIds, toAdd)
+    if toAdd.length > 0
+      if @exclusive
+        @removeAll()
+        if newIds.length > 1
+          newIds = toAdd = [ids[0]]
+      @ids.set(newIds)
+    _.each toAdd, (id) => @idsMap[id] = true
+    {added: toAdd, removed: []}
+
+  remove: (ids) ->
+    existingIds = @getIds()
+    toRemove = _.intersection(existingIds, ids)
+    newIds = _.difference(existingIds, toRemove)
+    @ids.set(newIds)
+    _.each toRemove, (id) => delete @idsMap[id]
+    {added: [], removed: toRemove}
+
+  removeAll: ->
+    toRemove = @getIds()
+    @remove(toRemove)
+    toRemove
+
+  toggle: (ids) ->
+    existingIds = @getIds()
+    toRemove = _.intersection(existingIds, ids)
+    toAdd = _.difference(ids, existingIds)
+    _.extend(@remove(toRemove), @add(toAdd))
+
+  contains: (id) -> !!@idsMap[id]
+
 ####################################################################################################
 # API
 ####################################################################################################


upgraded node animation

upgraded node animation

--- public/coffee/main-view.coffee
+++ public/coffee/main-view.coffee
@@ -36,8 +36,8 @@
             data.noiseExponent = loadingCanvas.elements.node.noiseExponent
             data.noiseFactor = loadingCanvas.elements.node.noiseFactor
          else
-            data.noiseExponent -= 0.91
-            data.noiseFactor -= 0.0012
+            data.noiseExponent -= 0.95
+            data.noiseFactor -= 0.001
          console.log("progress: "+progress+"  exponent: "+data.noiseExponent+"  factor: "+data.noiseFactor)
          clearInterval(id) if (progress == 1)
       , loadingCanvas.drawInterval
@@ -76,7 +76,10 @@
          canvas.height/2,
          canvas.width/1.5
       )
-      grd.addColorStop(0, '#1c2426')
+      if loadingCanvas.elements.mainCircle.connected
+         grd.addColorStop(0, '#3c484b')
+      else
+         grd.addColorStop(0, '#1c2426')
       grd.addColorStop(1, '#000000')

       # Fill BG


Recursive view enabling and disabling takes updateClass into account.

--- src/view.coffee
+++ src/view.coffee
@@ -229,7 +229,7 @@
 		@removeClass('disabled') if updateClass
 		if recursive is true
 			for viewName, view of @views
-				view.enable()
+				view.enable(recursive, updateClass)
 		@

 	disable: (recursive = true, updateClass = true) ->
@@ -237,7 +237,7 @@
 		@addClass('disabled') if updateClass
 		if recursive is true
 			for viewName, view of @views
-				view.disable()
+				view.disable(recursive, updateClass)
 		@

 	show: ->


- Multiple templates elements/containers are now supported

--- src/view.coffee
+++ src/view.coffee
@@ -46,16 +46,30 @@
 	@actionPattern = ///^(.*?):(.*?)\((.*?)\)///

 	@parseTemplates: ->
-		templateContainer = $('.templates')
-		templateContainer.remove()
-		templateEls = $(templateContainer.html()).filter('[data-template]')
+
+		# Define templates
 		@templates = {}
-
-		for template in templateEls
-			$template = $(template)
-			name = $template.attr('data-template')
-			$template.removeAttr('data-template')
-			@templates[name] = template.outerHTML
+
+		# Get all template containers.
+		templateContainers = document.querySelectorAll('.templates')
+		# Go over all of them to handle the templates inside.
+		for templateContainer in templateContainers
+			# Remove it from the DOM first.
+			templateContainer.parentNode.removeChild(templateContainer)
+			# Create a new element and copy the container's HTML into it to create a DOM structure if it was passed in a script tag.
+			templateContainerHTML = document.createElement('div')
+			templateContainerHTML.innerHTML = templateContainer.innerHTML
+
+			# Iterate all the children (templates)
+			for template in templateContainerHTML.children
+				# Get the name.
+				name = template.getAttribute('data-template')
+				# If it wasn't provided it's not a template; skip.
+				continue unless name
+				# Remove the data-template attribute.
+				template.removeAttribute('data-template')
+				# Save the HTML in @templates.
+				@templates[name] = template.outerHTML

 	constructor: (el = null, @options = null, @owner) ->
 		if @template and (not el or el.children().length is 0)


Adds sandbox.currentUser in the components

--- src/hull.coffee
+++ src/hull.coffee
@@ -58,6 +58,7 @@
     booted = apiParts.exports
     return booted unless appParts.components
     booted.component = componentRegistrar(define)
+    appParts.app.sandbox.currentUser = apiParts.exports.currentUser
     booted.util = appParts.app.sandbox.util
     booted.util.Handlebars = Handlebars
     booted.define = define


removing user agent checks, just looking for browser features now

--- src/init.coffee
+++ src/init.coffee
@@ -15,7 +15,8 @@
 			$('.descPara').height('auto');
 			$('#GraphGallery').width(300);
 			$( "#GraphGallery .col3").width(window.screen.width - 30);
-		else if ( ( !navigator.userAgent.match(/Chrome/i) && !navigator.userAgent.match(/Firefox/i) && !navigator.userAgent.match(/Safari/) ) || !window.File || !window.FileReader || !window.FileList || !window.Blob )
+		else if ( !window.File || !window.FileReader || !window.FileList || !window.Blob )
+			# ( !navigator.userAgent.match(/Chrome/i) && !navigator.userAgent.match(/Firefox/i) && !navigator.userAgent.match(/Safari/) )
 			$('#NotSupported').show();
 			$('#top_sec').height(50);
 			$('#viz_container, #bottom_sec, .pageName, hr, .orange_btn, #readFile, #recent, #graphNameCont').hide();


look for duplicate namespaces only on xml elements

--- src/xslt.coffee
+++ src/xslt.coffee
@@ -115,29 +115,17 @@
       xml = xml.substring(xml.indexOf(">") + 1, xml.lastIndexOf("<"))
     return xml

-  # If a ns is defined in the root node, it should not be redefined later
-  stripRedundantNamespaces = (xml) ->
-    # start with the first node
+  getRootNamespaces = (xml) ->
     matches = xml?.match(regex.xmlNode())
-    return xml unless matches?.length
-
-    rootNode = matches[0]
-    rootNamespaces = rootNode.match(regex.namespaces())
-    return xml unless rootNamespaces?.length
-
-    offset = xml.indexOf(rootNode)
-    start = xml.substring(0, offset + rootNode.length)
-    remainder = xml.substring(offset + rootNode.length)
-    return start + remainder.replace regex.namespaces(), (ns) ->
-      return '' if ns in rootNamespaces
-      return ns
-
-  stripDuplicateAttributes = (node, nodeName, closeTag) ->
+    return null unless matches?.length
+    matches[0].match(regex.namespaces())
+
+  stripDuplicateAttributes = (node, nodeName, closeTag, blacklist=[]) ->
     attrRegex = /([a-zA-Z0-9:\-]+)\s*=\s*("[^"]*")/g
     collection = {}
     parts = attrRegex.exec(node)
     while parts
-      collection[parts[1]] = parts[0]
+      collection[parts[1]] = parts[0] unless parts[0] in blacklist
       parts = attrRegex.exec(node)
     newStr = '<' + nodeName
     newStr += (' ' + val) for key, val of collection
@@ -160,11 +148,18 @@

   # Combine rules that apply to a single node at a time
   cleanupXmlNodes = (xml, opt) ->
+    rootNamespaces = if opt.removeDupNamespace then getRootNamespaces(xml) else []
+    isRootNode = true
     return xml?.replace new RegExp(regex.xmlNode().source, 'gi'), (node, nodeName, closeTag) ->
       node = stripNamespacedNamespace(node) if opt.removeNamespacedNamespace
       node = stripNullNamespaces(node) if opt.removeNullNamespace
       node = stripAllNamespaces(node) if opt.removeAllNamespaces
-      node = stripDuplicateAttributes(node, nodeName, closeTag) if opt.removeDupAttrs
+      if isRootNode
+        isRootNode = false
+        blacklist = []
+      else
+        blacklist = rootNamespaces
+      node = stripDuplicateAttributes(node, nodeName, closeTag, blacklist) if opt.removeDupAttrs or opt.removeDupNamespace
       return node

   collapseEmptyElements = (xml) ->
@@ -232,7 +227,6 @@
     outStr = stripHeader(outStr) if opt.normalizeHeader or !opt.xmlHeaderInOutput
     outStr = prependHeader(outStr, opt.encoding, standalone) if opt.xmlHeaderInOutput and needsHeader(outStr)
     outStr = cleanupXmlNodes(outStr, opt)
-    outStr = stripRedundantNamespaces(outStr) if opt.removeDupNamespace
     outStr = collapseEmptyElements(outStr) if opt.collapseEmptyElements
     return outStr


Remove unnecessary breakCaption

Remove unnecessary breakCaption

--- components/article/view.coffee
+++ components/article/view.coffee
@@ -56,7 +56,6 @@
     @setupStickyShare()

     # Resizing
-    @breakCaptions()
     @sizeVideo()

     # FS and Super Article setup


only chown repo if NODE_ENV defined

only chown repo if NODE_ENV defined

--- server/code/model/tool.coffee
+++ server/code/model/tool.coffee
@@ -40,7 +40,7 @@
         cmd = "git clone #{@gitUrl} #{@directory}; cd #{@directory}"
       else
         cmd = "cd #{@directory}; git pull"
-      cmd += "; chown -R www-data:www-data ."
+      cmd += "; chown -R www-data:www-data ." if process.env.NODE_ENV?
       child_process.exec cmd, callback

   # TODO: DRY


bugfix - on uidvaliditychanged

bugfix - on uidvaliditychanged

--- server/imap/connection.coffee
+++ server/imap/connection.coffee
@@ -83,6 +83,7 @@
     @search [['ALL']], (err, uids) =>
         log.debug "imap#fetchBoxMessageIds#result", uids
         return callback err if err
+        return callback null, [] if uids.length is 0

         fetch = @fetch uids, bodies: 'HEADER.FIELDS (MESSAGE-ID)'
         fetch.on 'error', (err) -> callback err


Fixes to passing through templates when hitting an error

Also adds the error to global Template data

--- server/routes/frontend.coffee
+++ server/routes/frontend.coffee
@@ -46,12 +46,6 @@

   hbs.registerHelper 'renderTime', -> getTime()

-  globalNext = null
-  hbs.registerHelper 'next', ->
-    if globalNext?
-      globalNext false
-      throw new Error '{{next}} called'
-
   templateData =
     adminSegment: config.buckets.adminSegment
     # Expose select items from the request object
@@ -61,6 +55,14 @@
       query: req.query unless _.isEmpty(req.query)
       params: {} # We fill this manually later
     user: req.user
+    errors: []
+
+  globalNext = null
+
+  hbs.registerHelper 'next', ->
+    if globalNext?
+      globalNext false
+      throw new Error '{{next}} called'

   # We could use a $where here, but it's basically the same
   # since a basic $where scans all rows (plus this gives us more flexibility)
@@ -79,17 +81,21 @@

         matchingRoutes.push localTemplateData

-    async.detectSeries matchingRoutes, (templateData, callback) ->
+    async.detectSeries matchingRoutes, (localTemplateData, callback) ->
       globalNext = callback
-      res.render templateData.template, templateData, (err, html) ->
+      localTemplateData = _.extend localTemplateData, templateData
+
+      res.render localTemplateData.template, localTemplateData, (err, html) ->
         if err
-          console.log 'Render error', err
-          callback err
+          tplErr = {}
+          tplErr[localTemplateData.template] = err.message
+          templateData.errors.push tplErr
+          callback false, "#{err.name} #{err.message}"
         else if html
           res.send 200, html
           callback true
         else
-          callback 'The rendered page was blank.'
+          callback false, 'The rendered page was blank.'
     , (rendered) ->
       return if rendered


Do not broadcast messages

Do not broadcast messages

--- server/stream/messages.coffee
+++ server/stream/messages.coffee
@@ -34,8 +34,8 @@

 	publishMessage = (type, record) ->
 		if record._hidden isnt true
-			msgStream.emit '__my_messages__', record, {}
-			msgStream.emit record.rid, record
+			msgStream.emitWithoutBroadcast '__my_messages__', record, {}
+			msgStream.emitWithoutBroadcast record.rid, record

 	query =
 		collection: RocketChat.models.Messages.model._name


Fix wrong way message

Fix wrong way message


domain: we will collect address information in any case

regardless of whether the user wants privacy protection, we still need to collect their address information.

--- client/app/Applications/Environments.kdapplication/views/domains/domainproductform.coffee
+++ client/app/Applications/Environments.kdapplication/views/domains/domainproductform.coffee
@@ -2,54 +2,36 @@

   viewAppended: ->
     locationController = KD.getSingleton 'locationController'
-    locationForm = locationController.createLocationForm
-      callback    : => @emit 'DataCollected', @locationForm.getData()
-      buttons     : no
+
+    @locationForm = locationController.createLocationForm
+      callback    : =>
+        @emit 'DataCollected', @locationForm.getData()
+      fields      :
+        privacyProtection:
+          label   : "Use privacy protection?"
+          itemClass: KDOnOffSwitch
       phone       :
         required  : yes

-    @locationForm = locationForm

-    @privacyProtection = new KDFormViewWithFields
-      fields         :
-        enabled      :
-          label      : 'Use privacy protection?'
-          itemClass  : KDOnOffSwitch
-          callback   : ->
-            do locationForm[if @getValue() is on then 'hide' else 'show']
+    # @buttons = new KDView

-    privacyExplanation = new KDView
-      partial: "We'll mask your address information.  Blah blah blah."
+    # @saveBtn = new KDButtonView
+    #   title     : "Next"
+    #   style     : 'modal-clean-green fr'
+    #   callback  : @bound 'processForm'

-    @privacyExplanation = privacyExplanation
-
-    @privacyExplanation.hide()
-
-    @buttons = new KDView
-
-    @saveBtn = new KDButtonView
-      title     : "Next"
-      style     : 'modal-clean-green fr'
-      callback  : @bound 'processForm'
-
-    @buttons.addSubView @saveBtn
+    # @buttons.addSubView @saveBtn

     super()

   processForm: ->
-    privacyEnabled = @privacyProtection.inputs.enabled.getValue()
-    if privacyEnabled is on
-      @emit 'DataCollected', privacyProtection: on
-    else
-      @locationForm.submit()
+    @locationForm.submit()

   pistachio: ->
     """

Please enter the address information that will be associated with this
        domain name registration.  Alternatively, you can choose to register
        this domain privately for an additional fee.

-    {{> @privacyProtection}}
-    {{> @privacyExplanation}}
     {{> @locationForm}}
-    {{> @buttons}}
     """


log some stuff.

log some stuff.

--- js/flight.coffee
+++ js/flight.coffee
@@ -224,25 +224,25 @@

   onTouchStart: (e) ->
     @fixInput(e)
-    console.log "touch start"
+
     if flight.prevClick? and @os.android
       flight.prevClick.blur(); #We need to blur any input fields on android
       flight.prevClick = null;

   onTouchEnd: (e) ->
-    console.log "on touch end", e.type
+    console.log "End"

   fixInput: (e) =>
     if not @os.android
       return
-
     target = e.touches[0].target

     if target and target.type != undefined
       tagname = target.tagName.toLowerCase()

       if tagname is "select" or tagname is "input" or tagname is "textarea"
-        flight.prevClick = target
+        flight.prevClick = target
+
     # think this is for if you move onto a form element
     document.addEventListener('touchmove', this, true);
     document.addEventListener('touchend', this, true)


Update mock workflow and gold standard classification

--- app/classifier/mock-data.coffee
+++ app/classifier/mock-data.coffee
@@ -7,7 +7,6 @@

 MISC_DRAWING_DETAILS = [{
   type: 'single'
-  required: true
   question: 'Cool?'
   answers: [
     {label: 'Yeah'}
@@ -45,14 +44,14 @@

     crop:
       type: 'crop'
-      instruction: 'Drag out a box around the face.'
-      help: 'The face is the thing with the nose.'
+      instruction: 'Drag out a box around the smaller rhino.'
+      help: 'That’s the adorable one.'
       next: 'write'

     write:
       type: 'text'
       required: true
-      instruction: 'Please describe what you see.'
+      instruction: 'What’s the name of this animal? Is it a rhino?'
       help: '''
         **Example**: If you see a bee, then type "Bee"
       '''
@@ -60,7 +59,7 @@

     features:
       type: 'multiple'
-      question: 'What **cool** features are present?'
+      question: 'What **cool** features are present? The first two?'
       answers: [
         {label: 'Cold water'}
         {label: 'Snow'}
@@ -72,7 +71,7 @@
     draw:
       type: 'drawing'
       required: true
-      instruction: 'Drop points on the rhino eyeballs.'
+      instruction: 'Drop a point on the rhino eyeball.'
       help: '''
         Do this:
         * Pick a tool
@@ -228,7 +227,7 @@
         hr:
           required: false
           multiple: true
-          label: 'Horns toggle'
+          label: 'Horns (toggle)'
           answersOrder: ['y']
           answers:
             y:
@@ -249,43 +248,46 @@
     'Region': 'Chicago, IL'

   expert_classification_data:
-    annotations: [{
-      task: 'init',
-      value: 0
-    }, {
-      task: 'crop',
-      value: {
-        x: 10,
-        y: 10,
-        width: 300,
-        height: 222
-      }
-    }, {
-      task: 'write',
-      value: 'Rhinos'
-    }, {
-      task: 'features',
-      value: [0, 2]
-    }, {
-      task: 'draw',
-      value: [{
-        tool: 0
-        x: 207
-        y: 134
-        frame: 0
-      }, {
-        tool: 0
-        x: 295
-        y: 97
-        frame: 0
+    MOCK_WORKFLOW_FOR_CLASSIFIER: apiClient.type('classifications').create
+      annotations: [{
+        task: 'init'
+        value: 0
+      }, {
+        task: 'crop'
+        value: {
+          x: 20
+          y: 57
+          width: 224
+          height: 142
+        }
+      }, {
+        task: 'write'
+        value: 'Rhino'
+      }, {
+        task: 'features'
+        value: [0, 1]
+      }, {
+        task: 'draw'
+        value: [{
+          tool: 0
+          frame: 0
+          x: 207
+          y: 134
+          details: [{
+            value: 0
+          }, {
+            value: []
+          }, {
+            value: ''
+          }]
+        }]
+      }, {
+        task: 'survey'
+        value: []
+      }, {
+        task: 'init'
+        value: 5
       }]
-    }, {
-      task: 'survey',
-      value: []
-    }, {
-      task: 'init',
-      value: 5
-    }]

 classification = apiClient.type('classifications').create
   annotations: []


add more null checks to health check

add more null checks to health check

--- app/coffee/HealthChecker.coffee
+++ app/coffee/HealthChecker.coffee
@@ -28,9 +28,11 @@
 					if err?
 						logger.err err:err, "docstore returned a error in health check get"
 						cb(err)
+					else if !res?
+						cb("no response from docstore with get check")
 					else if res?.statusCode != 200
 						cb("status code not 200, its #{res.statusCode}")
-					else if _.isEqual(body.lines, lines) and body._id == doc_id.toString()
+					else if _.isEqual(body?.lines, lines) and body?._id == doc_id.toString()
 						cb()
 					else
 						cb("health check lines not equal #{body.lines} != #{lines}")


Keyboard shortcuts can open links

Keyboard shortcuts can open links

--- app/controllers/classify.coffee
+++ app/controllers/classify.coffee
@@ -204,18 +204,23 @@
     return unless e.which of SHORTCUT_KEYS

     key = SHORTCUT_KEYS[e.which]
-    target = @el.find "[title*='#{key}]']"
+    target = @el.find "[title*='#{key}]']:visible"
+    return if target.length is 0

     e.preventDefault()

     target.focus()

-    if target.get(0).nodeName.toUpperCase() in ['A', 'BUTTON']
-      if target.attr('title').toUpperCase().indexOf('SHIFT') is -1
-        willClick = true
-      else if e.shiftKey
-        willClick = true
-
-    target.click() if willClick
+    if target.attr('title').toUpperCase().indexOf('SHIFT') is -1
+      willClick = true
+    else if e.shiftKey
+      willClick = true
+
+    nodeName = target.get(0).nodeName.toUpperCase()
+
+    if nodeName in ['BUTTON']
+      target.click() if willClick
+    else if nodeName is 'A'
+      location.href = target.attr 'href'

 module.exports = Classify


uncomment env test assertions

uncomment env test assertions

--- test/cli.coffee
+++ test/cli.coffee
@@ -381,7 +381,7 @@
       p   = path.join(__dirname, 'fixtures/compile/environments')
       @cli.run("compile #{p} --env #{env}")
         .done =>
-          # @spy.args[0][1].env.should.equal(env)
+          @spy.args[0][1].env.should.equal(env)
           done()

     it 'watch should handle environments args correctly', (done) ->
@@ -390,5 +390,5 @@
       @cli.run("watch #{p} --env #{env} --no-open")
         .catch(->)
         .done =>
-          # @spy.args[0][1].env.should.equal(env)
-          done()
+          @spy.args[0][1].env.should.equal(env)
+          done()


Add the modeling service extend and properties.

--- app/javascripts/modeling.coffee
+++ app/javascripts/modeling.coffee
@@ -57,6 +57,70 @@
       # Return the [[{protocol, source, results}, ...], ...] array.
       _.flatten(modelingArrays)

+    # A label map micro-service.
+    LabelMap =
+      # Adds the following modeling label map properties:
+      # * parameterResult - the parent parameter result object
+      # * key - the parent parameterResult key
+      #
+      # @param labelMap the object to extend
+      # @param paramResult the parent parameter result
+      extend: (labelMap, paramResult) ->
+        # Set the parent reference.
+        labelMap.parameterResult = paramResult
+        # Define the virtual properties.
+        Object.defineProperties labelMap,
+          key:
+            get: ->
+              @parameterResult.key
+
+    # A modeling parameter result micro-service.
+    ParameterResult =
+      # Adds the following modeling parameter result properties:
+      # * key - the parameter result access property name
+      # * modelingResult - the parent modeling result
+      #   reference
+      # * overlay - the label map, if it exists and has a
+      #   color table
+      #
+      # In addition, if there is a label map, then this function
+      # sets the label map parent modeling parameter reference.
+      #
+      # @param paramResult the object to extend
+      # @param modelingResult the parent modeling result object
+      # @param key the parameter result access property name
+      extend: (paramResult, modelingResult, key) ->
+        # The parameter key property identifies the
+        # parameter, e.g. ktrans.
+        paramResult.key = key
+        # Set the parent modeling result reference.
+        paramResult.modelingResult = modelingResult
+        if paramResult.labelMap?
+          # Set the label map parent reference.
+          LabelMap.extend(paramResult.labelMap)
+          # If the label map has a color table, then set
+          # the overlay property.
+          Object.defineProperties paramResult,
+            # @returns the parameter result label map
+            overlay:
+              get: ->
+                @labelMap
+
+    # A modeling result micro-service.
+    ModelingResult =
+      # Adds the modeling result parent modeling object
+      # reference property and extends the parameter result
+      # objects as described in extendParameterResult.
+      #
+      # @param modelingResult the modeling result object to extend
+      # @param modeling the parent modeling object
+      extend: (modelingResult, modeling) ->
+        # Set the modeling result parent reference.
+        modelingResult.modeling = modeling
+        # Extend each modeling parameter result object.
+        for key, paramResult of modelingResult
+          ParameterResult.extend(paramResult, modelingResult, key)
+
     # @param subject the parent subject
     # @returns the modeling [{source, results}] array, where
     #   each source value is a {source type: source id}
@@ -73,4 +137,61 @@
       )
       # Flatten into a [{protocol, source, results}, ...] array.
       _.flatten(modelingArrays)
+
+    # Extends the modeling result as described in
+    # extendModelingResult and adds the following
+    # modeling object properties:
+    # * session - the parent session object reference
+    # * overlays - the modeling result overlays
+    #
+    # @param modeling the modeling object to extend
+    # @param session the parent session object
+    extend: (modeling, session) ->
+      # Set the modeling parent session reference.
+      modeling.session = session
+      # Extend the modeling result object.
+      ModelingResult.extend(modeling.result, modeling)
+      # Add the virtual properties.
+      Object.defineProperties modeling,
+        # @returns the modeling results which have an overlay,
+        #   sorted by modeling parameter name
+        overlays:
+          get: ->
+            # Filter the modeling results for files with an overlay.
+            overlayed = (
+              res for res in _.values(@result) when res.overlay?
+            )
+            # Sort the overlayed results.
+            sorted = _.sortBy(overlayed, 'key')
+            # Pluck the overlay from the overlayed results.
+            _.map(sorted, 'overlay')
+
+      # Return the extended object.
+      modeling
+
+    # The property metadata, as follows:
+    # * text - plaintext label
+    # * html - HTML label
+    # * color - recommended HTML color
+    properties:
+      fxlKTrans:
+        text: 'FXL Ktrans'
+        html: 'FXL Ktrans'
+        color: 'BurlyWood'
+      fxrKTrans:
+        text: 'FXR Ktrans'
+        html: 'FXR Ktrans'
+        color: 'OliveDrab'
+      deltaKTrans:
+        text: 'Delta Ktrans'
+        html: 'ΔKtrans'
+        color: 'DarkGoldenRod'
+      tauI:
+        text: 'tau_i'
+        html: 'τi'
+        color: 'PaleVioletRed'
+      vE:
+        text: 'v_e'
+        html: 've'
+        color: 'MediumSeaGreen'
   ]


changed name of new test framework to console.test().

--- logging.coffee
+++ logging.coffee
@@ -1,20 +1,24 @@
-###
-
- This simple override will define the way we test Socrenchus.
+_.extend(console,
+  warn: (args...) ->
+    _console.warn(args...)
+  test: (args...) ->
+    usage = """

- Call it like this:
+     This simple function will define the way we test Socrenchus.

-  console.debug( ->
-    console.log( 'this writes to the log' )
-    console.info( 'this is an info message' )
-    console.warn( 'this is a warning' )
-    console.error( 'this is an error' )
-  )
-
-###
-console.debug = (args...) ->
-  for a in args
-    unless typeof a == 'function'
-      console.error('You need to pass a function that can throw its own errors to debug.')
-    else
-      a()+     Call it with your test function like this:
+
+      console.test( ->
+        console.log( 'this writes to the log' )
+        console.info( 'this is an info message' )
+        console.warn( 'this is a warning' )
+        console.error( 'this is an error' )
+      )
+
+    """
+    for a in args
+      unless typeof a == 'function'
+        console.warn(usage)
+      else
+        a()
+)


added regex validation

added regex validation

--- source/latte.coffee
+++ source/latte.coffee
@@ -2,6 +2,7 @@
   obj({a:1}) fun(supafn) obj({bla: 2}) -> code_1
   type(String) -> code_2

+['a', 'b', rest]

 w = (testelem) ->
   (rightParam) ->
@@ -21,6 +22,11 @@
           rightParam.call(_.assign(@, thisExtension), toEval)
         else
           throw 'broken'
+      else if Object.prototype.toString.call(testelem) is '[object RegExp]'
+        if testelem.test param
+          rightParam.call(@, toEval)
+        else
+          throw 'broken'
       else
         throw 'unsupported arg'


OX-261 Fix toggle tree visibility with branch points

--- app/assets/javascripts/oxalis/geometries/skeleton.coffee
+++ app/assets/javascripts/oxalis/geometries/skeleton.coffee
@@ -189,8 +189,6 @@
     # for branchPoint in @route.branchStack
     #   @setBranchPoint(true, branchPoint.id)
     @updateBranches()
-    # add branchesParticleSystem to scene
-    @trigger "newGeometries", [@branches]

     @setActiveNode()

@@ -241,7 +239,7 @@
     @flycam.hasChanged = true

   getMeshes : =>
-    return [@activeNodeParticle].concat(@nodes).concat(@nodesSpheres).concat(@routes)
+    return [@activeNodeParticle].concat(@nodes).concat(@nodesSpheres).concat(@routes).concat(@branches)

   setWaypoint : =>
     curGlobalPos = @flycam.getGlobalPos()


Wrap arrays/collections in [ ] in table view.

--- community/browser/app/scripts/directives/neoTable.coffee
+++ community/browser/app/scripts/directives/neoTable.coffee
@@ -50,7 +50,7 @@
           if angular.isString(cell)
             escapeHtml(cell)
           else if angular.isArray(cell)
-            (cell2html(el) for el in cell).join(', ')
+            "["+((cell2html(el) for el in cell).join(', '))+"]"
           else if angular.isObject(cell)
             json2html(cell)
           else


Make all scenery items equal in width

Make all scenery items equal in width

--- app/scripts/scenery/City.coffee
+++ app/scripts/scenery/City.coffee
@@ -53,7 +53,7 @@
     @addBackground(230, @level.visibleHeight - 145, Crafty.e('2D, Canvas, Color').color(col1).attr({ z: -500, w: (40 * .25) + 1, h: 15 }), .25)
     @addBackground(330, @level.visibleHeight - 157, Crafty.e('2D, Canvas, Color').color(col2).attr({ z: -501, w: (50 * .25) + 1, h: 15 }), .25)

-generator.defineElement 'city', ->
+generator.defineElement 'city', (offset = 0) ->
   # Coastline

   # Sky = 8080FF = 128, 128, 255
@@ -194,7 +194,7 @@
 generator.defineBlock class extends @Game.LevelScenery
   name: 'City.Ocean'
   delta:
-    x: 400
+    x: 800
     y: 0

   generate: ->
@@ -208,7 +208,7 @@
 generator.defineBlock class extends @Game.LevelScenery
   name: 'City.CoastStart'
   delta:
-    x: 400
+    x: 800
     y: 0
   autoNext: 'Coast'

@@ -222,7 +222,7 @@
 generator.defineBlock class extends @Game.LevelScenery
   name: 'City.Coast'
   delta:
-    x: 400
+    x: 800
     y: 0

   generate: ->
@@ -235,7 +235,7 @@
 generator.defineBlock class extends @Game.LevelScenery
   name: 'City.Bay'
   delta:
-    x: 400
+    x: 800
     y: 0

   generate: ->
@@ -244,6 +244,7 @@
     @addElement 'water'
     @addElement 'cityHorizon'
     @addElement 'city'
+    @addElement 'city', 400

 generator.defineBlock class extends @Game.LevelScenery
   name: 'City.UnderBridge'
@@ -258,6 +259,7 @@
     @addElement 'water'
     @addElement 'cityHorizon'
     @addElement 'city'
+    @addElement 'city', 400

     # Pillars
     pillarWidth = 80
@@ -298,4 +300,5 @@

     @addElement 'cityHorizon'
     @addElement 'city'
-
+    @addElement 'city', 400
+


Minor cleanup

Minor cleanup

--- app/scripts/scenery/City.coffee
+++ app/scripts/scenery/City.coffee
@@ -67,13 +67,8 @@
   col3 = '#66667D'
   @addBackground(0, @level.visibleHeight - 140, Crafty.e('2D, Canvas, Color').color(col3).attr({ z: -400, w: (@delta.x * .37) + 1, h: 83 }), .37)

-  col1 = '#AFAFBB'
-  col2 = '#8F8F9A'
-  col3 = '#63636E'
   e = Crafty.e('2D, Canvas, Image, Collision').image('images/city.png').attr({ z: -305 })
-  e.collision(
-    [20, 155, 20, 20, 70, 20, 70, 0, 130, 0, 130, 155]
-  )
+  e.collision([20, 155, 20, 20, 70, 20, 70, 0, 130, 0, 130, 155])
   @addBackground(offset, @level.visibleHeight - 290, e, .5)

 generator.defineBlock class extends @Game.LevelScenery


IDE: remove unused route

IDE: remove unused route

--- client/ide/routes.coffee
+++ client/ide/routes.coffee
@@ -163,10 +163,4 @@

       { machineLabel, workspaceSlug } = data.params

-      loadWorkspace machineLabel, workspaceSlug
-
-    '/:name?/IDE/:machineLabel/:workspaceSlug/:username/:channelId': (data) ->
-
-      { machineLabel, workspaceSlug, username, channelId } = data.params
-
-      loadWorkspace machineLabel, workspaceSlug, username, channelId
+      loadWorkspace machineLabel, workspaceSlug


Added lic38 group

Added lic38 group

--- client/lib/groups.coffee
+++ client/lib/groups.coffee
@@ -14,6 +14,15 @@
         startDayForWeeks: "2020-01-05"
         tableName: "Лицей 87",
         tableNameEn: "School 87, N.Novgorod",
+        canResetYear: true
+        starred: false
+        lang: ""
+    lic38:
+        paid: false
+        chocos: false
+        startDayForWeeks: "2020-01-05"
+        tableName: "Лицей 38",
+        tableNameEn: "School 38, N.Novgorod",
         canResetYear: true
         starred: false
         lang: ""


Disable X-Powered-By header set by Express

--- lib/app.coffee
+++ lib/app.coffee
@@ -20,6 +20,8 @@
 TeamController = require './controllers/team'

 app = express()
+
+app.set 'x-powered-by', no

 app.use (request, response, next) ->
     response.header 'Access-Control-Allow-Origin', 'http://' + process.env.DOMAIN


more changes for the shell (switching a object call to a site call)

--- lib/lt3.coffee
+++ lib/lt3.coffee
@@ -60,7 +60,7 @@
   get:sites [<slug>]              get sites with shell
   get:allsites                    returns a list of id_slug mappings for all sites
   get:objects_c <collection>      get objects with collection
-  get:objects_t <page_type>       get objects with type
+  get:objects_t <type>            get objects with type
   get:sitemap <slug>              get contents of site
   get:site_ids <slug>..           returns a id to slug lookup
   help/docs                       this document
@@ -104,7 +104,7 @@
         db.get('objects').find {site_id:site._id}, (err, objects) =>
           for object in objects
             log "_id: #{object.data._id}, collection:#{object.data.collection},
-            page_type:#{object.data.page_type}"
+            type:#{object.data.type}"
           rl.prompt();

     when 'get:site_ids'
@@ -201,16 +201,16 @@
           getDB (db) ->
             func=l_token.match(/f[a-zA-Z]+/)
             functionArgs=extractJsons(l_token)
-            db.get('sites')[func] functionArgs[0],functionArgs[1], (err, objects) ->
+            db.get('objects')[func] functionArgs[0],functionArgs[1], (err, objects) ->
               if typeIsArray objects
                 for object in objects
                   log '------Object------'
                   log JSON.stringify(object.val(), null, 4)
                   log '------End Object------'
               else
-                log '------Site------'
+                log '------Object------'
                 log JSON.stringify(objects.val(), null, 4)
-                log '------End Site------'
+                log '------End Object------'
               rl.prompt();
         else
           log """
@@ -735,8 +735,8 @@
           throw err if err
           exit('page already exists at this location') if object

-          # insert new page
-          db.get('objects').insert {
+
+          object= {
             data: {}
             page_type: page_type
             password: ''
@@ -749,10 +749,68 @@
             slug: page_slug
             tags: [page_slug, page_type]
             type: 'page'
-          }, (err, object) ->
+          }
+
+          # insert new page
+          db.get('objects').insert object, (err, object) ->
             throw err if err
             log "page added to #{site_slug}"
             exit()
+
+  when 'add' ##DOES NOT WORK YET IN PROGRESS
+    getDB (db) ->
+       db.get('objects').find({slug:"richardduran2"}) (err, site) ->
+        log site
+
+    ###
+    # parse arguments
+    site_slug = args[0]
+    collection_name = args[1]
+    object_type = args[2]
+    object_slug = args[3]
+
+    exit("must specify object type") unless object_type
+    exit("must specify collection name") unless collection_name
+    exit("must specify site slug") unless site_slug
+    exit("must specify object slug") unless object_slug
+    object_slug = object_slug.replace /^\/(.*)/, '$1'
+
+    getDB (db) ->
+
+      # make sure site exists
+      db.get('sites').findOne {
+        slug: site_slug
+      }, (err, site) ->
+        throw err if err
+        exit('site does not exist') unless site
+
+        # make sure page location isn't taken
+        db.get('objects').findOne {
+          'site_id': site.get('_id').val()
+          slug: object_slug
+        }, (err, object) ->
+          throw err if err
+          exit('object already exists at this location') if object
+
+          # insert new page
+          db.get('objects').insert {
+            data: {}
+            password: ''
+            seo:
+              title: ''
+              description: ''
+              keywords: ''
+              image: ''
+            site_id: site.get('_id').val()
+            slug: object_slug
+            tags: [object_type, object_slug]
+            type: object_type
+          }, (err, object) ->
+            throw err if err
+            log "object added to #{site_slug}"
+            exit()
+    ###
+

   when 'add:object'


Prevented source maps from being loaded as page resources

--- lib/nap.coffee
+++ lib/nap.coffee
@@ -82,7 +82,7 @@
   output = ''
   for filename, contents of preprocessPkg pkg, 'js'
     writeFile filename, contents unless @usingMiddleware
-    output += "<script src='#{@_assetsDir}/#{filename}' type='text/javascript'></script>"
+    output += if filename.match /\.map$/ then "" else "<script src='#{@_assetsDir}/#{filename}' type='text/javascript'></script>"
   output

 # Run css pre-processors & output the packages in dev.
@@ -104,7 +104,7 @@
   output = ''
   for filename, contents of preprocessPkg pkg, 'css'
     writeFile filename, contents unless @usingMiddleware
-    output += "<link href='#{@_assetsDir}/#{filename}' rel='stylesheet' type='text/css'>"
+    output += if filename.match /\.map$/ then "" else "<link href='#{@_assetsDir}/#{filename}' rel='stylesheet' type='text/css'>"
   output

 # Compile the templates into JST['file/path'] : functionString pairs in dev
@@ -119,7 +119,7 @@
     fingerprint = '-' + fingerprintForPkg('jst', pkg) if @mode is 'production'
     src = (@cdnUrl ? @_assetsDir) + '/' + "#{pkg}#{fingerprint ? ''}.jst.js"
     src += '.jgz' if gzip
-    return "<script src='#{src}' type='text/javascript'></script>"
+    return if filename.match /\.map$/ then "" else "<script src='#{src}' type='text/javascript'></script>"

   expandAssetGlobs()


Fix SocialFeed #stopVideo

Fix SocialFeed #stopVideo

--- app/assets/javascripts/martian_components/components/social_feeds.coffee
+++ app/assets/javascripts/martian_components/components/social_feeds.coffee
@@ -56,4 +56,4 @@
     @embeddedPlayerWrapper = $('.embedded-video-player-wrapper', @el)

   stopVideo: ->
-    @embeddedPlayerWrapper.data('player').stopVideo()
+    @embeddedPlayerWrapper.data('player')?.stopVideo()


[tests] ensure that `PLACEHOLDERS` exists

--- source/data/index.test.coffee
+++ source/data/index.test.coffee
@@ -59,6 +59,7 @@
     baseLocale: t.String
     googleAnalyticsId: t.union [t.String, r.False]
     yandexMetrikaId: t.union [t.String, r.False]
+  PLACEHOLDERS: t.dict(t.String, t.Any)
   PAGE_DEFAULTS: t.struct
     image: t.maybe t.String
     class: t.maybe t.String


Client: Activity: resetPaddedClassName method renamed to unsetPaddedClassName

--- client/activity/lib/components/chatpane/index.coffee
+++ client/activity/lib/components/chatpane/index.coffee
@@ -28,7 +28,7 @@
     @shouldScrollToBottom = yes  if isMessageBeingSubmitted

     scrollContainer = React.findDOMNode @refs.scrollContainer
-    @resetPaddedClassName()
+    @unsetPaddedClassName()

   onTopThresholdReached: (event) ->
@@ -68,7 +68,7 @@

   beforeScrollToBottom: ->

-    @resetPaddedClassName()
+    @unsetPaddedClassName()
     @setPaddedClassName()

@@ -77,7 +77,7 @@
     @setPaddedClassName()

-  resetPaddedClassName: ->
+  unsetPaddedClassName: ->

     scrollContainer = React.findDOMNode @refs.scrollContainer
     scrollContainer?.classList.remove 'padded'


client: remove unnecessary comments

client: remove unnecessary comments

--- client/app/lib/desktopnotificationscontroller.coffee
+++ client/app/lib/desktopnotificationscontroller.coffee
@@ -18,16 +18,11 @@
 ###
 permitted = (callback) -> (args...) ->
   browserNotifications
-    # request permissions first
     .requestPermissions()
-    # get the selection of user
     .then (isPermitted) ->
       return if isPermitted
-      # if permitted there is no error, and call given callback with args.
       then callback null, args...
-      # if user hasn't permitted, call given callback with an error object.
       else callback { message: 'Browser notifications are not permitted' }
-    # if there is an error call given callback with error.
     .catch (err) -> callback err


Teams: Disable Advanced settings tab in machine settings modal for now.

--- client/app/lib/providers/machinesettingsmodal.coffee
+++ client/app/lib/providers/machinesettingsmodal.coffee
@@ -75,7 +75,7 @@
     isMachineRunning            = machine.status.state is Machine.State.Running
     disabledTabsForNotRunningVM = [ 'Disk Usage', 'Domains', 'VM Sharing', 'Snapshots' ]
     disabledTabsForManagedVM    = [ 'Specs', 'Domains', 'Snapshots' ]
-    disabledTabsForTeams        = [ 'Domains' ]
+    disabledTabsForTeams        = [ 'Domains', 'Advanced' ]

     for item in PANE_CONFIG when item.title and item.viewClass


client: welcome: credential step copy change

--- client/home/lib/flux/stores/welcomestepsstore.coffee
+++ client/home/lib/flux/stores/welcomestepsstore.coffee
@@ -25,7 +25,7 @@
           actionTitle: 'Enter'
           videoLink: ''
           miniTitle: 'Enter Credentials'
-          description: "To be able to set up your machines we need your cloud provider credentials."
+          description: "To set up your machines we need your cloud provider credentials."
           isDone: no
           order: 2
         buildStack :
@@ -75,7 +75,8 @@
           path: '/Home/My-Team'
           title: 'Invite Your Team'
           actionTitle: 'Invite'
-          videoLink: '//www.koding.com/docs'
+          videoLink: ''
+          # videoLink: '//www.koding.com/docs'
           miniTitle: 'Invite Teammates'
           description: "Get your teammates working together."
           isDone: no


spawn a new word after shoot down a word

--- src/kick.coffee
+++ src/kick.coffee
@@ -1,4 +1,5 @@
-Array::pop = (index=@length-1) -> this.splice(index, 1)[0]
+Array::pop = (index=@length-1) -> @splice(index, 1)[0]
+Array::random = -> @[Math.floor(@length * Math.random())]

 stage_height = 300
 stage_width = 1000
@@ -38,6 +39,8 @@
         @left <= 0

+action_words = ['box', 'kick', 'punch', 'strike']
+
 pool_words.push(new ShootingWord('kick neizod'))
 pool_words.push(new ShootingWord('punch neizod'))
 pool_words.push(new ShootingWord('strike neizod'))
@@ -70,4 +73,6 @@

     if c == shoot_word?.remain[0]
         shoot_word.shot()
-        shoot_word = null if not shoot_word.remain
+    if not shoot_word.remain
+        shoot_word = null
+        pool_words.push(new ShootingWord("#{action_words.random()} neizod"))


use strict

use strict


added test

added test

--- test/crystal/validate/exports.coffee
+++ test/crystal/validate/exports.coffee
@@ -117,6 +117,22 @@
         (-> new crystal config)
           .should.not.throw "`exports.ConfigGenerator.spec` is required."

+      it 'should allow multiple modularized specs', () ->
+        # object
+        config = {
+          exports: {
+            ConfigGenerator: {
+              spec: [
+                'TestSpec2',
+                'TestSpec1'
+              ]
+              type: 'generator'
+            }
+          }
+        }
+        (-> new crystal config)
+          .should.not.throw "`exports.ConfigGenerator.spec` must be a `object` or `string`, not a `array`."
+
       it 'should not require a transformer', () ->
         # object
         config = {


update app routes to use the CDN when loading URLs

--- app/templates/app/routes.coffee
+++ app/templates/app/routes.coffee
@@ -1,8 +1,15 @@
 module.exports = (app)->
+
+    CDN = require('CDN')(app)
+
     # configure AngularJS
     app.config ($routeProvider)->
         # setup routes
         $routeProvider
         .when '/',
             controller: 'MainController'
-            templateUrl: 'app/views/main.html'+            templateUrl: CDN.template('main.html')
+        .when '/comments',
+            templateUrl: CDN.template('comments.html')
+        .when '/cors',
+            templateUrl: CDN.template('cors.html')


Add Chronograph plugin doc

Add Chronograph plugin doc

--- app/assets/javascripts/chronograph.coffee
+++ app/assets/javascripts/chronograph.coffee
@@ -1,5 +1,14 @@
 'use strict'

+###
+  @param  :
+    id: DOM id attribute
+    settings: Specify options in {}. May be on of size.
+  @example:
+    new Chronograph('chronograph', {
+      size: 420
+    });
+###
 class Chronograph
   constructor: (id, settings) ->
     @element = document.getElementById(id)


fixum, image hover

fixum, image hover

--- app/assets/javascripts/preorder.js.coffee
+++ app/assets/javascripts/preorder.js.coffee
@@ -21,10 +21,10 @@
       Selfstarter.firstTime = false

     # init placeholder image for video
-    $("#video_image").on "click", ->
-      $("#player").removeClass("hidden")
-      $("#player").css('display', 'block')
-      $(this).hide()
+    #$("#video_image").on "click", ->
+     # $("#player").removeClass("hidden")
+     # $("#player").css('display', 'block')
+     # $(this).hide()

     # if they are using the optional payment options section on the checkout page, need to conditional fix the email
     # field and button to the bottom of the page so it's displayed after selecting a radio button


Add tests for unscoped packets

Add tests for unscoped packets

--- spec/Scoping.coffee
+++ spec/Scoping.coffee
@@ -64,6 +64,27 @@
     datatype: 'string'
   c.inPorts.add 'in2',
     datatype: 'string'
+  c.outPorts.add 'out',
+    datatype: 'string'
+
+  c.forwardBrackets =
+    'in1': ['out']
+
+  c.process (input, output) ->
+    return unless input.has 'in1', 'in2', (ip) -> ip.type is 'data'
+    first = input.getData 'in1'
+    second = input.getData 'in2'
+
+    output.sendDone
+      out: "1#{first}:2#{second}:#{c.nodeId}"
+
+processMergeUnscoped = ->
+  c = new noflo.Component
+  c.inPorts.add 'in1',
+    datatype: 'string'
+  c.inPorts.add 'in2',
+    datatype: 'string'
+    scoped: false
   c.outPorts.add 'out',
     datatype: 'string'

@@ -112,6 +133,7 @@
       loader.registerComponent 'process', 'Async', processAsync
       loader.registerComponent 'process', 'Merge', processMerge
       loader.registerComponent 'process', 'MergeA', processMergeA
+      loader.registerComponent 'process', 'MergeUnscoped', processMergeUnscoped
       done()

   describe 'with WirePattern sending to Process API', ->
@@ -426,6 +448,122 @@
       in1.post new noflo.IP 'data', 'one', scope: 'x'
       in1.post new noflo.IP 'closeBracket', 1, scope: 'x'

+  describe 'Process API with unscoped port and scopes', ->
+    c = null
+    in1 = null
+    in2 = null
+    out = null
+    before (done) ->
+      fbpData = "
+      INPORT=Pc1.IN:IN1
+      INPORT=Pc2.IN:IN2
+      OUTPORT=PcMerge.OUT:OUT
+      Pc1(process/Async) -> IN1 PcMerge(process/MergeUnscoped)
+      Pc2(process/Async) -> IN2 PcMerge(process/MergeUnscoped)
+      "
+      noflo.graph.loadFBP fbpData, (err, g) ->
+        return done err if err
+        loader.registerComponent 'scope', 'MergeUnscoped', g
+        loader.load 'scope/MergeUnscoped', (err, instance) ->
+          return done err if err
+          c = instance
+          in1 = noflo.internalSocket.createSocket()
+          c.inPorts.in1.attach in1
+          in2 = noflo.internalSocket.createSocket()
+          c.inPorts.in2.attach in2
+          done()
+    beforeEach ->
+      out = noflo.internalSocket.createSocket()
+      c.outPorts.out.attach out
+    afterEach ->
+      c.outPorts.out.detach out
+      out = null
+    it 'should forward scopes as expected', (done) ->
+      expected = [
+        'x < 1'
+        'x DATA 1onePc1:2twoPc2:PcMerge'
+        'x >'
+      ]
+      received = []
+      brackets = []
+
+      out.on 'ip', (ip) ->
+        switch ip.type
+          when 'openBracket'
+            received.push "#{ip.scope} < #{ip.data}"
+            brackets.push ip.data
+          when 'data'
+            received.push "#{ip.scope} DATA #{ip.data}"
+          when 'closeBracket'
+            received.push "#{ip.scope} >"
+            brackets.pop()
+            return if brackets.length
+            chai.expect(received).to.eql expected
+            done()
+
+      in1.post new noflo.IP 'openBracket', 1, scope: 'x'
+      in1.post new noflo.IP 'data', 'one', scope: 'x'
+      in1.post new noflo.IP 'closeBracket', 1, scope: 'x'
+      in2.post new noflo.IP 'openBracket', 1, scope: 'x'
+      in2.post new noflo.IP 'data', 'two', scope: 'x'
+      in2.post new noflo.IP 'closeBracket', 1, scope: 'x'
+    it 'should forward packets without scopes', (done) ->
+      expected = [
+        'null < 1'
+        'null DATA 1onePc1:2twoPc2:PcMerge'
+        'null >'
+      ]
+      received = []
+      brackets = []
+
+      out.on 'ip', (ip) ->
+        switch ip.type
+          when 'openBracket'
+            received.push "#{ip.scope} < #{ip.data}"
+            brackets.push ip.data
+          when 'data'
+            received.push "#{ip.scope} DATA #{ip.data}"
+          when 'closeBracket'
+            received.push "#{ip.scope} >"
+            brackets.pop()
+            return if brackets.length
+            chai.expect(received).to.eql expected
+            done()
+      in1.post new noflo.IP 'openBracket', 1
+      in1.post new noflo.IP 'data', 'one'
+      in1.post new noflo.IP 'closeBracket'
+      in2.post new noflo.IP 'openBracket', 1
+      in2.post new noflo.IP 'data', 'two'
+      in2.post new noflo.IP 'closeBracket', 1
+    it 'should forward scopes also on unscoped packet', (done) ->
+      expected = [
+        'x < 1'
+        'x DATA 1onePc1:2twoPc2:PcMerge'
+        'x >'
+      ]
+      received = []
+      brackets = []
+
+      out.on 'ip', (ip) ->
+        switch ip.type
+          when 'openBracket'
+            received.push "#{ip.scope} < #{ip.data}"
+            brackets.push ip.data
+          when 'data'
+            received.push "#{ip.scope} DATA #{ip.data}"
+          when 'closeBracket'
+            received.push "#{ip.scope} >"
+            brackets.pop()
+            return if brackets.length
+            chai.expect(received).to.eql expected
+            done()
+      in2.post new noflo.IP 'openBracket', 1
+      in2.post new noflo.IP 'data', 'two'
+      in2.post new noflo.IP 'closeBracket', 1
+      in1.post new noflo.IP 'openBracket', 1, scope: 'x'
+      in1.post new noflo.IP 'data', 'one', scope: 'x'
+      in1.post new noflo.IP 'closeBracket', 1, scope: 'x'
+
   describe 'Process API with IIPs to addressable ports and scopes', ->
     c = null
     in1 = null


Update nota.coffee

Update nota.coffee

Reflect changes to make nota work as an npm library

--- src/nota.coffee
+++ src/nota.coffee
@@ -13,13 +13,13 @@
 class Nota

   # Load the (default) configuration
-  defaults: JSON.parse(fs.readFileSync('config-default.json', 'utf8'))
+  defaults: JSON.parse(fs.readFileSync('conf/config-default.json', 'utf8'))

   # Load the package definition so we have some meta data available such as
   # version number.
-  meta: JSON.parse(fs.readFileSync('package.json', 'utf8'))
-
-  constructor: ( ) ->
+  meta: JSON.parse(fs.readFileSync('conf/package.json', 'utf8'))
+
+  constructor: ( conf ) ->
     NotaHelper.on "warning", @logWarning, @

     nomnom.options
@@ -60,7 +60,7 @@
         help: 'Prevents overwriting when output path is already occupied'

     try
-      @options = @settleOptions nomnom.nom(), @defaults
+      @options = @settleOptions nomnom.nom(conf), @defaults
     catch e
       @logError e
       return


params.json is managed separately

params.json is managed separately

--- cli/Gruntfile.coffee
+++ cli/Gruntfile.coffee
@@ -166,7 +166,11 @@
       dev:
         expand: true
         cwd: 'dev/'
-        src: '**/*'
+        src: [
+          '**/*'
+          # Params are managed separately
+          '!params.json'
+        ]
         dest: 'public/'

       index:


Allow username or email for sign in/up Fixes #89`

--- client/header.coffee
+++ client/header.coffee
@@ -103,3 +103,6 @@

 Deps.autorun ->
   $('.search-input').val(Session.get 'currentSearchQuery')
+
+Accounts.ui.config
+  passwordSignupFields: 'USERNAME_AND_EMAIL'


Got rid of console logs, fixed a name issue.

--- client/import.coffee
+++ client/import.coffee
@@ -315,10 +315,8 @@
 Template.importOverlay.importingFiles = ->
   ImportingFile.documents.find()

-Handlebars.registerHelper 'checkImportDone', (v1, v2, v3, ret) ->
+Handlebars.registerHelper 'checkImportComplete', (v1, v2, v3, ret) ->
   if v1 or v2 or v3
-    console.log v1, v2, v3
-    console.log 'canceled', ret
     return ret
   return


Redirect to library now after importing multiple files.

--- client/import.coffee
+++ client/import.coffee
@@ -313,7 +313,7 @@
     Meteor.Router.toNew Meteor.Router.publicationPath finishedImportingFiles[0].publicationId
   else
     Notify.success "Imported #{ finishedImportingFiles.length } publications."
-    Meteor.Router.toNew Meteor.Router.profilePath Meteor.personId()
+    Meteor.Router.toNew Meteor.Router.libraryPath()

   Session.set 'importOverlayActive', false


really fix admin permissions on /users

really fix admin permissions on /users

--- client/router.coffee
+++ client/router.coffee
@@ -41,7 +41,8 @@
   Router.go 'signin' if not Meteor.user() and not Meteor.loggingIn()

 mustBeAdmin = (pause) ->
-  Router.go 'root' if not Roles.userIsInRole Meteor.userId(), ['admin']
+  Router.go 'root' if not Meteor.loggingIn() and
+    not Roles.userIsInRole Meteor.userId(), ['admin']

 Router.onBeforeAction mustBeSignedIn, except: ['signin']


Fix password reset and email verification bug I introduced in an earlier security fix.

--- client/routes.coffee
+++ client/routes.coffee
@@ -40,6 +40,8 @@

 Router.onBeforeAction mustBeAnAdmin, except: [
   'home',
+  'resetPassword',
+  'verifyEmail',
   'profileDetails',
   'profileEdit',
   'tournaments',


tweaks to notificaiton manager

tweaks to notificaiton manager

--- lib/build.coffee
+++ lib/build.coffee
@@ -16,6 +16,7 @@

     @root = project_paths[0]
     @buildView = new BuildView()
+    @notifications = new atom.NotificationManager

     atom.commands.add 'atom-workspace', 'build:sf-deploy-file', => @deploySingleFile()
     atom.commands.add 'atom-workspace', 'build:sf-deploy', => @deploy()
@@ -198,7 +199,7 @@

   isDeployRunning: ->
     if @child
-      atom.NotificationManager.addError("Hmm... a deployment process is already running.", dismissable: true)
+      @notifications.addError("Hmm... a deployment process is already running.", dismissable: true)
       return false
     else
       return true


Changed project repository

Changed project repository

--- lib/build.coffee
+++ lib/build.coffee
@@ -305,7 +305,7 @@
     }

     utils.runProcess(@child, @buildView, 'git init', args, null, @buildView.buildStarted, (exitcode) =>
-      utils.runProcess(@child, @buildView, 'git remote add origin https://github.com/jonathanrico/forcedotcom-project.git', args, null, null, (exitcode) =>
+      utils.runProcess(@child, @buildView, 'git remote add origin https://github.com/mrveress/forcedotcom-project.git', args, null, null, (exitcode) =>
         utils.runProcess(@child, @buildView, 'git pull origin master', args, null, null, (exitcode) =>
           utils.runProcess(@child, @buildView, 'git init', args, () ->
             utils.deleteFolderRecursive utils.getPlatformPath(newProjectPath + '/.git')


Fix superuser access to nonmember group files

--- lib/files.coffee
+++ lib/files.coffee
@@ -105,7 +105,7 @@
   Meteor.publish 'files', (group) ->
     check group, String
     @autorun ->
-      if memberOfGroup group, findUser @userId
+      if memberOfGroupOrReadable group, findUser @userId
         Files.find
           'metadata._Resumable': $exists: false
           'metadata.group': group


Add QuickTime to video mode (usually H.264)

--- lib/files.coffee
+++ lib/files.coffee
@@ -28,7 +28,7 @@
   file = findFile file unless file.contentType?
   if file.contentType[...6] == 'image/'
     'image'
-  else if file.contentType in ['video/mp4', 'video/ogg', 'video/webm']
+  else if file.contentType in ['video/mp4', 'video/quicktime', 'video/ogg', 'video/webm']
     'video'
   else
     'unknown'


change default

change default

--- lib/fonts.coffee
+++ lib/fonts.coffee
@@ -2,10 +2,9 @@

   config:
     fontFamily:
-      description: 'Use one of the fonts available in this package.
-        View the README for descriptions of each.'
+      description: 'Use one of the fonts available in this package.'
       type: 'string'
-      default: 'Inconsolata'
+      default: 'Source Code Pro'
       enum: [
         'Anka/Coder'
         'Anonymous Pro'


Add optimist to parse arguments and build a media data structure

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,22 +1,48 @@
+argv = require('optimist')
+    .usage('Usage: $0 -c [directory]')
+    .demand('c')
+    .alias('c', 'content')
+    .describe('c', 'Share the content of directory')
+    .argv
+
+dir = argv.c
+
 fs = require 'fs'
-upnp = require 'upnp-server'
 Tag = require('taglib').Tag
+upnp = require 'upnp-device'
+
+
+# If all keys in array are the same, return the value,
+# otherwise return a fallback value (such as 'Various Artists').
+getAlbumKey = (arr, key, fallback) ->
+    unless arr[0]?[key]?
+        return fallback or undefined
+    if arr.every((el) -> el[key] is arr[0][key])
+        return arr[0][key]
+    else
+        return fallback or undefined

 mediaServer = upnp.createDevice 'MediaServer', 'Bragi', (err, device, msg) ->
     throw err if err
     device.start (err) ->
         throw err if err
+        # Read through directory passed as argument.
         fs.readdir dir, (err, files) ->
+            media = type: 'musicalbum', children: []
+            tags = []
             for file in files
                 try
-                    t = new Tag(dir + file)
+                    tags.push new Tag(dir + file)
                 catch err
                     console.log err
-                finally
-                    media.children.push(
-                        title: t.title
-                        location: dir + file
-                    )
+
+            media.creator = getAlbumKey(tags, 'artist', 'Various Artists')
+            media.title = getAlbumKey(tags, 'album', tags[0].album)
+            for tag in tags
+                media.children.push(
+                    title: tag.title
+                    location: dir + file
+                )

             device.addMedia 0, media, (err, id) ->
-                console.log id
+                throw err if err


init 0.4

init 0.4

--- lib/index.coffee
+++ lib/index.coffee
@@ -17,6 +17,7 @@
   next()

 config.set('app', app)
+
 module.exports._app = app
 module.exports._express = express
 module.exports.listen = () -> app.listen.apply(app, arguments)


fix recursion error, component.json properties backward compatibility, constructur arg usage

--- lib/index.coffee
+++ lib/index.coffee
@@ -14,9 +14,10 @@
         unless @style in STYLE
             throw new Error 'style should either be "absolute" or "relative"'

-        @simulate = opt.simulate or false
-        @cwd = process.cwd()
-        debug "cwd is #{@cwd}"
+        @simulate = opt.simulate or true
+        if @rootPath[0] isnt '/'
+            @rootPath = path.join process.cwd(), @rootPath
+        debug "root path: #{@rootPath}"
         @localsMap = {}
         # flatten map like this:
         # localName: {
@@ -32,7 +33,7 @@

         fullPath = path.join @rootPath, @rootManifest
         debug "starting at #{fullPath}"
-        r = require path.join @cwd, fullPath
+        r = require fullPath
         debug "root component.json - ok"
         @lookupPath = r.paths or r.path
         if @lookupPath.length is 0
@@ -42,7 +43,7 @@
             throw new Error 'multiple paths not implemented'
         @lookupPath = @lookupPath[0]

-        @rootLocals =  r.locals or r.locals
+        @rootLocals =  r.locals or r.local

         debug "traversing locals for paths: #{@lookupPath}"
         @traverseLocals @rootLocals
@@ -56,7 +57,7 @@
             for script in scripts
                 debug "file #{script}"
                 componentPath = path.join absPath, script
-                filePath = path.join @cwd, @rootPath, componentPath
+                filePath = path.join @rootPath, componentPath
                 newContent = @rewriteRequire componentPath, filePath

                 if @simulate
@@ -71,17 +72,18 @@
         for local in locals
             continue if @localsMap[local]
             debug "traverse '#{local}'"
+            # TODO: try catch multiple lookup paths
             manifestPath = path.join @lookupPath, local, @localManifest
-            manifest = require path.join @cwd, @rootPath, manifestPath
+            manifest = require path.join @rootPath, manifestPath
             debug "found manifest"
             if manifest.paths?
                 console.log "'#{local}': ignore paths: #{manifest.paths}"
             @localsMap[local] =
                 scripts: manifest.scripts
                 absPath: path.dirname manifestPath
-            debug "locals: #{manifest.locals}"
-            @traverseLocals manifest.locals if manifest.locals?.length > 0
-            return
+            transLocals = manifest.locals or manifest.local or []
+            debug "locals: #{transLocals}"
+            @traverseLocals transLocals if transLocals.length > 0

     rewriteRequire: (scriptPath, fullPath) ->


Add parent type as argument of callback

Add parent type as argument of callback

When passed a callback, the callback will now be called with a third
argument: The type of the parent. This can be relevant as the parent can
be both an object or an array.

--- lib/index.coffee
+++ lib/index.coffee
@@ -27,7 +27,8 @@
       val = haystack[key]

       if isFunction(needle)
-        needle(key, val)
+        parent = if isArray(haystack) then 'array' else 'object'
+        needle(key, val, parent)

       if isObjectOrArray(val)
         find(val, needle, memo)


emit 'change' instead of 'update'

emit 'change' instead of 'update'

--- lib/model.coffee
+++ lib/model.coffee
@@ -32,7 +32,7 @@
     @binding = @binding.map (x) ->
       return nub [].concat(x).filter(Boolean)

-    @emit 'update'
+    @emit 'change'

     return this


Handle exceptions from url.parse

Handle exceptions from url.parse

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -519,7 +519,10 @@
       { id, xml } = create_authn_request @entity_id, @assert_endpoint, identity_provider.sso_login_url, options.force_authn, options.auth_context, options.nameid_format
       zlib.deflateRaw xml, (err, deflated) =>
         return cb err if err?
-        uri = url.parse identity_provider.sso_login_url, true
+        try
+          uri = url.parse identity_provider.sso_login_url, true
+        catch ex
+          return cb ex
         delete uri.search # If you provide search and query search overrides query :/
         if options.sign_get_request
           _(uri.query).extend sign_request(deflated.toString('base64'), @private_key, options.relay_state)
@@ -634,7 +637,10 @@
       {id, xml} = create_logout_request @entity_id, options.name_id, options.session_index, identity_provider.sso_logout_url
       zlib.deflateRaw xml, (err, deflated) =>
         return cb err if err?
-        uri = url.parse identity_provider.sso_logout_url, true
+        try
+          uri = url.parse identity_provider.sso_logout_url, true
+        catch ex
+          return cb ex
         query = null
         if options.sign_get_request
           query = sign_request deflated.toString('base64'), @private_key, options.relay_state
@@ -659,7 +665,10 @@
       xml = create_logout_response @entity_id, options.in_response_to, identity_provider.sso_logout_url
       zlib.deflateRaw xml, (err, deflated) =>
         return cb err if err?
-        uri = url.parse identity_provider.sso_logout_url
+        try
+          uri = url.parse identity_provider.sso_logout_url
+        catch ex
+          return cb ex
         if options.sign_get_request
           uri.query = sign_request deflated.toString('base64'), @private_key, options.relay_state, true
         else