SendGraphChanges: batch node metadata changes

Node metadata includes positions on the graph. The UI emits changes on
these for each event receive on the canvas, resulting as many changes
in the logs of the SendGraphChanges component.

Given that each metadata object contains all the information we need,
we can batch these.

--- components/SendGraphChanges.coffee
+++ components/SendGraphChanges.coffee
@@ -7,6 +7,7 @@
     @runtime = null
     @graph = null
     @changes = []
+    @changesStates = {}
     @subscribed = false
     @inPorts = new noflo.InPorts
       runtime:
@@ -83,6 +84,12 @@
       topic: topic
       payload: payload
     @outPorts.queued.send @changes.length
+    return @changes.length - 1
+
+  replaceChange: (offset, topic, payload) =>
+    @changes[offset] =
+      topic: topic
+      payload: payload

   addNode: (node) =>
     @registerChange 'addnode',
@@ -103,10 +110,15 @@
       graph: @graph.properties.id

   changeNode: (node) =>
-    @registerChange 'changenode',
+    key = 'changenode- ' + node.id
+    metadata =
       id: node.id
       metadata: node.metadata
       graph: @graph.properties.id
+    if @changesStates[key] is undefined
+      @changesStates[key] = @registerChange 'changenode', metadata
+    else
+      @replaceChange @changesStates[key], 'changenode', metadata

   addEdge: (edge) =>
     @registerChange 'addedge',
@@ -194,6 +206,7 @@
     @outPorts.sent.send true
     @outPorts.sent.endGroup() if @graph
     @outPorts.queued.send @changes.length
+    @changesStates = {}

   shutdown: ->
     do @unsubscribe


bugfix: link to original article was broken

--- overrides.coffee
+++ overrides.coffee
@@ -81,7 +81,7 @@
 rect_index = 0
 group_id = null

-bhl_link = document.querySelector('a[target=bhl]')
+bhl_link = document.querySelector('.drawing-controls a[target=bhl]')
 page_zoom = document.querySelector('input[name=pagezoom]')
 help = document.querySelector('input[name=help]')
 favorite = document.querySelector('input[name=favorite]')


Removing unnecessary dependencies from OrdersCtrl

--- app/assets/javascripts/darkswarm/controllers/orders_controller.js.coffee
+++ app/assets/javascripts/darkswarm/controllers/orders_controller.js.coffee
@@ -1,2 +1,2 @@
-Darkswarm.controller "OrdersCtrl", ($scope, $rootScope, $timeout, Orders, Search, $document, HashNavigation, FilterSelectorsService, EnterpriseModal, enterpriseMatchesNameQueryFilter, distanceWithinKmFilter) ->
+Darkswarm.controller "OrdersCtrl", ($scope, Orders) ->
   $scope.Orders = Orders


bugfix: id setter was bypassed

bugfix: id setter was bypassed

--- textures.coffee
+++ textures.coffee
@@ -269,7 +269,7 @@
     d = ""
     shapeRendering = "auto"
     fill = "transparent"
-    id = undefined
+    id = rand()

     # Contributions with custom paths are welcome,
     # for example have a look at the "hexagons" path below
@@ -307,7 +307,6 @@

     paths = (sel) ->
       path = svgPath d
-      id = rand()
       g = sel.append "defs"
         .append "pattern"
         .attr "id", id


fix(macAutocomplete): Don't allow ENTER in autocomplete to propagate up.

This was causing accidental form submissions.

--- src/directives/autocomplete.coffee
+++ src/directives/autocomplete.coffee
@@ -250,6 +250,10 @@
             $scope.$apply ->
               if $menuScope.items.length > 0
                 $menuScope.select $menuScope.index
+
+                # Prevent event from propagating up and possibly causing a form
+                # submission.
+                event.preventDefault()
           when keys.ESCAPE
             $scope.$apply -> reset()


The resourcePath should also be added to 'NODE_PATH'.

The core specs rely on this to find and run specs.

--- src/main.coffee
+++ src/main.coffee
@@ -16,7 +16,8 @@
     'vendor',
     'static',
     'node_modules',
-    'spec'
+    'spec',
+    '',
   ]

   homeDir = process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']


Enable console for hosts.

Enable console for hosts.

--- src/main.coffee
+++ src/main.coffee
@@ -47,7 +47,7 @@
 #=====================================================================

 getVmConsoleUrl = (xo, id) ->
-  vm = xo.getObject(id, 'VM')
+  vm = xo.getObject(id, ['VM', 'VM-controller'])
   return unless vm?.power_state is 'Running'

   {sessionId} = xo.getXAPI(vm)


Rename Signature to MethodSignature

Rename Signature to MethodSignature

--- src/mock.coffee
+++ src/mock.coffee
@@ -1,5 +1,4 @@
-# TODO: rename to MethodSignature?
-class Signature
+class MethodSignature

   constructor: (method_name) ->
     @method_name = method_name
@@ -23,7 +22,7 @@

   expects: (method_name) ->
     @_check_expects_usage(method_name)
-    @current_signature = new Signature(method_name)
+    @current_signature = new MethodSignature(method_name)
     @method_calls.push(@current_signature)
     @[ method_name ] = @_define_expected_method(method_name)
     @_set_state("expects")


GitProviders: GitLab: simply flow by supporting oauth_token

--- workers/social/lib/social/models/gitprovider/gitlabprovider.coffee
+++ workers/social/lib/social/models/gitprovider/gitlabprovider.coffee
@@ -27,39 +27,6 @@
   return templateData

-getPrivateToken = (options, callback) ->
-
-  { urlData: { baseUrl }, token, juser } = options
-
-  params    =
-    url     : "#{baseUrl}/api/v3/user"
-    headers : { Authorization : "Bearer #{token}" }
-
-  request.get params, (err, res, body) ->
-    return callback err  if err
-
-    try
-      data  = JSON.parse body
-      token = data.private_token
-    catch e
-      return callback new KodingError 'Auth failed'
-
-    unless token
-      console.log '[GITLAB] TOKEN NOT FOUND ON DATA:', data
-      return callback new KodingError 'Auth failed'
-
-    juser.update {
-      $set: {
-        'foreignAuth.gitlab.privateToken': token
-      }
-    }, (err) ->
-
-      console.warn 'Error while updating private token:', err  if err
-
-      # Swallow update error here thus we don't need it ~GG
-      callback null, token
-
-
 module.exports = GitLabProvider =

   getConfig: (client) ->
@@ -91,10 +58,10 @@
     unless urlData = @parseImportData { url, repo, gitlabHost }
       return no

-    if privateToken or privateToken = oauth.privateToken
-      @importStackTemplateWithPrivateToken privateToken, urlData, callback
-    else if oauth.token
-      @importStackTemplateWithOauth oauth, urlData, user, callback
+    if privateToken or oauthToken = oauth.token
+      @importStackTemplateWithToken {
+        privateToken, oauthToken
+      }, urlData, callback
     else
       @importStackTemplateWithRawUrl urlData, callback

@@ -145,34 +112,32 @@
     return { originalUrl : url, baseUrl, user, repo, branch }

-  importStackTemplateWithOauth: (oauth, urlData, juser, callback) ->
-
-    { baseUrl, user, repo, branch } = urlData
-    { token } = oauth
-
-    getPrivateToken { urlData, token, juser }, (err, privateToken) =>
-
-      return callback err  if err
-
-      @importStackTemplateWithPrivateToken privateToken, urlData, callback
-
-
-  importStackTemplateWithPrivateToken: (privateToken, urlData, callback) ->
-
+  importStackTemplateWithToken: (tokens, urlData, callback) ->
+
+    { privateToken, oauthToken }    = tokens
     { baseUrl, user, repo, branch } = urlData
     { TEMPLATE_PATH, README_PATH }  = Constants

