fix a mismerged file

fix a mismerged file

--- client/landing/app/MainApp/sidebar/sidebarview.coffee
+++ client/landing/app/MainApp/sidebar/sidebarview.coffee
@@ -73,6 +73,16 @@
     # @statusLEDs = new StatusLEDView
     @statusLEDs = new KDView
       cssClass : 'status-leds'
+
+    @virtualizationButtons = new VirtualizationControls
+
+  resetAdminNavController:->
+    @utils.wait 1000, =>
+      @adminNavController.removeAllItems()
+      if KD.isLoggedIn()
+        KD.whoami().fetchRole? (err, role)=>
+          if role is "super-admin"
+            @adminNavController.instantiateListItems adminNavItems.items

   setListeners:->

@@ -304,6 +314,14 @@
       { title : "Keyboard Shortcuts", icon : "shortcuts", action: "showShortcuts" }
     ]

+  adminNavItems =
+    id    : "admin-navigation"
+    title : "admin-navigation"
+    items : [
+      # { title : "Kite selector", loggedIn : yes, callback : -> new KiteSelectorModal }
+      { title : "Admin Panel",     loggedIn : yes, callback : -> new AdminModal }
+    ]
+
   footerMenuItems =
     id    : "footer-menu"
     title : "footer-menu"
@@ -315,4 +333,4 @@
         # unless location.hostname is "localhost"
         new KDNotificationView title : "Coming soon..."
       }
-    ]
+    ]


i guess singular width does make more sense

--- src/components/task/breadcrumbs.cjsx
+++ src/components/task/breadcrumbs.cjsx
@@ -67,7 +67,7 @@
     TaskStore.off('task.afterRecovery', @update)

   componentDidUpdate: (prevProps, prevState) ->
-    if @didWidthsChange(prevState, @state)
+    if @didWidthChange(prevState, @state)
       @setShouldShrink(@state)

   componentWillReceiveProps: (nextProps) ->
@@ -76,7 +76,7 @@
   crumbMounted: ->
     @calculateCrumbsWidth() if @state.crumbsWidth?

-  didWidthsChange: (prevState, currentState) ->
+  didWidthChange: (prevState, currentState) ->
     currentState.crumbsWidth isnt prevState.crumbsWidth or currentState.componentEl.width isnt prevState.componentEl.width

   setShouldShrink: (sizes) ->


DomainCreation: Disable buying domains

DomainCreation: Disable buying domains

--- client/Environments/views/domains/domaincreateform.coffee
+++ client/Environments/views/domains/domaincreateform.coffee
@@ -5,7 +5,7 @@

   domainOptions = [
     { title : "Create a subdomain",     value : "subdomain" }
-    { title : "Register a new domain",    value : "new"}
+    { title : "Register a new domain",  value : "new", disabled: yes}
     { title : "Use an existing domain", value : "existing", disabled : yes }
   ]


activity: instantiate blank `KDView` instance as settings menu button

--- client/Social/Activity/views/comments/listitemview.coffee
+++ client/Social/Activity/views/comments/listitemview.coffee
@@ -25,7 +25,7 @@
       then @getSettingsButton edit: yes, delete: yes
       else if isOwner or canDeleteOwn
       then @getSettingsButton delete: yes
-      else KDView
+      else new KDView

   getSettingsButton: (options) ->


activity: fix comment menu wrapper reference

--- client/Social/Activity/views/comments/listitemview.coffee
+++ client/Social/Activity/views/comments/listitemview.coffee
@@ -16,7 +16,7 @@

   showEditForm: ->

-    @settings.hide()
+    @menuWrapper.hide()
     @body.hide()
     @editInfo.hide()
     @likeView.hide()
@@ -35,7 +35,7 @@

     {meta: {createdAt, updatedAt}} = @getData()

-    @settings.show()
+    @menuWrapper.show()
     @likeView.show()
     @replyView?.show()
     @form.destroy()


Wrote own appendHtml method, because the compositeView coulnd't find the itemViewContainer (because template wasn't loaded yet?)

--- core/app/backbone/views/channels/editable_channels.coffee
+++ core/app/backbone/views/channels/editable_channels.coffee
@@ -10,3 +10,7 @@

   showEmptyView: => @$el.hide()
   closeEmptyView: => @$el.show()
+
+  appendHtml: (collectionView, itemView, index) ->
+    collectionView.$(@itemViewContainer).append(itemView.el);
+


Remove loadRules from commandline. It was unused since RuleLoader was created.

--- src/commandline.coffee
+++ src/commandline.coffee
@@ -104,41 +104,6 @@
 getFallbackConfig = (filename = null) ->
     unless options.argv.noconfig
         configfinder.getConfig(filename)
-
-# moduleName is a NodeJS module, or a path to a module NodeJS can load.
-loadRules = (moduleName, ruleName = undefined) ->
-    try
-        try
-            # Try to find the project-level rule first.
-            rulePath = resolve moduleName, {
-                basedir: process.cwd()
-            }
-            ruleModule = require rulePath
-        try
-            # This seems awkward, but the ?= will prevent it from trying to
-            # require if the previous step succeeded without an exception.
-            #
-            # Globally installed rule
-            ruleModule ?= require moduleName
-
-        # Maybe the user used a relative path from the command line. This
-        # doesn't make much sense from a config file, but seems natural
-        # with the --rules option.
-        #
-        # No try around this one, an exception here should abort the rest of
-        # this function.
-        ruleModule ?= require path.resolve(process.cwd(), moduleName)
-
-        # Most rules can export as a single constructor function
-        if typeof ruleModule is 'function'
-            coffeelint.registerRule ruleModule, ruleName
-        else
-            # Or it can export an array of rules to load.
-            for rule in ruleModule
-                coffeelint.registerRule rule
-    catch e
-        console.error "Error loading #{moduleName}"
-        throw e

 # These reporters are usually parsed by other software, so I can't just echo a
 # warning.  Creating a fake file is my best attempt.


fix: do not hide the translation window

fix: do not hide the translation window

--- app/assets/javascripts/libraries/jquery-initializer.coffee
+++ app/assets/javascripts/libraries/jquery-initializer.coffee
@@ -51,6 +51,7 @@
           #$('#fancybox_loader_container').hide()
           $.fancybox.wrap.bind 'onReset', (e) ->
             $('body > .main-container:last').remove()
+          $('.fancybox-overlay').height($(document).height())
         beforeClose: ->
           $form = $('.fancybox-wrap form:first')
           if serialized_data = $form.data('serialized')
@@ -92,7 +93,6 @@
           else
             show_fancybox(node)
             $(node).initialize()
-            $('.fancybox-overlay').height($(document).height())
       else if last_xhr.getResponseHeader('x-logged')? && $('body').data('logged')? && $('body').data('logged').toString() != last_xhr.getResponseHeader('x-logged').toString()
         $('#fancybox_loader_container').show()
         window.location.href = window.location.pathname


Authorize manuscript_manager during route transition

--- app/assets/javascripts/routes/paper_manage_route.js.coffee
+++ app/assets/javascripts/routes/paper_manage_route.js.coffee
@@ -1,4 +1,13 @@
-ETahi.PaperManageRoute = Ember.Route.extend
+ETahi.PaperManageRoute = ETahi.AuthorizedRoute.extend
+  afterModel: (paper, transition) ->
+    # Ping manuscript_manager url for authorization
+    promise = new Ember.RSVP.Promise (resolve, reject) ->
+      Ember.$.ajax
+        method:  'GET'
+        url:     "/papers/#{paper.get('id')}/manuscript_manager"
+        success: (json) -> Ember.run(null, resolve, json)
+        error:   (xhr, status, error) -> Ember.run(null, reject, xhr)
+
   actions:
     viewCard: (task) ->
       paper = @modelFor('paper')


Italic can be from first line.

Italic can be from first line.

--- app/assets/javascripts/views/clouds/chat/message.js.coffee
+++ app/assets/javascripts/views/clouds/chat/message.js.coffee
@@ -64,7 +64,7 @@
     content = content.replace(/(\[REDACTED\])/ig,"[REDACTED]")

     # Italic