-    gitlab  = GitlabAPI {
-      url   : baseUrl
-      token : privateToken
-    }
+    apiOptions = { url: baseUrl }
+
+    if oauthToken
+      apiOptions.oauth_token = oauthToken
+    else if privateToken
+      apiOptions.private_token = privateToken
+    else
+      return callback new KodingError 'Token not provided'
+
+    gitlab = GitlabAPI apiOptions

     queue = [

       (next) ->

         gitlab.projects.all (projects) ->
-          project = projects.filter((item) -> item.path_with_namespace is "#{user}/#{repo}")[0]
+
+          project = projects.filter((item) ->
+            item.path_with_namespace is "#{user}/#{repo}")[0]
+
           if project
           then next null, project.id
           else next new KodingError 'No repository found'
@@ -180,7 +145,11 @@
       (projectId, next) ->

         gitlab.projects.repository.listBranches projectId, (branches) ->
-          if branches.filter((_branch) -> _branch.name is branch).length is 0
+
+          branches = branches.filter (_branch) ->
+            _branch.name is branch
+
+          if not branches
           then next new KodingError 'No such branch exists'
           else next null, projectId


Suggested implementation to support feature #8

--- device-config-schema.coffee
+++ device-config-schema.coffee
@@ -3,6 +3,7 @@
   Smartmeterdevice: {
     title: "Smartmeter config options"
     type: "object"
+    extensions: ["xLink", "xAttributeOptions"]
     properties:
       serialport:
         description: "Serialport name (e.g. /dev/ttyUSB0)"


Float64Array is not supported by phantomjs, so use Array instead

--- bokehjs/src/coffee/main.coffee
+++ bokehjs/src/coffee/main.coffee
@@ -16,6 +16,10 @@
       exports: 'sprintf'

 define (require, exports, module) ->
+
+  if not window.Float64Array
+    console.warn("Float64Array is not supported. Using generic Array instead.")
+    window.Float64Array = Array

   Bokeh = {}


has bouncer, is broken

has bouncer, is broken

--- scripts/entrance.coffee
+++ scripts/entrance.coffee
@@ -11,7 +11,7 @@
     else if /shyguy/.test(username)
       msg.send "wb #{username}"
     else if /twerk/.test(username)
-      msg.send "wb #{username}!!!!!!!"
+      msg.send "#{username}: FIX YOUR DAMN BOUNCER!!!!!!!"
     else if /kin/.test(username)
       msg.send "Hello #{username}! We missed you!"
     else if /jense/i.test(username) or /prawn/i.test(username) or /sinjen/.test(username)


LeafletMap: Added double click event for markers. See #43158

--- src/elements/Map/LeafletMap.coffee
+++ src/elements/Map/LeafletMap.coffee
@@ -11,9 +11,9 @@

 	constructor: (@opts = {}) ->
 		if not CUI.LeafletMap.loadCSSPromise
-			CUI.LeafletMap.defaults.tileLayerOptions.maxZoom = CUI.Map.defaults.maxZoom
 			CUI.LeafletMap.loadCSSPromise = @__loadCSS()

+		CUI.LeafletMap.defaults.tileLayerOptions.maxZoom = CUI.Map.defaults.maxZoom
 		@__groups = {}
 		super(@opts)

@@ -85,8 +85,33 @@

 	__afterMarkerCreated: (marker, options) ->
 		onClickFunction = options["cui_onClick"]
+		onDoubleClickFunction = options["cui_onDoubleClick"]
+
+		# Workaround to fix a Leaflet non-implemented feature https://github.com/Leaflet/Leaflet/issues/108
+		if onClickFunction and onDoubleClickFunction
+			isDoubleClick = false
+
+			onClickFunction = (event) ->
+				CUI.setTimeout( ->
+					if not isDoubleClick
+						options["cui_onClick"](event)
+				, 200)
+
+			onDoubleClickFunction = (event) ->
+				isDoubleClick = true
+				options["cui_onDoubleClick"](event)
+				CUI.setTimeout( ->
+					isDoubleClick = false
+				, 250)
+
 		if onClickFunction
 			marker.on("click", onClickFunction)
+
+		if onDoubleClickFunction
+			@__map.doubleClickZoom.disable() # If at least 1 marker has 'onDoubleClick' event, the map disables zoom on double click.
+			marker.on("dblclick", onDoubleClickFunction)
+
+		return

 	__removeMarker: (marker) ->
 		for _, group of @__groups


firefox fix IME support in FindMode

firefox fix IME support in FindMode

--- pages/hud.coffee
+++ pages/hud.coffee
@@ -84,6 +84,12 @@
     hud.appendChild inputElement

     inputElement.addEventListener "input", executeQuery = (event) ->
+      # On Chrome when IME is on, the order of events is:
+      #   keydown, input.isComposing=true, keydown, input.true, ..., keydown, input.true, compositionend;
+      # while on Firefox, the order is: keydown, input.true, ..., input.true, keydown, compositionend, input.false.
+      # Therefore, check event.isComposing here, to avoid window focus changes during typing with IME,
+      # since such changes will prevent normal typing on Firefox (see #3480)
+      return if Utils.isFirefox() and event.isComposing
       # Replace \u00A0 ( ) with a normal space.
       findMode.rawQuery = inputElement.textContent.replace "\u00A0", " "
       UIComponentServer.postMessage {name: "search", query: findMode.rawQuery}


when spelling attempt failed, increase the fail counter by 1

--- cf/spell.coffee
+++ cf/spell.coffee
@@ -40,5 +40,6 @@
           alert("SUCCESS!")
           update_question(@data)
         else
+          @fail += 1
           alert("FAIL!")
     )


Add RouteMixin to admin/users route

Add RouteMixin to admin/users route

[#96757124]

--- review_front/app/routes/admin/users.coffee
+++ review_front/app/routes/admin/users.coffee
@@ -1,10 +1,13 @@
 `import Ember from 'ember'`
+`import RouteMixin from 'ember-cli-pagination/remote/route-mixin'`

-AdminUsers = Ember.Route.extend
-  model: ->
+AdminUsers = Ember.Route.extend RouteMixin,
+  perPage: 25
+
+  model: (params)->
     Ember.RSVP.hash
       projects: @store.find('project')
-      users: @store.find('user')
+      users: @findPaged 'user', params
       permissions: @store.find('permission')

 `export default AdminUsers`


Move setClasses to own method instead of in positionTooltip

--- tooltips.coffee
+++ tooltips.coffee
@@ -31,6 +31,9 @@
 	Tooltip.direction = DIRECTION_MAP[direction] if direction
 	dep.changed()

+setClasses = (classes) ->
+	Tooltip.classes = classes or ''
+
 hideTooltip = ->
 	setTooltip false

@@ -44,8 +47,6 @@
 	position = $el.offset()
 	offLeft = $el.attr('data-tooltip-left')
 	offTop = $el.attr('data-tooltip-top')
-
-	classes = $el.attr('data-tooltip-classes');

 	if _.isUndefined(offLeft)
 		offLeft = 0
@@ -85,6 +86,7 @@

 	setTooltip(content)
 	setPosition(top: 0, left: 0)
+	setClasses($el.attr('data-tooltip-classes'))

 	Tracker.afterFlush -> positionTooltip($el)


Moved main code into then() for actual promise resolution

--- src/beautifiers/rubocop.coffee
+++ src/beautifiers/rubocop.coffee
@@ -33,35 +33,35 @@
       rubocopPath = paths.find((p) -> p and path.isAbsolute(p)) or "rubocop"
       @verbose('rubocopPath', rubocopPath)
       @debug('rubocopPath', rubocopPath, paths)
+
+      # Find or generate a config file if non exists
+      configFile = @findFile(path.dirname(fullPath), ".rubocop.yml")
+      if !configFile?
+        yaml = require("yaml-front-matter")
+        config = {
+          "Style/IndentationWidth":
+            "Width": options.indent_size
+        }
+        tempConfig = @tempFile("rubocop-config", yaml.safeDump(config))
+
+      rubocopArguments = [
+        "--auto-correct"
+        "--force-exclusion"
+        "--stdin", relativePath || temp.path({suffix: '.rb'})
+      ]
+      rubocopArguments.push("--config", tempConfig) if tempConfig?
+      @debug("rubocop arguments", rubocopArguments)
+
+      @run(rubocopPath, rubocopArguments, {
+        ignoreReturnCode: true,
+        cwd: projectPath,
+        onStdin: (stdin) -> stdin.end text
+      }).then((stdout) =>
+        @debug("rubocop output", stdout)
+        # Rubocop output an error if stdout is empty
+        return text if stdout.length == 0
+
+        result = stdout.split("====================\n")
+        result[result.length - 1]
+      )
     )
-
-    # Find or generate a config file if non exists
-    configFile = @findFile(path.dirname(fullPath), ".rubocop.yml")
-    if !configFile?
-      yaml = require("yaml-front-matter")
-      config = {
-        "Style/IndentationWidth":
-          "Width": options.indent_size
-      }
-      tempConfig = @tempFile("rubocop-config", yaml.safeDump(config))
-
-    rubocopArguments = [
-      "--auto-correct"
-      "--force-exclusion"
-      "--stdin", relativePath || temp.path({suffix: '.rb'})
-    ]
-    rubocopArguments.push("--config", tempConfig) if tempConfig?
-    @debug("rubocop arguments", rubocopArguments)
-
-    @run(rubocopPath, rubocopArguments, {
-      ignoreReturnCode: true,
-      cwd: projectPath,
-      onStdin: (stdin) -> stdin.end text
-    }).then((stdout) =>
-      @debug("rubocop output", stdout)
-      # Rubocop output an error if stdout is empty
-      return text if stdout.length == 0
-
-      result = stdout.split("====================\n")
-      result[result.length - 1]
-    )


[browser] Add `set/getLastFocusedWindow` method

--- src/browser/Application.coffee
+++ src/browser/Application.coffee
@@ -13,6 +13,7 @@
 module.exports = class Application extends EventEmitter

     windows         : null
+    lastFocusedWindow   : null
     options         : null
     packageJson     : null

@@ -33,6 +34,10 @@
             options ?= @options
             new AppWindow(options)

+    ###
+    Window Managements
+    ###
+
     addWindow       : (window) ->
         @windows.push window
         return
@@ -41,3 +46,11 @@
         @windows.splice index, 1 for index, w of @windows when w is window
         return

+    setLastFocusedWindow    : (window) ->
+        AppWindow.lastFocusedWindow = window
+        return
+
+    getLastFocusedWindow    : ->
+        AppWindow.lastFocusedWindow
+        return
+


Make menu bar auto-hide.

Make menu bar auto-hide.

--- src/browser/atom-window.coffee
+++ src/browser/atom-window.coffee
@@ -27,7 +27,7 @@

     global.atomApplication.addWindow(this)

-    @browserWindow = new BrowserWindow show: false, title: 'Atom', icon: @constructor.iconPath
+    @browserWindow = new BrowserWindow show: false, title: 'Atom', icon: @constructor.iconPath, 'auto-hide-menu-bar': true
     @handleEvents()

     loadSettings = _.extend({}, settings)


Conditionally include deprecations in Pane

--- src/pane.coffee
+++ src/pane.coffee
@@ -42,7 +42,7 @@

   # Called by the Serializable mixin during serialization.
   serializeParams: ->
-    if typeof @activeItem?.getURI is 'function'
+    if Grim.includeDeprecations and typeof @activeItem?.getURI is 'function'
       activeItemURI = @activeItem.getURI()
     else if typeof @activeItem?.getUri is 'function'
       activeItemURI = @activeItem.getUri()
@@ -203,7 +203,466 @@
   onWillDestroyItem: (callback) ->
     @emitter.on 'will-destroy-item', callback

-  on: (eventName) ->
+  # Called by the view layer to indicate that the pane has gained focus.
+  focus: ->
+    @focused = true
+    @activate() unless @isActive()
+
+  # Called by the view layer to indicate that the pane has lost focus.
+  blur: ->
+    @focused = false
+    true # if this is called from an event handler, don't cancel it
+
+  isFocused: -> @focused
+
+  getPanes: -> [this]
+
+  unsubscribeFromItem: (item) ->
+    @itemSubscriptions.get(item)?.dispose()
+    @itemSubscriptions.delete(item)
+
+  ###
+  Section: Items
+  ###
+
+  # Public: Get the items in this pane.
+  #
+  # Returns an {Array} of items.
+  getItems: ->
+    @items.slice()
+
+  # Public: Get the active pane item in this pane.
+  #
+  # Returns a pane item.
+  getActiveItem: -> @activeItem
+
+  setActiveItem: (activeItem) ->
+    unless activeItem is @activeItem
+      @activeItem = activeItem
+      @emitter.emit 'did-change-active-item', @activeItem
+    @activeItem
+
+  # Return an {TextEditor} if the pane item is an {TextEditor}, or null otherwise.
+  getActiveEditor: ->
+    @activeItem if @activeItem instanceof TextEditor
+
+  # Public: Return the item at the given index.
+  #
+  # * `index` {Number}
+  #
+  # Returns an item or `null` if no item exists at the given index.
+  itemAtIndex: (index) ->
+    @items[index]
+
+  # Public: Makes the next item active.
+  activateNextItem: ->
+    index = @getActiveItemIndex()
+    if index < @items.length - 1
+      @activateItemAtIndex(index + 1)
+    else
+      @activateItemAtIndex(0)
+
+  # Public: Makes the previous item active.
+  activatePreviousItem: ->
+    index = @getActiveItemIndex()
+    if index > 0
+      @activateItemAtIndex(index - 1)
+    else
+      @activateItemAtIndex(@items.length - 1)
+
+  # Public: Move the active tab to the right.
+  moveItemRight: ->
+    index = @getActiveItemIndex()
+    rightItemIndex = index + 1
+    @moveItem(@getActiveItem(), rightItemIndex) unless rightItemIndex > @items.length - 1
+
+  # Public: Move the active tab to the left
+  moveItemLeft: ->
+    index = @getActiveItemIndex()
+    leftItemIndex = index - 1
+    @moveItem(@getActiveItem(), leftItemIndex) unless leftItemIndex < 0
+
+  # Public: Get the index of the active item.
+  #
+  # Returns a {Number}.
+  getActiveItemIndex: ->
+    @items.indexOf(@activeItem)
+
+  # Public: Activate the item at the given index.
+  #
+  # * `index` {Number}
+  activateItemAtIndex: (index) ->
+    @activateItem(@itemAtIndex(index))
+
+  # Public: Make the given item *active*, causing it to be displayed by
+  # the pane's view.
+  activateItem: (item) ->
+    if item?
+      @addItem(item)
+      @setActiveItem(item)
+
+  # Public: Add the given item to the pane.
+  #
+  # * `item` The item to add. It can be a model with an associated view or a
+  #   view.
+  # * `index` (optional) {Number} indicating the index at which to add the item.
+  #   If omitted, the item is added after the current active item.
+  #
+  # Returns the added item.
+  addItem: (item, index=@getActiveItemIndex() + 1) ->
+    return if item in @items
+
+    if typeof item.onDidDestroy is 'function'
+      @itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, true)
+    else if typeof item.on is 'function'
+      @subscribe item, 'destroyed', => @removeItem(item, true)
+
+    @items.splice(index, 0, item)
+    @emit 'item-added', item, index
+    @emitter.emit 'did-add-item', {item, index}
+    @setActiveItem(item) unless @getActiveItem()?
+    item
+
+  # Public: Add the given items to the pane.
+  #
+  # * `items` An {Array} of items to add. Items can be views or models with
+  #   associated views. Any objects that are already present in the pane's
+  #   current items will not be added again.
+  # * `index` (optional) {Number} index at which to add the items. If omitted,
+  #   the item is #   added after the current active item.
+  #
+  # Returns an {Array} of added items.
+  addItems: (items, index=@getActiveItemIndex() + 1) ->
+    items = items.filter (item) => not (item in @items)
+    @addItem(item, index + i) for item, i in items
+    items
+
+  removeItem: (item, destroyed=false) ->
+    index = @items.indexOf(item)
+    return if index is -1
+
+    if typeof item.on is 'function'
+      @unsubscribe item
+    @unsubscribeFromItem(item)
+
+    if item is @activeItem
+      if @items.length is 1
+        @setActiveItem(undefined)
+      else if index is 0
+        @activateNextItem()
+      else
+        @activatePreviousItem()
+    @items.splice(index, 1)
+    @emit 'item-removed', item, index, destroyed
+    @emitter.emit 'did-remove-item', {item, index, destroyed}
+    @container?.didDestroyPaneItem({item, index, pane: this}) if destroyed
+    @destroy() if @items.length is 0 and atom.config.get('core.destroyEmptyPanes')
+
+  # Public: Move the given item to the given index.
+  #
+  # * `item` The item to move.
+  # * `index` {Number} indicating the index to which to move the item.
+  moveItem: (item, newIndex) ->
+    oldIndex = @items.indexOf(item)
+    @items.splice(oldIndex, 1)
+    @items.splice(newIndex, 0, item)
+    @emit 'item-moved', item, newIndex
+    @emitter.emit 'did-move-item', {item, oldIndex, newIndex}
+
+  # Public: Move the given item to the given index on another pane.
+  #
+  # * `item` The item to move.
+  # * `pane` {Pane} to which to move the item.
+  # * `index` {Number} indicating the index to which to move the item in the
+  #   given pane.
+  moveItemToPane: (item, pane, index) ->
+    @removeItem(item)
+    pane.addItem(item, index)
+
+  # Public: Destroy the active item and activate the next item.
+  destroyActiveItem: ->
+    @destroyItem(@activeItem)
+    false
+
+  # Public: Destroy the given item.
+  #
+  # If the item is active, the next item will be activated. If the item is the
+  # last item, the pane will be destroyed if the `core.destroyEmptyPanes` config
+  # setting is `true`.
+  #
+  # * `item` Item to destroy
+  destroyItem: (item) ->
+    index = @items.indexOf(item)
+    if index isnt -1
+      @emit 'before-item-destroyed', item
+      @emitter.emit 'will-destroy-item', {item, index}
+      @container?.willDestroyPaneItem({item, index, pane: this})
+      if @promptToSaveItem(item)
+        @removeItem(item, true)
+        item.destroy?()
+        true
+      else
+        false
+
+  # Public: Destroy all items.
+  destroyItems: ->
+    @destroyItem(item) for item in @getItems()
+    return
+
+  # Public: Destroy all items except for the active item.
+  destroyInactiveItems: ->
+    @destroyItem(item) for item in @getItems() when item isnt @activeItem
+    return
+
+  promptToSaveItem: (item, options={}) ->
+    return true unless item.shouldPromptToSave?(options)
+
+    if typeof item.getURI is 'function'
+      uri = item.getURI()
+    else if typeof item.getUri is 'function'
+      uri = item.getUri()
+    else
+      return true
+
+    chosen = atom.confirm
+      message: "'#{item.getTitle?() ? uri}' has changes, do you want to save them?"
+      detailedMessage: "Your changes will be lost if you close this item without saving."
+      buttons: ["Save", "Cancel", "Don't Save"]
+
+    switch chosen
+      when 0 then @saveItem(item, -> true)
+      when 1 then false
+      when 2 then true
+
+  # Public: Save the active item.
+  saveActiveItem: (nextAction) ->
+    @saveItem(@getActiveItem(), nextAction)
+
+  # Public: Prompt the user for a location and save the active item with the
+  # path they select.
+  #
+  # * `nextAction` (optional) {Function} which will be called after the item is
+  #   successfully saved.
+  saveActiveItemAs: (nextAction) ->
+    @saveItemAs(@getActiveItem(), nextAction)
+
+  # Public: Save the given item.
+  #
+  # * `item` The item to save.
+  # * `nextAction` (optional) {Function} which will be called after the item is
+  #   successfully saved.
+  saveItem: (item, nextAction) ->
+    if typeof item?.getURI is 'function'
+      itemURI = item.getURI()
+    else if typeof item?.getUri is 'function'
+      itemURI = item.getUri()
+
+    if itemURI?
+      try
+        item.save?()
+      catch error
+        @handleSaveError(error)
+      nextAction?()
+    else
+      @saveItemAs(item, nextAction)
+
+  # Public: Prompt the user for a location and save the active item with the
+  # path they select.
+  #
+  # * `item` The item to save.
+  # * `nextAction` (optional) {Function} which will be called after the item is
+  #   successfully saved.
+  saveItemAs: (item, nextAction) ->
+    return unless item?.saveAs?
+
+    itemPath = item.getPath?()
+    newItemPath = atom.showSaveDialogSync(itemPath)
+    if newItemPath
+      try
+        item.saveAs(newItemPath)
+      catch error
+        @handleSaveError(error)
+      nextAction?()
+
+  # Public: Save all items.
+  saveItems: ->
+    @saveItem(item) for item in @getItems()
+    return
+
+  # Public: Return the first item that matches the given URI or undefined if
+  # none exists.
+  #
+  # * `uri` {String} containing a URI.
+  itemForURI: (uri) ->
+    find @items, (item) ->
+      if typeof item.getURI is 'function'
+        itemUri = item.getURI()
+      else if typeof item.getUri is 'function'
+        itemUri = item.getUri()
+
+      itemUri is uri
+
+  # Public: Activate the first item that matches the given URI.
+  #
+  # Returns a {Boolean} indicating whether an item matching the URI was found.
+  activateItemForURI: (uri) ->
+    if item = @itemForURI(uri)
+      @activateItem(item)
+      true
+    else
+      false
+
+  copyActiveItem: ->
+    if @activeItem?
+      @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
+
+  ###
+  Section: Lifecycle
+  ###
+
+  # Public: Determine whether the pane is active.
+  #
+  # Returns a {Boolean}.
+  isActive: ->
+    @container?.getActivePane() is this
+
+  # Public: Makes this pane the *active* pane, causing it to gain focus.
+  activate: ->
+    throw new Error("Pane has been destroyed") if @isDestroyed()
+
+    @container?.setActivePane(this)
+    @emit 'activated'
+    @emitter.emit 'did-activate'
+
+  # Public: Close the pane and destroy all its items.
+  #
+  # If this is the last pane, all the items will be destroyed but the pane
+  # itself will not be destroyed.
+  destroy: ->
+    if @container?.isAlive() and @container.getPanes().length is 1
+      @destroyItems()
+    else
+      super
+
+  # Called by model superclass.
+  destroyed: ->
+    @container.activateNextPane() if @isActive()
+    @emitter.emit 'did-destroy'
+    @emitter.dispose()
+    item.destroy?() for item in @items.slice()
+    @container?.didDestroyPane(pane: this)
+
+  ###
+  Section: Splitting
+  ###
+
+  # Public: Create a new pane to the left of this pane.
+  #
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
+  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
+  #
+  # Returns the new {Pane}.
+  splitLeft: (params) ->
+    @split('horizontal', 'before', params)
+
+  # Public: Create a new pane to the right of this pane.
+  #
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
+  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
+  #
+  # Returns the new {Pane}.
+  splitRight: (params) ->
+    @split('horizontal', 'after', params)
+
+  # Public: Creates a new pane above the receiver.
+  #
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
+  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
+  #
+  # Returns the new {Pane}.
+  splitUp: (params) ->
+    @split('vertical', 'before', params)
+
+  # Public: Creates a new pane below the receiver.
+  #
+  # * `params` (optional) {Object} with the following keys:
+  #   * `items` (optional) {Array} of items to add to the new pane.
+  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
+  #
+  # Returns the new {Pane}.
+  splitDown: (params) ->
+    @split('vertical', 'after', params)
+
+  split: (orientation, side, params) ->
+    if params?.copyActiveItem
+      params.items ?= []
+      params.items.push(@copyActiveItem())
+
+    if @parent.orientation isnt orientation
+      @parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
+
+    newPane = new @constructor(params)
+    switch side
+      when 'before' then @parent.insertChildBefore(this, newPane)
+      when 'after' then @parent.insertChildAfter(this, newPane)
+
+    newPane.activate()
+    newPane
+
+  # If the parent is a horizontal axis, returns its first child if it is a pane;
+  # otherwise returns this pane.
+  findLeftmostSibling: ->
+    if @parent.orientation is 'horizontal'
+      [leftmostSibling] = @parent.children
+      if leftmostSibling instanceof PaneAxis
+        this
+      else
+        leftmostSibling
+    else
+      this
+
+  # If the parent is a horizontal axis, returns its last child if it is a pane;
+  # otherwise returns a new pane created by splitting this pane rightward.
+  findOrCreateRightmostSibling: ->
+    if @parent.orientation is 'horizontal'
+      rightmostSibling = last(@parent.children)
+      if rightmostSibling instanceof PaneAxis
+        @splitRight()
+      else
+        rightmostSibling
+    else
+      @splitRight()
+
+  close: ->
+    @destroy() if @confirmClose()
+
+  confirmClose: ->
+    for item in @getItems()
+      return false unless @promptToSaveItem(item)
+    true
+
+  handleSaveError: (error) ->
+    if error.code is 'EISDIR' or error.message.endsWith('is a directory')
+      atom.notifications.addWarning("Unable to save file: #{error.message}")
+    else if error.code is 'EACCES' and error.path?
+      atom.notifications.addWarning("Unable to save file: Permission denied '#{error.path}'")
+    else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST'] and error.path?
+      atom.notifications.addWarning("Unable to save file '#{error.path}'", detail: error.message)
+    else if error.code is 'EROFS' and error.path?
+      atom.notifications.addWarning("Unable to save file: Read-only file system '#{error.path}'")
+    else if error.code is 'ENOSPC' and error.path?
+      atom.notifications.addWarning("Unable to save file: No space left on device '#{error.path}'")
+    else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
+      fileName = errorMatch[1]
+      atom.notifications.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
+    else
+      throw error
+
+if Grim.includeDeprecations
+  Pane::on = (eventName) ->
     switch eventName
       when 'activated'
         Grim.deprecate("Use Pane::onDidActivate instead")
@@ -221,7 +680,7 @@
         Grim.deprecate("Subscribing via ::on is deprecated. Use documented event subscription methods instead.")
     super

-  behavior: (behaviorName) ->
+  Pane::behavior = (behaviorName) ->
     switch behaviorName
       when 'active'
         Grim.deprecate("The $active behavior property is deprecated. Use ::observeActive or ::onDidChangeActive instead.")
@@ -236,468 +695,10 @@

     super

-  # Called by the view layer to indicate that the pane has gained focus.
-  focus: ->
-    @focused = true
-    @activate() unless @isActive()
-
-  # Called by the view layer to indicate that the pane has lost focus.
-  blur: ->
-    @focused = false
-    true # if this is called from an event handler, don't cancel it
-
-  isFocused: -> @focused
-
-  getPanes: -> [this]
-
-  unsubscribeFromItem: (item) ->
-    @itemSubscriptions.get(item)?.dispose()
-    @itemSubscriptions.delete(item)
-
-  ###
-  Section: Items
-  ###
-
-  # Public: Get the items in this pane.
-  #
-  # Returns an {Array} of items.
-  getItems: ->
-    @items.slice()
-
-  # Public: Get the active pane item in this pane.
-  #
-  # Returns a pane item.
-  getActiveItem: -> @activeItem
-
-  setActiveItem: (activeItem) ->
-    unless activeItem is @activeItem
-      @activeItem = activeItem
-      @emitter.emit 'did-change-active-item', @activeItem
-    @activeItem
-
-  # Return an {TextEditor} if the pane item is an {TextEditor}, or null otherwise.
-  getActiveEditor: ->
-    @activeItem if @activeItem instanceof TextEditor
-
-  # Public: Return the item at the given index.
-  #
-  # * `index` {Number}
-  #
-  # Returns an item or `null` if no item exists at the given index.
-  itemAtIndex: (index) ->
-    @items[index]
-
-  # Public: Makes the next item active.
-  activateNextItem: ->
-    index = @getActiveItemIndex()
-    if index < @items.length - 1
-      @activateItemAtIndex(index + 1)
-    else
-      @activateItemAtIndex(0)
-
-  # Public: Makes the previous item active.
-  activatePreviousItem: ->
-    index = @getActiveItemIndex()
-    if index > 0
-      @activateItemAtIndex(index - 1)
-    else
-      @activateItemAtIndex(@items.length - 1)
-
-  # Public: Move the active tab to the right.
-  moveItemRight: ->
-    index = @getActiveItemIndex()
-    rightItemIndex = index + 1
-    @moveItem(@getActiveItem(), rightItemIndex) unless rightItemIndex > @items.length - 1
-
-  # Public: Move the active tab to the left
-  moveItemLeft: ->
-    index = @getActiveItemIndex()
-    leftItemIndex = index - 1
-    @moveItem(@getActiveItem(), leftItemIndex) unless leftItemIndex < 0
-
-  # Public: Get the index of the active item.
-  #
-  # Returns a {Number}.
-  getActiveItemIndex: ->
-    @items.indexOf(@activeItem)
-
-  # Public: Activate the item at the given index.
-  #
-  # * `index` {Number}
-  activateItemAtIndex: (index) ->
-    @activateItem(@itemAtIndex(index))
-
-  # Public: Make the given item *active*, causing it to be displayed by
-  # the pane's view.
-  activateItem: (item) ->
-    if item?
-      @addItem(item)
-      @setActiveItem(item)
-
-  # Public: Add the given item to the pane.
-  #
-  # * `item` The item to add. It can be a model with an associated view or a
-  #   view.
-  # * `index` (optional) {Number} indicating the index at which to add the item.
-  #   If omitted, the item is added after the current active item.
-  #
-  # Returns the added item.
-  addItem: (item, index=@getActiveItemIndex() + 1) ->
-    return if item in @items
-
-    if typeof item.onDidDestroy is 'function'
-      @itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, true)
-    else if typeof item.on is 'function'
-      @subscribe item, 'destroyed', => @removeItem(item, true)
-
-    @items.splice(index, 0, item)
-    @emit 'item-added', item, index
-    @emitter.emit 'did-add-item', {item, index}
-    @setActiveItem(item) unless @getActiveItem()?
-    item
-
-  # Public: Add the given items to the pane.
-  #
-  # * `items` An {Array} of items to add. Items can be views or models with
-  #   associated views. Any objects that are already present in the pane's
-  #   current items will not be added again.
-  # * `index` (optional) {Number} index at which to add the items. If omitted,
-  #   the item is #   added after the current active item.
-  #
-  # Returns an {Array} of added items.
-  addItems: (items, index=@getActiveItemIndex() + 1) ->
-    items = items.filter (item) => not (item in @items)
-    @addItem(item, index + i) for item, i in items
-    items
-
-  removeItem: (item, destroyed=false) ->
-    index = @items.indexOf(item)
-    return if index is -1
-
-    if typeof item.on is 'function'
-      @unsubscribe item
-    @unsubscribeFromItem(item)
-
-    if item is @activeItem
-      if @items.length is 1
-        @setActiveItem(undefined)
-      else if index is 0
-        @activateNextItem()
-      else
-        @activatePreviousItem()
-    @items.splice(index, 1)
-    @emit 'item-removed', item, index, destroyed
-    @emitter.emit 'did-remove-item', {item, index, destroyed}
-    @container?.didDestroyPaneItem({item, index, pane: this}) if destroyed
-    @destroy() if @items.length is 0 and atom.config.get('core.destroyEmptyPanes')
-
-  # Public: Move the given item to the given index.
-  #
-  # * `item` The item to move.
-  # * `index` {Number} indicating the index to which to move the item.
-  moveItem: (item, newIndex) ->
-    oldIndex = @items.indexOf(item)
-    @items.splice(oldIndex, 1)
-    @items.splice(newIndex, 0, item)
-    @emit 'item-moved', item, newIndex
-    @emitter.emit 'did-move-item', {item, oldIndex, newIndex}
-
-  # Public: Move the given item to the given index on another pane.
-  #
-  # * `item` The item to move.
-  # * `pane` {Pane} to which to move the item.
-  # * `index` {Number} indicating the index to which to move the item in the
-  #   given pane.
-  moveItemToPane: (item, pane, index) ->
-    @removeItem(item)
-    pane.addItem(item, index)
-
-  # Public: Destroy the active item and activate the next item.
-  destroyActiveItem: ->
-    @destroyItem(@activeItem)
-    false
-
-  # Public: Destroy the given item.
-  #
-  # If the item is active, the next item will be activated. If the item is the
-  # last item, the pane will be destroyed if the `core.destroyEmptyPanes` config
-  # setting is `true`.
-  #
-  # * `item` Item to destroy
-  destroyItem: (item) ->
-    index = @items.indexOf(item)
-    if index isnt -1
-      @emit 'before-item-destroyed', item
-      @emitter.emit 'will-destroy-item', {item, index}
-      @container?.willDestroyPaneItem({item, index, pane: this})
-      if @promptToSaveItem(item)
-        @removeItem(item, true)
-        item.destroy?()
-        true
-      else
-        false
-
-  # Public: Destroy all items.
-  destroyItems: ->
-    @destroyItem(item) for item in @getItems()
-    return
-
-  # Public: Destroy all items except for the active item.
-  destroyInactiveItems: ->
-    @destroyItem(item) for item in @getItems() when item isnt @activeItem
-    return
-
-  promptToSaveItem: (item, options={}) ->
-    return true unless item.shouldPromptToSave?(options)
-
-    if typeof item.getURI is 'function'
-      uri = item.getURI()
-    else if typeof item.getUri is 'function'
-      uri = item.getUri()
-    else
-      return true
-
-    chosen = atom.confirm
-      message: "'#{item.getTitle?() ? uri}' has changes, do you want to save them?"
-      detailedMessage: "Your changes will be lost if you close this item without saving."
-      buttons: ["Save", "Cancel", "Don't Save"]
-
-    switch chosen
-      when 0 then @saveItem(item, -> true)
-      when 1 then false
-      when 2 then true
-
-  # Public: Save the active item.
-  saveActiveItem: (nextAction) ->
-    @saveItem(@getActiveItem(), nextAction)
-
-  # Public: Prompt the user for a location and save the active item with the
-  # path they select.
-  #
-  # * `nextAction` (optional) {Function} which will be called after the item is
-  #   successfully saved.
-  saveActiveItemAs: (nextAction) ->
-    @saveItemAs(@getActiveItem(), nextAction)
-
-  # Public: Save the given item.
-  #
-  # * `item` The item to save.
-  # * `nextAction` (optional) {Function} which will be called after the item is
-  #   successfully saved.
-  saveItem: (item, nextAction) ->
-    if typeof item?.getURI is 'function'
-      itemURI = item.getURI()
-    else if typeof item?.getUri is 'function'
-      itemURI = item.getUri()
-
-    if itemURI?
-      try
-        item.save?()
-      catch error
-        @handleSaveError(error)
-      nextAction?()
-    else
-      @saveItemAs(item, nextAction)
-
-  # Public: Prompt the user for a location and save the active item with the
-  # path they select.
-  #
-  # * `item` The item to save.
-  # * `nextAction` (optional) {Function} which will be called after the item is
-  #   successfully saved.
-  saveItemAs: (item, nextAction) ->
-    return unless item?.saveAs?
-
-    itemPath = item.getPath?()
-    newItemPath = atom.showSaveDialogSync(itemPath)
-    if newItemPath
-      try
-        item.saveAs(newItemPath)
-      catch error
-        @handleSaveError(error)
-      nextAction?()
-
-  # Public: Save all items.
-  saveItems: ->
-    @saveItem(item) for item in @getItems()
-    return
-
-  # Public: Return the first item that matches the given URI or undefined if
-  # none exists.
-  #
-  # * `uri` {String} containing a URI.
-  itemForURI: (uri) ->
-    find @items, (item) ->
-      if typeof item.getURI is 'function'
-        itemUri = item.getURI()
-      else if typeof item.getUri is 'function'
-        itemUri = item.getUri()
-
-      itemUri is uri
-
-  itemForUri: (uri) ->
+  Pane::itemForUri = (uri) ->
     Grim.deprecate("Use `::itemForURI` instead.")
     @itemForURI(uri)