-    content = content.replace(/\s\/([^\/]+)\/\s/ig,"$1")
+    content = content.replace(/(\A|\s)\/([^\/]+)\/(\z|\s)/ig,"$2")

     elem = $("

")


Fix playlist sorting

Fix playlist sorting

--- app/assets/javascripts/add-playlist-option.js.coffee
+++ app/assets/javascripts/add-playlist-option.js.coffee
@@ -17,7 +17,9 @@

 sortWithNew = (data) ->
   return data.sort((a, b) ->
-    if (a.text < b.text || a.text == addnew)
+    if (b.text.trim() == addnew)
+      return 1
+    if (a.text < b.text || a.text.trim() == addnew)
       return -1
     if (a.text > b.text)
       return 1


Implemented issue #953 - More precise filesize.

--- app/assets/javascripts/app/lib/app_post/utils.coffee
+++ app/assets/javascripts/app/lib/app_post/utils.coffee
@@ -635,7 +635,7 @@
   # human readable file size
   @humanFileSize: (size) ->
     if size > ( 1024 * 1024 )
-      size = Math.round( size / ( 1024 * 1024 ) ) + ' MB'
+      size = (Math.round( size * 10 / ( 1024 * 1024 ) ) / 10 ) + ' MB'
     else if size > 1024
       size = Math.round( size / 1024 ) + ' KB'
     else


fetch subjects mixin

fetch subjects mixin

--- app/assets/javascripts/lib/fetch-subjects-mixin.cjsx
+++ app/assets/javascripts/lib/fetch-subjects-mixin.cjsx
@@ -3,7 +3,6 @@

 module.exports =
   componentDidMount: ->
-    alert("here")

     if @props.params.subject_set_id
       @fetchSubject @props.params.subject_set_id,@props.workflow.id


Organized Data class into MVC.

Organized Data class into MVC.

--- app/static/coffee/gbrowse/Hello.coffee
+++ app/static/coffee/gbrowse/Hello.coffee
@@ -1,5 +1,5 @@
 class App
-    model : () =>
+    model: () =>
         @data ?= new Data()
         @table ?= new Table(@data)
         return
@@ -16,13 +16,11 @@
         ]

 class Data
-    #model
-    resp = {}
-    meta=(response)->
-        resp.headers = response.head.vars
-        resp.genomes = response.results.bindings
-        return
-    constructor: () ->
+    model: () =>
+        meta=(response)=>
+            @headers = response.head.vars
+            @genomes = response.results.bindings
+            return
         m.request(
             method: "POST",
             url: 'http://10.139.14.121:5000/mithril/meta',
@@ -30,11 +28,14 @@
             datatype: 'json'
             type: meta)
     #controller
-    response: () ->
-        return resp
+    constructor: () ->
+        @model()
     #view
-    view: () ->
-        return JSON.stringify(resp)
+    response: () =>
+        return {
+            headers: @headers
+            genomes: @genomes
+        }

 class Table
     ###


altering log stack saving

altering log stack saving

--- server/api/lib/log.coffee
+++ server/api/lib/log.coffee
@@ -1,5 +1,5 @@

-
+import { Random } from 'meteor/random'
 import moment from 'moment'

 _log_today = moment(Date.now(), "x").format "YYYYMMDD"
@@ -82,11 +82,13 @@
       if API.settings.log?.bulk isnt 0 and API.settings.log?.bulk isnt false
         API.settings.log.bulk = 5000 if API.settings.log.bulk is undefined
         API.settings.log.timeout ?= 300000
+        opts._id = Random.id()
         _log_stack.unshift opts
         if _log_stack.length >= API.settings.log.bulk or Date.now() - _log_last > API.settings.log.timeout
-          logged = _log_index.import _log_stack
+          ls = JSON.parse(JSON.stringify(_log_stack))
           _log_stack = []
           _log_last = Date.now()
+          Meteor.setTimeout (() -> _log_index.import ls), 1
       else
         _log_index.insert opts


Fix coverage

Fix coverage

--- lib/gocov.coffee
+++ lib/gocov.coffee
@@ -1,5 +1,6 @@
 {spawn} = require 'child_process'
 temp = require 'temp'
+path = require 'path'
 fs = require 'fs-plus'
 {Subscriber, Emitter} = require 'emissary'
 GocovAreaView = require './gocov/gocov-area-view'


feature: able to provide local require method

--- lib/index.coffee
+++ lib/index.coffee
@@ -12,13 +12,18 @@

 class Symfio extends kantaina.Container
+  _require = require
+
   constructor: (@name, @applicationDirectory) ->
     super()

   require: (name, module = name) ->
-    @unless name, (logger) ->
-      logger.debug "require module", name: module
-      require module
+    if typeof name is "function"
+      _require = name
+    else
+      @unless name, (logger) ->
+        logger.debug "require module", name: module
+        _require module

   injectAll: (plugins) ->
     w.map plugins, @inject.bind @


switch from async.waterfall to chain-builder

--- lib/index.coffee
+++ lib/index.coffee
@@ -1,5 +1,6 @@
 async    = require 'async'
 buildSearch = require 'stream-search-helper'
+buildChain  = require 'chain-builder'

 class Kevas extends (require 'stream').Transform
   constructor: (options) ->
@@ -11,11 +12,10 @@
       recurse:true # get all results at once from chunk
       groups:2 # two regex groups we want
     @_key = ''
-    starter = (next) -> next undefined, key:starter.key, values:[]
-    @_keyListeners = [starter]
+
     if options?.values?
       values = options.values
-      @on 'key', (context) ->
+      @on 'key', (control, context) ->
         context.values.push values[context.key] if values[context.key]?

   _appendKey: (string) -> @_key += string  # accumulate key value
@@ -84,25 +84,31 @@

   on: (event, listener) ->
     if event is 'key'
-      if listener.length < 2 # then there's no callback arg. so, we want to call it for them
-        listener = do(fn = listener) -> (context, next) ->
-          try
-            fn context
-            next undefined, context
-          catch error
-            next error
-      @_keyListeners ?= []
-      @_keyListeners.push listener
+      unless @_chain?
+        @_chain = buildChain array:[listener]
+      else
+        @_chain.add listener
     else super event, listener

+  once: (event, listener) ->
+    if event is 'key'
+      fn = (next, context) =>
+        result = listener next, context
+        @_chain.remove fn
+        return result
+      @_chain.add fn
+    else super event, listener # there are no other events we use...
+
+  off: (event, listener) ->
+    if event is 'key' then @_chain.remove listener
+    else super event, listener # there are no other events we use...
+
   _emitKey: (key, done) ->
-    # TODO: slice() array to make a copy?
-    @_keyListeners[0].key = key
-    async.waterfall @_keyListeners, (error, result) =>
-      if error? then done error
-      else
-        @push value for value in result.values
-        process.nextTick done
+    context = key:key, values:[]
+    @_chain.run context:context, done:(error) =>
+      if error? then return done error
+      @push value for value in context.values
+      process.nextTick done

 # Use these ways:
 #  1. buildKevas = require 'kvstream'


Made extra db reindex happen serially

Made extra db reindex happen serially

--- lib/index.coffee
+++ lib/index.coffee
@@ -156,17 +156,18 @@
                 #console.log 'retry'
                 return retry()

-              # And SOLR or whatever. Not entirely sure of the timing here.
-              for name, db of extraDbs
-                db.submit? cName, docName, opData, snapshot, @publish, (err) ->
-                  console.warn "Error updating db #{db.name} #{cName}.#{docName} with new snapshot data: ", err if err
-
               # Call callback with op submit version
               return callback? null, opData.v, transformedOps if snapshotDb.closed # Happens in the tests sometimes. Its ok.

               # Update the snapshot for queries
               snapshotDb.setSnapshot cName, docName, snapshot, (err) ->
                 return callback? err if err
+
+                # And SOLR or whatever. Not entirely sure of the timing here.
+                for name, db of extraDbs
+                  db.submit? cName, docName, opData, snapshot, @publish, (err) ->
+                    console.warn "Error updating db #{db.name} #{cName}.#{docName} with new snapshot data: ", err if err
+
                 opData.docName = docName
                 redis.publish prefixChannel(cName), JSON.stringify opData
                 callback? null, opData.v, transformedOps


again cleanup

again cleanup

--- lib/input.coffee
+++ lib/input.coffee
@@ -12,7 +12,7 @@
     @editorElement.classList.add('narrow-input')
     @container.appendChild(@editorElement)

-  destroy: =>
+  destroy: ->
     return if @destroyed
     @destroyed = true
     @editor.destroy()
@@ -29,12 +29,12 @@
     @disposables.add atom.workspace.onDidChangeActivePaneItem(@destroy)

     # Cancel on mouse click
-    @clientEditorElement = atom.workspace.getActiveTextEditor().element
-    @clientEditorElement.addEventListener('click', @destroy)
-    @disposables.add new Disposable => @clientEditorElement.removeEventListener('click', @destroy)
-    @focus()
+    destroy = @destroy.bind(this)
+    clientEditorElement = atom.workspace.getActiveTextEditor().element
+    clientEditorElement.addEventListener('click', destroy)
+    @disposables.add new Disposable ->
+      clientEditorElement.removeEventListener('click', destroy)

-  focus: ->
     @editorElement.focus()
     new Promise (@resolve) =>


Formatting: Fixing tabs.

Formatting: Fixing tabs.

--- lib/jumpy.coffee
+++ lib/jumpy.coffee
@@ -1,14 +1,14 @@
 JumpyView = require './jumpy-view'

 module.exports =
-  jumpyView: null
+    jumpyView: null

-  activate: (state) ->
-    @jumpyView = new JumpyView(state.jumpyViewState)
-    atom.workspaceView.statusBar?.prependLeft("

")
+    activate: (state) ->
+        @jumpyView = new JumpyView(state.jumpyViewState)
+        atom.workspaceView.statusBar?.prependLeft("

")

-  deactivate: ->
-    @jumpyView.destroy()
+    deactivate: ->
+        @jumpyView.destroy()

-  serialize: ->
-    jumpyViewState: @jumpyView.serialize()
+    serialize: ->
+        jumpyViewState: @jumpyView.serialize()


Fix typo

Fix typo

--- lib/latex.coffee
+++ lib/latex.coffee
@@ -26,7 +26,7 @@
     else if @logger? and @log?
       @log.warning '''
         No PDF opener found.
-        For cross-platform viewing, consider install the pdf-view package.
+        For cross-platform viewing, consider installing the pdf-view package.
         '''

   createLogProxy: ->


System

System

--- src/core/engine.coffee
+++ src/core/engine.coffee
@@ -1,6 +1,7 @@
 type = require '../utils/type'
 debug = require '../debug/debug'
 config = require '../config/config'
+extend = require '../utils/extend'

 register = require './register'

@@ -28,6 +29,8 @@

         @_entities = []
         @_systems = new OrderedLinkedList()
+
+        @_singletonSystemsPresentInEngine = {}

         @_searchingBitSet = new BitSet config 'max_components_count'
         @_excludingBitSet = new BitSet config 'max_components_count'
@@ -88,8 +91,6 @@
     addComponentToPool: (name, component) ->
         @_componetsPool.push name, component

-
-
     create: (name, args...) ->
         args.unshift @game

@@ -101,74 +102,6 @@
         @_addEntityToEngine entity

         return @
-
-    _addEntityToFamilies: (entity) ->
-        families = entity.getPattern()._families
-
-        for family in families
-            @_families[family] ?= new DoublyLinkedList()
-            @_families[family].append entity
-
-        return @
-
-    _addEntityToEngine: (entity) ->
-        @_entities[entity.getId()] = entity
-        @_entitiesCount++
-
-        return @
-
-    remove: (entity, args...) ->
-        id = entity.getId()
-        if not @_entities[id]?
-            return @
-
-        args.unshift @game
-
-        pattern = entity.getPattern()
-
-        pattern.remove and pattern.remove.apply entity, args
-
-        @_removeEntityFromFamilies entity
-        entity.removeAllComponents()
-
-        @_entitiesPool.push entity.name, entity
-
-        delete @_entities[id]
-
-        @_entityIdsToReuse.push id
-        @_entitiesCount--
-
-        return @
-
-    removeAllEntities: ->
-        if not @_updating
-            @remove entity for entity in @_entities
-        else
-            debug.warning 'cannot remove entities during engine update'
-
-        return @
-
-    markForRemoval: (entity) ->
-        @_entitiesToRemove.push entity
-
-        return @
-
-    _removeMarkedEntities: ->
-        @remove entity for entity in @_entitiesToRemove
-        @_entitiesToRemove.length = 0
-
-        return @
-
-    _removeEntityFromFamilies: (entity) ->
-        families = entity.getPattern()._families
-
-        for family in families
-            @_families[family]?.remove entity
-
-        return @
-
-    getEntity: (id) ->
-        return @_entities[id] ? null

     _getNewEntity: (name) ->
         if @_entitiesPool.has name
@@ -179,4 +112,107 @@

         return entity

+    _addEntityToFamilies: (entity) ->
+        families = entity.getPattern()._families
+
+        for family in families
+            @_families[family] ?= new DoublyLinkedList()
+            @_families[family].append entity
+
+        return @
+
+    _addEntityToEngine: (entity) ->
+        @_entities[entity.getId()] = entity
+        @_entitiesCount++
+
+        return @
+
+    remove: (entity, args...) ->
+        id = entity.getId()
+        if not @_entities[id]?
+            return @
+
+        args.unshift @game
+
+        pattern = entity.getPattern()
+
+        pattern.remove and pattern.remove.apply entity, args
+
+        @_removeEntityFromFamilies entity
+        entity.removeAllComponents()
+
+        @_entitiesPool.push entity.name, entity
+
+        delete @_entities[id]
+
+        @_entityIdsToReuse.push id
+        @_entitiesCount--
+
+        return @
+
+    removeAllEntities: ->
+        if not @_updating
+            @remove entity for entity in @_entities
+        else
+            debug.warning 'cannot remove entities during engine update'
+
+        return @
+
+    markForRemoval: (entity) ->
+        @_entitiesToRemove.push entity
+
+        return @
+
+    _removeMarkedEntities: ->
+        @remove entity for entity in @_entitiesToRemove
+        @_entitiesToRemove.length = 0
+
+        return @
+
+    _removeEntityFromFamilies: (entity) ->
+        families = entity.getPattern()._families
+
+        for family in families
+            @_families[family]?.remove entity
+
+        return @
+
+    getEntity: (id) ->
+        return @_entities[id] ? null
+
+    getFamily: (name) ->
+        return (@_families[name] ? @_BLANK_FAMILY).reset()
+
+    addSystem: (name, priority, args...) ->
+        if name of @_singletonSystemsPresentInEngine
+            debug.warning 'system % is defined as singleton and is already present in engine'
+
+            return @
+
+        pattern = register.getSystemPattern name
+
+        system =
+            game: @game
+            engine: @
+            priority: priority
+
+        extend system, pattern
+
+        system.initialize and system.initialize.apply system, args
+
+        @_systems.insert system, priority
+
+        if system.singleton
+            @_singletonSystemsPresentInEngine[name] = true
+
+        return @
+
+    addSystems: (args...) ->
+        @addSystem arg... for arg in args
+        return @
+
+    removeSystem: (system, args) ->
+        if not @_updating
+
+
 module.exports = Engine


(- client fix) Prepare groundwork for Client-compatible debugging output

There's no meat here; just splitting things out so the client doesn't spit a nut at undefined crap.

--- Paws/Source/additional.coffee
+++ Paws/Source/additional.coffee
@@ -3,127 +3,133 @@
 # exports-object. It's called as the first action in `Paws.coffee`.
 util = require 'util'

+class Debugging
+
+class CommandLineDebugging extends Debugging
+   use_colour = true # Default
+   verbosity = 4 # Default
+   max_verbosity = Infinity
+
+   constructor: ->
+      # FIXME: This `require` will break browserify.
+      @tput = new (require('blessed').Tput) term: process.env['TERM']
+
+      # Patching Tput's column measurement
+      if not @tput?.columns? or @tput.columns == 80
+         @tput.columns = process.stdout.columns
+      process.stdout.on 'resize', => @tput.columns = process.stdout.columns
+
+      @tput.sgr = (flags...)-> @csi flags.join(';') + 'm'
+      @tput.csi = (text)->  '\x1b[' + text
+
+      @tput.fg = (colour, text)-> if use_colour then switch
+         when colour < 10 then @sgr(30+colour) + text + @sgr(39)
+         when colour < 20 then @sgr(80+colour) + text + @sgr(39)
+         else                  @xfg colour, text
+      else text
+
+      @tput.bg = (colour, text)-> if use_colour then switch
+         when colour < 10 then @sgr(40+colour) + text + @sgr(49)
+         when colour < 20 then @sgr(90+colour) + text + @sgr(49)
+         else                  @xbg colour, text
+      else text
+
+      @tput.xfg = (colour, text)->
+         if use_colour then @sgr(38,5,colour) + text + @sgr(39) else text
+      @tput.xbg = (colour, text)->
+         if use_colour then @sgr(48,5,colour) + text + @sgr(49) else text
+
+      @tput.bold      = (text)-> if use_colour then @sgr(1) + text + @sgr(22) else text
+      @tput.underline = (text)-> if use_colour then @sgr(4) + text + @sgr(24) else text
+      @tput.invert    = (text)-> if use_colour then @sgr(7) + text + @sgr(27) else text
+   # This is an exposed, bi-directional mapping of verbosity-names:
+   #
+   #     Paws.verbosities[4] === Paws.verbosities['warning']
+   verbosities: verbosities =
+      #    0       1      2       3      4      5     6     7      8     9
+      "emergency alert critical error warning notice info debug verbose wtf".split(' ')
+   verbosities[name] = minimum for name, minimum in verbosities
+
+
+   write_browser = (console?.error || console?.log || noop).bind console
+   write_cli = (objects...)->
+      output = util.format.apply(util, arguments) + '\n'
+      process.stderr.write output, 'utf8'
+
+   # FIXME: Temporary. I'd really like this to be external, and more robust.
+   write: write = if window? or process?.browser?
+      write_browser
+   else
+      write_cli
+
+   debug: -> null # NYI
+
+   inject: (exports)->
+      exports.verbosity  = -> verbosity
+      exports.is_silent  = -> verbosity == 0
+
+      exports.use_colour = -> use_colour
+
+      for name, v in verbosities
+         exports[name] = do (name, v)->-> if v <= verbosity
+
+            if verbosity > 9 or process.env.DEBUG_VERBOSITY
+               write "-- Verbosity of #{v}/#{verbosities[v]} ≤ "+
+                   "#{verbosity}/#{verbosities[verbosity] ? '???'}; printing message:"
+
+            write.apply debugging, arguments
+
+      variables = silent: 0, quiet: 2, verbose: 8, WTF: 9
+      # We configure the `verbosity` itself in one of two ways: by calling an internal API at
+      # runtime; or by setting an environment-variable before entry to the Paws library. (The
+      # latter cannot affect the verbosity after the execution of this file.)
+      #
+      #     Paws.VERBOSE(9) // Very verbose output
+      #     // (or `VERBOSE=9 ./program.paws`)
+      #
+      #     Paws.QUIET()    // Silence errors
+      #     // (or `QUIET=true ./program.paws`)
+      #
+      # NOTE: Quieter-overrides-louder, in environment variables. For instance, if `SILENT` is
+      #       defined, that overrides `VERBOSE`. To boot, the verbosity cannot be set *higher*
+      #       via the API, if it has already been set by an environment variable (meaning
+      #       `SILENT` will always mean `SILENT`.)
+      # ----
+      # TODO: Verify that this is compatible all the way back to IE6. I'm a bit iffy about the
+      #       isFinite() shit.
+      for own name, ddefault of variables
+         name = name.toUpperCase()
+         exports[name] = do (name, ddefault)-> (level = true, opts = {environmental: no})->
+            if isFinite (l = parseInt level, 10)
+               verbosity = l
+            else if level == true or
+                    level.charAt?(0) == 'y' or # yes
+                    level.charAt?(0) == 't'    # true
+               verbosity = ddefault unless max_verbosity < ddefault # Silence reigns.
+
+            max_verbosity = verbosity if opts.environmental
+
+            exports.wtf "-- Verbosity set to: #{verbosity}/#{verbosities[verbosity] ? '???'}"
+
+         if process.env[name]? and (max_verbosity == Infinity)
+            exports[name](process.env[name], environmental: yes)
+
+      exports.colour = exports.color = (use = true)->
+         if use == no or
+            use.charAt?(0) == 'n' or # no
+            use.charAt?(0) == 'f'    # false
+          use_colour = no
+         else
+          use_colour = yes
+
+         exports.wtf "-- Colour set to: #{use_colour}"
+
+      if env_colour = process.env['COLOUR'] ? process.env['COLOR']
+         exports.colour env_colour
+
+class BrowserDebugging extends Debugging
+   inject: -> #noop
+
 module.exports = additional =

-   debugging: debugging = new class Debugging
-      use_colour = true # Default
-      verbosity = 4 # Default
-      max_verbosity = Infinity
-
-      constructor: ->
-         # FIXME: This `require` will break browserify.
-         @tput = new (require('blessed').Tput) term: process.env['TERM']
-
-         # Patching Tput's column measurement
-         if @tput.columns == 80
-            @tput.columns = process.stdout.columns
-         process.stdout.on 'resize', => @tput.columns = process.stdout.columns
-
-         @tput.sgr = (flags...)-> @csi flags.join(';') + 'm'
-         @tput.csi = (text)->  '\x1b[' + text
-
-         @tput.fg = (colour, text)-> if use_colour then switch
-            when colour < 10 then @sgr(30+colour) + text + @sgr(39)
-            when colour < 20 then @sgr(80+colour) + text + @sgr(39)
-            else                  @xfg colour, text
-         else text
-
-         @tput.bg = (colour, text)-> if use_colour then switch
-            when colour < 10 then @sgr(40+colour) + text + @sgr(49)
-            when colour < 20 then @sgr(90+colour) + text + @sgr(49)
-            else                  @xbg colour, text
-         else text
-
-         @tput.xfg = (colour, text)->
-            if use_colour then @sgr(38,5,colour) + text + @sgr(39) else text
-         @tput.xbg = (colour, text)->
-            if use_colour then @sgr(48,5,colour) + text + @sgr(49) else text
-
-         @tput.bold      = (text)-> if use_colour then @sgr(1) + text + @sgr(22) else text
-         @tput.underline = (text)-> if use_colour then @sgr(4) + text + @sgr(24) else text
-         @tput.invert    = (text)-> if use_colour then @sgr(7) + text + @sgr(27) else text
-
-      # This is an exposed, bi-directional mapping of verbosity-names:
-      #
-      #     Paws.verbosities[4] === Paws.verbosities['warning']
-      verbosities: verbosities =
-         #    0       1      2       3      4      5     6     7      8     9
-         "emergency alert critical error warning notice info debug verbose wtf".split(' ')
-      verbosities[name] = minimum for name, minimum in verbosities
-
-
-      write_browser = (console?.error || console?.log || noop).bind console
-      write_cli = (objects...)->
-         output = util.format.apply(util, arguments) + '\n'
-         process.stderr.write output, 'utf8'
-
-      # FIXME: Temporary. I'd really like this to be external, and more robust.
-      write: write = if window? or process?.browser?
-         write_browser
-      else
-         write_cli
-
-      debug: -> null # NYI
-
-      inject: (exports)->
-         exports.verbosity  = -> verbosity
-         exports.is_silent  = -> verbosity == 0
-
-         exports.use_colour = -> use_colour
-
-         for name, v in verbosities
-            exports[name] = do (name, v)->-> if v <= verbosity
-
-               if verbosity > 9 or process.env.DEBUG_VERBOSITY
-                  write "-- Verbosity of #{v}/#{verbosities[v]} ≤ "+
-                      "#{verbosity}/#{verbosities[verbosity] ? '???'}; printing message:"
-
-               write.apply debugging, arguments
-
-         variables = silent: 0, quiet: 2, verbose: 8, WTF: 9
-         # We configure the `verbosity` itself in one of two ways: by calling an internal API at
-         # runtime; or by setting an environment-variable before entry to the Paws library. (The
-         # latter cannot affect the verbosity after the execution of this file.)
-         #
-         #     Paws.VERBOSE(9) // Very verbose output
-         #     // (or `VERBOSE=9 ./program.paws`)
-         #
-         #     Paws.QUIET()    // Silence errors
-         #     // (or `QUIET=true ./program.paws`)
-         #
-         # NOTE: Quieter-overrides-louder, in environment variables. For instance, if `SILENT` is
-         #       defined, that overrides `VERBOSE`. To boot, the verbosity cannot be set *higher*
-         #       via the API, if it has already been set by an environment variable (meaning
-         #       `SILENT` will always mean `SILENT`.)
-         # ----
-         # TODO: Verify that this is compatible all the way back to IE6. I'm a bit iffy about the
-         #       isFinite() shit.
-         for own name, ddefault of variables
-            name = name.toUpperCase()
-            exports[name] = do (name, ddefault)-> (level = true, opts = {environmental: no})->
-               if isFinite (l = parseInt level, 10)
-                  verbosity = l
-               else if level == true or
-                       level.charAt?(0) == 'y' or # yes
-                       level.charAt?(0) == 't'    # true
-                  verbosity = ddefault unless max_verbosity < ddefault # Silence reigns.
-
-               max_verbosity = verbosity if opts.environmental
-
-               exports.wtf "-- Verbosity set to: #{verbosity}/#{verbosities[verbosity] ? '???'}"
-
-            if process.env[name]? and (max_verbosity == Infinity)
-               exports[name](process.env[name], environmental: yes)
-
-         exports.colour = exports.color = (use = true)->
-            if use == no or
-               use.charAt?(0) == 'n' or # no
-               use.charAt?(0) == 'f'    # false
-             use_colour = no
-            else
-             use_colour = yes
-
-            exports.wtf "-- Colour set to: #{use_colour}"
-
-         if env_colour = process.env['COLOUR'] ? process.env['COLOR']
-            exports.colour env_colour
+   debugging: new (if process?.browser then BrowserDebugging else CommandLineDebugging)


Remove reference to I18n

Remove reference to I18n

--- assets/scripts/app/models/build.coffee
+++ assets/scripts/app/models/build.coffee
@@ -56,7 +56,7 @@

   configKeys: (->
     keys = @get('rawConfigKeys')
-    headers = (I18n.t(key) for key in ['build.job', 'build.duration', 'build.finished_at'])
+    headers = ['Job', 'Duration', 'Finished']
     $.map(headers.concat(keys), (key) -> if Travis.CONFIG_KEYS_MAP.hasOwnProperty(key) then Travis.CONFIG_KEYS_MAP[key] else key)
   ).property('rawConfigKeys.length')


debug

debug

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -45,8 +45,8 @@
             select: (event, ui) =>
                 if (@_userInteractionEnabled == false)
                     return
-                JSLog(ui.item[0])
-                item = ui.item.context.textContent
+                obj = ui.item
+                JSLog(obj.value())
                 @selectMenuItem(item)
                 @closeMenu()
             classes:


debug

debug

--- JSKit/04_JSMenuView.coffee
+++ JSKit/04_JSMenuView.coffee
@@ -51,6 +51,9 @@
                 @closeMenu()
             classes:
                 "ui-menu": "highlight"
+            position:
+                my: "left top"
+                at: "right-5 top+5"

     selectMenuItem:(item)->
         if (@_action?)


MachineListItem: add Softlayer as a vm provider

--- client/app/lib/environment/machineslistitem.coffee
+++ client/app/lib/environment/machineslistitem.coffee
@@ -143,7 +143,7 @@
       #{unless isKoding() then '<div>{{> @sidebarToggle}}</div>' else ''}
     """

-  PROVIDERS      =
+  PROVIDERS         =
     Azure           : url: 'https://azure.microsoft.com/en-us'
     HPCloud         : url: 'http://www.hpcloud.com'
     Joyent          : url: 'https://www.joyent.com/'
@@ -157,3 +157,4 @@
     # handle `jMachine.provider` field.
     aws             : url: 'http://aws.amazon.com'
     koding          : url: 'http://aws.amazon.com', logo: 'aws'
+    softlayer       : url: 'http://softlayer.com',  logo: 'softlayer'


disable specs to test travis phantomjs

disable specs to test travis phantomjs

It crashes on something in there, but works locally

--- exercises/test/all-tests.coffee
+++ exercises/test/all-tests.coffee
@@ -1,6 +1,6 @@
 require './components/app.spec'
-require './components/exercise/controls.spec'
-# require './components/exercise.spec'
+# require './components/exercise/controls.spec'
+require './components/exercise.spec'

 # testsContext = require.context("./", true, /\.spec\.(cjsx|coffee)$/)
 # testsContext.keys().forEach(testsContext)


video: first fix then show participant

video: first fix then show participant

--- client/app/lib/videocollaboration/viewmodel.coffee
+++ client/app/lib/videocollaboration/viewmodel.coffee
@@ -66,9 +66,8 @@
 ###
 showParticipant = (participant) ->

+  fixParticipantVideo participant
   participant.videoData.element.style.display = 'block'
-
-  fixParticipantVideo participant

 ###*


Also ensure editor is *visible* before recalculating dimensions

--- src/editor-view.coffee
+++ src/editor-view.coffee
@@ -885,7 +885,7 @@
     @setHeightInLines()

   recalculateDimensions: ->
-    return unless @attached
+    return unless @attached and @isVisible()

     oldCharWidth = @charWidth
     oldLineHeight = @lineHeight


Remove stray log

Remove stray log

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -147,7 +147,6 @@
   # We want the user to be able to log-in to force via the microgravity subdomain
   # the initial use case being the professional buyer application
   # (this is specific to responsive pages that require log-in)
-  console.log('[APP_URL, MOBILE_URL]', [APP_URL, MOBILE_URL])
   app.use cors origin: [APP_URL, MOBILE_URL, /\.artsy\.net$/]

   app.use artsyPassport _.extend config,


Added a few more tests for feedback models

--- cms/static/coffee/spec/models/feedback_spec.coffee
+++ cms/static/coffee/spec/models/feedback_spec.coffee
@@ -1,5 +1,46 @@
 describe "CMS.Models.SystemFeedback", ->
-  m = new CMS.Models.SystemFeedback()
+  beforeEach ->
+    @model = new CMS.Models.SystemFeedback()

   it "should have an empty message by default", ->
-    expect(m.get("message")).toEqual("")
+    expect(@model.get("message")).toEqual("")
+
+  it "should have an empty title by default", ->
+    expect(@model.get("title")).toEqual("")
+
+  it "should not have a type set by default", ->
+    expect(@model.get("type")).toBeNull()
+
+  it "should be shown by default", ->
+    expect(@model.get("shown")).toEqual(true)
+
+  it "should trigger a change event on calling .hide()", ->
+    spy = jasmine.createSpy()
+    @model.on("change", spy)
+
+    @model.hide()
+
+    expect(@model.get("shown")).toEqual(false)
+    expect(spy).toHaveBeenCalled()
+
+describe "CMS.Models.WarningMessage", ->
+  beforeEach ->
+    @model = new CMS.Models.WarningMessage()
+
+  it "should have the correct type", ->
+    expect(@model.get("type")).toEqual("warning")
+
+describe "CMS.Models.ErrorMessage", ->
+  beforeEach ->
+    @model = new CMS.Models.ErrorMessage()
+
+  it "should have the correct type", ->
+    expect(@model.get("type")).toEqual("error")
+
+describe "CMS.Models.ConfirmationMessage", ->
+  beforeEach ->
+    @model = new CMS.Models.ConfirmationMessage()
+
+  it "should have the correct type", ->
+    expect(@model.get("type")).toEqual("confirmation")
+


lower batch size

lower batch size

--- server/lib/indexer.coffee
+++ server/lib/indexer.coffee
@@ -18,7 +18,7 @@

 indexQueue = {}
 batchInProgress = false
-BATCH_SIZE = 100
+BATCH_SIZE = 20
 batchCounter = 1
 indexdefinitions = {}
 indexdefinitionsID = {}


Removed leftover mp_track

Removed leftover mp_track

--- core/app/backbone/views/facts/fact_bottom_view.coffee
+++ core/app/backbone/views/facts/fact_bottom_view.coffee
@@ -38,8 +38,6 @@
     FactlinkApp.Modal.show 'Repost Factlink',
       new AddToChannelModalView(model: @model)

-    mp_track "Factlink: Open repost modal"
-
   showStartConversation: (e) ->
     e.preventDefault()
     e.stopPropagation()


Remove the explicit i18nDescription from the settings, since those are generated automatically

--- packages/rocketchat-lib/server/startup/settings.coffee
+++ packages/rocketchat-lib/server/startup/settings.coffee
@@ -2,6 +2,8 @@
 if not RocketChat.models.Settings.findOneById 'uniqueID'
 	RocketChat.models.Settings.createWithIdAndValue 'uniqueID', process.env.DEPLOYMENT_ID or Random.id()

+# When you define a setting and want to add a description, you don't need to automatically define the i18nDescription
+# if you add a node to the i18n.json with the same setting name but with `_Description` it will automatically work.
 RocketChat.settings.addGroup 'Accounts', ->
 	@add 'Accounts_AllowDeleteOwnAccount', false, { type: 'boolean', public: true, enableQuery: { _id: 'Accounts_AllowUserProfileChange', value: true } }
 	@add 'Accounts_AllowUserProfileChange', true, { type: 'boolean', public: true }
@@ -92,9 +94,9 @@
 	@add 'Force_SSL', false, { type: 'boolean', public: true }
 	@add 'GoogleTagManager_id', '', { type: 'string', public: true }
 	@add 'GoogleSiteVerification_id', '', { type: 'string', public: false }
-	@add 'PiwikAnalytics_url', '', { type: 'string', public: false, i18nDescription: 'PiwikAnalytics_url_Description' }
-	@add 'PiwikAnalytics_siteId', '', { type: 'string', public: false, i18nDescription: 'PiwikAnalytics_siteId_Description' }
-	@add 'PiwikAnalytics_domains', '', { type: 'string', public: false, i18nDescription: 'PiwikAnalytics_domains_Description' }
+	@add 'PiwikAnalytics_url', '', { type: 'string', public: false }
+	@add 'PiwikAnalytics_siteId', '', { type: 'string', public: false }
+	@add 'PiwikAnalytics_domains', '', { type: 'string', public: false }
 	@add 'Restart', 'restart_server', { type: 'action', actionText: 'Restart_the_server' }

 	@section 'UTF8', ->


Remove logWarning function

Remove logWarning function

--- src/instruments.coffee
+++ src/instruments.coffee
@@ -1,17 +1,9 @@
-_warningMessages = []
-_logWarning = (message) ->
-  unless message in _warningMessages
-    console.error message
-    _warningMessages.push message
-
-
 # Oscillator.construct() arguments (named parameters, e.g.: Oscillator.construct(type: Oscillator.SINE)):
 # ------------------------------------------------------------------------------
 #   type: Optional. A Oscillator type, e.g. Oscillator.SINE.
 #   frequency: Optional. Default 440. Number, or a function that takes a time parameter and returns a frequency. the time argument specifies how long the uGen has been running.
 #   phase: Optional. Default 0. Number, or a function that takes a time parameter and returns a phase shift. the time argument specifies how long the uGen has been running.
 #   amplitude: Optional. Default 1. Number, or a function that takes a time parameter and returns an amplitude multiplier. *Not* in DeciBell's. the time argument specifies how long the uGen has been running.
-
 class Oscillator
   # Types
   @SINE           = 0
@@ -150,7 +142,7 @@
       options.sustainLevel = 0

     unless options.attackTime? && options.decayTime? && options.sustainTime? && options.releaseTime?
-      _logWarning "Options must specify 'attackTime', 'decayTime', 'sustainTime' and 'releaseTime' values for ADSR envelope type."
+      throw new Error "Options must specify 'attackTime', 'decayTime', 'sustainTime' and 'releaseTime' values for ADSR envelope type."

     @type         = options.type
     @sustainLevel = options.sustainLevel


OPCE3: Added trim to remove unused spaces

--- lib/y-rte.coffee
+++ lib/y-rte.coffee
@@ -1,12 +1,32 @@
+buildWord = (word, prepend='', append='') ->
+  tmp = word.split ' '
+  tmp2 = []
+
+  if tmp.length == 1
+    tmp2.push (new Word (prepend+tmp.pop()+append))
+  else
+    tmp2.push (new Word (prepend+tmp.pop(0)))
+    while tmp.length > 1
+      tmp2.push (new Word (tmp.pop(0) + ' '))
+
+    tmp2.push (new Word (tmp.pop() + append + ' '))
+
+  tmp2.reverse()
+
+# Simple class that contains a word and links to the selections pointing
+# to it
 class Word
   @word = ''
   @selections = []
+
+  # Construct a new list of words
+  # @param [String] word The initial string value
+  # @return [Word] a word instance
   constructor: (@word) ->
   removeSel: (selToRemove) ->
     @selections.forEach sel, index, array ->
       if sel.equals(selToRemove)
         array.splice(index, 1)
-

 class Selection
@@ -117,7 +137,7 @@
   # Try to merge this selection with the one given in argument
   #
   merge: (s, rte) ->
-    # Check if their contiguous
+    # Check if they're contiguous
     if s.endPos.word == @startPos.word or
        (s.endPos.word == @startPos.word -1 and
         s.endPos.loc == rte.getWord(s.endPos.word).length() and
@@ -171,30 +191,43 @@
       @push(content)
     else
       # TODO: support breaks (br, new paragraph, …)
-      (e.word for e in @_rte.words).join(' ')
-
-  # Split the content around ' ' and append all the words created to the object
-  #
+      (e.word for e in @_rte.words).join('')
+
+  # Returns the string representation of a word.
+  # @param index [Integer] the index of the word to return
   getWord: (index) ->
     if not (0 <= index < @_rte.words.length)
       throw new Error "Index out of bounds"
-    @_rte.words[index].word
+    i = j = 0                   # i : index in array, j : index of word
+    while j < index
+      if (@_rte.words[i].word isnt '' and @_rte.words[i] isnt ' ')
+        j += 1
+      i += 1
+    @_rte.words[i].word

   getWords: (begin, end) ->
     if not end?
       end = @_rte.words.length
-    out = wObj for wObj in @_rte.words[begin..end]
-
-  setWord: (index, content="") ->
+    if not (0 <= begin <= end <= @_rte.words.length)
+      throw new Error "Index out of bounds: #{[begin, end]}"
+
+    ret = @_rte.words[begin..end]
+    if ret?
+      ret
+    else
+      []
+
+  # Set the content of a word
+  # @param index [Integer] the index of the word to modify
+  # @param content [String] the content to set the word to
+  setWord: (index, content) ->
     if not (0 <= index < @_rte.words.length)
       throw new Error "Index out of bounds"
     @_rte.words[index].word = content

   push: (content) ->
-    for w in content.split(' ')
-      wObj = new Word(w)
-
-      @_rte.words.push(wObj)
+    wObj = buildWord content
+    @_rte.words = @_rte.words.concat(wObj)

   insertWords: (position, words)->
@@ -204,45 +237,19 @@
     if words.constructor isnt Array
       throw new Error 'Expected a string array as second parameter'
     if 0 <= position <= @_rte.words.length
+      after = [new Word ' '].concat(@getWords(position))
+      before = @getWords(0, Math.max(position-1, 0)).append(new Word ' ')
       if position == 0
         before = []
-      else
-        before = @getWords(0, position-1)
+      if position == @_rte.words.length - 1
+        after = []
+
       wordsObj = (new Word w for w in words)
-      after = @getWords(position)
+
       @_rte.words = before.concat(wordsObj).concat(after)

     else
       throw new Error 'Index #{position} out of bound in word list'
-
-  # insert l words at pos wNum
-  updateInsertWords: (wNum, n)->
-    # TODO: check that we don't get negative positions
-    if typeof wNum isnt "number"
-      throw new Error "Expected a number as first parameter"
-    if typeof n isnt "number"
-      throw new Error "Expected a number as second parameter"
-
-    for pt in @_rte.selections when pt.endPos.word >= wNum
-      if pt.startPos.word >= wNum
-        pt.startPos.word += n
-      pt.endPos.word += n
-
-  # insert n caracters in word wNum at position pos
-  updateInsert: (wNum, pos, n)->
-    # TODO: check that we don't get negative positions
-    if typeof wNum isnt "number"
-      throw new Error "Expected a number as first parameter"
-    if typeof pos isnt "number"
-      throw new Error "Expected a number as second parameter"
-    if typeof n isnt "number"
-      throw new Error "Expected a number as third parameter"
-
-    for pt in @_rte.selections
-      if pt.startPos.word == wNum and pt.startPos.pos >= pos
-        pt.startPos.pos += n
-      if pt.endPos.word == wNum and pt.endPos.pos >= pos
-        pt.endPos.pos += n

   deleteWords: (start, end) ->
@@ -260,7 +267,7 @@

   merge: (n) ->
     if 0 <= n < @_rte.words.length
-      w = @getWord(n)
+      w = @getWord(n).trimRight()
       @deleteWords(n)
       @insert({startPos: {word: n, pos:0}}, w)
     else
@@ -302,7 +309,6 @@
     if typeof content isnt "string"
       throw new Error "Expected a string as second argument"

-    #TODO: check boundaries
     n = sel.startPos.word
     pos = sel.startPos.pos
     word = @getWord(n)
@@ -364,13 +370,17 @@
     position = 0
     for delta in deltas.ops
       if delta.retain?
-        selection = new Selection position (position+=delta.retain)
+        selection = new Selection position (position + delta.retain)
       else if delta.delete?
-        selection = new Selection position (position+=delta.retain)
+        selection = new Selection position (position + delta.retain)
         @delete(selection)
       else if delta.insert?
-        selection = new Selection position (position+=delta.insert.length)
+        end = position + delta.insert.length
+        selection = new Selection position, end, @
         @insert(selection, delta.insert)
+        position = end
+      else
+        throw new Error "Unknown operation"

       if delta.attributes?
         for attr in delta.attributes
@@ -379,6 +389,7 @@
 if window?
   window.Rte = Rte
   window.Selection = Selection
+  window.buildWord = buildWord

 if module?
   module.exports = [Rte, Selection]


Don't include bootstrap site files

Don't include bootstrap site files

--- build/tasks/build-task.coffee
+++ build/tasks/build-task.coffee
@@ -46,7 +46,9 @@
       path.join('oniguruma', 'deps')
       path.join('less', 'dist')
       path.join('bootstrap', 'docs')
-      path.join('bootstrap', 'examples')
+      path.join('bootstrap', '_config.yml')
+      path.join('bootstrap', '_includes')
+      path.join('bootstrap', '_layouts')
       path.join('pegjs', 'examples')
       path.join('jasmine-reporters', 'ext')
       path.join('jasmine-node', 'node_modules', 'gaze')


clean up code some more

clean up code some more

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -45,40 +45,25 @@
     @addMarkToGraph(e)

   onMouseMove: (e) =>
-    # e.preventDefault()
-    console.log 'onMouseMove()'
-
+    # console.log 'onMouseMove()'
     return if classifier.el.find('#graph').hasClass('is-zooming')
-
     zoomLevel = classifier.zoomLevel
     zoomRanges = classifier.zoomRanges
     val = +classifier.el.find("#ui-slider").val()
-
-    # console.log 'ZOOM LEVEL: ', zoomLevel
-    # console.log 'PLOT RANGE [',val,',',val+zoomRanges[zoomLevel],']'
-    # console.log '--------------------------------------------------'
     xClick = e.pageX - e.target.getBoundingClientRect().left - window.scrollX
     yClick = e.pageY - e.target.getBoundingClientRect().top - window.scrollY
-
     @plotPoints(classifier.prevZoomMin, classifier.prevZoomMax)

     if xClick < 80
-
       @ctx.fillStyle = '#FC4542'
-
       # draw triangle
       w = 10
       s = w*Math.tan(60)
-
       @ctx.beginPath()
       @ctx.moveTo(w,yClick)
       @ctx.lineTo(0,yClick+s)
       @ctx.lineTo(0,yClick-s)
       @ctx.fill()
-
-
-
-      # @plotPoints(classifier.prevZoomMin, classifier.prevZoomMax)
       @ctx.font = '10pt Arial'
       @ctx.textAlign = 'left'
       @ctx.lineWidth = 1


improve mark moving and resizing

improve mark moving and resizing

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -25,6 +25,9 @@
     canvas.addEventListener 'mousemove', (e) =>
       e.preventDefault()
       @mark.draw(e) if @mark?.dragging
+      @mark.move(e) if @mark?.moving
+      @mark.resize(e) if @mark?.resizing
+      @mark.updateCursor(e) if @mark?.hovering

   drawAxes: ->
     #draws non-scaled axes
@@ -127,11 +130,9 @@
     @save(markLeftX, markRightX)

   move: (e) ->
-    markLeftX = @domXMin - (@movingStart - e.x)
-    markRightX = @domXMax - (@movingStart - e.x)
-
-    @element.style.left = (Math.min markLeftX, markRightX) + "px"
-    @element.style.width = (Math.abs markRightX - markLeftX) + "px"
+    leftXPos = (e.x-@pointerOffset)
+    @element.style.left = leftXPos + "px"
+    @save(leftXPos, leftXPos+parseInt(@element.style.width, 10))

   save: (markLeftX, markRightX) ->
     #dom coords
@@ -168,7 +169,7 @@
       @dragging = true
     else if e.layerY > 15
       @moving = true
-      @movingStart = e.x
+      @pointerOffset = (e.x-@domXMin)

   onMouseUp: (e) ->
     if @dragging
@@ -177,9 +178,7 @@
       console.log "Marks", @canvasGraph.marks
       @dragging = false
     else if @moving
-      console.log "OLD SP", @startingPoint
       @save(@domXMin, @domXMax)
-      console.log "NEW SP", @startingPoint
       @moving = false

 module?.exports = CanvasGraph: CanvasGraph, Marks: Marks, Mark: Mark


Remove logging

Remove logging

--- app/models/dashboard.coffee
+++ app/models/dashboard.coffee
@@ -60,8 +60,7 @@
       if tool.dataSource.get('type') is 'internal'
         source = @tools.find (sourceTool) =>
           sourceTool.get('channel') is tool.dataSource.get('source')
-        console.log source
-        source.dataSource.on 'source:dataReceived', => console.log 'here'; tool.dataSource.fetchData()
+        source.dataSource.on 'source:dataReceived', => tool.dataSource.fetchData()
     @tools.map (tool) =>
       tool.dataSource.fetchData() if tool.dataSource.get('type') is 'external'


Addition of Snapshot Serengeti Link

Addition of Snapshot Serengeti Link

--- app/pages/lab/glossary.cjsx
+++ app/pages/lab/glossary.cjsx
@@ -81,7 +81,7 @@

       + **Subject** - The chunk of data/thing a volunteer on a Zooniverse project is being presented with and asked to review and analyze.  It typically is an image, graph, photo, audio recording, video,  or a collection of these different things.

-      + **Subject Set** - This is a group of subjects (subjects being data that volunteers are presented to in projects). Subjects are uploaded into subject sets through the [Project Builder](lab), and it is subject sets that can be linked to workflows in order to get the desired subjects showing on your project’s webpage. You can group subjects into subject sets however you wish. You might want to group Subjects together, for example to represent a season’s worth of images in Snapshot Serengeti or a particular cell dye staining as in Cell Slider.
+      + **Subject Set** - This is a group of subjects (subjects being data that volunteers are presented to in projects). Subjects are uploaded into subject sets through the [Project Builder](lab), and it is subject sets that can be linked to workflows in order to get the desired subjects showing on your project’s webpage. You can group subjects into subject sets however you wish. You might want to group Subjects together, for example to represent a season’s worth of images in [Snapshot Serengeti](https://www.snapshotserengeti.org/) or a particular cell dye staining as in Cell Slider.

       + **Talk** - is the object-orientated discussion tool associated with your project. [Talk](talk) enables volunteers to comment on the subjects they've reviewed and promotes discussion amongst the volunteer community.  Talk is also a place where the research team and project volunteers can interact. Talk has a series of message boards for longer discussions. Additionally, each subject has a dedicated page on its project Talk where a registered volunteer can write a comment, add searchable Twitter-like hashtags, or  link multiple subjects together into groups called collections


fix setImmediate context

fix setImmediate context

--- task.coffee
+++ task.coffee
@@ -94,7 +94,7 @@
           @runTask task
         , (err) =>
           if err.message.match /duplicate/
-            setImmediate @triggerTask
+            setImmediate @triggerTask.bind(@)
           else
             @emit 'error', _.extend err,
               context:


Groups: back to koding link fixed

Groups: back to koding link fixed

--- client/Main/avatararea/avatarareagroupswitcherpopup.coffee
+++ client/Main/avatararea/avatarareagroupswitcherpopup.coffee
@@ -76,9 +76,24 @@
         router.handleRoute '/Pricing/Enterprise', entryPoint : 'koding'
         @hide()

+    backToKoding = new KDCustomHTMLView
+      tagName    : 'a'
+      attributes : href : '/'
+      cssClass   : 'bottom bb'
+      partial    : 'Go back to Koding'
+      click      : (event)=>
+        KD.utils.stopDOMEvent event
+        location.href = '/'
+
+    groupsController.ready ->
+      backToKoding.destroy()  if groupsController.getCurrentGroup().slug is 'koding'
+
+
     @groupSubMenuWrapper.addSubView createGroupLink, '.content'
+    @groupSubMenuWrapper.addSubView backToKoding, '.content'
     @groupSubMenuWrapper.addSubView @listControllerPending.getView(), '.content'
     @groupSubMenuWrapper.addSubView @listController.getView(), '.content'
+

     submenuShown = no


Fix account discovery, refs #562

Fix account discovery, refs #562

--- client/app/components/account_config_main.coffee
+++ client/app/components/account_config_main.coffee
@@ -1,3 +1,4 @@
+AccountActionCreator = require '../actions/account_action_creator'
 {
     div, p, form, label, input, button, ul, li, a, span, i,
     fieldset, legend
@@ -304,7 +305,6 @@

     onMethodChange: (event) ->
-        console.log "blash"
         @state.smtpMethod.requestChange event.target.dataset.value


Clear game and search when stream is cleared

--- client/components/network/network.service.coffee
+++ client/components/network/network.service.coffee
@@ -111,6 +111,9 @@
       clearstream: ->
         @activeStream = null
         @scope.$broadcast "clearStream"
+        @activeGame = null
+        @activeSearch = null
+        @activePerms = null
         @activePlayerCount = 0
         @activeFollowerCount = 0
         @activeSubscriberCount = 0


remove pistachios from kd: WIP

remove pistachios from kd: WIP

--- client/finder/filetree/itemviews/fileitem.coffee
+++ client/finder/filetree/itemviews/fileitem.coffee
@@ -1,4 +1,6 @@
 class NFileItemView extends KDCustomHTMLView
+
+  JView.mixin @prototype

   loaderRequiredEvents = ['job', 'remove', 'save', 'saveAs']


conn: minor fix

conn: minor fix

--- client/landing/app/MainApp/monitor_status.coffee
+++ client/landing/app/MainApp/monitor_status.coffee
@@ -206,7 +206,7 @@
         # morning; if reconnection happens before failureCallback is trigged,
         # we won't know that disconnection has happened.
         if lastPong && (Date.now() - lastPong) > 30*1000
-          log "lastPong greater than 50 secs, disconnecting"
+          log "lastPong too long ago, possible computer sleep; disconnecting"
           status = KD.singletons.status
           status.disconnect({reason:"internetDown"})


TEST:VirtualMachines add after function

TEST:VirtualMachines add after function

--- client/test/lib/dashboard/virtualmachines.coffee
+++ client/test/lib/dashboard/virtualmachines.coffee
@@ -66,3 +66,7 @@
     ]

     async.series queue, (err, result) ->
+
+
+  after: (browser) ->
+    browser.end()


simplified the activation code

simplified the activation code

--- core/app/backbone/views/auto_completes_view.coffee
+++ core/app/backbone/views/auto_completes_view.coffee
@@ -20,23 +20,15 @@

     return key

-  setActiveAutoComplete:  (key, scroll=true)->
+  setActiveAutoComplete:  (key)->
     this.options.mainView.deActivateCurrent()
     key = this.fixKeyModulo(key)

-    console.info "SETTING ACTIVE TO", key, @list.length
+    @list[key].trigger('activate');

-    if 0 <= key < @list.length
-      @list[key].trigger('activate');
-
-      if (  scroll == true)
-        list = @$("ul")[0];
-        if (list.scrollHeight > list.clientHeight)
-          @list[key].el.scrollIntoView(scroll)
-
-
-    else
-      @options.mainView.activateAddNew()
+    list = @list[key].$el.closest("ul")[0]
+    if (list.scrollHeight > list.clientHeight)
+      @list[key].el.scrollIntoView()

     @setActiveChannelKey(key)


event.key is in working draft now, use event.keyCode instead

--- app/assets/javascripts/autocomplete.coffee
+++ app/assets/javascripts/autocomplete.coffee
@@ -94,7 +94,7 @@

   # handle non-input events
   $('.js-autocomplete-textfield').on('keydown', (event) ->
-    if event.key == 'Enter'
+    if event.keyCode == 13 # Enter
       event.preventDefault()
       return false
   )
@@ -102,17 +102,17 @@
   $('.js-autocomplete-textfield').on('keyup', (event) ->
     return if event.ctrlKey || event.metaKey || event.shiftKey

-    switch event.key
-      when 'Enter'
+    switch event.keyCode
+      when 13 # Enter
         highlighted_items = $('.js-autocomplete-suggestion-item.highlighted')
         if highlighted_items.length == 1
           update_textfield(highlighted_items[0])
           set_suggestions_visible(false)
-      when 'Escape'
+      when 27 # Escape
         set_suggestions_visible(false)
-      when 'ArrowUp'
+      when 38 # ArrowUp
         highlight_previous_item()
-      when 'ArrowDown'
+      when 40 # ArrowDown
         highlight_next_item()
   )


Ensure filters hide on load

Ensure filters hide on load

--- app/assets/javascripts/companies.js.coffee
+++ app/assets/javascripts/companies.js.coffee
@@ -5,7 +5,7 @@
 window.Application ||= {}

 # Hide filters on load
-window.onload = () ->
+$(document).on 'ready page:load', () ->
   $("#company_filters").hide()

 # Toggle Filter Visibility


set minimum to 70

set minimum to 70

--- app/assets/javascripts/general/init.coffee
+++ app/assets/javascripts/general/init.coffee
@@ -10,11 +10,11 @@
         type: 'numeric'
         tickInterval: 5
       yAxis:
-        min: 0
+        min: 70
         max: 100
         type: 'numeric'
-        minorTickInterval: 10
-        tickInterval: 20
+        minorTickInterval: 1
+        tickInterval: 5
       tooltip:
         # headerFormat: '{series.name}
',
         pointFormat: 'epoch = {point.x}, score = {point.y}'


main.coffee reverted back to my changes

main.coffee reverted back to my changes

--- client/landing/app/MainApp/main.coffee
+++ client/landing/app/MainApp/main.coffee
@@ -1,101 +1,82 @@
 #Broker.channel_auth_endpoint = KD.config.apiUri+'/1.0/channel/auth';
 #Broker.channel_auth_endpoint = 'http://localhost:8008/auth'

-# THIS OVERRIDE NEEDS TO WAIT
-# NOT YET TOTALLY INTEROPERABLE
-# WITH Encoder.htmlDecode
-
-# Encoder.htmlEncode = do->
-
-#    htmlMap =
-#      '&' : 'amp'
-#      '<' : 'lt'
-#      '"' : 'quot'
-#      '<' : 'lt'
-#      '>' : 'gt'
-#      "'" : '#39'
-#      '`' : '#96'
-#      '!' : '#33'
-#      '@' : '#36'
-#      '%' : '#37'
-#      '(' : '#40'
-#      ')' : '#41'
-#      '=' : '#61'
-#      '+' : '#43'
-#      '{' : '#123'
-#      '}' : '#125'
-#      '[' : '#91'
-#      ']' : '#93'
-
-#    (str)-> str.replace /(&(?!\w\w+;)|'|<|>|"|'|`|\!|\@|\$|\%|\(|\)|\=|\+|\{|\}|\[|\])/g, (match)-> "&#{htmlMap[match]};"
-
-mainController = new MainController
-
-
-KD.remote.on 'loggedInStateChanged', (account)->
-  KD.socketConnected()
-  mainController.accountChanged account
-  AccountMixin.init(KD.remote.api)
-
-# Pistachio.MODE = if KD.env is 'dev' then 'development' else 'production'
-
-#$.cookie 'clientId', localStorage.clientId
-
-# Cacheable = new Cacheable
-
-# setTimeout ->
-#   appManager.openApplication("Chat")
-# ,5000
-
-firstLoad = yes
-connectionLostModalId = null
-connectionLostNotification = null
-
-KD.remote.on 'connected', -> console.log('kd connected')
-
-KD.remote.on 'disconnected', ->
-  #
-  # CONNECTIVITY NOTIFICATIONS PART START
-  #
-  setTimeout -> # to avoid modal to appear on page refresh
-
-    if connectionLostNotification?
-      connectionLostNotification.destroy()
-      connectionLostNotification = null
-
-    # THE MODAL APPEARS WHEN CONNECTION IS LOST
-    unless connectionLostModalId?
-      connectionLostModalId = KDModalController.createAndShowNewModal
-        type    : 'blocking'
-        title   : "Server connection lost"
-        content : """
-

-            Your internet connection may be down, or our server is.

-            If you have unsaved work please close this dialog and
back up your unsaved work locally until the connection is re-established.

-             Trying to reconnect...
-

-          """
-        height  : "auto"
-        overlay : yes
-        buttons :
-          "Close and Refresh later" :
-            style     : "modal-clean-red"
-            callback  : ()->
-              @propagateEvent KDEventType:'KDModalShouldClose'
-              connectionLostModalId = null
-              if connectionLostNotification?
-                connectionLostNotification.destroy()
-                connectionLostNotification = null
-
-              updateModalActive = no
-
-              # THE NOTIFICATION APPEARS WHEN MODAL WAS CLOSED BEFORE CONNECTION RE-ESTABLISHES
-              connectionLostNotification = new KDNotificationView
-                title    : "Server Connection Has Been Lost"
-                content  : "Trying to reconnect..., changes will not be saved until server reconnects, please back up locally."
-                duration : 0
-
-KD.remote.connect -> console.log 'koding is now connected to the backend'
+do ->
+  mainController             = new MainController
+  firstLoad                  = yes
+  connectionLostModal        = null
+  connectionLostNotification = null
+
+  destroyNotification = ->
+    connectionLostNotification?.destroy()
+    connectionLostNotification = null
+
+  # destroyConnectionSetNotification = ->
+  #   connectionSetNotification?.destroy()
+  #   connectionSetNotification = null
+
+  destroyModal = (reconnected = no)->
+    connectionLostModal?.destroy()
+    destroyNotification()
+    if reconnected isnt no
+      new KDNotificationView
+        title         : "Reconnected"
+        type          : "tray"
+        content       : "Server connection has been reset, you can continue working."
+        duration      : 3000
+    else
+      connectionLostNotification = new KDNotificationView
+        title         : "Trying to reconnect..."
+        type          : "tray"
+        closeManually : no
+        content       : "Server connection has been lost, changes will not be saved until server reconnects, please back up locally."
+        duration      : 0
+      connectionLostModal = null
+
+  showModal = ->
+    return if connectionLostModal
+    destroyNotification()
+    connectionLostModal = new KDBlockingModalView
+      title   : "Server connection lost"
+      content : """
+

+          Your internet connection may be down, or our server is.

+          If you have unsaved work please close this dialog and
back up your unsaved work locally until the connection is re-established.

+           Trying to reconnect...
+

+        """
+      height  : "auto"
+      overlay : yes
+      buttons :
+        "Close and Refresh later" :
+          style     : "modal-clean-red"
+          callback  : -> destroyModal()
+
+    connectionLostModal.once "KDObjectWillBeDestroyed", -> destroyModal()
+
+  ###
+  # CONNECTIVITY EVENTS
+  ###
+
+  KD.remote.on 'loggedInStateChanged', (account)->
+    KD.socketConnected()
+    mainController.accountChanged account
+    AccountMixin.init(KD.remote.api)
+
+
+  KD.remote.on 'connected', ->
+    if firstLoad
+      log 'kd remote connected'
+      firstLoad = no
+    else
+      log 'kd remote re-connected'
+      destroyModal yes
+
+  KD.remote.on 'disconnected', ->
+    # to avoid modal to appear on page refresh
+    __utils.wait 500, showModal
+
+  KD.remote.connect()

   #initConnectionEvents conn


Tests: Pricing: Payment: Added wait selector for upgradeAccount test.

--- client/test/lib/pricing/payment.coffee
+++ client/test/lib/pricing/payment.coffee
@@ -43,6 +43,7 @@
             .click                   '.payment-modal button'
             .waitForElementVisible   '.payment-modal', 20000
             .pause                   5000
+            .waitForElementVisible   '.payment-modal button', 20000
             .click                   '.payment-modal button'
             .waitForElementVisible   '[testpath=main-sidebar]', 20000
             .url                     helpers.getUrl() + '/Pricing'


further logging

further logging

--- app/assets/javascripts/oxalis/model.coffee
+++ app/assets/javascripts/oxalis/model.coffee
@@ -30,15 +30,18 @@
     @timestamps.push(new Date().getTime())

     bytes = 0
-    buckets = 0
+    buckets = 0
+    totalBytes = if @totalBytes.length > 0 then @totalBytes[@totalBytes.length - 1] else 0
+    totalBuckets = if @totalBuckets.length > 0 then @totalBuckets[@totalBuckets.length - 1] else 0
+
     for dataLayerName of @binary
       bytes += @binary[dataLayerName].queue.loadedBytes
       buckets += @binary[dataLayerName].queue.loadedBuckets

     @bytes.push(bytes)
     @buckets.push(buckets)
-    @totalBytes.push(bytes + @totalBytes[@totalBytes.length - 1])
-    @totalBuckets.push(buckets + @totalBuckets[@totalBuckets.length - 1])
+    @totalBytes.push(bytes + totalBytes)
+    @totalBuckets.push(buckets + totalBuckets)

   initialize : =>


Move to input event as trigger instead of keyup

JavaScript refactor away from problematic use of keyup & change in favor
of "input" event from HTML5. Needs testing in IE8.

Supports #54 & #255

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

   $('textarea').css('overflow', 'hidden').autogrow()

-  $('#question_title').keyup (event) ->
+  $('#question_title').on 'input', (event) ->
     $target = $ event.target
     $target.parent().find('.summary_count').text($(this).attr('maxlength') - $target.val().length)

@@ -41,10 +41,13 @@
       success: (data) ->
         refreshPersonList(data, 'reload', 'div.twitter')

-  $("#twitter").on 'keyup change',  (->
+  $("#twitter").on 'input',  (->
     nameLength = $(this).val().length
-    if $(this).is(':focus') or isHeadless and nameLength > 1
+    if nameLength > 1
       getTwitter($(this))
+    else
+      if nameLength == 0
+        clearPersonList 'div.twitter'
   )

   $('span.toggle a.select').click (event) ->
@@ -76,12 +79,14 @@
       $('span.toggle a.active').removeClass('active')
       $(this).addClass('active')

-  $('#name-lookup').on 'keyup change', (e) ->
+  $('#name-lookup').on 'input', (e) ->
     theInput = $('#name-lookup')

-    if $(theInput).is(':focus') or isHeadless
+    if $(theInput).val().length > 0
       jurisdiction = $(location).attr('pathname').split('/')[1]
       showPeopleForName theInput, jurisdiction
+    else
+      clearPersonList 'div.name-lookup'

   showPeopleForName = (theInput, jurisdiction) ->
     value = $.trim(theInput.val())
@@ -116,15 +121,17 @@

     window.location.href = url

-  $("#question_user_attributes_postal_code").on 'keyup change', (e) ->
+  $("#question_user_attributes_postal_code").on 'input', (e) ->
     theInput = e.delegateTarget
-    if $(theInput).is(':focus') or isHeadless
+    if $(theInput).val().length > 0
       showPeopleForAddress theInput
+    else
+      clearPersonList

   showPeopleForAddress = (theInput) ->
     unless $('div.address_lookup').length == 0
       zipLength = $(theInput).val().length
-      if zipLength is 5 or zipLength > 5
+      if zipLength is 5
         # redundant, but covers case where zip is pasted in
         if $('label.select-person').is(':hidden')
           $('label.select-person').fadeTo(300, 1)
@@ -182,6 +189,10 @@
       success: (data) ->
         refreshPersonList(data, "update")

+  clearPersonList = (lookupDiv = 'div.address_lookup') ->
+    personList = $("#{lookupDiv} ol.people-list").first()
+    personList.html('')
+
   refreshPersonList = (data, updateOrReload, lookupDiv = 'div.address_lookup') ->
     loading = $('.loading')
     loading.show()
@@ -303,18 +314,18 @@
   $('#edit-button').click (event) ->
     beginAgain event

-  $('#question_title').keyup (event) ->
+  $('#question_title').on 'input', (event) ->
     value = $('#question_title').val()
     $('#confirm-question-title').text value

-  $('#question_body').keyup (event) ->
+  $('#question_body').on 'input', (event) ->
     value = $('#question_body').val()
     $('#confirm-question-body').text value

-  $('#question_user_attributes_given_name').keyup (event) ->
+  $('#question_user_attributes_given_name').on 'input', (event) ->
     $('.author .firstname').text $('#question_user_attributes_given_name').val()

-  $('#question_user_attributes_family_name').keyup (event) ->
+  $('#question_user_attributes_family_name').on 'input', (event) ->
     $('.author .lastname').text $('#question_user_attributes_family_name').val()

   $('#question_subject').change (event) ->


Implemented XSS encoder to AceView save action.

--- client/ace/lib/aceview.coffee
+++ client/ace/lib/aceview.coffee
@@ -1,13 +1,15 @@
-kd = require 'kd'
-KDButtonViewWithMenu = kd.ButtonViewWithMenu
-KDCustomHTMLView = kd.CustomHTMLView
-KDModalView = kd.ModalView
-JView = require 'app/jview'
-FSHelper = require 'app/util/fs/fshelper'
-Ace = require './ace'
-AceFindAndReplaceView = require './acefindandreplaceview'
-AceSettingsView = require './acesettingsview'
-showSaveDialog = require 'app/util/showSaveDialog'
+kd                        = require 'kd'
+KDButtonViewWithMenu      = kd.ButtonViewWithMenu
+KDCustomHTMLView          = kd.CustomHTMLView
+KDModalView               = kd.ModalView
+JView                     = require 'app/jview'
+FSHelper                  = require 'app/util/fs/fshelper'
+Ace                       = require './ace'
+AceFindAndReplaceView     = require './acefindandreplaceview'
+AceSettingsView           = require './acesettingsview'
+showSaveDialog            = require 'app/util/showSaveDialog'
+Encoder                   = require 'htmlencode'
+

 ###
   todo:
@@ -162,7 +164,7 @@
     showSaveDialog container, (input, finderController, dialog) =>

       [node] = finderController.treeController.selectedNodes
-      name   = input.getValue()
+      name   = Encoder.XSSEncode input.getValue()

       return @ace.notify 'Please type valid file name!'   , 'error'  unless FSHelper.isValidFileName name
       return @ace.notify 'Please select a folder to save!', 'error'  unless node


Click/brick/tic stars

Click/brick/tic stars

--- src/layout/mixins/done_dialog.coffee
+++ src/layout/mixins/done_dialog.coffee
@@ -13,6 +13,9 @@
       email:
         description: text + "\n\nClick: " + encodeURIComponent(changelog.url())

+star = { fill: "", empty: "" }
+metrics = ['clicks','bricks','ticks']
+
 module.exports.world = (level) ->
   goals = level.get 'goal'

@@ -22,66 +25,40 @@
       when 'Next'  then next(world)
       else ''

-  retry = (world) ->
-    window.location.reload()
+  retry = (world) -> window.location.reload()

-  next = (world) ->
-    window.open world.get('next_url'), '_self'
+  next = (world) -> window.open world.get('next_url'), '_self'

-  star_count = ->
-    ticks_diff  = level.get('ticks')  - goals.ticks
-    clicks_diff = level.get('clicks') - goals.clicks
-    bricks_diff = level.get('bricks') - goals.bricks
-
-    # calculate total score with a minimum for each being 0
-    extra_moves = [ticks_diff, clicks_diff, bricks_diff].reduce (sum, diff) ->
-      sum + if diff < 0 then 0 else diff
-
-    if extra_moves <= 0
-      3
-    else if extra_moves <= 3
-      2
-    else
-      1
+  star_for = (key) ->
+    return star.empty if level.get(key) > goals[key]
+    star.fill

   star_string = ->
-    stars = star_count()
-    empty_stars = 3 - stars
-    star = {
-      fill: ""
-      empty: ""
-    }
-    Array(stars + 1).join(star.fill) + Array(empty_stars + 1).join(star.empty)
+    result = metrics.map (key) -> star_for(key)
+    result.join ' '

   buttons = make.buttons 'dialog', [
     'Retry',
     'Next'
   ], {}, dialogAction

-  # TODO: refactor messages into an explicit array with conditional elements?
-  messages = []

-  messages.push
-    class: 'stars'
-    name:   star_string
+  share_message = "I programmed a turtle to solve this level on the Hour of NODE. Can you?"

-  if level.get('message')?
-    messages.push
-      class: 'message'
-      name:   level.get('message')
+  hint = 'Hint: Use fewer clicks, bricks, or ticks to improve your score'
+  messages = [
+    { class: 'stars', name: star_string() }
+    { class: 'metrics', name: metrics.join ' | ' }
+    { class: 'message', name: level.get('message') }
+    { class: 'share-button', name: share_message }
+    { class: 'hint', hint }
+  ]

   if level.get('completion', false)? && level.get('last_level')
     messages.push
       class: 'completion'
       name:  level.rx().rxt.rawHtml("<p>#{level.get 'completion', false}</p>")

-  messages.push
-    class: 'share-button'
-    name:   -> "I programmed a turtle to get #{star_count()} stars on the Hour of Node. Can you solve this puzzle and beat my score?"
-  messages.push
-    class: 'hint'
-    name:   'Hint: Use fewer clicks, bricks, or ticks to improve your score'
-
   {
     class: 'done_dialog',
     _CHILDREN: [make.rows '', [].concat(messages, buttons)]


correctly remove the return type

correctly remove the return type

--- lib/atom-ternjs-type.coffee
+++ lib/atom-ternjs-type.coffee
@@ -128,8 +128,10 @@
           @destroyOverlay()
           return
         data.type = @manager.helper.formatType(data)
-        type = data.type.substring(data.type.indexOf('(') + 1, data.type.lastIndexOf(')'))
+        type = data.type.substring(data.type.indexOf('(') + 1, data.type.length)
         matches = type.match(@manager.regExp.params)
+        if (matches[matches.length - 1]).startsWith(' :')
+          matches.splice(matches.length - 1)
         if matches?[paramPosition]
           offsetFix = if paramPosition > 0 then ' ' else ''
           data.type = data.type.replace(matches[paramPosition], offsetFix + "#{matches[paramPosition]}")


:lipstick:

:lipstick:

--- build/tasks/publish-build-task.coffee
+++ build/tasks/publish-build-task.coffee
@@ -9,8 +9,8 @@
 grunt = null
 maxReleases = 10
 assets = [
-  name: 'atom-mac.zip', source: 'Atom.app'
-  name: 'atom-mac-symbols.zip', source: 'Atom.breakpad.syms'
+  {assetName: 'atom-mac.zip', sourceName: 'Atom.app'}
+  {assetName: 'atom-mac-symbols.zip', sourceName: 'Atom.breakpad.syms'}
 ]
 commitSha = process.env.JANKY_SHA1
 token = process.env.ATOM_ACCESS_TOKEN
@@ -31,9 +31,9 @@
     createBuildRelease (error, release) ->
       return done(error) if error?

-      for {name, source} in assets
-        assetPath = path.join(buildDir, name)
-        zipApp buildDir, source, name, (error) ->
+      for {assetName, sourceName} in assets
+        assetPath = path.join(buildDir, assetName)
+        zipApp sourceName, assetName, assetPath, (error) ->
           return done(error) if error?
           uploadAsset release, assetName, assetPath, (error) ->
             return done(error) if error?
@@ -50,8 +50,7 @@
   grunt.log.error(error.message ? error) if error?
   grunt.log.error(details) if details

-zipApp = (buildDir, sourceName, assetName, callback) ->
-  assetPath = path.join(buildDir, assetName)
+zipApp = (sourceName, assetName, assetPath, callback) ->
   fs.removeSync(assetPath)

   options = {cwd: path.dirname(assetPath), maxBuffer: Infinity}


Fix live reload issue

Fix live reload issue

--- lib/broadcast-server.coffee
+++ lib/broadcast-server.coffee
@@ -12,7 +12,6 @@
   server: null
   sockets: []
   io: null
-  ioSocket: null

   start: ->
     isRestart = @server?
@@ -62,8 +61,7 @@
     @io = socketIo @server

     @io.on 'connection', (socket) =>
-      @ioSocket = socket
-      @updateContent()
+      @updateContent socket

   stop: ->
     if !@server?
@@ -88,11 +86,13 @@

     @io = null

-  updateContent: ->
-    return unless @ioSocket?
+  updateContent: (socket) ->
+    content = @getContent()

-    content = @getContent()
-    @ioSocket.emit 'update', content
+    if socket?
+      socket.emit 'update', content
+    else
+      @io.emit 'update', content

   getContent: ->
     if @editor[0]?


added some german translations

added some german translations

--- app/locale/de-DE.coffee
+++ app/locale/de-DE.coffee
@@ -324,12 +324,12 @@
     few_gems: "Ein paar Edelsteine"
     pile_gems: "Stapel von Edelsteinen"
     chest_gems: "Kiste von Edelsteinen"
-#    purchasing: "Purchasing..."
-#    declined: "Your card was declined"
-#    retrying: "Server error, retrying."
-#    prompt_title: "Not Enough Gems"
-#    prompt_body: "Do you want to get more?"
-#    prompt_button: "Enter Shop"
+    purchasing: "Kaufabwicklung..."
+    declined: "Deine Karte wurde abgelehnt"
+    retrying: "Serverfehler, versuche erneut."
+    prompt_title: "Nicht genug Edelsteine"
+    prompt_body: "Benötigst du mehr?"
+    prompt_button: "Laden betreten"

   choose_hero:
     choose_hero: "Wähle deinen Helden"
@@ -357,23 +357,23 @@
     skills: "Fähigkeiten"
 #    available_for_purchase: "Available for Purchase"
 #    level_to_unlock: "Level to unlock:"
-#    restricted_to_certain_heroes: "Only certain heroes can play this level."
+    restricted_to_certain_heroes: "Nur bestimmte Helden können dieses Level spielen."

 #  skill_docs:
 #    writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
 #    read_only: "read-only"
-#    action_name: "name"
-#    action_cooldown: "Takes"
-#    action_specific_cooldown: "Cooldown"
-#    action_damage: "Damage"
-#    action_range: "Range"
-#    action_radius: "Radius"
-#    action_duration: "Duration"
-#    example: "Example"
+    action_name: "Name"
+    action_cooldown: "Benötigt"
+    action_specific_cooldown: "Cooldown"
+    action_damage: "Schaden"
+    action_range: "Reichweite"
+    action_radius: "Radius"
+    action_duration: "Dauer"
+    example: "Beispiel"
 #    ex: "ex" # Abbreviation of "example"
-#    current_value: "Current Value"
-#    default_value: "Default value"
-#    parameters: "Parameters"
+    current_value: "Aktueller Wert"
+    default_value: "Standardwert"
+    parameters: "Parameter"
 #    returns: "Returns"
 #    granted_by: "Granted by"


Update es-ES.coffee (#5730)

Update es-ES.coffee (#5730)

Co-authored-by: Valentin Bryukhanov <53545457af642b0f8712bb2cc004f120ce4436f4@gmail.com>

--- app/locale/es-ES.coffee
+++ app/locale/es-ES.coffee
@@ -156,6 +156,11 @@
     view_progress: "Ver mi progreso"
     go_to_courses: "Ir a mis cursos"
     want_coco: "¿Quieres CodeCombat en tu escuela?"
+    educator: "Educador"
+    student: "Estudiante"
+
+  nav:
+    educators: "Estudiantes"
 #    our_coding_programs: "Our Coding Programs"
 #    codecombat: "CodeCombat"
 #    ozaria: "Ozaria"
@@ -163,9 +168,6 @@
 #    ozaria_blurb: "An adventure game and Computer Science program where students master the lost magic of coding to save their world. Recommended for educators and students."
 #    try_codecombat: "Try CodeCombat"
 #    try_ozaria: "Try Ozaria"
-
-  nav:
-    educators: "Maestros"
     follow_us: "Síguenos"
     general: "General"
     map: "Mapa"
@@ -453,6 +455,7 @@
     coming_soon: "¡Próximamente!"
     continue: "Continuar" # When used as an action verb, like "Continue forward"
     next: "Siguiente"
+#    default_code: "Default Code"
     default_code: "Código por defecto"
     loading: "Cargando..."
     overview: "Vista global"
@@ -481,7 +484,7 @@
     save_changes: "Guardar cambios"
     required_field: "Campo requerido" # {change}
     submit: "Enviar"
-    replay: "Repetir"
+    replay: "Repetición"
     complete: "Completar"

   general:
@@ -616,8 +619,8 @@
     reload_really: "¿Estás seguro que quieres reiniciar el nivel?"
     reload_confirm: "Recargarlo todo"
 #    restart_really: "Are you sure you want to restart the level? You'll loose all the code you've written."
-    restart_confirm: "Sí, Reiniciar"
-#    test_level: "Test Level"
+    restart_confirm: "Si, reiniciar"
+    test_level: "Nivel de prueba"
     victory: "Victoria"
     victory_title_prefix: "¡"
     victory_title_suffix: " Completado!"
@@ -634,7 +637,7 @@
     victory_experience_gained: "XP Conseguida"
     victory_gems_gained: "Gemas Conseguidas"
     victory_new_item: "Nuevo artículo"
-    victory_new_hero: "Nuevo heroe"
+    victory_new_hero: "Nuevo Héroe"
     victory_viking_code_school: "Jolines, el nivel que acabas de pasar era difícil! Si todavía no eres un desarrollador de software, deberías serlo. Acabas de conseguir una aceptación por vía rápida con la Escuela Vikinga de Có, donde tú puedes llevar tus habilidades al siguiente nivel y convertirte en un desarrollador web profesional en 14 semanas."
     victory_become_a_viking: "Convertirse en un vikingo"
     victory_no_progress_for_teachers: "El progreso no se guarda para los profesores. Pero, puedes añadir una cuenta de estudiante a tu clase para ti."
@@ -840,8 +843,8 @@
     difficulty: "Dificultad"
     gold_collected: "Oro conseguido"
     survival_time: "Sobrevivió"
-#    defeated: "Enemies Defeated"
-#    code_length: "Lines of Code"
+    defeated: "Enemigos Derrotados"
+    code_length: "Líneas de código"
 #    score_display: "__scoreType__: __score__"

   inventory:
@@ -871,9 +874,9 @@
     prompt_button: "Ingresa a la tienda"
     recovered: "Las gemas compradas con anterioridad han sido recuperadas. Por favor, actualiza la página."
     price: "x{{gems}} / mes"
-#    buy_premium: "Buy Premium"
-    purchase: "Compra"
-#    purchased: "Purchased"
+    buy_premium: "Comprar Premium"
+    purchase: "Comprar"
+    purchased: "Comprado"

 #  subscribe_for_gems:
 #    prompt_title: "Not Enough Gems!"
@@ -1035,7 +1038,7 @@
     restricted_to_certain_heroes: "Solo ciertos héroes pueden jugar este nivel."

   char_customization_modal:
-    heading: "Personalizar tu héroe"
+    heading: "Personalizar tu héroeo"
     body: "Cuerpo"
     name_label: "Nombre del héroe"
     hair_label: "Color de pelo"


Bind to member method rather than lambda to avoid explicit @ receiver

--- js-library/app/js/jail_iframe/classes/icon_buttons.coffee
+++ js-library/app/js/jail_iframe/classes/icon_buttons.coffee
@@ -96,10 +96,9 @@

     @_iconButton.fadeIn()

-    FactlinkJailRoot.on 'reposition_icons', ->
-        @_iconButton.resetOffset(@_targetOffset())
-      , @
+    FactlinkJailRoot.on 'reposition_icons', @_reposition_icons

+  _reposition_icons: => @_iconButton.resetOffset(@_targetOffset())

   _targetOffset: ->
     # Calculate a vertical percentage to position the icon relative
@@ -111,9 +110,8 @@

     "#{verticalOffsetPercentage}% 0"

-
   destroy: ->
-    FactlinkJailRoot.off null, null, @
+    FactlinkJailRoot.off 'reposition_icons', @_reposition_icons
     @_iconButton.destroy()

   _onHover: =>


Use server React with addons by default.

--- app/server/index.coffee
+++ app/server/index.coffee
@@ -8,7 +8,7 @@
     require '../../tmp/deps.js'

   # Mock client-side stuff for server-side.
-  global.React = require 'react'
+  global.React = require 'react/addons'
   doc = require('jsdom').jsdom()
   global.window = doc.parentWindow
   global.document = doc.parentWindow.document


Increment style

Increment style

--- lib/assets/javascripts/cable/connection_monitor.js.coffee
+++ lib/assets/javascripts/cable/connection_monitor.js.coffee
@@ -20,8 +20,7 @@
     @pingedAt = now()

   disconnected: ->
-    if @reconnectAttempts is 0
-      @reconnectAttempts += 1
+    if @reconnectAttempts++ is 0
       setTimeout =>
         @consumer.connection.open() unless @consumer.connection.isOpen()
       , 200
@@ -57,7 +56,7 @@

   reconnectIfStale: ->
     if @connectionIsStale()
-      @reconnectAttempts += 1
+      @reconnectAttempts++
       @consumer.connection.reopen()

   connectionIsStale: ->


Adjust the known keys for the added `page_size` key

--- src/loaders/collection-loader.coffee
+++ src/loaders/collection-loader.coffee
@@ -4,7 +4,7 @@
 Collection = require '../collection'
 AbstractLoader = require './abstract-loader'

-knownResponseKeys = ['count', 'page_count', 'page_number', 'results']
+knownResponseKeys = ['count', 'page_count', 'page_number', 'page_size', 'results']

 class CollectionLoader extends AbstractLoader


add loading screen with a bit of delay

add loading screen with a bit of delay

--- coach/src/course/new-registration.cjsx
+++ coach/src/course/new-registration.cjsx
@@ -26,21 +26,27 @@

   getDefaultProps: ->
     title: 'Register for this Concept Coach course'
+
+  getInitialState: ->
+    course: getCourse.call(@)
     isAutoRegistering: false

   componentWillMount: ->
-    course = getCourse.call(@)
-    @registerIfReady(course)
-    course.channel.on('change', @onCourseChange)
-    @setState({course})
+    @state.course.channel.on('change', @onCourseChange)
+    @registerIfReady()

   componentWillUnmount: ->
     @state.course.channel.off('change', @onCourseChange)

-  registerIfReady: (course) ->
-    if User.isLoggedIn() and @props.enrollmentCode and not course.isRegistered()
-      course.register(@props.enrollmentCode, User)
-      @setState(isAutoRegistering: true)
+  registerIfReady: ->
+    {course} = @state
+
+    if User.isLoggedIn() and
+      @props.enrollmentCode and
+      not course.isPending() and
+      not course.isRegistered()
+        course.register(@props.enrollmentCode, User)
+        @setState(isAutoRegistering: true)

   onComplete: ->
     @state.course.persist(User)
@@ -48,11 +54,14 @@

   onCourseChange: ->
     if @state.course.isRegistered()
-      @setState(isAutoRegistering: false)
       # wait 1.5 secs so our success message is briefly displayed, then call onComplete
       _.delay(@onComplete, 1500)
     else if @state.course.isValidated()
       @onComplete()
+    else if @state.course.isPending()
+      _.delay( =>
+        @setState(isAutoRegistering: false)
+      , 1000)

     @forceUpdate()

@@ -60,29 +69,33 @@

Redirecting to login...

   renderComplete: (course) ->
-

-      You have successfully joined {course.description()}
-

+

+

+        You have successfully joined {course.description()}
+

+
We are loading your first exercise.

+

   isTeacher: ->
     User.isTeacherForCourse(@props.collectionUUID)

   renderCurrentStep: ->
     {course, isAutoRegistering} = @state
+
     if course.isValidated()
       @renderValidated()
+    else if isAutoRegistering
+

+
Please wait while we enroll you in this course.

+        <LaptopAndMug height=400 />
+

+    else if course.isPending()
+      <ConfirmJoin course={course} />
     else if course.isIncomplete()
       title = if @isTeacher() then '' else @props.title
       <EnrollmentCodeInput course={course} currentCourses={User.registeredCourses()} title={title} />
     else if course.isConflicting()
       <JoinConflict course={course} />
-    else if course.isPending()
-      <ConfirmJoin course={course} />
-    else if isAutoRegistering
-

-
Please wait while we enroll you in this course.

-        <LaptopAndMug height=400 />
-

     else
       @renderComplete(course)


Update _tracklist.coffee

Update _tracklist.coffee

--- coffee/_tracklist.coffee
+++ coffee/_tracklist.coffee
@@ -79,7 +79,7 @@
       )

     menu.append new gui.MenuItem(
-      label: l10n.get('open_in_youtube'),
+      label: 'Open in Youtube',
       click: ->
         artist = _this.find('.artist').text()
         title = _this.find('.title').text()


Redundant modal.show() removed

Redundant modal.show() removed

--- coffee/codesearch.coffee
+++ coffee/codesearch.coffee
@@ -20,8 +20,6 @@
         id = $(this).data('id')
         my_display_url = display_url(id)
         my_ajax_url = ajax_display_url(id)
-
-        $('#myModal').modal(show=true)

         title = basename $(event.target).text()


Remove unused require

Remove unused require

--- spec/autosave-spec.coffee
+++ spec/autosave-spec.coffee
@@ -1,5 +1,5 @@
 Autosave = require '../lib/autosave'
-{$, WorkspaceView, Editor} = require 'atom'
+{$, WorkspaceView} = require 'atom'

 describe "Autosave", ->
   [initialActiveItem, otherItem1, otherItem2] = []


Social: Fix copy of filter warning

Social: Fix copy of filter warning

--- client/Social/Activity/AppView.coffee
+++ client/Social/Activity/AppView.coffee
@@ -166,6 +166,6 @@

   showWarning:(tag)->
     @warning.updatePartial \
-      """You are now looking activities tagged with ##{tag} """
+      """You are now looking at activities tagged with ##{tag} """

     @show()


fix all-day event, refs #285

fix all-day event, refs #285

--- client/app/views/calendar_view.coffee
+++ client/app/views/calendar_view.coffee
@@ -217,9 +217,12 @@
         # Let's split them to put them in the right element.
         $displayedElement = $element.find '.fc-title'
         titleAndTime = $displayedElement.html()
-        [time, title...] = titleAndTime.split ' '
-        title = title.join ' '
-
+        if event.allDay
+            time = ''
+            title = titleAndTime
+        else
+            [time, title...] = titleAndTime.split ' '
+            title = title.join ' '
         # Append the right values to the right elements.
         $element.find('.fc-time').html time
         $element.find('.fc-title').html title


Nodejs: metrics: remove node process taglist

--- node_modules_koding/koding-datadog/lib/metricsbase.coffee
+++ node_modules_koding/koding-datadog/lib/metricsbase.coffee
@@ -2,54 +2,6 @@
 DogStatsD = require './dogstatsd'

 module.exports = class MetricsBase
-
-  @commonTagList : {
-
-    process : [
-      'cwd'
-      'pid'
-      'argv'
-      'title'
-      'getuid'
-      'getgid'
-      'version'
-      'platform'
-      'execPath'
-    ]
-
-    os : [
-      'type'
-      'release'
-      'hostname'
-    ]
-
-  }
-
-
-  @populateCommonTags : (tags) ->
-
-    tags ?= []
-
-    for key, value of @commonTagList
-      for prop in value
-        tagName  = "#{key}.#{prop}"
-        tagValue = ''
-
-        source = switch
-          when key is 'process'  then process
-          when key is 'os'       then os
-
-        tagValue = switch
-          when not source[prop]                   then 0
-          when Array.isArray source[prop]         then source[prop].join()
-          when typeof source[prop] is 'function'  then source[prop]()
-          else                                         source[prop]
-
-        tagValue = @sanitizeTagValue tagValue
-        tags.push "#{tagName}:#{tagValue}"
-
-    return tags
-

   @sanitizeTagValue : (tagValue) ->


data.first_build is the build data

data.first_build is the build data

--- js/inner/repo.coffee
+++ js/inner/repo.coffee
@@ -39,7 +39,7 @@
         _gaq.push(['_trackEvent', 'Repos', 'Add']);
         @following(true)
         if data.first_build
-          VM.visit_local_url data.build_url
+          VM.visit_local_url data.first_build.build_url
         else
           $('html, body').animate({ scrollTop: 0 }, 0);
           VM.loadRecentBuilds()


Fix visual indents when document row doesn't match screen row.

--- app/views/play/level/tome/SpellView.coffee
+++ app/views/play/level/tome/SpellView.coffee
@@ -263,6 +263,7 @@
           return true

     if me.level() < 20 or aceConfig.indentGuides
+      # Add visual ident guides
       @aceSession.addDynamicMarker
         update: (html, markerLayer, session, config) =>
           Range = ace.require('ace/range').Range
@@ -281,17 +282,21 @@
           for row in [0..@aceSession.getLength()]
             foldWidgets[row] = @aceSession.getFoldWidget(row) unless foldWidgets[row]?
             continue unless foldWidgets? and foldWidgets[row] is "start"
-            range = @aceSession.getFoldWidgetRange(row)
-            if not range?
+            docRange = @aceSession.getFoldWidgetRange(row)
+            if not docRange?
               guess = startOfRow(row)
-              range = new Range(row,guess,row,guess+4)
-
-            if /^\s+$/.test lines[range.end.row+1]
-              range.end.row += 1
-
-            xstart = startOfRow(range.start.row)
+              docRange = new Range(row,guess,row,guess+4)
+
+            if /^\s+$/.test lines[docRange.end.row+1]
+              docRange.end.row += 1
+
+            rstart = @aceSession.documentToScreenPosition docRange.start.row, docRange.start.column
+            rend = @aceSession.documentToScreenPosition docRange.end.row, docRange.end.column
+            range = new Range rstart.row, rstart.column, rend.row, rend.column
+
+            xstart = startOfRow(row)
             level = Math.floor(xstart / 4)
-            indent = startOfRow(range.start.row + 1)
+            indent = startOfRow(row + 1)
             color = colors[level % colors.length]
             t = markerLayer.$getTop(range.start.row + 1, config)
             h = config.lineHeight * (range.end.row - range.start.row)


DropdownView: allow icons prefixed with

DropdownView: allow icons prefixed with

--- coffee/src/views/dropdown_view.coffee
+++ coffee/src/views/dropdown_view.coffee
@@ -8,7 +8,7 @@
   template: _.template '''
     <button class="btn dropdown-toggle" data-toggle="dropdown" href="#">
       <% if (selected.icon) { %>
-        <i class="icon-<%= selected.icon %>">
+        <i class="icon-<%= selected.icon.replace(/^icon-/, '') %>">
       <% } %>
       <%= selected.name %>

@@ -17,7 +17,7 @@
       <% _.each(items, function(item) { %>

           <% if (item.icon) { %>
-            <i class="icon-<%= item.icon %>">
+            <i class="icon-<%= item.icon.replace(/^icon-/, '') %>">
           <% } %>
           <%= item.name %>
           <% if (item.name == selected.name) { %>


Fixing categorization

Fixing categorization

--- config/categories.coffee
+++ config/categories.coffee
@@ -28,7 +28,7 @@
       'pc'
       'mac'
       'psx'
-      'xbox 360'
+      'xbox'
       'wii'
       'handheld'
       'ios'


Remove unnecessary quotes.

Remove unnecessary quotes.

--- coffeescripts/cards-es.coffee
+++ coffeescripts/cards-es.coffee
@@ -839,10 +839,10 @@
             name: "Llamarada"
             ship: "Caza TIE/sf"
             text: '''Cuando ataques a una nave dentro de tu arco de fuego auxiliar,  puedes añadir 1 resultado %CRIT%.'''
-        '"Omega Specialist"':
+        'Omega Specialist':
             name: "Especialista Omega"
             ship: "Caza TIE/sf"
-        '"Zeta Specialist"':
+        'Zeta Specialist':
             name: "Especialista Zeta"
             ship: "Caza TIE/sf"
         'Fenn Rau':


lower case

lower case

--- project/coffee/view/modals/OverlayContent.coffee
+++ project/coffee/view/modals/OverlayContent.coffee
@@ -1,5 +1,5 @@
 AbstractModal = require './AbstractModal'
-BreadCrumbs   = require '../components/BreadCrumbs'
+BreadCrumbs   = require '../components/Breadcrumbs'
 ObjectsList   = require '../components/ObjectsList'

 class OverlayContent extends AbstractModal


Fixed vertical positioning of button when scrolling

--- js-library/app/js/jail_iframe/classes/buttons.coffee
+++ js-library/app/js/jail_iframe/classes/buttons.coffee
@@ -47,6 +47,7 @@

   placeNearSelection: (mouseX=null) ->
     selectionBox = window.document.getSelection().getRangeAt(0).getBoundingClientRect()
+    top = selectionBox.top + $(window).scrollTop() - 2
     left = mouseX ? (selectionBox.left + selectionBox.width/2)

-    @setCoordinates selectionBox.top, left
+    @setCoordinates top, left