-  # Public: Activate the first item that matches the given URI.
-  #
-  # Returns a {Boolean} indicating whether an item matching the URI was found.
-  activateItemForURI: (uri) ->
-    if item = @itemForURI(uri)
-      @activateItem(item)
-      true
-    else
-      false
-
-  activateItemForUri: (uri) ->
+  Pane::activateItemForUri = (uri) ->
     Grim.deprecate("Use `::activateItemForURI` instead.")
     @activateItemForURI(uri)
-
-  copyActiveItem: ->
-    if @activeItem?
-      @activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
-
-  ###
-  Section: Lifecycle
-  ###
-
-  # Public: Determine whether the pane is active.
-  #
-  # Returns a {Boolean}.
-  isActive: ->
-    @container?.getActivePane() is this
-
-  # Public: Makes this pane the *active* pane, causing it to gain focus.
-  activate: ->
-    throw new Error("Pane has been destroyed") if @isDestroyed()
-
-    @container?.setActivePane(this)
-    @emit 'activated'
-    @emitter.emit 'did-activate'
-
-  # Public: Close the pane and destroy all its items.
-  #
-  # If this is the last pane, all the items will be destroyed but the pane
-  # itself will not be destroyed.
-  destroy: ->
-    if @container?.isAlive() and @container.getPanes().length is 1
-      @destroyItems()
-    else
-      super
-
-  # Called by model superclass.
-  destroyed: ->
-    @container.activateNextPane() if @isActive()
-    @emitter.emit 'did-destroy'
-    @emitter.dispose()
-    item.destroy?() for item in @items.slice()
-    @container?.didDestroyPane(pane: this)
-
-  ###
-  Section: Splitting
-  ###
-
-  # Public: Create a new pane to the left of this pane.
-  #
-  # * `params` (optional) {Object} with the following keys:
-  #   * `items` (optional) {Array} of items to add to the new pane.
-  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
-  #
-  # Returns the new {Pane}.
-  splitLeft: (params) ->
-    @split('horizontal', 'before', params)
-
-  # Public: Create a new pane to the right of this pane.
-  #
-  # * `params` (optional) {Object} with the following keys:
-  #   * `items` (optional) {Array} of items to add to the new pane.
-  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
-  #
-  # Returns the new {Pane}.
-  splitRight: (params) ->
-    @split('horizontal', 'after', params)
-
-  # Public: Creates a new pane above the receiver.
-  #
-  # * `params` (optional) {Object} with the following keys:
-  #   * `items` (optional) {Array} of items to add to the new pane.
-  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
-  #
-  # Returns the new {Pane}.
-  splitUp: (params) ->
-    @split('vertical', 'before', params)
-
-  # Public: Creates a new pane below the receiver.
-  #
-  # * `params` (optional) {Object} with the following keys:
-  #   * `items` (optional) {Array} of items to add to the new pane.
-  #   * `copyActiveItem` (optional) {Boolean} true will copy the active item into the new split pane
-  #
-  # Returns the new {Pane}.
-  splitDown: (params) ->
-    @split('vertical', 'after', params)
-
-  split: (orientation, side, params) ->
-    if params?.copyActiveItem
-      params.items ?= []
-      params.items.push(@copyActiveItem())
-
-    if @parent.orientation isnt orientation
-      @parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
-
-    newPane = new @constructor(params)
-    switch side
-      when 'before' then @parent.insertChildBefore(this, newPane)
-      when 'after' then @parent.insertChildAfter(this, newPane)
-
-    newPane.activate()
-    newPane
-
-  # If the parent is a horizontal axis, returns its first child if it is a pane;
-  # otherwise returns this pane.
-  findLeftmostSibling: ->
-    if @parent.orientation is 'horizontal'
-      [leftmostSibling] = @parent.children
-      if leftmostSibling instanceof PaneAxis
-        this
-      else
-        leftmostSibling
-    else
-      this
-
-  # If the parent is a horizontal axis, returns its last child if it is a pane;
-  # otherwise returns a new pane created by splitting this pane rightward.
-  findOrCreateRightmostSibling: ->
-    if @parent.orientation is 'horizontal'
-      rightmostSibling = last(@parent.children)
-      if rightmostSibling instanceof PaneAxis
-        @splitRight()
-      else
-        rightmostSibling
-    else
-      @splitRight()
-
-  close: ->
-    @destroy() if @confirmClose()
-
-  confirmClose: ->
-    for item in @getItems()
-      return false unless @promptToSaveItem(item)
-    true
-
-  handleSaveError: (error) ->
-    if error.code is 'EISDIR' or error.message.endsWith('is a directory')
-      atom.notifications.addWarning("Unable to save file: #{error.message}")
-    else if error.code is 'EACCES' and error.path?
-      atom.notifications.addWarning("Unable to save file: Permission denied '#{error.path}'")
-    else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST'] and error.path?
-      atom.notifications.addWarning("Unable to save file '#{error.path}'", detail: error.message)
-    else if error.code is 'EROFS' and error.path?
-      atom.notifications.addWarning("Unable to save file: Read-only file system '#{error.path}'")
-    else if error.code is 'ENOSPC' and error.path?
-      atom.notifications.addWarning("Unable to save file: No space left on device '#{error.path}'")
-    else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
-      fileName = errorMatch[1]
-      atom.notifications.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
-    else
-      throw error


Some whitespace for better readability of code.

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -35,7 +35,9 @@

         # Transform the working copy into the actual feeds list
         $rootScope.feeds = feeds_copy
+
       $rootScope.feeds_loaded = true
+
     .error ->
       timerFlagSvc.start 'error_loading_feeds'


Fixed unsubscribe functionality.

Fixed unsubscribe functionality.

With the recent changes, findSvc returns a null if it cannot find what it's been asked for (feeds in a folder in this case). This null return value was not properly supported in the angularjs code that is executed when the user clicks on "Unsubscribe".

I'll have to check if there are other places where this happens.

--- app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
+++ app/assets/javascripts/ng-services/ng-FeedsFoldersSvc.js.coffee
@@ -75,7 +75,8 @@
     folder = findSvc.find_folder folder_id
     if folder != null
       # Remove folder if it's empty
-      if findSvc.find_folder_feeds(folder).length == 0
+      feeds = findSvc.find_folder_feeds folder
+      if !feeds || feeds?.length == 0
         index = $rootScope.folders.indexOf folder
         $rootScope.folders.splice index, 1 if index != -1


Add a copy XDate to the object returned by getYearsAndWeeks()

--- app/assets/javascripts/views/_shared/date_driven_view.js.coffee
+++ app/assets/javascripts/views/_shared/date_driven_view.js.coffee
@@ -50,6 +50,7 @@

     while from < to
       @yearsAndWeeks.push
+        xdate:  new XDate(from)
         year:  from.getUTCFullYear()
         cweek: from.getWeek()
         month: from.getMonth() + 1 # NOTE: Months in moment.js are 0-indexed


Catch insert errors in reconfigure modal

--- admin/static/coffee/modals.coffee
+++ admin/static/coffee/modals.coffee
@@ -502,6 +502,8 @@
         driver.run_once query, (error, result) =>
             if error?
                 @model.set server_error: error.msg
+            else if result.first_error?
+                @model.set server_error: result.first_error
             else
                 @reset_buttons()
                 @remove()


Allows a function to be passed and evaluated in the constructor


Write .coffee_history in $XDG_CACHE_HOME (#4472)

* Write .coffee_history in $XDG_CACHE_HOME

Previously, if the $HOME environmental variable was set, the
.coffee_history file was written there. This is still the case, but
first we check if $XDG_CACHE_HOME is set, and if so we write it
there instead. This allows users who want to follow the XDG Base
Directory Specification [0] to do so. Actually, this helps out *all*
users who want to keep their home directory a bit cleaner. It seems
that using this standardized environmental variable is the cleanest
way to do this, rather than involving configuration files or command
line flags or anything else.

Fixes #3705.

[0] https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html

* Stylistic corrections

Corrected some style deficiencies, thanks to helpful advice from
@GeoffreyBooth.

--- src/repl.coffee
+++ src/repl.coffee
@@ -7,7 +7,9 @@

 replDefaults =
   prompt: 'coffee> ',
-  historyFile: path.join process.env.HOME, '.coffee_history' if process.env.HOME
+  historyFile: do ->
+    historyPath = process.env.XDG_CACHE_HOME or process.env.HOME
+    path.join historyPath, '.coffee_history' if historyPath
   historyMaxInputSize: 10240
   eval: (input, context, filename, cb) ->
     # XXX: multiline hack.


request restart on unhandled exceptions to avoid memory leak

--- services/filestore/app.coffee
+++ services/filestore/app.coffee
@@ -37,6 +37,8 @@
 	requestDomain.add req
 	requestDomain.add res
 	requestDomain.on "error", (err)->
+		# request a shutdown to prevent memory leaks
+		appIsOk = false
 		setTimeout(->
 			if !res.headerSent
 				res.send(500)


fix(rest): response object

fix(rest): response object

--- src/rest.coffee
+++ src/rest.coffee
@@ -34,7 +34,7 @@
     _.extend @options, _.omit options, 'baseUrl', 'debug', 'apikey'

   getRateLimit: (response) ->
-    headers = response.headers
+    headers = response?.headers

     if headers['x-ratelimit-limit']
       @rateLimit = headers['x-ratelimit-limit']


added delete user script in grunt

added delete user script in grunt

--- services/web/Gruntfile.coffee
+++ services/web/Gruntfile.coffee
@@ -356,4 +356,24 @@
 							#{settings.siteUrl}/user/password/set?passwordResetToken=#{token}

 					"""
-					done()+					done()
+
+	grunt.registerTask 'delete-user', "deletes a user and all their data", () ->
+		done = @async()
+		email = grunt.option("email")
+		if !email?
+			console.error "Usage: grunt delete-user --email joe@example.com"
+			process.exit(1)
+		settings = require "settings-sharelatex"
+		UserGetter = require "./app/js/Features/User/UserGetter"
+		UserDeleter = require "./app/js/Features/User/UserDeleter"
+		UserGetter.getUser email:email, (error, user) ->
+			if error?
+				throw error
+			if !user?
+				console.log("user #{email} not in database, potentially already deleted")
+				return done()
+			UserDeleter.deleteUser user._id, (err)->
+				if err?
+					throw err
+				done()


[Middleware] Guard via headersSent in HSTS

--- src/lib/middleware/hsts.coffee
+++ src/lib/middleware/hsts.coffee
@@ -10,5 +10,5 @@
 module.exports = (req, res, next) ->
   protocol = req.get('X-Forwarded-Proto') or req.protocol
   if protocol is 'https' and parse(APP_URL).protocol is 'https:'
-    res.set('Strict-Transport-Security', 'max-age=31536000')
+    res.set('Strict-Transport-Security', 'max-age=31536000') unless res.headersSent
   next()


changing router.coffee

changing router.coffee

--- src/scripts/router.coffee
+++ src/scripts/router.coffee
@@ -24,7 +24,7 @@

       # Match and extract uuid and page numbers separated by a colon
       #@route /^contents\/([^:]+):?([0-9]*)/, 'media', (uuid, page) ->
-      @route /^contents\/([^:]+)/, 'media', (uuid, page) ->
+      @route /^contents\/([^:]+):?([^:]+)/, 'media', (uuid, page) ->
         uuid = uuid.toLowerCase()
         uuid = settings.shortcodes[uuid] if settings.shortcodes[uuid]
         @appView.render('contents', {uuid: uuid, page: Number(page)})


fix troll-me.coffee maybe

fix troll-me.coffee maybe

--- scripts/troll-me.coffee
+++ scripts/troll-me.coffee
@@ -17,5 +17,5 @@
 """

 module.exports = (robot) ->
-  robot.respond /troll/i, (msg) ->
+  robot.respond /.*troll.*/i, (msg) ->
     msg.send troll


Fix debug mode toggle not persisting between turbonav bug

--- _coffee/keyboard.coffee
+++ _coffee/keyboard.coffee
@@ -25,12 +25,13 @@

 $(window).load ->
   # DO NOT DO ON EVERY TURBONAV
+  bindArrows()
+
+$(document).ready ->
   if localStorage.debug_mode is "yes"
     $('[data-debug-toggle]').show()
-
-  bindArrows()

 document.addEventListener 'turbolinks:visit', ->
   window.jekyll_page_up = null
   window.jekyll_page_previous = null
-  window.jekyll_page_next = null
+  window.jekyll_page_next = null


Use removeListener instead of off

Use removeListener instead of off

--- src/selection-view.coffee
+++ src/selection-view.coffee
@@ -47,7 +47,7 @@

   _unbindFromObject: (object) ->
     return unless object
-    object.off 'insert:node', @onInsertNode
+    object.removeListener 'insert:node', @onInsertNode

   _createNodeEditors: (object) ->
     @_nodeEditorStash = @nodeEditors


a little

a little

--- salvus/page/worksheet1.coffee
+++ salvus/page/worksheet1.coffee
@@ -428,10 +428,10 @@
                     c = worksheet.find(".salvus-cell").salvus_cell()
                     $(c[0]).find(".salvus-cell-input").focus()
                     page.append(worksheet)
+
                 worksheet.hide()
                 setTimeout((() -> worksheet.show()), 500)

-
     salvus_client.on "connected", load_scratch_worksheet
     salvus_client.on "signed_in", load_scratch_worksheet


Adding much needed documentation to the coffeescript

--- app/assets/javascripts/app.coffee
+++ app/assets/javascripts/app.coffee
@@ -1,13 +1,22 @@
 # Main javascript file for the application
 # contains the angular module and its main controller

+# Recievers
+# A global set of receivers is in action which takes the data from an incomming we-socket push
+# One by one, each function in this array is evaluated against the data and the bolean result is taken. If true, the sequence stops
+# so, each function may do whatever it needs with the data, and if the data happens to be exclusive for it, then it returns true else false
 receivers = []      # Set of receiving functions

 app = angular.module('Veracious', [])

 # Navigation Controller
+# There are 4 sections of the interface:
+#   Logging: Where the user logs are constantly being displayed. Its directly tied to the LogController
+#   Data: Here the user data is shown. Which icludes the source data-sets (from the BatchController) and result data-sets (from the ResultController)
+#   Batch: This section is used to create and submit batches. Tied to the BatchController
+#   Result: This section is used to submit result requests and generate charts. Tied to the ResultController
 app.controller 'NavigationController', () ->
-    this.visible = "logging"
+    this.visible = "logging"        # We start with logging

     this.setLogging = () -> this.visible = "logging"
     this.setData = () -> this.visible = "data"      # Will have data-sets and results
@@ -30,58 +39,63 @@
                  message: String
                }
     ###
-    #$scope.showCtrl = true          # Show this controller by default
-    # Parts: #loggingWell -> The area of the log
-    loggingWell = $("#loggingWell")[0];
-    $scope.logs = []
+
+    loggingWell = $("#loggingWell")[0];     # The div id of the area of log
+    $scope.logs = []                        # Actual logs data

     # simple match for status
     $scope.isStatus = (status, log) ->
         log.status == status

-    #$scope.test = () ->
-    #    $scope.logs.push status: "SUCCESS", message: "Helloooo", activity: "Yohoooo"
-    #    console.log "DEBUG: running test, this.logs: "+JSON.stringify($scope.logs)
-
-    # Okay, trying out an IDEA
-    # Let us see, If we want to separate everything into different controllers, we need a common receiver for the
-    # web-socket...
-    # Now, each controller decides to make a function for itself (like a scala partial function) that accepts its kind of
-    # data and declares true if it is or else declares false
-    # If we add such receiver functions into an array and then make a master receiver that goes through the array with
-    # the received server data and stopping till it gets a true, then it might just work right??
-
-    # UPDATE, got it to work, FINALLLY!!
+    # Now for the recieve method
+    # It needs to call $scope.$apply or else the changes to scope may not be digested
+    # by the model immediately as this function will be called outside the control of angularJs
     $scope.receiveFunction = (data) -> $scope.$apply () ->
-        if data.logs or data.log
+        if data.logs or data.log                                # we only accept logs or log messages
             if data.log
                 $scope.logs.push(data.log)
             else $scope.logs = data.logs
-            loggingWell.scrollTop = loggingWell.scrollHeight
+            loggingWell.scrollTop = loggingWell.scrollHeight    # scroll to the bottom of the well
             true
         else
             false

-    #$scope.toggle = (b) -> $scope.$apply () -> $scope.showCtrl = b
-
-    receivers.push($scope.receiveFunction)     # Add this receiver to the
-    #toggleFunc.logging = $scope.toggle
-
-    return
-
-
+    receivers.push($scope.receiveFunction)     # Add this receiver to the global queue
+    return
+
+
+# A global set for operations
 operations = [
     { name: "MnALS", pretty: "ALS mining" },
     { name: "MnClustering", pretty: "Cluster Mining" },
     { name: "MnFPgrowth", pretty: "FP growth algorithm" },
-    { name: "MnSVM", pretty: "State Vector Machine" },
+    { name: "MnSVM", pretty: "Support Vector Machine" },
     { name: "DsAddDirect", pretty: "Upload data-set" },
     { name: "DsAddFromUrl", pretty: "Upload data-set from URL" },
     { name: "DsDelete", pretty: "Delete data-set" },
     { name: "DsRefresh", pretty: "Refresh data-set" }]

 # Batch Controller
+# This heavy weight is responsible for handling batch creations, submissions and data-source updates
 app.controller 'BatchController', ($scope) ->
+    # -------------- Opertations ---------------
+    $scope.operations = operations      # Op names
+
+    # Get full name for UI perposes
+    $scope.getPretty = (opName) ->      # this function needs to be present in the Result controller as well, but with a different implementation
+        return op.pretty for op in $scope.operations when op.name is opName
+
+    $scope.algorithms = $scope.operations[0...4]        # Algorithm names,official
+
+    $scope.operationTypes = [          # OpTypes
+        { name: "MineOp", pretty: "Mining Operations"},
+        { name: "DataSetOp", pretty: "Data-set Operations"}]
+
+    $scope.getGroup = (op) ->          # Finding the opType from opName
+        if not op then {}
+        else if op.substr(0,2) == "Mn"
+            $scope.operationTypes[0]
+        else $scope.operationTypes[1]

     # Batch Manipulation
     newJob = () ->
@@ -91,56 +105,43 @@
         textParams: []
         numParams: []

-    $scope.currentJob = newJob()
-
-    # -------------- Opertations ---------------
-    $scope.operations = operations
-
-    $scope.getPretty = (opName) ->      # todo: Need this one in the other controller as well
-        return op.pretty for op in $scope.operations when op.name is opName
-
-    $scope.algorithms = $scope.operations[0...4]
-
-    $scope.operationTypes = [
-        { name: "MineOp", pretty: "Mining Operations"},
-        { name: "DataSetOp", pretty: "Data-set Operations"}]
-
-    $scope.getGroup = (op) ->
-        if not op then {}
-        else if op.substr(0,2) == "Mn"
-            $scope.operationTypes[0]
-        else $scope.operationTypes[1]
-
-    $scope.checkName = (name) ->
+    $scope.currentJob = newJob()        # We manipulate the current job to get the dynamic form
+
+    $scope.checkName = (name) ->        # for UI perposes
         $scope.currentJob.opName == name

     # Setup batch here -----------------------------------
-    $scope.batch = []
+    $scope.batch = []                   # basically we add currentJob to this
+
+    # We did a lot of jugad, now we formalize the job description so that the batch
+    # is automatically submission ready
     finaliseJob = (job) ->
         job.opType = $scope.getGroup(job.opName).name
-        if (job.optionalTextParam == "")
+        if (job.optionalTextParam == "")    # The optionalTextParam is marked as Option[String] at server. A missing attribute will give None
             delete job.optionalTextParam
-        if (job.opName == "DsAddDirect")
+        if (job.opName == "DsAddDirect")    # The only operation where we allow for a file upload, we need to handle that appropriately
             job.file = $("#dsFile")[0].files[0]

+        # Adding a data-set means that the user may require it for a subsequent job in the batch
+        # so we add the data-set name to optimisticDsList (optimistic because we have no garuntee that the ds operation will succeed)
         if (job.opName == "DsAddDirect" || job.opName == "DsAddFromUrl")
             optimisticDsList.push({name: job.textParams[0], algo: job.textParams[2]})
-        # More
         return job

-    $scope.addToBatch = () ->
+    $scope.addToBatch = () ->       # Add the job to the batch on button click
         $scope.batch.push( finaliseJob($scope.currentJob) )
         $scope.currentJob = newJob()

-    $scope.clearBatch = () ->
-        $("#batchDisplay").remove()
+    $scope.clearBatch = () ->       # clear the current batch and lets start over. on button click
+        $("#batchDisplay").remove() # this was a trial hack that failed. I think we can delete this line, todo
         $scope.batch = []
         optimisticDsList = []

+    # Convert the batch to a multi-part form data in compliance with the joblist mapping in models.batch.job
     createUFormData = (batch) ->
         formData = new FormData()
         for job, i in batch
-            str = "jobs[#{i}]."
+            str = "jobs[#{i}]."                         # Don't ask, got this trough trial and error
             formData.append(str+"opType", job.opType)
             formData.append(str+"opName", job.opName)
             for text in job.textParams
@@ -154,10 +155,10 @@
                 formData.append(str+"optionalTextParam", job.optionalTextParam)
         return formData

+    # Lets make the official submission
     $scope.submitBatch = () ->
-        # call the method on window from connect
         fData = createUFormData ($scope.batch)
-        window.submitBatch fData, (status) ->
+        window.submitBatch fData, (status) ->               # from connect.js
             if status == 200 then alert "Batch submitted successfully"
             else alert "There was a problem submitting the batch, status: "+status
         $scope.clearBatch()
@@ -166,37 +167,40 @@

     # ----------- Data-set manipulation
     #$scope.dsList = []                     # Actual Ds list from server
-    $scope.dsList = [
+    $scope.dsList = [                       # Sample dsList for testing perposes
         { name: "SampleDsForALS", algo: "MnALS", desc: "Short description for the set", type: "dataset", status: "available", source: "http://som.sdf.com" },
         { name: "SampleDsForFP", desc: "Short description for the set", type: "dataset", status: "unavailable",  algo: "MnFPgrowth" },
         { name: "SampleDsForALS", algo: "MnALS", desc: "Short description for the set", type: "dataset", status: "available",  source: ""},
         { name: "SampleDsForClustering", desc: "Short description for the set", type: "dataset", status: "available",  algo: "MnClustering" },
         { name: "SampleDsForSVM", desc: "Short description for the set", type: "dataset", status: "unavailable",  algo: "MnSVM", source: "https://www.google.com" },
         { name: "SampleDsForALS", desc: "Short description for the set", type: "dataset", status: "removed",  algo: "MnALS" } ]
+
     optimisticDsList = []           # Names of ds that are entered from previous Job

     $scope.getAllDs = () -> $scope.dsList.concat(optimisticDsList)
     $scope.refreshables = () ->
         ds.name for ds in $scope.dsList when ds.source != ""

+    # this is for selecting data-sets for a perticular algorithm.. the system is a little fool proof you know :D
     $scope.getValidDs = (algoName) ->
         ds.name for ds in $scope.getAllDs() when ds.algo is algoName && ds.status == "available" # TODO: confirm

     getDataSets = (dsList) ->            # filters out the result types
         ds for ds in dsList when ds.type == "dataset" && ds.status != "removed" #TODO: confirm

+
+    # The BatchController's receiver
     $scope.receiveFunction = (data) -> $scope.$apply () ->
-        if data.datasets
-            $scope.dsList = convertDataSets getDataSets data.datasets
+        if data.datasets                                        # Only concerned with the data-set list
+            $scope.dsList = convertDataSets getDataSets data.datasets      # conversions necessary because of API difference (algo naming), courtesy of @Chetan
         false   # The other controller needs this data

-
     receivers.push($scope.receiveFunction)     # Add this receiver to the line
     return

 ## A conversion function to make up for the difference in API

-getOfficialName = (opname) ->
+getOfficialName = (opname) ->           # Official as per me you know
     switch opname
         when "clustering" then "MnClustering"
         when "svm" then "MnSVM"
@@ -209,6 +213,7 @@
 # ---------------------------------

+# Simple controller to show results, and request visualisation
 app.controller 'ResultController', ($scope) ->
     #$scope.results = []
     $scope.results = [
@@ -222,7 +227,7 @@
     $scope.getAvailResults = () ->
         res for res in $scope.results when res.status is "available"

-    $scope.getPretty = (opName) ->      # todo: Need this one in the other controller as well
+    $scope.getPretty = (opName) ->
         actual = getOfficialName(opName)
         return op.pretty for op in operations when op.name is actual


fixed a variable naming bug

fixed a variable naming bug

--- app/helper/parameterHelper.coffee
+++ app/helper/parameterHelper.coffee
@@ -160,13 +160,13 @@
       fs.writeFileSync(path, JSON.stringify(content))

   loadParamInfo: (process, rootFolder, method) ->
-    path = nconf.get('paths:imageRootPath') + '/' + rootFolder + '/' + method + '.json'
+    paramPath = nconf.get('paths:imageRootPath') + '/' + rootFolder + '/' + method + '.json'
     data =
       parameters: process.parameters.params
       folder: process.outputFolder
     try
-      fs.statSync(path).isFile()
-      content = JSON.parse(fs.readFileSync(path,'utf8'))
+      fs.statSync(paramPath).isFile()
+      content = JSON.parse(fs.readFileSync(paramPath,'utf8'))
       if((info = _.where(content,{'parameters':data.parameters})).length > 0)
         #found some information about this method
         ioHelper = new IoHelper()


Add lots of TODOs and a tentative qiCorrelationChart directive.

--- app/javascripts/directives.coffee
+++ app/javascripts/directives.coffee
@@ -85,6 +85,21 @@

     # Displays the correlation charts.
+    #
+    # TODO - Why is this not an entity (E) directive?
+    #   Why is the attribute removed after rendering? The intent seems to
+    #   be to disable the directive after the link. However, removing the
+    #   attribute does not appear to have any effect. The directive is
+    #   called on AngularJS compile. Once compiled, the element is not
+    #   recompiled. Thus, removing the attribute is superfluous. Is this
+    #   assessment correct?
+    # TODO - why is there a config? test. Shouldn't config always exist?
+    # TODO - Why is $compile called? This call does not appear to have any
+    #   effect. The link is performed during $compile, so calling $compile
+    #   here seems to be at best unnecessary and at worst dangerous.
+    # TODO - same effect might be obtained more cleanly on each chart
+    #   using the qiCorrelationChart directive below.
+    #
     directives.directive 'qiCorrelationCharts', ['Correlation', '$compile',
       (Correlation, $compile) ->
         restrict: 'A'
@@ -93,10 +108,24 @@
             if config?
               # The page must completely load before the chart rendering call
               # is made.
+              # TODO - element should already be ready before link is called.
+              #   Also, this element ready is no guarantee that the chart
+              #   elements are ready.
               element.ready ->
-                Correlation.renderCharts(config)
-                element.removeAttr 'qi-correlation-charts'
+                Correlation.renderCharts(scope.config)
+                element.removeAttr('qi-correlation-charts')
                 $compile(element)(scope)
+    ]
+
+
+    # TODO - try enabling this in conjunction with the TODO above.
+    directives.directive 'qiCorrelationChart', ['Correlation',
+      (Correlation) ->
+        restrict: 'E'
+        link: (scope, element) ->
+          # Here, the scope config is set on the individual chart, not
+          # all of the charts.
+          Correlation.renderChart(scope.config)
     ]

@@ -107,8 +136,8 @@
         link: (scope, element) ->
           # TODO - obtain the updateImage argument.
           # SliceDisplay.updateImage(???)
-          # TODO - what does the following line do? Document or remove.
-          element.removeAttr 'qi-slice-display'
+          # TODO - see qiCorrelationCharts link TODO.
+          element.removeAttr('qi-slice-display')
           $compile(element)(scope)
     ]

@@ -121,6 +150,7 @@
           scan: '='
         link: (scope, element) ->
           # Wait for a session extended with detail to digest the scan.
+          # TODO - see qiCorrelationCharts link TODO.
           scope.$parent.$watch 'scan', (scan) ->
             if scan?
               scope.config = Intensity.configure(scan, element)


Allow library to keep existing properties like command


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com


sorting preinit class list

sorting preinit class list

--- src/runtime.coffee
+++ src/runtime.coffee
@@ -84,40 +84,40 @@
   # much as possible.
   preinitialize_core_classes: (resume_cb, except_cb) ->
     core_classes = [
+      'java/io/ExpiringCache'
+      'java/io/FileDescriptor'
+      'java/io/FileNotFoundException'
+      'java/io/IOException'
+      'java/io/Serializable'
+      'java/io/UnixFileSystem'
+      'java/lang/ArithmeticException'
+      'java/lang/ArrayIndexOutOfBoundsException'
+      'java/lang/ArrayIndexOutOfBoundsException'
+      'java/lang/ArrayStoreException'
+      'java/lang/Class'
+      'java/lang/ClassCastException'
+      'java/lang/Cloneable'
+      'java/lang/Error'
+      'java/lang/ExceptionInInitializerError'
+      'java/lang/IllegalMonitorStateException'
+      'java/lang/InterruptedException'
+      'java/lang/NegativeArraySizeException'
+      'java/lang/NoSuchFieldError'
+      'java/lang/NoSuchMethodError'
+      'java/lang/NullPointerException'
+      'java/lang/NullPointerException'
+      'java/lang/reflect/Constructor'
+      'java/lang/reflect/Field'
+      'java/lang/reflect/Method'
+      'java/lang/StackTraceElement'
+      'java/lang/String'
+      'java/lang/System'
+      'java/lang/Thread'
+      'java/lang/ThreadGroup'
+      'java/lang/Throwable'
+      'java/nio/ByteOrder'
       'sun/misc/VM'
-      'java/lang/String'
-      'java/lang/NoSuchFieldError'
-      'java/lang/ArrayIndexOutOfBoundsException'
-      'java/lang/ClassCastException'
-      'java/lang/Thread'
-      'java/lang/Throwable'
-      'java/lang/NullPointerException'
-      'java/lang/reflect/Field'
-      'java/lang/Error'
-      'java/lang/reflect/Method'
-      'java/lang/reflect/Constructor'
-      'java/lang/Class'
-      'java/lang/StackTraceElement'
-      'java/nio/ByteOrder'
-      'java/lang/ArrayStoreException'
-      'java/io/IOException'
-      'java/lang/IllegalMonitorStateException'
-      'java/lang/ArrayIndexOutOfBoundsException'
-      'java/lang/System'
-      'java/lang/InterruptedException'
-      'java/io/ExpiringCache'
-      'java/io/UnixFileSystem'
-      'java/io/FileNotFoundException'
-      'java/io/FileDescriptor'
-      'java/lang/ThreadGroup'
-      'java/lang/NullPointerException'
-      'java/lang/NegativeArraySizeException'
-      'java/lang/NoSuchMethodError'
-      'java/lang/Cloneable'
-      'java/io/Serializable'
-      'java/lang/ArithmeticException'
       'sun/reflect/ConstantPool'
-      'java/lang/ExceptionInInitializerError'
     ]
     i = -1
     init_next_core_class = =>


Simple big fixes

Simple big fixes

--- src/todo.coffee
+++ src/todo.coffee
@@ -3,26 +3,29 @@
 document.addEventListener 'DOMContentLoaded', ->
   'use strict'

-  newTodo = document.getElementById('newTodo').value
   ref = new Firebase 'https://amber-fire-5590.firebaseio.com'

   todo = new Vue
     el: '#todo'
     data:
-      #The displayed list
       todos: [
         done: false
         content: 'Loading data from Firebase...'
       ]
     methods:
-      #Takes the input box and pushes it to Firebase
-      addTo: ->
+      #
+      # Adds todo to list
+      #
+      addTo: ->
+        newTodo = document.getElementById('newTodo').value
         if newTodo.trim() isnt ''
           ref.push
             done: false
             content: newTodo
           newTodo = ''
-      #Removes all the todos that are done.
+      #
+      # Removes all the todos that are done.
+      #
       clearCompleted: ->
         _.each todo.$get('todos'), (t, todoKey) ->
           if t and t.done


focus when switching convs

focus when switching convs

--- src/ui/views/input.coffee
+++ src/ui/views/input.coffee
@@ -28,6 +28,8 @@
     el.value = val
     setTimeout (-> cursorToEnd el), 1

+lastConv = null
+
 module.exports = view (models) ->
     div class:'input', -> div ->
         textarea autofocus:true, placeholder:'Message', rows: 1, ''
@@ -45,6 +47,13 @@
             if e.keyIdentifier is "Up" then historyWalk e.target, -1
             if e.keyIdentifier is "Down" then historyWalk e.target, +1

+    # focus when switching convs
+    if lastConv != models.viewstate.selectedConv
+        lastConv = models.viewstate.selectedConv
+        laterMaybeFocus()
+
+laterMaybeFocus = -> later maybeFocus
+
 maybeFocus = ->
     # no active element? or not focusing something relevant...
     el = document.activeElement


update demo

update demo

--- demo.coffee
+++ demo.coffee
@@ -1,8 +1,13 @@
 sys = require 'util'
 Pane = require './lib/Pane'

-window1 = new Pane 'http://google.com', 200, 200, true, false
-console.log window1
+window1 = new Pane
+  url: 'http://trycodex.nodester.com'
+  height: 300 # Default 250
+  width: 300 # Default 250
+  resizable: true # Default true
+  scrollable: false # Default false
+
 window1.on 'open', ->
   console.log 'window1 opened'
   window1.on 'close', ->


Prevent web request on form view. tweak disabled classes for bootstrap3

--- app/assets/javascripts/effective_addresses/address_fields.js.coffee
+++ app/assets/javascripts/effective_addresses/address_fields.js.coffee
@@ -17,10 +17,10 @@
     $stateSelect = $container.find("select[data-effective-address-state='#{uuid}']:first")

     if countryCode.length == 0
-      $stateSelect.prop('disabled', true).addClass('disabled').parent('.form-group').addClass('disabled')
+      $stateSelect.prop('disabled', true).addClass('disabled').parent('.form-group').addClass('disabled').find('label').addClass('disabled')
       $stateSelect.html('<option value="">Please choose a country first</option>')
     else
-      $stateSelect.removeAttr('disabled').removeClass('disabled').parent('.form-group').removeClass('disabled')
+      $stateSelect.removeAttr('disabled').parent('.form-group').removeClass('disabled').find('.disabled').removeClass('disabled')
       $stateSelect.find('option').first().text('loading...')
       $stateSelect.load url, ->
         stateSelectAvailable = $(@).find('option:last').val().length > 0
@@ -32,6 +32,3 @@
           $(@).prop('disabled', true)

 $(document).on 'change', 'select[data-effective-address-country]', -> loadSubregions($(@), true)
-
-$ -> loadSubregions($('select[data-effective-address-country]'))
-$(document).on 'page:change', -> loadSubregions($('select[data-effective-address-country]'))


fix the availability search invalidation

--- app/assets/javascripts/lib/components/availability_search.js.coffee
+++ app/assets/javascripts/lib/components/availability_search.js.coffee
@@ -2,7 +2,7 @@
 # Constructor class for the Availability form manager
 #
 # ------------------------------------------------------------------------------
-
+
 define ['jquery', 'lib/extends/events', 'lib/utils/page_state', 'lib/utils/serialize_form', 'lib/managers/select_group_manager', 'lib/components/datepicker'], ($, EventEmitter, PageState, Serializer, SelectManager, AvailabilityDatepicker) ->

   class AvailabilitySearch extends PageState
@@ -21,12 +21,12 @@
       @$form = @$el.find('form')
       @$submit ?= @$form.find('#js-booking-submit')
       formDatePicker = new AvailabilityDatepicker( target: @$el )
-      guestSelect =  new SelectManager('.js-guest-select')
-      currencySelect = new SelectManager('.js-currency-select')
+      guestSelect = new SelectManager('.js-guest-select')
+      currencySelect = new SelectManager('.js-currency-select')
       @listen()
-      @broadcast()
-
-
+      @broadcast()
+
+
     # Subscribe
     listen: ->
       $(LISTENER).on ':cards/request', =>
@@ -37,10 +37,10 @@
         @_unblock()
         @_set('page_offsets', data.pagination.page_offsets) if data.pagination && data.pagination.page_offsets

-      $(LISTENER).on ':search/change', =>
+      $(LISTENER).on ':search/change', =>
         @_show()

-      $(LISTENER).on ':search/hide', =>
+      $(LISTENER).on ':search/hide', =>
         @_hide()

     # Publish
@@ -55,9 +55,9 @@

     _setDefaultDates : ->
       currentDate = new Date()
-      today = [currentDate.getFullYear(), (currentDate.getMonth() + 1), currentDate.getDate()]
-      @$el.find('#js-av-start').data('pickadate').setDate(today[0], today[1], today[2] + 1)
-      @$el.find('#js-av-end').data('pickadate').setDate(today[0], today[1], today[2] + 2)
+      today = [currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()]
+      @$el.find('#js-av-start').data('pickadate').set('select', [today[0], today[1], today[2] + 1])
+      @$el.find('#js-av-end').data('pickadate').set('select', [today[0], today[1], today[2] + 2])

     _getSearchData : ->
       if @$form.find('#js-av-start').val() is ''  or @$form.find('#js-av-start').val() is undefined


Demo app Safari fix

Demo app Safari fix

--- demo/client/upload-form.coffee
+++ demo/client/upload-form.coffee
@@ -24,7 +24,11 @@
           return
       return

-    transport = event.currentTarget?.transport?.value or 'ddp'
+    for radio in event.currentTarget.transport
+      if radio.checked
+        transport = radio.value
+
+    transport ?= 'ddp'
     ClientStorage.set 'uploadTransport', transport

     created_at = +new Date


Really fix nasty throw problem

Really fix nasty throw problem

--- src/util.coffee
+++ src/util.coffee
@@ -5,10 +5,8 @@
 module.exports = Util =
   isDirectory: (dir) ->
     switch
-      when dir instanceof Directory
-        return fs.statSync(dir.getPath()).isDirectory()
-      when dir instanceof File
-        return fs.statSync(dir.getPath()).isDirectory()
+      when (dir instanceof Directory) or (dir instanceof File)
+        return Util.isDirectory(dir.getPath())
       when typeof dir is 'string'
         return ((try fs.statSync(dir).isDirectory()) ? false)
       else


chage socials-share-buttons-container, fix VK

--- vendor/assets/javascripts/jquery.socialShareButtons.js.coffee
+++ vendor/assets/javascripts/jquery.socialShareButtons.js.coffee
@@ -11,7 +11,7 @@
         fbSelector: '.js-fb'
         gpSelector: '.js-gp'
         okSelector: '.js-ok'
-        containerSelector: '.post-likes'
+        containerSelector: '.socials-share-buttons-container'
         url: location.href
         isInitScroller: false
       , options
@@ -118,14 +118,13 @@
         open "http://www.facebook.com/sharer.php?m2w&#{params}", "_blank", "scrollbars=0, resizable=1, menubar=0, left=100, top=100, width=550, height=440, toolbar=0, status=0"

   class SocialVk extends SocialBase
+    regexVk = /VK.Share.count\(\d+, (\d+)\);/
     getCount: ->
-      window.VK ||= {}
-      window.VK.Share = count: (idx, number) =>
-        @$selector.parent().find('span').text number
-
       $.ajax
         url: "http://vk.com/share.php?act=count&index=1&url=#{@url}"
-        dataType: 'jsonp'
+        success: (data) =>
+          result = parseInt data.match(regexVk)?[1]
+          @$selector.parent().find('span').text result

     initClick: ->
       @$selector.on 'click', (e) =>


library export

library export

--- chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
+++ chrome/content/zotero-better-bibtex/zotero-better-bibtex.coffee
@@ -596,6 +596,7 @@
       Zotero.BetterBibTeX.keymanager.get({itemID: id}, 'on-change')

   collections = Zotero.Collections.getCollectionsContainingItems(ids, true)
+  collections ?= []
   collections = Zotero.BetterBibTeX.withParentCollections(collections) unless collections.length == 0
   collections.push("'library'")
   Zotero.DB.query("update betterbibtex.autoexport set status = 'pending' where collection in #{Zotero.BetterBibTeX.SQLSet(collections)}")


Account Settings: Credentials: Don't show "Add credential" menu when "dontShowCredentialMenu" variable is true or yes

--- client/account/lib/views/accountcredentiallistcontroller.coffee
+++ client/account/lib/views/accountcredentiallistcontroller.coffee
@@ -58,16 +58,18 @@
     super

     view = @getView()
+
     view.on 'ShowShareCredentialFormFor', @bound 'showShareCredentialFormFor'
     view.on 'ItemDeleted', (item) =>
       @removeItem item
       @noItemView.show()  if @listView.items.length is 0

-    {provider, requiredFields} = @getOptions()
+    { provider, requiredFields, dontShowCredentialMenu } = @getOptions()

     if provider
-    then @createAddDataButton()
-    else @createAddCredentialMenu()
+      @createAddDataButton()
+    else
+      @createAddCredentialMenu()  unless dontShowCredentialMenu

   createAddDataButton: ->


Add hyperlink and tweak on showing origin channel

--- client/app/Applications/Chat.kdapplication/AppController.coffee
+++ client/app/Applications/Chat.kdapplication/AppController.coffee
@@ -3,6 +3,7 @@
 are in, handling the communication between the ChatView and each
 Channel instance.
 ###
+TOPICREGEX = /#([\w-]+)/g
 class Chat12345 extends AppController
   {mq} = bongo
   PUBLIC = 'public'
@@ -49,7 +50,7 @@
         channel.removeOfflineUser presence

     channel.view.on "ChatMessageSent", (messageBody) =>
-      @parseMessageForChannels messageBody
+      @parseMessageForChannels messageBody, channel
       @broadcastOwnMessage messageBody, channel
       if name isnt PUBLIC
         @broadcastOwnMessage messageBody, @channels[PUBLIC], channel
@@ -59,12 +60,11 @@

     @channels[name] = channel

-  parseMessageForChannels: (message) ->
-    topicExp = /#([\w-]+)/g
-    while match = topicExp.exec message
+  parseMessageForChannels: (message, fromChannel) ->
+    while match = TOPICREGEX.exec message
       channelName = match[1]
       channel = @joinChannel channelName
-      @broadcastOwnMessage message, channel
+      @broadcastOwnMessage message, channel, fromChannel

   broadcastOwnMessage: (messageBody, toChannel, fromChannel) ->
     chatItem =
@@ -192,12 +192,15 @@
     @template.update()

   pistachio:->
+    parsedBody = @getData().body.replace(TOPICREGEX, "$&")
+    parsedChannel = @getData().channel?.replace(TOPICREGEX, "$&")
+
     """

       [{{#(meta.createdAt)}}]
-      #{if @getData().channel? then "[{{#(channel)}}]" else ''}
+      #{if @getData().channel? then "[#{parsedChannel}]" else ''}
       {{#(author)}}:
-      {{#(body)}}
+      #{parsedBody}

     """


hook up subject and type ui

hook up subject and type ui

--- source/javascripts/all.coffee
+++ source/javascripts/all.coffee
@@ -49,25 +49,33 @@
     .attr('width', width)
     .attr('height', height)

-  menu = d3.select('.js-menu').on 'change', (d) ->
-    key = d3.event.target.value
-    highlight key
+  menu = d3.select('.js-menu-subject').on 'change', (d) ->
+    subject = d3.event.target.value
+    type = d3.select('.js-menu-type').node().selectedIndex
+    highlight subject, type
+
+  d3.select('.js-menu-type').on 'change', (d) ->
+    subjectMenu = d3.select '.js-menu-subject'
+    type = d3.event.target.selectedIndex
+    subjectIndex = subjectMenu.node().selectedIndex
+    subject = d3.select(subjectMenu.node().options[subjectIndex]).attr 'value'
+    highlight subject, type

   for key of __mappings
     menu.append('option')
       .attr('value', key)
       .text(key)

-highlight = (key) ->
-  [min, max] = d3.extent places, (d) -> d.value[key][1]
+highlight = (subject, type) ->
+  [min, max] = d3.extent places, (d) -> d.value[subject][type]
+
   fill.domain [min, max]
+
   vis.selectAll('.neighborhood:not(.shared):not(.unclaimed)')
-    .style('fill', (d) ->
+    .style 'fill', (d) ->
       name = d.properties.NAME
       place = places.filter((p) -> p.key == name)[0]
-      console.log place, name
-      fill(place.value[key][0])
-    )
+      fill(place.value[subject][type])

 mapDataToNeighborhoods = (data) ->
   nhoods = {}


fix(account-store): Set focused perspective when account removed

--- src/flux/stores/account-store.coffee
+++ src/flux/stores/account-store.coffee
@@ -80,6 +80,7 @@
       ipc = require('electron').ipcRenderer
       ipc.send('command', 'application:reset-config-and-relaunch')
     else
+      Actions.focusDefaultMailboxPerspectiveForAccounts(@_accounts)
       @trigger()

   _onReorderAccount: (id, newIdx) =>


activity feed: commenting out fetchSomeActivities till it's ported to use neo4j

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -45,9 +45,9 @@

     @status = KD.getSingleton "status"
     @status.on "reconnected", (conn)=>
-      if conn && conn.reason is "internetDownForLongTime"
-      then @refresh()
-      else @fetchSomeActivities()
+      #if conn && conn.reason is "internetDownForLongTime"
+      #then @refresh()
+      #else @fetchSomeActivities()

   loadView:->
     # Do we really need this? ~ GG


show vm if user has multiple vms

show vm if user has multiple vms

--- client/landing/app/Applications/Ace.kdapplication/aceappview.coffee
+++ client/landing/app/Applications/Ace.kdapplication/aceappview.coffee
@@ -34,6 +34,10 @@
       ace.focus()

       title = FSHelper.minimizePath(ace.data.path).replace /^localfile:\//, ''
+      if KD.getSingleton("finderController").vms.length > 1
+        vm    = FSHelper.getVMNameFromPath ace.data.path
+        title = "#{title} on #{vm}"  if vm
+
       pane.tabHandle.setTitle title
       ace.on "AceDidSaveAs", (name, parentPath) =>
         pane.tabHandle.setTitle title


app-settings-menu: Added app settings menu button support for internal Koding apps like WebTerm and Ace.

--- client/landing/app/MainApp/maincontroller/mainviews/mainview.coffee
+++ client/landing/app/MainApp/maincontroller/mainviews/mainview.coffee
@@ -92,8 +92,8 @@
       cssClass : "kdtabhandlecontainer"
       delegate : @

-    @mainSettingsMenuButton = @getMainSettingsMenuButton()
-    @mainSettingsMenuButton.hide()
+    @appSettingsMenuButton = new AppSettingsMenuButton
+    @appSettingsMenuButton.hide()

     @mainTabView = new MainTabView
       domId              : "main-tab-view"
@@ -104,15 +104,20 @@
     ,null

     @mainTabView.on "PaneDidShow", =>
-      appManifest = getFrontAppManifest()
-      @mainSettingsMenuButton[if appManifest?.menu then "show" else "hide"]()
+      appManifest = KD.getSingleton("appManager").getFrontAppManifest()
+      menu = appManifest?.menu or appManager.get(@mainTabView.getActivePane()?.name)?.getOptions().menu
+      if menu
+        @appSettingsMenuButton.setData menu
+        @appSettingsMenuButton.show()
+      else
+        @appSettingsMenuButton.hide()

     @mainTabView.on "AllPanesClosed", ->
       @getSingleton('router').handleRoute "/Activity"

     @contentPanel.addSubView @mainTabView
     @contentPanel.addSubView @mainTabHandleHolder
-    @contentPanel.addSubView @mainSettingsMenuButton
+    @contentPanel.addSubView @appSettingsMenuButton

   createSideBar:->


Optimize snapshot selection restoration

Optimize snapshot selection restoration

--- src/trix/models/composition.coffee
+++ src/trix/models/composition.coffee
@@ -19,6 +19,7 @@
     selectedRange: @getLocationRange()

   restoreSnapshot: ({document, selectedRange}) ->
+    @notifyDelegateOfIntentionToSetLocationRange()
     @document.replaceDocument(document)
     @setLocationRange(selectedRange)


:white_check_mark: Add test for package update

Added an 'it' test for when a disabled package is updated. Checks to
make sure that disabled packages stay disabled after an update.

--- spec/package-card-spec.coffee
+++ spec/package-card-spec.coffee
@@ -187,6 +187,31 @@
       expect(card.enablementButton.find('.disable-text').text()).toBe('Disable')
       card.enablementButton.click()
       expect(atom.packages.disablePackage).toHaveBeenCalled()
+
+    it "will stay disabled after an update", ->
+      pack = atom.packages.getLoadedPackage('package-with-config')
+      pack.latestVersion = '1.1.0'
+      packageUpdated = false
+
+      packageManager.on 'package-updated', -> packageUpdated = true
+      packageManager.runCommand.andCallFake (args, callback) ->
+        callback(0, '', '')
+        onWillThrowError: ->
+
+      originalLoadPackage = atom.packages.loadPackage
+      spyOn(atom.packages, 'loadPackage').andCallFake ->
+        originalLoadPackage.call(atom.packages, path.join(__dirname, 'fixtures', 'package-with-config'))
+
+      pack.disable()
+      card = new PackageCard(pack, packageManager)
+      expect(atom.packages.isPackageDisabled('package-with-config')).toBe true
+      card.update()
+
+      waitsFor ->
+        packageUpdated
+
+      runs ->
+        expect(atom.packages.isPackageDisabled('package-with-config')).toBe true

     it "is uninstalled when the uninstallButton is clicked", ->
       setPackageStatusSpies {installed: true, disabled: false}


Israel05 patch 1 (#4327)

Israel05 patch 1 (#4327)

* Update es-ES.coffee

* Fix formatting for es-ES

--- app/locale/es-ES.coffee
+++ app/locale/es-ES.coffee
@@ -7,9 +7,9 @@
     im_a_teacher: "Soy un profesor"
     im_a_student: "Soy un alumno"
     learn_more: "Aprende más"
-#    classroom_in_a_box: "A classroom in-a-box for teaching computer science."
+    classroom_in_a_box: "Una clase todo en uno para enseñar informática."
     codecombat_is: "CodeCombat es una plataforma para que los estudiantes aprendan ciencia de la computación mientras juegan a un juego real."
-#    our_courses: "Our courses have been specifically playtested to excel in the classroom, even by teachers with little to no prior programming experience."
+    our_courses: "Nuestros cursos han sido especialmente diseñados para  ser lo mejor del aula, incluso con profesores con poca o nula preparación en programación."
     top_screenshots_hint: "Los estudiantes escriben código y ven sus cambios en tiempo real."
     designed_with: "Diseñado pensando en los profesores"
     real_code: "Real, escribe código"
@@ -37,7 +37,7 @@
     conventional: "contra los métodos convencionales de evaluación, la diferencia es clara: los juegos son mejores ayudando a los alumnos a retener conocimiento, concentrarse y"
     perform_at_higher_level: "desempeñarse a un nivel mas alto de ejecución."
 #    feedback: "Games also provide real-time feedback that allows students to adjust their solution path and understand concepts more holistically, instead of being limited to just “correct” or “incorrect” answers."
-#    real_game: "A real game, played with real coding."
+    real_game: "Un juego real, jugado con código de verdad."
 #    great_game: "A great game is more than just badges and achievements - it’s about a player’s journey, well-designed puzzles, and the ability to tackle challenges with agency and confidence."
 #    agency: "CodeCombat is a game that gives players that agency and confidence with our robust typed code engine, which helps beginner and advanced students alike write proper, valid code."
 #    request_demo_title: "Get your students started today!"
@@ -45,7 +45,7 @@
 #    get_started_title: "Set up your class today"
 #    get_started_subtitle: "Set up a class, add your students, and monitor their progress as they learn computer science."
     request_demo: "Pide una demostración"
-#    setup_a_class: "Set Up a Class"
+    setup_a_class: "Configurar una clase"
     have_an_account: "¿Tienes una cuenta?"
 #    logged_in_as: "You are currently logged in as"
 #    computer_science: "Computer science courses for all ages"
@@ -61,7 +61,7 @@
 #    goto_classes: "Go to My Classes"
 #    view_profile: "View My Profile"
 #    view_progress: "View Progress"
-#    go_to_courses: "Go to My Courses"
+    go_to_courses: "Ir a mis cursos"
 #    want_coco: "Want CodeCombat at your school?"

   nav:
@@ -76,12 +76,12 @@
     home: "Inicio"
     contribute: "Colaborar"
     legal: "Legalidad"
-#    privacy: "Privacy"
+    privacy: "Privacidad"
     about: "Sobre nosotros"
     contact: "Contacta"
     twitter_follow: "Síguenos en Twitter"
-#    my_classrooms: "My Classes"
-#    my_courses: "My Courses"
+    my_classrooms: "Mis Clases"
+    my_courses: "Mis Cursos"
     careers: "Carreras"
     facebook: "Facebook"
     twitter: "Twitter"
@@ -136,8 +136,8 @@
     change_hero: "Seleccionar Heroe" # Go back from choose inventory to choose hero
     buy_gems: "Comprar Joyas"
     subscription_required: "Suscripción requerida"
-#    subscribers_only: "Subscribers Only!"
-#    subscribe_unlock: "Subscribe to Unlock!"
+    subscribers_only: "¡Solo para suscriptores!"
+    subscribe_unlock: "¡Subscríbete para desbloquearlos!"
 #    subscriber_heroes: "Subscribe today to immediately unlock Amara, Hushbaum, and Hattori!"
 #    subscriber_gems: "Subscribe today to purchase this hero with gems!"
     anonymous: "Jugador Anonimo"