:fire: Remove deprecations from Pane

:fire: Remove deprecations from Pane

--- src/pane.coffee
+++ src/pane.coffee
@@ -1,7 +1,6 @@
 {find, compact, extend, last} = require 'underscore-plus'
 {Emitter} = require 'event-kit'
 Serializable = require 'serializable'
-Grim = require 'grim'
 Model = require './model'
 PaneAxis = require './pane-axis'
 TextEditor = require './text-editor'
@@ -18,9 +17,8 @@
   constructor: (params) ->
     super

-    unless Grim.includeDeprecatedAPIs
-      @container = params?.container
-      @activeItem = params?.activeItem
+    @container = params?.container
+    @activeItem = params?.activeItem

     @emitter = new Emitter
     @itemSubscriptions = new WeakMap
@@ -34,8 +32,6 @@
   serializeParams: ->
     if typeof @activeItem?.getURI is 'function'
       activeItemURI = @activeItem.getURI()
-    else if Grim.includeDeprecatedAPIs and typeof @activeItem?.getUri is 'function'
-      activeItemURI = @activeItem.getUri()

     id: @id
     items: compact(@items.map((item) -> item.serialize?()))
@@ -51,9 +47,6 @@
     params.activeItem = find params.items, (item) ->
       if typeof item.getURI is 'function'
         itemURI = item.getURI()
-      else if Grim.includeDeprecatedAPIs and typeof item.getUri is 'function'
-        itemURI = item.getUri()
-
       itemURI is activeItemURI
     params

@@ -360,11 +353,8 @@

     if typeof item.onDidDestroy is 'function'
       @itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, true)
-    else if Grim.includeDeprecatedAPIs and typeof item.on is 'function'
-      @subscribe item, 'destroyed', => @removeItem(item, true)

     @items.splice(index, 0, item)
-    @emit 'item-added', item, index if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-add-item', {item, index}
     @setActiveItem(item) unless @getActiveItem()?
     item
@@ -388,9 +378,6 @@
     return if index is -1

     @emitter.emit 'will-remove-item', {item, index, destroyed}
-
-    if Grim.includeDeprecatedAPIs and typeof item.on is 'function'
-      @unsubscribe item
     @unsubscribeFromItem(item)

     if item is @activeItem
@@ -401,7 +388,6 @@
       else
         @activatePreviousItem()
     @items.splice(index, 1)
-    @emit 'item-removed', item, index, destroyed if Grim.includeDeprecatedAPIs
     @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')
@@ -414,7 +400,6 @@
     oldIndex = @items.indexOf(item)
     @items.splice(oldIndex, 1)
     @items.splice(newIndex, 0, item)
-    @emit 'item-moved', item, newIndex if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-move-item', {item, oldIndex, newIndex}

   # Public: Move the given item to the given index on another pane.
@@ -442,7 +427,6 @@
   destroyItem: (item) ->
     index = @items.indexOf(item)
     if index isnt -1
-      @emit 'before-item-destroyed', item if Grim.includeDeprecatedAPIs
       @emitter.emit 'will-destroy-item', {item, index}
       @container?.willDestroyPaneItem({item, index, pane: this})
       if @promptToSaveItem(item)
@@ -582,7 +566,6 @@
     throw new Error("Pane has been destroyed") if @isDestroyed()

     @container?.setActivePane(this)
-    @emit 'activated' if Grim.includeDeprecatedAPIs
     @emitter.emit 'did-activate'

   # Public: Close the pane and destroy all its items.
@@ -732,58 +715,12 @@
     else
       throw error

+Grim = require 'grim'
 if Grim.includeDeprecatedAPIs
   Pane.properties
     container: undefined
     activeItem: undefined
     focused: false
-
-  Pane.behavior 'active', ->
-    @$container
-      .switch((container) -> container?.$activePane)
-      .map((activePane) => activePane is this)
-      .distinctUntilChanged()
-
-  Pane::on = (eventName) ->
-    switch eventName
-      when 'activated'
-        Grim.deprecate("Use Pane::onDidActivate instead")
-      when 'destroyed'
-        Grim.deprecate("Use Pane::onDidDestroy instead")
-      when 'item-added'
-        Grim.deprecate("Use Pane::onDidAddItem instead")
-      when 'item-removed'
-        Grim.deprecate("Use Pane::onDidRemoveItem instead")
-      when 'item-moved'
-        Grim.deprecate("Use Pane::onDidMoveItem instead")
-      when 'before-item-destroyed'
-        Grim.deprecate("Use Pane::onWillDestroyItem instead")
-      else
-        Grim.deprecate("Subscribing via ::on is deprecated. Use documented event subscription methods instead.")
-    super
-
-  Pane::behavior = (behaviorName) ->
-    switch behaviorName
-      when 'active'
-        Grim.deprecate("The $active behavior property is deprecated. Use ::observeActive or ::onDidChangeActive instead.")
-      when 'container'
-        Grim.deprecate("The $container behavior property is deprecated.")
-      when 'activeItem'
-        Grim.deprecate("The $activeItem behavior property is deprecated. Use ::observeActiveItem or ::onDidChangeActiveItem instead.")
-      when 'focused'
-        Grim.deprecate("The $focused behavior property is deprecated.")
-      else
-        Grim.deprecate("Pane::behavior is deprecated. Use event subscription methods instead.")
-
-    super
-
-  Pane::itemForUri = (uri) ->
-    Grim.deprecate("Use `::itemForURI` instead.")
-    @itemForURI(uri)
-
-  Pane::activateItemForUri = (uri) ->
-    Grim.deprecate("Use `::activateItemForURI` instead.")
-    @activateItemForURI(uri)
 else
   Pane::container = undefined
   Pane::activeItem = undefined


Alias name to label

Alias name to label

--- src/config/turtles.coffee
+++ src/config/turtles.coffee
@@ -49,8 +49,10 @@
       next_v_j = -dir*world.get('v_i')
       world.put('v_i', next_v_i )
       world.put('v_j', next_v_j )
+    name: (world, args) ->
+      world.label()
     _CHILDREN: [
-      name: "ME"
+      _LABEL: "me"
       i: 2
       j: 1
       v_i: 1


Never break when past the last row

Never break when past the last row

--- src/display-buffer.coffee
+++ src/display-buffer.coffee
@@ -662,7 +662,7 @@
     targetLeft = pixelPosition.left
     defaultCharWidth = @defaultCharWidth
     row = Math.floor(targetTop / @getLineHeightInPixels())
-    isLastRow = row > @getLastRow()
+    pastLastRow = row > @getLastRow()
     row = Math.min(row, @getLastRow())
     row = Math.max(0, row)

@@ -670,14 +670,11 @@
     column = 0
     for token in @tokenizedLineForScreenRow(row).tokens
       charWidths = @getScopedCharWidths(token.scopeDescriptor)
-      if !isLastRow
-        for char in token.value
-          charWidth = charWidths[char] ? defaultCharWidth
-          break if targetLeft <= left + (charWidth / 2)
-          left += charWidth
-          column++
-      else
-        column = token.value.length
+      for char in token.value
+        charWidth = charWidths[char] ? defaultCharWidth
+        break if not pastLastRow and targetLeft <= left + (charWidth / 2)
+        left += charWidth
+        column++

     new Point(row, column)


update to new api

update to new api

--- services/spelling/app/coffee/LearnedWordsManager.coffee
+++ services/spelling/app/coffee/LearnedWordsManager.coffee
@@ -30,13 +30,8 @@
 			callback null, words

-metrics.timeAsyncMethod(
-	LearnedWordsManager, 'learnWord',
-	'LearnedWordsManager.learnWord',
-	logger
-)
-metrics.timeAsyncMethod(
-	LearnedWordsManager, 'getLearnedWords',
-	'LearnedWordsManager.getLearnedWords',
-	logger
-)
+[
+	'learnWord',
+	'getLearnedWords'
+].map (method) ->
+	metrics.timeAsyncMethod(LearnedWordsManager, method, 'mongo.LearnedWordsManager', logger)


Extend layout directive to support custom togglers.

--- services/web/public/coffee/ide/directives/layout.coffee
+++ services/web/public/coffee/ide/directives/layout.coffee
@@ -2,11 +2,16 @@
 	"base"
 	"libs/jquery-layout"
 ], (App) ->
-	App.directive "layout", ["$parse", "ide", ($parse, ide) ->
+	App.directive "layout", ["$parse", "$compile", "ide", ($parse, $compile, ide) ->
 		return {
 			compile: () ->
 				pre: (scope, element, attrs) ->
 					name = attrs.layout
+
+					customTogglerPane = scope.$eval(attrs.customTogglerPane)
+					customTogglerTooltipOpen = scope.$eval(attrs.customTogglerTooltipOpen)
+					customTogglerTooltipClose = scope.$eval(attrs.customTogglerTooltipClose)
+					hasCustomToggler = customTogglerPane? and customTogglerTooltipOpen? and customTogglerTooltipClose?

 					if attrs.spacingOpen?
 						spacingOpen = parseInt(attrs.spacingOpen, 10)
@@ -62,6 +67,21 @@
 									right: state.east.size
 								})

+					repositionCustomToggler = () ->
+						if !customTogglerEl?
+							return
+						state = element.layout().readState()
+						if customTogglerPane == "east"
+							if state.east?
+								customTogglerEl.css({
+									right: if state.east.initClosed then 0 else state.east.size
+								})
+						else if customTogglerPane == "west"
+							if state.west?
+								customTogglerEl.css({
+									left: if state.west.initClosed then 0 else state.west.size
+								})
+
 					resetOpenStates = () ->
 						state = element.layout().readState()
 						if attrs.openEast? and state.east?
@@ -73,6 +93,8 @@
 						state = element.layout().readState()
 						scope.$broadcast "layout:#{name}:resize", state
 						repositionControls()
+						if hasCustomToggler
+							repositionCustomToggler()
 						resetOpenStates()

 					oldWidth = element.width()
@@ -94,6 +116,24 @@
 					if attrs.resizeOn?
 						scope.$on attrs.resizeOn, () -> onExternalResize()

+					if hasCustomToggler
+						state = element.layout().readState()
+						customTogglerScope = scope.$new()
+						if state.east?.initClosed
+							customTogglerScope.customTogglerTooltip = customTogglerTooltipOpen
+						else
+							customTogglerScope.customTogglerTooltip = customTogglerTooltipClose
+						customTogglerEl = $compile("
+							<a href
+							   class=\"custom-toggler\"
+							   tooltip=\"{{ customTogglerTooltip }}\"
+							   tooltip-placement=\"#{ if customTogglerPane == "east" then "left" else "right" }\">
+						")(customTogglerScope)
+						element.append(customTogglerEl)
+					# if hasCustomToggler
+					# 	element.find("> .custom-toggler").on "click", () ->
+					# 		element.layout().toggle("east")
+					# 		repositionCustomToggler()
 					# Save state when exiting
 					$(window).unload () ->
 						ide.localStorage("layout.#{name}", element.layout().readState())


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- source/javascripts/_cornerstone/command_stack.js.coffee
+++ source/javascripts/_cornerstone/command_stack.js.coffee
@@ -27,6 +27,9 @@

       return command

+  current: ->
+    stack[index-1]
+
   canUndo: ->
     index > 0


wires up the ./lib/gets-problem unit for the server

--- node/app.coffee
+++ node/app.coffee
@@ -8,7 +8,7 @@
   res.send 200

 app.get "/problem", (req, res, err) ->
-  res.send(501)
+  res.json(require('./lib/gets-problem').get())

 app.get "/problem/:id", (req, res, err) ->
   res.send(501)


Removed the console.log code

Removed the console.log code

--- test/unit/rerunUpdateTransactionTask.coffee
+++ test/unit/rerunUpdateTransactionTask.coffee
@@ -84,7 +84,6 @@
 				should.not.exist (task.transactions[2].rerunID)

 			rerunUpdateTransactionTask.updateTask ctx, (err, task) ->
-				console.log task
 				task.should.have.property "_id", ObjectId("53e34b915d0180cf6eef2d01")
 				task.should.have.property "remainingTransactions", 2
 				task.transactions[0].tid.should.be.eql "53e096fea0af3105689acd6a"


Remove check or will stop displayPage form executing. Check is already performed.

--- js/flight.coffee
+++ js/flight.coffee
@@ -120,7 +120,6 @@

    # displays pages when transiton is false
    displayPage: =>
-    if @isTransitioning is true then return else @isTransitioning = true
     @targetPanel.style.display = "block"
     @currentPanel.style.display = "block"
     @targetPanel.style.left = "0%"


Some fixes and new options for the scrollbar class. Better horizontal scroll detection. New option "wrap indices" to prevent index manipulation required by carousels but not the thumb gallery.

--- src/jquery.rondell.coffee
+++ src/jquery.rondell.coffee
@@ -46,6 +46,7 @@
           width: 0
           height: 0
       repeating: true           # Will show first item after last item and so on
+      wrapIndices: true         # Will modify relative item indices to fix positioning when repeating
       alwaysShowCaption: false  # Don't hide caption on mouseleave
       autoRotation:             # If the cursor leaves the rondell continue spinning
         enabled: false
@@ -64,7 +65,7 @@
         next: 'next'
       mousewheel:
         enabled: true
-        threshold: 2
+        threshold: 0
         minTimeBetweenShifts: 500
       touch:
         enabled: true
@@ -80,17 +81,23 @@
         _instance: null
         enabled: false
         orientation: "bottom"
-        start: 0
+        start: 1
         end: 100
         stepSize: 1
-        position: 50
+        position: 1
         padding: 10
-        width: "100%"
+        style:
+          width: "100%"
+          height: 20
+          left: "auto"
+          right: "auto"
+          top: "auto"
+          bottom: "auto"
         repeating: false
         onScroll: undefined
         scrollOnHover: false
         scrollOnDrag: true
-        animationDuration: 200
+        animationDuration: 300
         easing: "easeInOutQuad"
         _drag:
           _dragging: false
@@ -406,10 +413,11 @@

       selfYCenter = @container.offset().top + @container.outerHeight() / 2

-      if selfYCenter > viewportTop and selfYCenter < viewportBottom and Math.abs(dx) >= @mousewheel.threshold
+      if selfYCenter > viewportTop and selfYCenter < viewportBottom and Math.abs(dx) > @mousewheel.threshold
+        e.preventDefault()
         if dx < 0 then @shiftLeft() else @shiftRight()
         @mousewheel._lastShift = now
-
+
     _onTouch: (e) =>
       return unless @touch.enabled

@@ -507,8 +515,8 @@
       layerDist = Math.abs(layerNum - @currentLayer)
       layerPos = layerNum

-      # Find new layer position
-      if layerDist > @visibleItems and layerDist > @maxItems / 2 and @repeating
+      # Find new layer position if rondell is repeating and indices are wrapped
+      if layerDist > @visibleItems and layerDist > @maxItems / 2 and @repeating and @wrapIndices
         if layerNum > @currentLayer then layerPos -= @maxItems else layerPos += @maxItems
         layerDist = Math.abs(layerPos - @currentLayer)

@@ -673,13 +681,14 @@

       $.extend true, @, $.rondell.defaults.scrollbar, options

-      @container.addClass("rondell-scrollbar-#{@orientation}")
-      .css "width", @width
+      @container.addClass("rondell-scrollbar-#{@orientation}").css @style

       @_initControls()

       @_minX = @padding + @scrollLeftControl.outerWidth() + @scrollControl.outerWidth() / 2
       @_maxX = @container.innerWidth() - @padding - @scrollRightControl.outerWidth() - @scrollControl.outerWidth() / 2
+
+      @setPosition @position, false, true

     _initControls: =>
       @scrollLeftControl = $("<div class=\"rondell-scrollbar-left\"/>")
@@ -719,12 +728,12 @@
       newPosition = Math.round((x - @_minX) / (@_maxX - @_minX) * (@end - @start)) + @start
       @updatePosition(newPosition, fireCallback) if newPosition isnt @position

-    setPosition: (position, fireCallback=true) =>
+    setPosition: (position, fireCallback=true, force=false) =>
       if @repeating
         position = @end if position < @start
         position = @start if position > @end

-      return if position < @start or position > @end or position is @position
+      return if not force and (position < @start or position > @end or position is @position)

       # Translate position to new position for control dot in container
       newX = Math.round((position - @start) / (@end - @start) * (@_maxX - @_minX)) + @_minX


Store: `set` method now validates the given value before set.

Sets a new key value pair into store. It will also make a validation before setting the new value. If you set a validation rule in options.validations for that property, when validation failed, the property won't be updated. Store::set will return no and emit a 'PropertyRejected' event otherwise it will return true and it will also emit 'ProperySet'. Both events will include key and value. If the key and value passed to set does not related with a validation rule, it will directly set to store. This makes validation optional.

--- src/lib/core/Store.coffee
+++ src/lib/core/Store.coffee
@@ -36,14 +36,32 @@

   ###*
-    Sets a new key value pair into store.
+    Sets a new key value pair into store. It will also make a validation before
+    setting the new value. If you set a validation rule in options.validations
+    for that property, when validation failed, the property won't be updated.
+    Store::set will return no and emit a 'PropertyRejected' event otherwise
+    it will return true and it will also emit 'ProperySet'. Both events will
+    include key and value. If the key and value passed to set does not related
+    with a validation rule, it will directly set to store. This makes validation
+    optional.

     @export
     @param {string} key Key to be returned.
     @param {*} value Value of the key.
+    @return {boolean} Whether the set is completed or not.
   ###
   set: (key, value) ->
-    @map_.set key, value if value?
+    return no if not key or not value?
+
+    if @validate key, value
+      @map_.set key, value
+      @emit 'PropertySet', { key, value }
+      return yes
+    else
+      @emit 'PropertyRejected', { key, value }
+      return no
+
+
   ###*
     Validates the given value for given key's validation rules.


Use correct path in error message

Use correct path in error message

--- src/ls-archive-cli.coffee
+++ src/ls-archive-cli.coffee
@@ -9,7 +9,7 @@
     do (archivePath) ->
       archive.list archivePath, (error, files) ->
         if error?
-          console.error("Error reading: #{file}")
+          console.error("Error reading: #{archivePath}")
         else
           console.log("#{archivePath} (#{files.length})")
           for file, index in files


fixed tests

fixed tests

--- src/model/channels.coffee
+++ src/model/channels.coffee
@@ -25,7 +25,7 @@
     "description": { type: String, required: false }
     "urlPattern": { type: String, required: true }
     "type": { type: String, default: 'http', enum: ['http', 'tcp', 'tls', 'polling'] }
-    "tcpPort": { type: Number, required: false, unique: true, min: 0, max: 65536 }
+    "tcpPort": { type: Number, required: false, min: 0, max: 65536 }
     "tcpHost": { type: String, required: false }
     "pollingSchedule": { type: String, required: false }
     "allow": [


Coffeescriptify a bit

Coffeescriptify a bit

--- src/quip.coffee
+++ src/quip.coffee
@@ -24,20 +24,20 @@
     attachments = []
     for msg in strings
       period = msg.lastIndexOf(".")
-      if @.isImageUrl(msg)
+      if @isImageUrl(msg)
         attachments.push(msg)
       else
         text.push(msg)
     return unless text.length or attachments.length
-    options = {"threadId": envelope.room, "serviceId": envelope.message.id}
-    if attachments.length
-      options.attachments = attachments.join(",")
-    if text.length
-        options.content = text.join("\n\n")
+    options =
+        threadId: envelope.room
+        serviceId: envelope.message.id
+    options.attachments = attachments.join "," if attachments.length
+    options.content = text.join "\n\n" if text.length
     if envelope.message.quipMessage.annotation
         options.annotationId = envelope.message.quipMessage.annotation.id
     @robot.logger.info "Sending to #{envelope.room}: #{JSON.stringify(options)}"
-    @client.newMessage options, @.messageSent
+    @client.newMessage options, @messageSent

   isImageUrl: (url) ->
     return false unless url.substring(0, 4) == "http"
@@ -58,7 +58,7 @@

     @robot.logger.info "Fetching websocket URL..."
     @client = new Quip.Client options
-    @client.getWebsocket(@.websocketUrl)
+    @client.getWebsocket @websocketUrl

   websocketUrl: (error, response) =>
     if error
@@ -67,13 +67,13 @@
         @retries++
         @logger.info "Trying again in %ds", @retries * 1000
         setTimeout =>
-          @client.getWebsocket(@.websocketUrl)
+          @client.getWebsocket @websocketUrl
         , @retries * 1000
       else
         @robot.logger.error "Giving up"
     else
       @socketUrl = response.url
-      @selfMention = @.quipMention(response.user_id)
+      @selfMention = @quipMention response.user_id
       @connect()

   quipMention: (userId) ->
@@ -90,12 +90,12 @@
     @ws.on "open", =>
       @robot.logger.info "Opened"
       @connected = true
-      @lastMessageSeen = Date.now()
+      @lastMessageSeen = Date.now
       @heartbeatTimeout = setInterval =>
         if not @connected then return
-        if Date.now() - @lastMessageSeen > 30000
-          @robot.logger.error "Heartbeat too old at %ds", (Date.now() - @lastMessageSeen) / 1000
-          @reconnect()
+        if Date.now - @lastMessageSeen > 30000
+          @robot.logger.error "Heartbeat too old at %ds", (Date.now - @lastMessageSeen) / 1000
+          @reconnect
         else
           @ws.send JSON.stringify({"type": "heartbeat"})
           @robot.logger.info "Sent heartbeat"
@@ -103,13 +103,13 @@
       @emit "connected" unless @alreadyConnected
       @alreadyConnected = true
     @ws.on "message", (data, flags) =>
-      @websocketMessage JSON.parse(data)
+      @websocketMessage JSON.parse data
     @ws.on "error", (error) =>
       @robot.logger.error error
-      @reconnect()
+      @reconnect
     @ws.on "close", =>
       @robot.logger.info "Closed"
-      @reconnect()
+      @reconnect
     @ws.on "ping", (data, flags) =>
       @ws.pong

@@ -118,10 +118,10 @@
     if @heartbeatTimeout
       clearInterval @heartbeatTimeout
       @heartbeatTimeout = null
-    @ws.close()
+    @ws.close
     @connected = false
     setTimeout =>
-      @connect()
+      @connect
     , 5000

   websocketMessage: (packet) ->
@@ -130,9 +130,9 @@
       when "error"
         @robot.logger.error packet.message
       when "message"
-        if @robot.name.indexOf(packet.user.id) != -1
+        if -1 != @robot.name.indexOf packet.user.id
           return
-        user = @robot.brain.userForId packet.user.id, name: @.quipMention(packet.user.id), room: packet.thread.id
+        user = @robot.brain.userForId packet.user.id, name: @quipMention packet.user.id, room: packet.thread.id
         text = packet.message.text
         if @selfMention == text.substr 0, @selfMention.length
           text = @robot.name + text.substr @selfMention.length


:keyboard: hey look some keybindings

:keyboard: hey look some keybindings

--- keymaps/git.cson
+++ keymaps/git.cson
@@ -3,6 +3,19 @@
   'ctrl-shift-8': 'github:toggle-github-tab'
   'ctrl-9': 'github:toggle-git-tab-focus'
   'ctrl-shift-9': 'github:toggle-git-tab'
+  'alt-g enter': 'github:commit'
+  'alt-g f': 'github:fetch'
+  'alt-g F': 'github:pull'
+  'alt-g p': 'github:push'
+  'alt-g P': 'github:force-push'
+  'alt-g =': 'github:clone'
+
+'atom-text-editor':
+  'alt-m 1': 'github:resolve-as-ours'
+  'alt-m 2': 'github:resolve-as-theirs'
+  'alt-m 3': 'github:resolve-as-base'
+  'alt-m enter': 'github:resolve-as-current'
+  'alt-m r': 'github:revert-current'

 '.github-Panel':
   'cmd-enter': 'github:commit'


Add limit to file metadata processing to avoid using up all fd's

--- lib/index.coffee
+++ lib/index.coffee
@@ -112,7 +112,7 @@
         mediaServer.addMedia parentId, item, callback

 add = (parentId, path, sortedFiles, callback) ->
-    async.forEach Object.keys(sortedFiles),
+    async.forEachLimit Object.keys(sortedFiles), 5,
         (type, callback) ->
             async.forEach sortedFiles[type],
                 (item, callback) ->


added method to fetch list of queued files

--- lib/index.coffee
+++ lib/index.coffee
@@ -5,10 +5,13 @@
 fs = require 'fs'

 module.exports = do ->
-  options   = level: 9
-  zip       = archiver 'zip', options
-  out       = undefined
-  fileStack = []
+  options     = level: 9
+  initialized = false
+  type        = 'zip'
+  zip         = undefined
+  out         = undefined
+  fileList    = []
+  fileStack   = []

   return {
     getStream: ->
@@ -29,11 +32,15 @@
           fs.stat file, (err, stats) ->
             if stats.isFile()
               do (file) ->
+                fileList.push file
                 fileStack.push (cb) ->
                   zip.append(fs.createReadStream(file), name: file, cb)

             if i is files.length-1
               callback()
+
+    getFiles: ->
+      return fileList

     compress: (callback) ->
       zip.pipe out


Move some code from element to node constructor

--- lib/karen.coffee
+++ lib/karen.coffee
@@ -14,29 +14,32 @@

 class MockElement extends Evented
   constructor: (@type) ->
+    super()
+
+  addEventListener: (event, listener) ->
+    @on(event, listener)
+
+  removeEventListener: (event, listener) ->
+    @remove(event, listener)
+
+  attachEvent: (event, listener) ->
+    @on(event, listener)
+
+  detachEvent: (event, listener) ->
+    @remove(event, listener)
+
+class MockNode extends MockElement
+  constructor: (type) ->
     super

-    if @type?.toLowerCase() == 'iframe'
+    if type?.toLowerCase() == 'iframe'
       @contentWindow = new MockWindow
+
+    @attributes = {}

     @__defineGetter__ 'ownerDocument', -> new MockDocument
     @__defineGetter__ 'parentNode', -> new MockNode

-    @attributes = {}
-
-  addEventListener: (event, listener) ->
-    @on(event, listener)
-
-  removeEventListener: (event, listener) ->
-    @remove(event, listener)
-
-  attachEvent: (event, listener) ->
-    @on(event, listener)
-
-  detachEvent: (event, listener) ->
-    @remove(event, listener)
-
-class MockNode extends MockElement
   style: {}

   createElement: (type) ->


add schema for Meteor.users

add schema for Meteor.users

--- lib/model.coffee
+++ lib/model.coffee
@@ -224,3 +224,29 @@
 Schemas.BinaryData = new SimpleSchema(
   compose(NamedWithTagsHistoryComments,BinaryDataSpec))
 BinaryData.attachSchema( Schemas.BinaryData )
+
+
+Schemas.User = new SimpleSchema(
+  username:
+    type: String
+    regEx: /^[a-z0-9A-Z_\.]{3,15}$/
+  emails:
+    type: [ Object ]
+    optional: true
+  'emails.$.address':
+    type: String
+    regEx: SimpleSchema.RegEx.Email
+  'emails.$.verified': type: Boolean
+  createdAt:
+    type: Date
+    denyUpdate: true
+  services:
+    type: Object
+    optional: true
+    blackbox: true
+  roles:
+    type: Object
+    optional: true
+    blackbox: true
+  )
+Meteor.users.attachSchema Schemas.User


fix search condition

fix search condition

--- lib/token.coffee
+++ lib/token.coffee
@@ -27,7 +27,7 @@
     pattern = ///#{CONTRACT_NAME}:\s(0x[0-9a-f]{40})///
     contractAddress = pattern.exec(output)?[1]
     return contractAddress if contractAddress
-    if output.search /Error: Insufficient funds/
+    if output.search /Error: Insufficient funds/ >= 0
       throw Promise.OperationalError "Insufficient funds in account
         #{config.from} -- full output: [[ #{output} ]]"
     throw Promise.OperationalError "No contract address


use isEmpty rather than doing manual check

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -253,8 +253,7 @@
   pointIsAtVimEndOfFile(cursor.editor, cursor.getBufferPosition())

 isEmptyRow = (editor, row) ->
-  {start, end} = editor.bufferRangeForBufferRow(row)
-  start.column is 0 and end.column is 0
+  editor.bufferRangeForBufferRow(row).isEmpty()

 cursorIsAtEmptyRow = (cursor) ->
   isEmptyRow(cursor.editor, cursor.getBufferRow())


OPCE3: All tests passing, using new Word interface

--- lib/y-rte.coffee
+++ lib/y-rte.coffee
@@ -1,3 +1,14 @@
+class Word
+  @word = ''
+  @selections = []
+  constructor: (@word) ->
+  removeSel: (selToRemove) ->
+    @selections.forEach sel, index, array ->
+      if sel.equals(selToRemove)
+        array.splice(index, 1)
+
+
+
 class Selection
   constructor: (a, b, c, d, e)->
     if a? and b? and c? and d?
@@ -9,7 +20,7 @@
       @startPos = {word: a, pos: b}
       @endPos = {word: c, pos: d}

-      attr = e
+      style = e

     else if a? and b? and c?
       if !( typeof a is "number" and
@@ -17,19 +28,19 @@
         throw new Error "Expecting numbers as arguments"
       c.constructor is "Rte"

-      [@startPos, @endPos] = @relativeFromAbsolute(a, b, c)
-      attr = d
+      [@startPos, @endPos] = @_relativeFromAbsolute(a, b, c)
+      style = d

     else if a? and b? and a.pos? and a.word? and b.pos? and b.word?
       @startPos = a
       @endPos = b

-      attr = c
+      style = c

     else throw new Error "Wrong set of parameters #{[a, b, c, d, e]}"

-    if attr?
-      @attr = attr
+    if style?
+      @style = style

     @startPos.lt = @endPos.lt = (s) ->
       @word < s.word or (@word == s.word and @pos <= s.pos)
@@ -43,7 +54,7 @@

     words = rte._rte.words
     for i in [0..words.length-1]
-      l = words[i].length
+      l = rte.getWord(i).length
       if l >= untilStart
         spp = untilStart
         untilStart = 0
@@ -63,12 +74,27 @@

     [{word: spw, pos: spp}, {word: epw, pos: epp}]

+  # Compares *the bounds* of two selections
+  #
+  # @param [Selection] s the selection to compare to this
+  #
   equals: (s)->
     @startPos.word == s.startPos.word and
     @startPos.pos == s.startPos.pos and
     @endPos.word == s.endPos.word and
     @endPos.pos == s.endPos.pos

+  # Compares *the bounds* of two selections
+  #
+  # @param [Selection] s the selection to compare to this
+  #
+  notEquals: (s) ->
+    not equals(s)
+
+  # Returns true if the given selection is in the current selection
+  #
+  # @param [Selection] s the selection to compare to this
+  #
   in: (s) ->
     @startPos.lt(s) and @endPos.gt(s)

@@ -78,7 +104,8 @@
   overlaps: (s) ->
     @startPos.lt(s.endPos) or @endPos.gt(s.startPos)

-  setAttr: (@attr) ->
+  #TODO
+  setStyle: (@style) ->

   clone: ->
     new Selection(@startPos, @endPos)
@@ -86,26 +113,55 @@
   isValid: ->
     @startPos.lt(@endPos)

-class Rte # Rich Text Editor
+
+  # Try to merge this selection with the one given in argument
+  #
+  merge: (s, rte) ->
+    # Check if their contiguous
+    if s.endPos.word == @startPos.word or
+       (s.endPos.word == @startPos.word -1 and
+        s.endPos.loc == rte.getWord(s.endPos.word).length() and
+        @startPos.loc == 0)
+      # boundaries of new selection
+      start = s.startPos
+      end = @endPos
+      # remove link of middle words to selection
+      rte.getWord(s.endPos.word).removeSel(s)
+      rte.getWord(@startPos.word).removeSel(@)
+    else if @endPos.word == s.startPos.word or
+       (@endPos.word == s.startPos.word -1 and
+        @endPos.loc == rte.getWord(@endPos.word).length() and
+        s.startPos.loc == 0)
+      start = @startPos
+      end = s.endPos
+      rte.getWord(@endPos.word).removeSel(@)
+      rte.getWord(s.endPos.word).removeSel(s)
+    else
+      return
+
+    nSelec = new Selection start, end, s.style
+    rte.getWord()
+
+
+# Class describing the Rich Text Editor type
+#
+class Rte
+  # @property [Options] _rte the RTE object
+  # @param [String] content the initial content to set
+  # @option _rte [Array<Selection>] selections array containing all the current selections
+  # @option _rte [Array<String>] words array containing all the words of the text
+  #
   constructor: (content = '')->
-    if content.constructor is String
-      @_rte = {}
-      # TODO: convert HTML
-      @_rte.align = 'left'
-      @_rte.style = []
-      @_rte.selections = []
-      @_rte.cursorInformation = {}
-      @_rte.words = []
-      @push(content)
-    else
+    if content.constructor isnt String
       throw new Error "Only accepts strings."
+    @_rte = {}
+    @_rte.styles = []
+    @_rte.selections = []
+    @_rte.cursorInformation = {}
+    @_rte.words = []
+    @push(content)

   _name: "Rich Text Editor"
-
-  # _getModel:
-  # _setModel:
-  # observe:
-  # unobserve:

   val: (content)->
     if content?
@@ -115,11 +171,30 @@
       @push(content)
     else
       # TODO: support breaks (br, new paragraph, …)
-      (e for e in @_rte.words).join(' ')
+      (e.word for e in @_rte.words).join(' ')
+
+  # Split the content around ' ' and append all the words created to the object
+  #
+  getWord: (index) ->
+    if not (0 <= index < @_rte.words.length)
+      throw new Error "Index out of bounds"
+    @_rte.words[index].word
+
+  getWords: (begin, end) ->
+    if not end?
+      end = @_rte.words.length
+    out = wObj for wObj in @_rte.words[begin..end]
+
+  setWord: (index, content="") ->
+    if not (0 <= index < @_rte.words.length)
+      throw new Error "Index out of bounds"
+    @_rte.words[index].word = content

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

   insertWords: (position, words)->
@@ -132,10 +207,11 @@
       if position == 0
         before = []
       else
-        before = @_rte.words[..position-1]
-      after = @_rte.words[position..]
-      @_rte.words = before.concat(words).concat(after)
-      @updateInsertWords(position, words.length)
+        before = @getWords(0, position-1)
+      wordsObj = (new Word w for w in words)
+      after = @getWords(position)
+      @_rte.words = before.concat(wordsObj).concat(after)
+
     else
       throw new Error 'Index #{position} out of bound in word list'

@@ -177,14 +253,14 @@
       if start - 1 < 0
         before = []
       else
-        before = @_rte.words[..start-1]
-
-      @_rte.words = before.concat(@_rte.words[end..])
+        before = @getWords(0, start-1)
+
+      @_rte.words = before.concat(@getWords(end))
     @_rte.words

   merge: (n) ->
     if 0 <= n < @_rte.words.length
-      w = @_rte.words[n]
+      w = @getWord(n)
       @deleteWords(n)
       @insert({startPos: {word: n, pos:0}}, w)
     else
@@ -193,25 +269,25 @@
   deleteSel: (sel) ->
     if not sel.isValid()
       throw new Error "Invalid selection"
-    if not 0 <= sel.startPos.loc <= @_rte.words[sel.startPos.word].length
+    if not 0 <= sel.startPos.loc <= @getWord(sel.startPos.word).length
       throw new Error "Invalid selection"
-    if not 0 <= sel.endPos.loc <= @_rte.words[sel.endPos.word].length
+    if not 0 <= sel.endPos.loc <= @getWord(sel.endPos.word).length
       throw new Error "Invalid selection"

     s = sel.startPos.word
     e = sel.endPos.word

-    newLeft = @_rte.words[s].substring(0, sel.startPos.pos)
-    newRight = @_rte.words[e].substring(sel.endPos.pos)
+    newLeft = @getWord(s).substring(0, sel.startPos.pos)
+    newRight = @getWord(e).substring(sel.endPos.pos)

     if s == e
-      @_rte.words[s] = newLeft + newRight
+      @setWord(s, newLeft + newRight)

       # delete the words in between
       @deleteWords(s+1, e)
     else
-      @_rte.words[s] = newLeft
-      @_rte.words[e] = newRight
+      @setWord(s, newLeft)
+      @setWord(e, newRight)

       # delete the words in between
       @deleteWords(s+1, e)
@@ -229,11 +305,8 @@
     #TODO: check boundaries
     n = sel.startPos.word
     pos = sel.startPos.pos
-    word = @_rte.words[n]
-
-    # throw new Error @_rte.words[n]
-
-    @_rte.words[n] = word.substring(0, pos) + content + word.substring(pos)
+    word = @getWord(n)
+    @setWord(n, word.substring(0, pos) + content + word.substring(pos))

   # relative jump from position
   _jump: (position, relJump) ->
@@ -246,7 +319,7 @@
         if pos < jump
           word -= 1
           jump -= pos
-          pos = @_rte.words[word].length - 1
+          pos = @getWord(word).length - 1

           if word < 0
             return null
@@ -269,62 +342,25 @@
       pos = pos
     {word: word, pos: pos}

-  # get all selections with right (or left end) at location
-  _getSel: (location, rightOrLeft) ->
-    ret = []
-    if rightOrLeft == 'right'
-      selList = @_rte.words[location.word].refRight
-      for sel in selList
-        if sel.endPos.pos == location.pos
-          ret.push(sel)
-
-    else if rightOrLeft == 'left'
-      selList = @_rte.words[location.word].refLeft
-      for sel in selList
-        if sel.startPos.pos == location.pos
-          ret.push(sel)
-
-    else
-      throw new Error "Error in second argument of _getSel"
-
-    ret
-
-  # set the attribute to the selection and try to extend as much
+  # Set the style of the selection and try to extend as much
   # as possible existing ones
-  setAttr: (thisSel)->
+  #
+  setStyle: (selection, style)->
     if not thisSel.isValid()
       throw new Error "Invalid selection"
-    # goto left of selection, see if extendable
-    leftPos = _jump(thisSel.startPos, (-1))
-    if leftPos isnt null
-      w = @_rte.words[leftPos.word]
-      doLeft = true
-      # pop and repush references except if merge possible
-      while s = w.refRight.pop()
-        if s.type == thisSel.type and s.endPos.pos == leftPos.pos
-          doLeft = false
-        else
-          w.push(s)
-
-      if doLeft
-        @_rte.words[thisSel.startPos.word].refLeft.push(thisSel)
-
-    rightPos = _jump(thisSel.startPos, 1)
-    if rightPos isnt null
-      w = @_rte.words[rightPos.word]
-      doRight = true
-      # pop and repush references except if merge possible
-      while s = w.refRight.pop()
-        if s.type == thisSel.type and s.startPos.pos == rightPos.pos
-          doRight = false
-        else
-          w.push(s)
-
-      if doRight
-        @_rte.words[rightPos.left].refRight.push(thisSel)
-
-
-  apply: (deltas)->
+
+    # Try to merge with previous / next contiguous selection
+    {prevWord, position} = @_jump(s.startPos, -1)
+    {nextWord, position} = @_jump(s.endPos,   +1)
+    for s in prevWord.selections when selection.style == s.style
+      s.merge selection, @
+    for s in nextWord.selections when selection.style == s.style
+      s.merge selection, @
+
+  # Apply a delta to the object
+  # @see http://quilljs.com/docs/deltas/
+  #
+  delta: (deltas) ->
     position = 0
     for delta in deltas.ops
       if delta.retain?
@@ -338,7 +374,7 @@

       if delta.attributes?
         for attr in delta.attributes
-          @setAttr(selection.clone().attr = attr)
+          @setAttr(selection.clone().style = attr)

 if window?
   window.Rte = Rte


Clean up translated handles after handoff

* Solves a condition where lifeguard remains a listening process for the
  sockets that went through handoff. That led to random incoming connections
  getting lost.

--- lifeguard.coffee
+++ lifeguard.coffee
@@ -203,10 +203,13 @@
         # send SIGUSR2 to start the process
         process.kill old_instance.child.pid, "SIGUSR2"

+        handles = []
+
         # proxy messages between old and new
         oToN = (msg,handle) =>
           console.log "LIFEGUARD: oToN ", msg, handle?
           new_instance.child.send msg, handle
+          handles.push handle if handle?

         nToO = (msg,handle) =>
           console.log "LIFEGUARD: nToO ", msg, handle?
@@ -220,6 +223,9 @@
           # detach our proxies
           new_instance.child.removeListener "message", nToO
           console.log "Handoff done."
+
+          for h in handles
+            h.close?()

     else
       # INT will gracefully shut down workers and immediately kill the manager


get current group again in case of group changed event fired after user goes into activity feed

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -211,19 +211,21 @@
       #since it is not working, disabled it,
       #to-do add isExempt control.
       #@isExempt (exempt)=>
-        #if exempt or @getFilter() isnt activityTypes
+      #if exempt or @getFilter() isnt activityTypes
+      groupObj = KD.getSingleton('groupsController').getCurrentGroup()
+
       options =
         to     : options.to or Date.now()
         group  :
-          slug : currentGroup.slug or "koding"
-          id   : currentGroup.getId()
+          slug : groupObj?.slug or "koding"
+          id   : groupObj.getId()
         limit  : 20
         facets : @getActivityFilter()

       eventSuffix = "#{@getFeedFilter()}_#{@getActivityFilter()}"

-      group = KD.getSingleton('groupsController').getCurrentGroup().slug
       {roles} = KD.config
+      group   = groupObj?.slug

       if "koding" is group and  "guest" in roles
         @listFeaturedActivities callback


activity feed: refactor app controller

activity feed: refactor app controller

--- client/app/Applications/Activity.kdapplication/AppController.coffee
+++ client/app/Applications/Activity.kdapplication/AppController.coffee
@@ -292,18 +292,21 @@
         @isLoading = no
         log "fetchSomeActivities timeout reached"

+  # Enables switching between cache/neo4j.
+  getCacheUrl:->
+
+    defaultUrlPrefix = "cache"
+    if KD.config.useNeo4j then defaultUrlPrefix = "neo4j"
+
+    return defaultUrlPrefix
+
   fetchCachedActivity:(options = {}, callback)->

-    urlPrefix = "cache"
-
-    if KD.config.useNeo4j
-      urlPrefix = "neo4j"
-
     $.ajax
-      url     : "/-/#{urlPrefix}/#{options.slug or 'latest'}"
+      url     : "/-/#{@getCacheUrl()}/#{options.slug or 'latest'}"
       cache   : no
       error   : (err)->   callback? err
-      success : (cache)=>
+      success : (cache)->
         cache.overview.reverse()  if cache?.overview
         callback null, cache

@@ -311,7 +314,6 @@

     return  if @isLoading

-    # HACK: this gets called multiple times if there's no wait
     lastTimeStamp = (new Date @lastFrom or Date.now()).getTime()
     @populateActivity {slug : "before/#{lastTimeStamp}", to: lastTimeStamp}


myteam/teamsettings: show verify password modal with partial

--- client/home/lib/myteam/components/hometeamsettings/container.coffee
+++ client/home/lib/myteam/components/hometeamsettings/container.coffee
@@ -98,7 +98,14 @@

   onLeaveTeam: (event) ->

-    TeamFlux.actions.leaveTeam()
+    partial = '

+                CAUTION! You are going to leave your team and you will not be able to login again.
+                This action CANNOT be undone.
+

+
Please enter current password into the field below to continue:
'
+
+    TeamFlux.actions.leaveTeam(partial).catch (err) ->
+      showError err

   render: ->


Removed tab tooltips for ace.

Removed tab tooltips for ace.

--- client/landing/app/Applications/Ace.kdapplication/aceappview.coffee
+++ client/landing/app/Applications/Ace.kdapplication/aceappview.coffee
@@ -14,10 +14,8 @@
       saveSession          : yes
       sessionName          : "AceTabHistory"

-    @on 'ViewClosed', => @emit 'AceAppViewWantsToClose'
-
     @on 'AllViewsClosed', =>
-      appManager = KD.getSingleton('appManager')
+      appManager = KD.getSingleton 'appManager'
       appManager.quit appManager.frontApp

     @on 'UpdateSessionData', (openPanes, data = {}) =>
@@ -42,17 +40,19 @@
       ace.on "ace.ready", -> ace.focus()
       ace.focus()

-      unless pane.tabHandle.tooltipCreated
-        {nickname} = KD.whoami().profile
-        title      = ace.data.path.replace("/Users/#{nickname}/", "~/").replace "localfile:/", ""
-        pane.tabHandle.setTooltip
-          title     : title
-          placement : "bottom"
-          delayIn   : 800
-        pane.tabHandle.tooltipCreated = yes
+      # TODO: fatihacet - should add tab handle tooltips here
+
+      # unless pane.tabHandle.tooltipCreated
+      #   {nickname} = KD.whoami().profile
+      #   title      = ace.data.path.replace("/Users/#{nickname}/", "~/").replace "localfile:/", ""
+      #   pane.tabHandle.setTooltip
+      #     title     : title
+      #     placement : "bottom"
+      #     delayIn   : 800
+      #   pane.tabHandle.tooltipCreated = yes

       # ace.on "AceDidSaveAs", (name, parentPath) =>
-        # update tooltip title here
+      #   update tooltip title here

   viewAppended:->
     super


Ask SVG to tell us its bounding box.

Ask SVG to tell us its bounding box.

--- community/browser/lib/visualization/components/visualization.coffee
+++ community/browser/lib/visualization/components/visualization.coffee
@@ -202,17 +202,7 @@
     ].join(' '))

   viz.boundingBox = ->
-    border = (node) -> parseFloat(style.forNode(node).get("border-width"))
-    minX = Math.floor(d3.min(graph.nodes(), (node) -> node.x - node.radius - border(node)))
-    maxX = Math.ceil(d3.max(graph.nodes(), (node) -> node.x + node.radius + border(node)))
-    minY = Math.floor(d3.min(graph.nodes(), (node) -> node.y - node.radius - border(node)))
-    maxY = Math.ceil(d3.max(graph.nodes(), (node) -> node.y + node.radius + border(node)))
-    {
-      x: minX
-      y: minY
-      width: maxX - minX
-      height: maxY - minY
-    }
+    container.node().getBBox()

   clickHandler = neo.utils.clickHandler()
   clickHandler.on 'click', onNodeClick


Use correct labels for 'Favourite' button

--- core/app/backbone/views/channels/favourite_topic_button_view.coffee
+++ core/app/backbone/views/channels/favourite_topic_button_view.coffee
@@ -8,9 +8,9 @@
     @user = currentUser

   templateHelpers: =>
-    disabled_label: Factlink.Global.t.follow.capitalize()
-    disable_label:  Factlink.Global.t.unfollow.capitalize()
-    enabled_label:  Factlink.Global.t.following.capitalize()
+    disabled_label: Factlink.Global.t.favourite.capitalize()
+    disable_label:  Factlink.Global.t.unfavourite.capitalize()
+    enabled_label:  Factlink.Global.t.favourited.capitalize()

   buttonEnabled: ->
     @user.favourite_topics.contains @model


Activity: sidebar removeItem logic fixed

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -201,10 +201,13 @@
     return item

-  removeItem: (id) ->
-
-    if item = @getItemByData data
+   removeItem: (id) ->
+
+    if item = @itemsById[id]
+
+      data           = item.getData()
       listController = @getListController data.typeConstant
+
       @unregisterItem item
       listController.removeItem item


Activity: onboarding notification count logic taken to public channel

... actvity link removed

--- client/Social/Activity/sidebar/activitysidebar.coffee
+++ client/Social/Activity/sidebar/activitysidebar.coffee
@@ -310,21 +310,6 @@
       listController.deselectAllItems()

-  getItemByRouteParams: (type, slug) ->
-
-    typeConstant = k for own k, v of typeMap when v is type
-
-    itemWeWant = null
-    for own name, section of @sections
-
-      section.listController.getListItems().forEach (item)->
-        if item.typeConstant is typeConstant
-          slugProp = slugProps[item.bongo_.constructorName]
-          itemWeWant = item[slugProp] is slug
-
-    return itemWeWant
-
-
   viewAppended: ->

     super
@@ -334,26 +319,19 @@
     @addConversations()
     @addMessages()

-    unless KD.singletons.mainController.isFeatureDisabled 'activity-link'
-
-      @addSubView @activityLink = new CustomLinkView
-        title    : 'Activity'
-        cssClass : 'kdlistitemview-sidebar-item activity'
-        href     : '/Activity/Public'
-        click    : ->
-          @setClass 'selected'
-          @$('cite.count').remove()
-        icon     : {}
-
-      @activityLink.setPartial '<cite class=\'count hidden\'>1</cite>'

   initiateFakeCounter: ->

-    return  if KD.singletons.mainController.isFeatureDisabled 'activity-link'
-
     KD.utils.wait 5000, =>
-      @activityLink.setClass 'unread'
-      @activityLink.$('cite').removeClass 'hidden'
+      publicLink = @sections.channels.listController.getListItems().first
+      publicLink.setClass 'unread'
+      publicLink.unreadCount.updatePartial 1
+      publicLink.unreadCount.show()
+
+      publicLink.on 'click', ->
+        KD.utils.wait 177, ->
+          publicLink.unsetClass 'unread'
+          publicLink.unreadCount.hide()


TeamMembersView: Deleted unused class.

TeamMembersView: Deleted unused class.

--- client/admin/lib/views/members/teammembersview.coffee
+++ client/admin/lib/views/members/teammembersview.coffee
@@ -1,8 +0,0 @@
-TeamMembersCommonView = require './teammemberscommonview'
-
-
-module.exports = class TeamMembersView extends TeamMembersCommonView
-
-  constructor: (options = {}, data) ->
-
-    super options, data


proper checking for propchanges for line_properties

--- bokehjs/src/coffee/renderer/properties.coffee
+++ bokehjs/src/coffee/renderer/properties.coffee
@@ -302,18 +302,49 @@
       @cache.setLineDash       = @source_v_select(@line_dash_name, @, datasource)
       @cache.setLineDashOffset = @source_v_select(@line_dash_offset_name, @, datasource)

+      @last_strokeStyle       = false
+      @last_globalAlpha       = false
+      @last_lineWidth         = false
+      @last_lineJoin          = false
+      @last_lineCap           = false
+      @last_setLineDash       = false
+      @last_setLineDashOffset = false
+
     clear_prop_cache: () ->
       @cache = {}

     set_vectorize: (ctx, i) ->
-      ctx.strokeStyle = @cache.strokeStyle[i]
-      ctx.globalAlpha = @cache.globalAlpha[i]
-      ctx.lineWidth   = @cache.lineWidth[i]
-      ctx.lineJoin    = @cache.lineJoin[i]
-      ctx.lineCap     = @cache.lineCap[i]
-      ctx.setLineDash(@cache.setLineDash[i])
-      ctx.setLineDashOffset(@cache.setLineDashOffset[i])
-      return true
+      didchange = false
+      if not (@last_strokeStyle == @cache.strokeStyle[i])
+        ctx.strokeStyle = @cache.strokeStyle[i]
+        @last_strokeStyle = @cache.strokeStyle[i]
+        didchange = true
+      if not (@last_globalAlpha == @cache.globalAlpha[i])
+        ctx.globalAlpha = @cache.globalAlpha[i]
+        @last_globalAlpha = @cache.globalAlpha[i]
+        didchange = true
+      if not (@last_lineWidth == @cache.lineWidth[i])
+        ctx.lineWidth   = @cache.lineWidth[i]
+        @last_lineWidth = @cache.lineWidth[i]
+        didchange = true
+      if not (@last_lineJoin == @cache.lineJoin[i])
+        ctx.lineJoin    = @cache.lineJoin[i]
+        @last_lineJoin = @cache.lineJoin[i]
+        didchange = true
+      if not (@last_lineCap == @cache.lineCap[i])
+        ctx.lineCap     = @cache.lineCap[i]
+        @last_lineCap = @cache.lineCap[i]
+        didchange = true
+      if not (@last_setLineDash == @cache.setLineDash[i])
+        ctx.setLineDash(@cache.setLineDash[i])
+        @last_setLineDash = @cache.setLineDash[i]
+        didchange = true
+      if not (@last_setLineDashOffset == @cache.setLineDashOffset[i])
+        ctx.setLineDashOffset(@cache.setLineDashOffset[i])
+        @last_setLineDashOffset = @cache.setLineDashOffset[i]
+        didchange = true
+
+      return didchange

   class fill_properties extends properties
@@ -344,12 +375,10 @@
     set_vectorize: (ctx, i) ->
       didchange = false
       if not (@cache.fillStyle[i] == @last_fillstyle)
-        #console.log("fillstyle changed", i, @cache.fillStyle[i] , @last_fillstyle)
         ctx.fillStyle   = @cache.fillStyle[i]
         @last_fillstyle = @cache.fillStyle[i]
         didchange = true
       if not (@cache.globalAlpha[i] == @last_globalalpha)
-        #console.log("fillalpha changed", i)
         ctx.globalAlpha = @cache.globalAlpha[i]
         @last_globalalpha = @cache.globalAlpha[i]
         didchange=true


MachineSettingsModal: do not focus on domain field [completes #77775990]

--- client/Main/machinesettingsmodal.coffee
+++ client/Main/machinesettingsmodal.coffee
@@ -197,6 +197,7 @@

     @convertToSubdomainField()
     domain.setValue _domain
+    domain.blur()

   handleChange: ->
@@ -225,7 +226,6 @@
     domain.setValue ''

     @handleChange()
-    domain.setFocus()
     toggle?()


activity/flux: add & export MessageThread related getters

--- client/activity/lib/flux/getters.coffee
+++ client/activity/lib/flux/getters.coffee
@@ -13,10 +13,12 @@
 ChannelsStore                  = [['ChannelsStore'], withEmptyMap]
 MessagesStore                  = [['MessagesStore'], withEmptyMap]
 ChannelThreadsStore            = [['ChannelThreadsStore'], withEmptyMap]
+MessageThreadsStore            = [['MessageThreadsStore'], withEmptyMap]
 FollowedPublicChannelIdsStore  = [['FollowedPublicChannelIdsStore'], withEmptyMap]
 FollowedPrivateChannelIdsStore = [['FollowedPrivateChannelIdsStore'], withEmptyMap]
 ChannelPopularMessageIdsStore  = [['ChannelPopularMessageIdsStore'], withEmptyMap]
 SelectedChannelThreadIdStore   = ['SelectedChannelThreadIdStore'] # no need for default
+SelectedMessageThreadIdStore   = ['SelectedMessageThreadIdStore']
 SuggestionsStore               = [['SuggestionsStore'], withEmptyList]
 SuggestionsQueryStore          = ['SuggestionsQueryStore']
 SuggestionsFlagsStore          = [['SuggestionsFlagsStore'], withEmptyMap]
@@ -134,6 +136,39 @@
   (messages, id) -> messages.get id
 ]

+selectedMessageThreadId = SelectedMessageThreadIdStore
+
+selectedMessage = [
+  MessagesStore
+  selectedMessageThreadId
+  (messages, id) -> if id then messages.get id else null
+]
+
+# Maps channels message ids with relevant message instances.
+messageThreads = [
+  MessageThreadsStore
+  MessagesStore
+  (threads, messages) ->
+    threads.map (thread) ->
+      # replace messageIds in list with message instances.
+      thread.update 'comments', (comments) ->
+        comments.map (id) -> messages.get id
+]
+
+selectedMessageThread = [
+  messageThreads
+  selectedMessage
+  (threads, message) ->
+    return null  unless message
+    thread = threads.get message.get('id')
+    return thread.set 'message', message
+]
+
+selectedMessageThreadComments = [
+  selectedMessageThread
+  (thread) ->
+    return null  unless thread
+    thread.get 'comments'
 ]

 # Aliases for providing consistent getter names for suggestion stores
@@ -150,6 +185,10 @@
   selectedChannelThread
   selectedChannelThreadMessages

+  selectedMessageThreadId
+  selectedMessageThread
+  selectedMessageThreadComments
+
   selectedChannelPopularMessages

   currentSuggestionsQuery


Added capability to get all commits or do not limit them by giving -1 as limit

--- src/repo.coffee
+++ src/repo.coffee
@@ -73,6 +73,9 @@
   #
   #   # Skip some (for pagination):
   #   repo.commits "master", 30, 30, (err, commits) ->
+  #
+  #   # Do not limit commits amount
+  #   repo.commits "master", -1, (err, commits) ->
   #
   commits: (start, limit, skip, callback) ->
     [skip,  callback] = [callback, skip]  if !callback
@@ -82,8 +85,12 @@
     start ?= "master"
     limit ?= 10
     skip  ?= 0
-
-    Commit.find_all this, start, {"max-count": limit, skip}, callback
+    options = {skip}
+
+    if limit != -1
+      options["max-count"] = limit
+
+    Commit.find_all this, start, options, callback

   # Internal: Returns current commit id


KlientKite: remove from sessions list if it's already dead [completes #90770620]

--- client/app/lib/kite/kites/klient.coffee
+++ client/app/lib/kite/kites/klient.coffee
@@ -81,20 +81,30 @@
         return remote

+  _removeSession: (session)->
+
+    @removeFromActiveSessions session
+    @terminalSessions = @terminalSessions.filter (currentSession)->
+      session isnt currentSession
+
+
   webtermKillSession: (options)->

     {session} = options

     @tell 'webterm.killSession', options

-      .then (state) =>
+    .then (state) =>

-        @removeFromActiveSessions session
+      @_removeSession session
+      return state

-        @terminalSessions = @terminalSessions.filter (currentSession)->
-          session isnt currentSession
+    .catch (err)=>

-        return state
+      if err.name is 'KiteError' and /No screen session found/i.test err.message
+        @_removeSession session
+
+      throw err

   fetchTerminalSessions: (session)->


Actually fix path bug. Fixes #2

Actually fix path bug. Fixes #2

--- lib/linter-flow.coffee
+++ lib/linter-flow.coffee
@@ -100,19 +100,15 @@
     @flowPath = @findFlowInPath()

     @flowEnabled = true
-    console.log @flowEnabled
     @flowEnabled &= atom.project.path and fs.existsSync(atom.project.path)
-    console.log @flowEnabled
     @flowEnabled &= fs.existsSync(path.join(atom.project.path, '.flowconfig'))
-    console.log @flowEnabled
-    @flowEnabled &= !(@flowPath is null)
-    console.log @flowEnabled
-
-    @flowPath = path.join(@flowPath, 'flow')
+    @flowEnabled &= @flowPath?

     unless @flowEnabled
       console.log 'Flow is disabled, exiting!'
       return
+
+    @flowPath = path.join(@flowPath, 'flow')

     flowServer = spawn(@flowPath, ['start', '--all', '--module', 'node', path.resolve(atom.project.path)])
     flowServer.on 'close', (code) => @flowEnabled &= (code is 0)


#fix regexp bug in chrome when stripping markup

The expression '/<(?:.|\s)*?>/g' chrashes the current version of chrome.
The new expression is simpler, does not chrash the browser and should otherwise have the same effect.

--- lib/combo.coffee
+++ lib/combo.coffee
@@ -413,7 +413,7 @@
         $(li).addClass('selected')

     stripMarkup: (text) ->
-      text?.replace(/<br[^>]*>/g, " | ").replace(/<(?:.|\s)*?>/g, '').replace(/[\n\r]/g, '')
+      text?.replace(/<br[^>]*>/g, " | ").replace(/<.*?>/g, '').replace(/[\n\r]/g, '')

     searchAndExpand: (options = {}) =>
       @lastQuery = @input.val()


Added some comments to Node.coffee

Added some comments to Node.coffee

--- src/node.module.js.coffee
+++ src/node.module.js.coffee
@@ -82,12 +82,13 @@
 			else if action.transition == Node.Transitions.DEACTIVATE
 				@deactivate(action.node)

-	# # # instance
+	# Instance functions

 	constructor: (opts={})->
 		parent = null
 		children = []

+		# Did we receive an array of children from the opts hash?
 		if opts.children
 			for node in opts.children
 				node.setParent @
@@ -100,8 +101,8 @@
 		onActivatedAction = null
 		onDeactivatedAction = null

+		# Create and return the state machine instance
 		smRef = null
-		# Create and return the state machine instance
 		@sm = ->
 			smRef ||= new StateMachine
 				states: [Node.States.DEACTIVATED, Node.States.ACTIVATED]
@@ -114,14 +115,14 @@
 						from: Node.States.ACTIVATED
 						to: Node.States.DEACTIVATED

+		# Add a callback lambda to the SM and map the two
+		# state transitions to methods in this class.
 		@sm().performTransition = (t) =>
 			if t == Node.Transitions.ACTIVATE
 				@beforeActivate()
-				#@trigger 'activating', @
 				@doActivate()
 			else if t == Node.Transitions.DEACTIVATE
 				@beforeDeactivate()
-				#@trigger 'deactivating', @
 				@doDeactivate()

 		@setOnActivatedAction = (action) ->
@@ -150,12 +151,6 @@

 		@addChild = (node) ->
 			node.setParent(@)
-
-			# node.bind 'onActivated', =>
-			# 	@onChildActivated node
-			# node.bind 'onDeactivated', =>
-			# 	@onChildDeactivated node
-
 			children.push node

 		@removeChild = (node) ->


upd: test

upd: test

--- test/lab.coffee
+++ test/lab.coffee
@@ -5,19 +5,11 @@
 Promise_Bird.longStackTraces()

 test = (Promise) ->
-    resolved = (v) ->
-        new Promise (r) ->
-           r v
-
-    promise = resolved({ dummy: "dummy" }).then(->
-        {
-            then: (resolvePromise) ->
-                resolvePromise resolved({
-                    then: (onFulfilled) ->
-                        onFulfilled({ sentinel: "sentinel" });
-                })
-        };
-    );
+    promise = new Promise (r) ->
+        r {
+            then: (ful) ->
+                ful 'done'
+        }

     promise.then((value) ->
         console.log(value)


Python vs Python Console

Python vs Python Console

--- lib/Repl.coffee
+++ lib/Repl.coffee
@@ -19,7 +19,7 @@
     @subscriptions = new CompositeDisposable

      # Register command that toggles this view
-    @subscriptions.add atom.commands.add 'atom-workspace', 'Repl:Repl Python': => @create("Python Console")
+    @subscriptions.add atom.commands.add 'atom-workspace', 'Repl:Repl Python': => @create("Python")
     @subscriptions.add atom.commands.add 'atom-workspace', 'Repl:Repl Coffee': => @create("CoffeeScript")
     @subscriptions.add atom.commands.add 'atom-workspace', 'Repl:Repl Bash': => @create('Shell Session')
     @subscriptions.add atom.commands.add 'atom-workspace', 'Repl:Repl Ocaml': => @create('OCaml')


Update URLs

Update URLs

--- grammars/csound.cson
+++ grammars/csound.cson
@@ -135,13 +135,13 @@
   # The formatSpecifiers patterns are included in quoted and braced strings.
   # This means that format specifiers are highlighted in all strings, even
   # though only
-  #   fprintks        https://csound.github.io/docs/manual/fprintks.html
-  #   fprints         https://csound.github.io/docs/manual/fprints.html
-  #   printf/printf_i https://csound.github.io/docs/manual/printf.html
-  #   printks         https://csound.github.io/docs/manual/printks.html
-  #   prints          https://csound.github.io/docs/manual/prints.html
-  #   sprintf         https://csound.github.io/docs/manual/sprintf.html
-  #   sprintfk        https://csound.github.io/docs/manual/sprintfk.html
+  #   fprintks        https://csound.com/docs/manual/fprintks.html
+  #   fprints         https://csound.com/docs/manual/fprints.html
+  #   printf/printf_i https://csound.com/docs/manual/printf.html
+  #   printks         https://csound.com/docs/manual/printks.html
+  #   prints          https://csound.com/docs/manual/prints.html
+  #   sprintf         https://csound.com/docs/manual/sprintf.html
+  #   sprintfk        https://csound.com/docs/manual/sprintfk.html
   # work with strings that contain format specifiers. In addition, these
   # opcodes’ handling of format specifiers is inconsistent:
   #   - fprintks, fprints, printks, and prints do accept %a and %A specifiers,


display locale in error message, remove unused argument

--- lib/preferences.coffee
+++ lib/preferences.coffee
@@ -1,11 +1,10 @@
 PU = require './preferences-util'
 PreferencesSettings = require './preferences-settings'
+Util = require './util'

 class Preferences

-  # NOTE entry point of setting
   @localize: (defS) ->
-    # NOTE settings.cson
     @defS = defS
     @updateSettings()  # first time localize
     atom.workspace.onDidChangeActivePaneItem (item) =>
@@ -13,10 +12,10 @@
       if item.__proto__.constructor.name is 'SettingsView'
         @updateSettings(true)

-  @updateSettings: (onSettingsOpen = false) ->
-    setTimeout(@delaySettings, 0, onSettingsOpen)
+  @updateSettings: () ->
+    setTimeout(@delaySettings, 0)

-  @delaySettings: (onSettingsOpen) =>  # NOTE onSettingsOpen is not used
+  @delaySettings: () =>
     settingsTab = document.querySelector('.tab-bar [data-type="SettingsView"]')
     settingsEnabled = settingsTab.className.includes 'active' if settingsTab
     return unless settingsTab && settingsEnabled
@@ -41,8 +40,7 @@

       window.I18N.pref.done = true
     catch e
-      console.error "I18N failed with locale: ", e
-      # TODO print current locale
+      console.error "I18N failed with locale #{atom.config.get('atom-i18n.locale')}: ", e

   @applyFonts: () =>
     if process.platform == 'win32'


runner: detailsLog is now disabled by default

--- spec/runner.coffee
+++ spec/runner.coffee
@@ -13,6 +13,7 @@
     timeout: null # set in beforeEach
     hardtimeout: null # set in beforeEach
     verbose: false
+    detailsLog: true

   before (done) ->
     solver = new jsjob.Runner solveroptions


test to ensure hoisting works with virtual defined in ruleset selector


Syntax.

Syntax.

--- lib/publication.coffee
+++ lib/publication.coffee
@@ -54,10 +54,10 @@
   thumbnailUrl: (page) =>
     Storage.url @thumbnail page

-  thumbnailUrls: () =>
+  thumbnailUrls: =>
     @thumbnailUrl page for page in [1..@numberOfPages]

-  createdDay = =>
+  createdDay: =>
     moment(@created).format 'MMMM Do YYYY'

 @Publications = Publications


Refactored code

Refactored code

--- src/scss.coffee
+++ src/scss.coffee
@@ -32,8 +32,6 @@
           path.relative(file.cwd, file.base),
           path.dirname(file.relative)
         )
-
-        compilerPromise = q.defer()
         q.nfcall(mkdirp, tmpDir).then(
           ->
             defer = q.defer()
@@ -84,18 +82,15 @@
               mapConverter.removeMapFileComments(contents).trim()
             )
             cb null, file
-            compilerPromise.resolve()
         ).catch(
           (e) ->
             error = new gutil.PluginError(
               "gulp-scss",
               "Compilation failed.: #{e}\n\nStack Trace:\n#{e.stack}"
             )
-            compilerPromise.reject error
             cb error
             throw error
         )
-        return compilerPromise.promise
 ], (
   "exec": require("child_process").spawn
   "mkdirp": require "mkdirp"


Explicit whitelist of image types. Fix #174

--- lib/files.coffee
+++ lib/files.coffee
@@ -52,7 +52,7 @@

 @fileType = (file) ->
   file = findFile file unless file.contentType?
-  if file?.contentType[...6] == 'image/'
+  if file?.contentType in ['image/gif', 'image/jpeg', 'image/png', 'image/svg+xml', 'image/webp', 'image/x-icon']
     'image'
   else if file?.contentType in ['video/mp4', 'video/ogg', 'video/webm']
     'video'


Update script view to use editor.getSelection()

--- lib/script-view.coffee
+++ lib/script-view.coffee
@@ -95,26 +95,22 @@
       return false

     filename = editor.getTitle()
-
-    # Get selected text
-    selectedText = editor.getSelectedText()
     filepath = editor.getPath()
-
-    # If no text was selected, either use the file
-    # or select ALL the code in the editor
-
-    # Brand new file, text not selected, "select" ALL the text
-    if (not selectedText? or not selectedText) and not filepath?
-      selectedText = editor.getText()
+    selection = editor.getSelection()

     # No selected text on a file that does exist, use filepath
-    if (not selectedText? or not selectedText) and filepath?
+    if selection.isEmpty() and filepath?
       argType = 'File Based'
       arg = filepath
       editor.save()
     else
       argType = 'Selection Based'
-      arg = selectedText
+      # If the selection was empty "select" ALL the text
+      # This allows us to run on new files
+      if selection.isEmpty()
+         arg = editor.getText()
+      else
+         arg = selection.getText()

     try
       if not @runOptions.cmd? or @runOptions.cmd is ''


add vbd and pbd

add vbd and pbd

--- src/spec.coffee
+++ src/spec.coffee
@@ -211,7 +211,7 @@
           # FIXME: Do not include control domains.
           VMs: get('resident_VMs')

-          $PBDs: [] # TODO
+          $PBDs: get('PBDs')

           $pool: get('$pool')

@@ -259,7 +259,7 @@
           # VM is not on a host.
           $container: get('resident_on')

-          $VBDs: [] # TODO
+          $VBDs: get('VBDs')

       'VM-controller':
         extends: 'VM'


documentation :doge:

documentation :doge:

--- lib/index.coffee
+++ lib/index.coffee
@@ -6,6 +6,12 @@
 module.exports = (opts) ->

   class Records
+
+    ###*
+     * Creates a locals object if one isn't set and
+     * creates a records object to store results.
+     * @constructor
+     ###

     constructor: (@roots) ->
       @roots.config.locals ||= {}
@@ -25,6 +31,13 @@
         return if @roots.config.locals.records?
         @roots.config.locals.records = @__records

+    ###*
+     * Determines and calls the appropriate function
+     * for retrieving json based on keys in object.
+     * @param {string} key - the record key
+     * @param {object} obj - the key's parameters
+    ###
+
     get = (key, obj) ->
       if obj.url?
         return url.call(@, key, obj)
@@ -35,22 +48,57 @@
       else
         throw new Error "A valid key is required"

+    ###*
+     * Runs http request for json if URL is passed,
+     * adds result to records, and returns a promise.
+     * @param {string} key - the record key
+     * @param {object} obj - the key's parameters
+     ###
+
     url = (key, obj) ->
       nodefn.call(request, obj.url)
         .tap (response) =>
           respond.call(@, key, obj, JSON.parse(response[0].body))
+
+    ###*
+     * Reads a file if a path is passed, adds result
+     * to records, and returns a promise.
+     * @param {string} key - the record key
+     * @param {object} obj - the key's parameters
+     ###

     file = (key, obj) ->
       W ->
         f = fs.readFileSync obj.file, 'utf8'
         respond.call(@, key, obj, JSON.parse(f))

+    ###*
+     * If an object is passed, adds object
+     * to records, and returns a promise.
+     * @param {string} key - the record key
+     * @param {object} obj - the key's parameters
+     ###
+
     data = (key, obj) ->
       W ->
         respond.call(@, key, obj, obj.data)

+    ###*
+     * Takes json and adds to records object
+     * @param {string} key - the record key
+     * @param {object} obj - the key's parameters
+     * @params {object} json - the json result
+     ###
+
     respond = (key, obj, json) ->
       @__records[key] = to(json, obj.path)
+
+    ###*
+     * Navigates object based on "path."
+     * ex: 'book/items' returns obj['books']['items']
+     * @param {string} key - the record key
+     * @param {object} obj - the key's parameters
+     ###

     to = (json, path) ->
       if not path then return json


fixed indent

fixed indent

--- lib/index.coffee
+++ lib/index.coffee
@@ -9,4 +9,4 @@
     else
       bumped.logger.plugin term.stdout

-     cb err
+    cb err


and thats why you always run the tests before publishing

--- lib/index.coffee
+++ lib/index.coffee
@@ -43,7 +43,7 @@
     compiler = _.find @roots.config.compilers, (c) ->
       _.contains(c.extensions, path.extname(tpl).substring(1))

-    compiler.renderFile(tpl, _.deepClone(locals))
+    compiler.renderFile(tpl, _.cloneDeep(locals))
       .then((res) => @util.write(output, res.result))
       .yield(output)


Clear presets hash if needed based on changing inform parameters or bootstrap event

--- lib/cwmp.coffee
+++ lib/cwmp.coffee
@@ -139,13 +139,8 @@
   actions.set._lastBoot = now if '1 BOOT' in cwmpRequest.methodRequest.event
   actions.set._lastBootstrap = lastBootstrap = now if '0 BOOTSTRAP' in cwmpRequest.methodRequest.event

-  db.redisClient.get("#{currentRequest.deviceId}_inform_hash", (err, res) ->
+  db.redisClient.get("#{currentRequest.deviceId}_inform_hash", (err, oldInformHash) ->
     throw err if err
-
-    if not res?
-      db.redisClient.setex("#{currentRequest.deviceId}_inform_hash", config.PRESETS_CACHE_DURATION, informHash, (err, res) ->
-        throw err if err
-      )

     updateAndRespond = () ->
       updateDevice(currentRequest, actions, (err) ->
@@ -154,8 +149,12 @@
         writeResponse(currentRequest, res)
       )

-    if res == informHash
+    if oldInformHash == informHash and not lastBootstrap?
       return updateAndRespond()
+
+    db.redisClient.setex("#{currentRequest.deviceId}_inform_hash", config.PRESETS_CACHE_DURATION, informHash, (err, res) ->
+      throw err if err
+    )

     # populate projection and parameterStructure
     parameterStructure = {}
@@ -201,6 +200,12 @@
         for cmd of deviceCustomCommands
           actions.deletedObjects ?= []
           actions.deletedObjects.push("_customCommands.#{cmd}")
+
+        # clear presets hash if parameters are potentially modified
+        if _tasks.length > 0
+          db.redisClient.del("#{currentRequest.deviceId}_presets_hash", (err) ->
+            throw err if err
+          )

         apiFunctions.insertTasks(_tasks, {}, () ->
           updateAndRespond()


:bug: Fix a bug with Errors Display A bug where you would need to change activate tab to show errors for the first time

--- lib/typechecker.coffee
+++ lib/typechecker.coffee
@@ -4,18 +4,16 @@
   Subscriptions = []
   Decorations = []
   Errors = []
-  Editor = null
+  Editor = atom.workspace.getActiveEditor()
   EditorView = atom.workspaceView.getActiveView()
-  ActiveFile = null
+  ActiveFile = Editor?.getPath()
   TooltipInstance = null
   class TypeChecker
     @activate:->
       return unless !Main.Status.TypeChecker
       Main.Status.TypeChecker = true
       Subscriptions.push atom.workspace.observeTextEditors (editor)=>
-        Editor = editor
         editor.buffer.onDidSave (info)=>
-          ActiveFile = info.path
           @Lint()
       Subscriptions.push atom.workspace.onDidChangeActivePaneItem =>
         EditorView?.off 'click.atom-hack'


fix: unified parameter wants a number

fix: unified parameter wants a number

from `hg help diff`:
    -U --unified NUM         number of lines of context to show
set it to 0 to correct parse the output.

--- lib/diff.coffee
+++ lib/diff.coffee
@@ -10,14 +10,14 @@
 			return cmd.files([], opts)
 		# using unified diff format
 		rlist = revs.map (x) -> "-r #{x}"
-		args = ['--unified', diff_opts(opts)..., rlist...]
+		args = ['--unified=0', diff_opts(opts)..., rlist...]
 		hg.run('diff', args).then(parse)
 	# diff files
 	cmd.files = (files, opts) ->
 		# todo support more options
 		files = [] if not files
 		# using unified diff format
-		args = ['--unified', diff_opts(opts)...]
+		args = ['--unified=0', diff_opts(opts)...]
 		hg.run('diff', [args..., files...]).then(parse)
 	cmd


Remove match rules on initialization

Remove match rules on initialization

--- lib/form.coffee
+++ lib/form.coffee
@@ -59,6 +59,13 @@
     showIndicators: @props.showIndicators

   componentDidMount: ->
+    @props.schema = _.cloneDeep @props.schema
+
+    _.each @props.schema, (field, name) ->
+      # Delete match rules since they will not work with only one field.
+      if field.rules?.match
+        delete field.rules.match
+
     @setState focused: _.first @state.fieldOrder

   onFieldChange: (field, value) ->


Remove listeners for specific events in Task class

This change is necessary for Electron 3 to a bug in Node.js 10.2.x which causes
the `removeAllListeners` method to no longer remove all listeners for any event
name when no argument is passed:

https://github.com/nodejs/node/pull/20924

This issue has been fixed in Node.js 10.3.0+ so we will have to remove this
workaround when we move to Electron 4.0 to avoid future event handler leaks.

--- src/task.coffee
+++ src/task.coffee
@@ -84,17 +84,17 @@

   # Routes messages from the child to the appropriate event.
   handleEvents: ->
-    @childProcess.removeAllListeners()
+    @childProcess.removeAllListeners('message')
     @childProcess.on 'message', ({event, args}) =>
       @emitter.emit(event, args) if @childProcess?

     # Catch the errors that happened before task-bootstrap.
     if @childProcess.stdout?
-      @childProcess.stdout.removeAllListeners()
+      @childProcess.stdout.removeAllListeners('data')
       @childProcess.stdout.on 'data', (data) -> console.log data.toString()

     if @childProcess.stderr?
-      @childProcess.stderr.removeAllListeners()
+      @childProcess.stderr.removeAllListeners('data')
       @childProcess.stderr.on 'data', (data) -> console.error data.toString()

   # Public: Starts the task.
@@ -147,9 +147,9 @@
   terminate: ->
     return false unless @childProcess?

-    @childProcess.removeAllListeners()
-    @childProcess.stdout?.removeAllListeners()
-    @childProcess.stderr?.removeAllListeners()
+    @childProcess.removeAllListeners('message')
+    @childProcess.stdout?.removeAllListeners('data')
+    @childProcess.stderr?.removeAllListeners('data')
     @childProcess.kill()
     @childProcess = null


Use || instead of tenary operator

Use || instead of tenary operator

Follow-up of PR #2

--- src/tell.coffee
+++ src/tell.coffee
@@ -41,7 +41,7 @@
     recipients = msg.match[2].split(',').filter((x) -> x?.length)
     message = msg.match[3]

-    room = if msg.message.user.reply_to then msg.message.user.reply_to else msg.message.user.room
+    room = msg.message.user.reply_to || msg.message.user.room
     tellmessage = [msg.message.user.name, new Date(), message]
     if not localstorage[room]?
       localstorage[room] = {}


:new: [linter] Add batchUpdateMessages config

--- lib/main.coffee
+++ lib/main.coffee
@@ -48,6 +48,11 @@
       enum: ['Left', 'Right']
       type: 'string'
       default: 'Left'
+    batchUpdateMessages:
+      title: 'Update messages when all providers are done'
+      description: 'It stops panel from jumping up and down'
+      type: 'boolean'
+      default: false

   activate: (@state) ->
     LinterPlus = require('./linter.coffee')


common & variations

common & variations

--- lib/main.coffee
+++ lib/main.coffee
@@ -39,3 +39,58 @@
     lang = window.navigator.language || window.navigator.userLanguage
   if lang && _.has(langList, lang)
     Lang.init(lang)
+
+common =
+  en: "English"
+  de: "Deutsch"
+  fr: "Français"
+  es: "Español"
+  it: "Italiano"
+  pt: "Português"
+  ru: "Русский"
+  zh: "中文"
+  id: "Bahasa Indonesia"
+  bs: "Bosanski"
+  ca: "Català"
+  cs: "Čeština"
+  da: "Dansk"
+  hr: "Hrvatski"
+  sw: "Kiswahili"
+  lv: "Latviešu"
+  lt: "Lietuvių"
+  hu: "Magyar"
+  ms: "Melayu"
+  nl: "Nederlands"
+  nb: "Norsk bokmål"
+  pl: "Polski"
+  ro: "Română"
+  sq: "Shqip"
+  sl: "Slovenski"
+  sk: "Slovenský"
+  sr: "Srpski"
+  fi: "Suomi"
+  sv: "Svenska"
+  tl: "Tagalog"
+  vi: "Tiếng Việt"
+  tr: "Türkçe"
+  el: "Ελληνικά"
+  bg: "Български"
+  uk: "Українська"
+  he: "עברית"
+  ar: "العربية"
+  hi: "हिंदी"
+  th: "ไทย"
+  ja: "日本語"
+  ko: "한국어"
+
+variations =
+  "en-us": "United States"
+  "en": "UK"
+  "es": "España"
+  "es-co": "Colombia"
+  "es-mx": "México"
+  "es-ar": "Argentina"
+  "pt": "Brasil"
+  "pt-pt": "Portugal"
+  "zh": "简体"
+  "zh-Hant": "繁體"


OK worked again!!

OK worked again!!

--- lib/main.coffee
+++ lib/main.coffee
@@ -99,7 +99,7 @@
     @Pane      ?= thisPane.constructor
     @PaneAxis  ?= root.constructor

-    parent.removeChild(thisPane, true)
+    # parent.removeChild(thisPane, true)
     if root.getOrientation() isnt orientation
       @debug "Different orientation"
       root = new @PaneAxis({container, orientation, children: [@copyRoot(root)]})
@@ -114,7 +114,7 @@

     container.setRoot(root) if root isnt paneInfo.root
     container.destroyEmptyPanes()
-    # @reparentPaneAxis(axis) for axis in @getAllAxis(root)
+    @reparentPaneAxis(axis) for axis in @getAllAxis(root)
     newPane.activateItemAtIndex index
     newPane.activate()

@@ -168,6 +168,9 @@
     return list

   copyPaneAxis: (paneAxis) ->
+    # unsubscribe before copy
+    paneAxis.unsubscribeFromChild(child) for child in paneAxis.getChildren()
+
     {container, orientation} = paneAxis
     new paneAxis.constructor({container, orientation, children: paneAxis.getChildren()})

@@ -175,9 +178,6 @@
     newRoot = @copyPaneAxis(root)
     root.destroy()
     for paneAxis in @getAllAxis(newRoot)
-      # unsubscribe before copy
-      paneAxis.unsubscribeFromChild(child) for child in paneAxis.getChildren()
-
       newPaneAxis = @copyPaneAxis paneAxis
       paneAxis.parent.replaceChild paneAxis, newPaneAxis
       paneAxis.destroy()


remove old code; refactoring

remove old code; refactoring

--- lib/init.coffee
+++ lib/init.coffee
@@ -243,7 +243,7 @@
             onMouseOut: handleDestroyDecorations,
             entities: entitiesAtPos
         }), statusBar)
-        #statusBar.innerHTML = "
#{entitiesAtPos.join(', ')}
"
+

@@ -287,37 +287,8 @@
     if !filename
         return

-    if debug
-        safeToRun = editor.buffer.getText().match(/\/\/ safe to run/)
-        if safeToRun
-            require.cache[editor.buffer.file.path] = null
-            try
-                result = require(editor.buffer.file.path)
-            catch e
-                result = e
-        else
-            result = '???'
-        block = document.createElement('div')
-        block.innerHTML = result
-        #block.src = 'http://localhost:8000'
-        block.width = '100%'
-        block.height = '900px'
-        #block.style.height = (15 * 5) + 'px';
-
-        range = new Range([0, 0], [0, 0])
-        if previewMarker
-            previewMarker.destroy()
-        previewMarker = editor.markScreenPosition([0, 0]) #editor.markBufferRange(range)
-        decoration = editor.decorateMarker(previewMarker, {item:block, type: 'block', position: 'before'})
-
-

     update = (f)->
-        if dashboard
-            plugin.filterFiles((v) -> v).toArray().subscribe( (files)->
-                dashboard.setFiles(files, addLabelDecoration) # TODO remove addLabelDecoration from here. This is hack
-            )
-

         refreshStructure plugin, f, (metadata) ->
             editor.metadata = metadata
@@ -343,6 +314,8 @@
     window.lupaEntities = files.reduce (res,f) ->
         res.concat(f.metadata)
     ,[]
+    if dashboard
+        dashboard.setFiles(files, addLabelDecoration) # TODO remove addLabelDecoration from here. This is hack

     atom.notifications.addSuccess("#{files && files.length} files have been indexed.")
     document.getElementById('lupa-index-project-wrapper').style.display = 'none';


add debug. print saml XML response string before/after decryption.

--- lib/saml2.coffee
+++ lib/saml2.coffee
@@ -1,6 +1,7 @@
 _             = require 'underscore'
 async         = _.extend require('async'), require('async-ext')
 crypto        = require 'crypto'
+debug         = require('debug') 'saml2'
 {parseString} = require 'xml2js'
 url           = require 'url'
 util          = require 'util'
@@ -264,6 +265,7 @@
     (cb_wf) ->
       decrypt_assertion saml_response, sp_private_key, cb_wf
     (result, cb_wf) ->
+      debug result
       decrypted_assertion = (new xmldom.DOMParser()).parseFromString(result)
       unless _.some(idp_certificates, (cert) -> check_saml_signature result, cert)
         return cb_wf new Error("SAML Assertion signature check failed! (checked #{idp_certificates.length} certificate(s))")
@@ -316,6 +318,7 @@
             return zlib.inflateRaw raw, cb_wf
           setImmediate cb_wf, null, raw
         (response_buffer, cb_wf) ->
+          debug saml_response
           saml_response = (new xmldom.DOMParser()).parseFromString(response_buffer.toString())
           async.lift(parse_response_header) saml_response, cb_wf
         (response_header, cb_wf) =>


adds newsletter settings

adds newsletter settings

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -107,6 +107,8 @@
       'shortcuts_id',
       'avatar_image',
       'registered',
+      'receive_email',
+      'receive_newsletter',
       'post_address',
       'show_tour',
       'is_pro'


adds registration app to setup

adds registration app to setup

--- lib/setup.coffee
+++ lib/setup.coffee
@@ -114,6 +114,7 @@

   # Mount apps
   app.use require "../apps/root"
+  app.use require "../apps/registration"
   app.use require "../apps/auth"
   app.use require "../apps/about"
   app.use require "../apps/search"


Use a CompositeDisposable in main.coffee.

--- lib/main.coffee
+++ lib/main.coffee
@@ -1,10 +1,12 @@
+{CompositeDisposable} = require 'event-kit'
+
 EnterDialog = require './enter-dialog'
 {Stacktrace} = require './stacktrace'
 {StacktraceView} = require './stacktrace-view'
 {NavigationView} = require './navigation-view'
 {decorate, cleanup} = require './editor-decorator'

-subs = []
+subs = new CompositeDisposable()

 module.exports =

@@ -23,23 +25,22 @@
     atom.workspaceView.command 'stacktrace:follow-call', ->
       NavigationView.current()?.navigateToCalled()

-    subs.push atom.workspace.eachEditor decorate
-    subs.push Stacktrace.on 'active-changed', (e) ->
+    subs.add atom.workspace.observeTextEditors decorate
+    subs.add Stacktrace.on 'active-changed', (e) ->
       cleanup()
       if e.newTrace?
-        decorate(e) for e in atom.workspace.getEditors()
+        decorate(e) for e in atom.workspace.getTextEditors()

     @navigationView = new NavigationView
     atom.workspaceView.appendToBottom @navigationView

     StacktraceView.registerIn(atom.workspace)

-    subs.push atom.on 'stacktrace:accept-trace', ({trace}) =>
+    subs.add atom.on 'stacktrace:accept-trace', ({trace}) =>
       for trace in Stacktrace.parse(trace)
         trace.register()
         atom.workspace.open trace.getUrl()

   deactivate: ->
     @navigationView.remove()
-
-    sub.off() for sub in subs
+    subs.dispose()


Address cursor and editor context deprecations.

--- lib/main.coffee
+++ lib/main.coffee
@@ -30,7 +30,7 @@

         @subscriptions.add atom.workspace.observeActivePaneItem(@updateCurrentEditor.bind(this))

-        @editor = atom.workspace.getActiveEditor()
+        @editor = atom.workspace.getActiveTextEditor()

     deactivate: ->
         @subscriptions.dispose()
@@ -132,7 +132,7 @@
                 delete @markerBubbleMap[marker.id]

     run: ->
-        editor = atom.workspace.getActiveEditor()
+        editor = atom.workspace.getActiveTextEditor()
         language = editor.getGrammar().name.toLowerCase()

         if KernelManager.languageHasKernel(language)
@@ -181,7 +181,7 @@
             selectedRange = @editor.getSelectedBufferRange()
             return [selectedText, selectedRange.end.row]

-        cursor = @editor.getCursor()
+        cursor = @editor.getLastCursor()

         row = cursor.marker.bufferMarker.range.start.row
         console.log "row:", row


wrap registerProvider

wrap registerProvider

--- lib/main.coffee
+++ lib/main.coffee
@@ -37,7 +37,7 @@
     return unless event.detail is 2 # handle double click only

     if not Ui.getSize()
-      if settings.get('search.startByDoubleClick')
+      if settings.get('Search.startByDoubleClick')
         @narrow('search', queryCurrentWord: true, focus: false)
         suppressEvent(event)
     else
@@ -91,7 +91,7 @@
       'narrow:atom-scan': => @narrow('atom-scan')
       'narrow:atom-scan-by-current-word': => @narrow('atom-scan', queryCurrentWord: true)

-      'narrow:toggle-search-start-by-double-click': -> settings.toggle('search.startByDoubleClick')
+      'narrow:toggle-search-start-by-double-click': -> settings.toggle('Search.startByDoubleClick')

   observeStopChangingActivePaneItem: ->
     atom.workspace.onDidStopChangingActivePaneItem (item) =>
@@ -160,7 +160,10 @@
       'vim-mode-plus-user:narrow:atom-scan': => @narrow('atom-scan', query: confirmSearch())
       'vim-mode-plus-user:narrow:search-current-project': =>  @narrow('search', query: confirmSearch(), currentProject: true)

+  registerProvider: (name, klassOrFilePath) ->
+    ProviderBase.registerProvider(name, klassOrFilePath)
+
   provideNarrow: ->
     ProviderBase: ProviderBase
-    registerProvider: (args...) -> ProviderBase.registerProvider(args...)
+    registerProvider: @registerProvider.bind(this)
     narrow: @narrow.bind(this)


Remove lodash/parseint

Remove lodash/parseint

Change-type: patch

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -18,7 +18,6 @@
 { fetch: normalFetch, Headers: HeadersPonyfill } = require('fetch-ponyfill')({ Promise })
 urlLib = require('url')
 qs = require('qs')
-parseInt = require('lodash/parseInt')
 assign = require('lodash/assign')
 includes = require('lodash/includes')
 errors = require('balena-errors')


Bug fix for initialization

Bug fix for initialization

--- lib/utils.coffee
+++ lib/utils.coffee
@@ -129,6 +129,9 @@
   ##############################
   @resetSchemas: (model_types, options, callback) ->
     [options, callback] = [{}, options] if arguments.length is 2
+
+    # ensure all models all initialized (reverse relationships may be initialized in a dependent model)
+    model_type.schema() for model_type in model_types

     failed_schemas = []
     queue = new Queue(1)


plantuml live preview performance tune

plantuml live preview performance tune

--- lib/puml.coffee
+++ lib/puml.coffee
@@ -3,42 +3,34 @@
 md5 = require('md5');

 plantumlJarPath = path.resolve(__dirname, '../dependencies/plantuml/plantuml.jar')
-#
-# addJob = (content, fileDirectoryPath='', callback) ->
-#   @jobs.push({content, fileDirectoryPath, callback})
-#   @executeJob()
-#
-# executeJob = () ->
-#   if @inProgress
-#     return
-#
-#   @inProgress = true
-#   try
-#     job = @jobs.shift()
-#     while job
-#       @generateSVG(job.content, job.fileDirectoryPath, job.callback)
-#       job = @jobs.shift()
-#   finally
-#     @inProgress = false
-

 # Async call
 generateSVG = (content, fileDirectoryPath='', callback)->
-  plantumlAPI.callbacks.push(callback)
-  console.log("init callbacks length ->" + plantumlAPI.callbacks.length)
+
   content = content.trim()
-  # ' @mpe_file_directory_path:/fileDirectoryPath
-  # fileDirectoryPath
-
   fileDirectoryPath = content.match(/^'\s@mpe_file_directory_path:(.+)$/m)?[1] or fileDirectoryPath

   if !content.startsWith('@start')
-    content = """
-@startuml
+    content = """@startuml
 #{content}
-@enduml
+@enduml"""

-    """
+  content = content.replace(/\r/g, "")
+
+  hash = md5(content)
+
+  # the cache not work , still dont know why
+  # if plantumlAPI.callbacks[hash]?
+  #   console.log("content cached!!")
+  #   callback?(plantumlAPI.callbacks[hash].result)
+  #   return
+
+  plantumlAPI.callbacks[hash] = {cb:callback, content: content, result: null}
+
+  #keep call size tiny
+  if Object.keys(plantumlAPI.callbacks).length > 20
+    # console.log("callbacks too large, shifting:" + plantumlAPI.callbacks.length + " hash:" + Object.keys(plantumlAPI.callbacks)[0]);
+    delete plantumlAPI.callbacks[Object.keys(plantumlAPI.callbacks)[0]]

   if @task == null
     @task = spawn 'java', [    '-Djava.awt.headless=true',
@@ -49,37 +41,36 @@
                             '-tsvg',
                             '-charset', 'UTF-8']

+    data = ""
+    @task.stdout.on 'data', (chunk)->
+      data = data + chunk.toString()
+      if data.endsWith("--></g></svg>")
+        #data may contains many diagrams, lets split it
+        diagrams = data.split("--></g></svg>")
+        for key, diag of diagrams
+          if diag.trim() == ""
+            continue
+
+          diag = diag + "--></g></svg>"
+          #same diagram svg data will be trigger times while the diagram initialize
+          #use hash to prevent callbacks error, but it still not work very well
+          data = ""
+          uml = diag.match(/@startuml(.|\n|\r)*@enduml/);
+          if(uml != null)
+            #walk around of plantuml bug sometimes "-->" will become "- ->"
+            hash = md5(uml[0].replace(/\r/g, "").replace(/- -/g, "--"))
+            plantumlAPI.callbacks[hash]?.result = diag
+            plantumlAPI.callbacks[hash]?.cb(diag)
+
+
   @task.stdin.write(content)
-  data = ""
-  @task.stdout.on 'data', (chunk)->
-    data = data + chunk.toString()
-    if data.endsWith("--></g></svg>")
-      #data may contains many diagrams, lets split it
-      diagrams = data.split("--></g></svg>")
-      for key, diag of diagrams
-        if diag.trim() == ""
-          continue
-
-        diag = diag + "--></g></svg>"
-        #same diagram svg data will be trigger times while the diagram initialize
-        #use hash to prevent callbacks error, but it still not work very well
-        hash = md5(diag)
-        if plantumlAPI.oldHash != hash
-          plantumlAPI.oldHash = hash
-          data = ""
-          console.debug("oldhash:" + plantumlAPI.oldHash + " new hash:" + hash)
-          console.debug("chunks:" + diag)
-          console.debug("callbacks.length:" + plantumlAPI.callbacks.length)
-          callback = plantumlAPI.callbacks.shift()
-          callback?(diag)
-
+  @task.stdin.write("\n")

 # generateSVG('A -> B')
 plantumlAPI = {
-  oldHash : ""
   callbacks : []
+  render: generateSVG
   task: null
-  render: generateSVG
 }

 module.exports = plantumlAPI


fixed startup relation with press and status msg

--- lib/seed.coffee
+++ lib/seed.coffee
@@ -35,7 +35,7 @@
 fetchAll = (method, path, startups, cb, result = []) ->
   done = 0

-  for startup in startups
+  startups.forEach (startup) ->
     id = startup.id.split("-")[1]
     query = startup_id: id
     angellist[method] query, (err, results) ->


Making the domain constraining optional

Making the domain constraining optional

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -772,11 +772,11 @@
         [ start, end ] = [ end, start ] if end < start

         diff = end - start
-        if start < @options.domain.start
+        if @options.constrain && start < @options.domain.start
             start = @options.domain.start
             newEnd = new Date(start.getTime() + diff)
             end = if newEnd < @options.domain.end then newEnd else @options.domain.end
-        if end > @options.domain.end
+        if @options.constrain && end > @options.domain.end
             end = @options.domain.end
             newStart = new Date(end.getTime() - diff)
             start = if newStart > @options.domain.start then newStart else @options.domain.start


decrease alternativeBrush size, add opaque overlay over mainaxis

--- src/d3.timeslider.coffee
+++ src/d3.timeslider.coffee
@@ -143,6 +143,14 @@
         @svg.append('g')
             .attr('class', 'mainaxis')
             .call(@axis.x)
+
+        # by default hidden overlay area, can be styled when TimeSlider instance is created to show a background for dataset area, which does not obscure tick texts
+        @svg.select('.mainaxis').append('rect')
+            .attr('class', 'mainaxis-overlay')
+            .style('opacity', 0)
+            .style('fill', '#666')
+            .attr('height', @options.height-22)
+            .attr('width', @options.width)

         # translate the main x axis
         d3.select(@element).select('g.mainaxis .domain')
@@ -240,8 +248,8 @@
         else
         # alternative brush
             @gBrush.selectAll('rect')
-                .attr('height', 6)
-                .attr('y', "#{@options.height + 6}")
+                .attr('height', 4)
+                .attr('y', "#{@options.height + 4}")

             @gBrush.selectAll('.resize').append('circle')
                 .attr('class', 'handle-circle')
@@ -249,7 +257,7 @@
                 .attr('cursor', 'ew-resize')
                 .attr('r', 8)
                 .attr('cx', 0)
-                .attr('cy', @options.height + 9)
+                .attr('cy', @options.height + 6)

         if @brushTooltip
             # setup hover events on brush


Support nested angular module structure

Support nested angular module structure

--- grunt/angular.coffee
+++ grunt/angular.coffee
@@ -16,6 +16,7 @@
 		rename: (destBase, destPath) ->
 			destPath = destPath.replace 'app/coffee/', ''
 			destBase + destPath.replace /\.coffee$/, '.js'
+
 	angularEmptyMock =
 		src: 'app/coffee/empty-mocks.coffee'
 		dest: 'app/js/empty-mocks.js'
@@ -31,11 +32,11 @@
 		(angularModulesByType[matches[1]] ?= []).push mapping.dest

 		angularModules.push mapping.dest
-
+
 	angularModuleConcat = for directory in directories
-		src: "app/js/#{directory}/*.js"
+		src: "app/js/#{directory}/**/*.js"
 		dest: "app/js/#{directory}.js"
-
+
 	config.clean ?= {}
 	config.concat ?= {}
 	config.coffee ?= {}
@@ -75,24 +76,18 @@
 		files: angularCoffees
 		tasks: 'compile-angular'

-	config.wrap.angularControllers =
-		files: ['app/js/controllers.js'].map (file) -> src: file, dest: file
-		wrapper: [
-			"'use strict';\n\n"
-			''
-		]
-
 	parseAngularModuleTypeAndName = (filepath) ->
-
-		matches = filepath.match /^app\/js\/(.*)\/(.*\.js)$/

-		moduleType: matches[1]
-		moduleName: path.basename filepath, path.extname matches[2]
+		parts = path.dirname(filepath).split path.sep
+		parts.push path.basename filepath, '.js'
+
+		moduleType: parts[2]
+		moduleName: parts.slice(3).join '.'

 	config.wrap.angularModules =
 		files: angularModules.map (file) -> src: file, dest: file
 		options:
-			indent: '    '
+			indent: '  '
 			wrapper: (filepath) ->

 				{moduleType, moduleName} = parseAngularModuleTypeAndName filepath
@@ -123,7 +118,6 @@

 				moduleNames = []
 				for module in angularModulesByType[moduleType] ? []
-
 					{moduleName} = parseAngularModuleTypeAndName module
 					moduleNames.push moduleName

@@ -158,7 +152,6 @@
 		'wrap:angularModules'
 		'concat:angularModules'
 		'wrap:angularParentModules'
-		'wrap:angularControllers'
 		'clean:angularBuild'
 	]


Expand change region to surrounding empty lines

So that we can update indent guides accordingly.

--- src/display-layer.coffee
+++ src/display-layer.coffee
@@ -94,8 +94,7 @@
     @emitter.on 'did-change-sync', callback

   bufferDidChange: (change) ->
-    {oldRange, newRange} = change
-
+    {oldRange, newRange} = @expandChangeRegionToSurroundingEmptyLines(change.oldRange, change.newRange)
     {bufferStart, bufferEnd: bufferOldEnd} = @expandBufferRangeToScreenLineStarts(oldRange)
     bufferOldExtent = traversal(bufferOldEnd, bufferStart)
     bufferNewExtent = Point(bufferOldExtent.row + (newRange.end.row - oldRange.end.row), 0)
@@ -128,6 +127,22 @@
       oldExtent: extent,
       newExtent: extent
     }]
+
+  expandChangeRegionToSurroundingEmptyLines: (oldRange, newRange) ->
+    oldRange = oldRange.copy()
+    newRange = newRange.copy()
+
+    while oldRange.start.row > 0
+      break if @buffer.lineForRow(oldRange.start.row - 1).length isnt 0
+      oldRange.start.row--
+      newRange.start.row--
+
+    while newRange.end.row < @buffer.getLastRow()
+      break if @buffer.lineForRow(newRange.end.row + 1).length isnt 0
+      oldRange.end.row++
+      newRange.end.row++
+
+    {oldRange, newRange}

   expandBufferRangeToScreenLineStarts: (range) ->
     # Expand the start of the change to the buffer row that starts


更改atom菜单

更改atom菜单

--- menus/awiki.cson
+++ menus/awiki.cson
@@ -4,11 +4,14 @@
     'submenu': [{
            'label': 'amWiki 文库',
            'submenu': [{
-                'label': '刷新 “_navigation_.md”',
+                'label': '通过 “config.json” 创建新amWiki文库',
+                'command': 'amWiki:create'
+           },{
+                'label': '提取h1-h3标题为页内目录',
+                'command': 'amWiki:contents'
+           },{
+                'label': '手动刷新 “_navigation_.md”',
                 'command': 'amWiki:updateNav'
-           },{
-                'label': '通过 “config.json” 文件创建新amWiki文库',
-                'command': 'amWiki:create'
            }]
     }]
   }


improve error message

improve error message

--- lib/rule.coffee
+++ lib/rule.coffee
@@ -51,7 +51,10 @@
     else
       @infer().then((detectedSetting) =>
         if detectedSetting isnt @setting
-          throw new EditorConfigError(@propertyName, @file.path)
+          throw new EditorConfigError(
+            "found setting '#{detectedSetting}', should be '#{@setting}'"
+            @file.path
+          )
       )

   ###*


Added assertion for missing JSON schemas (credit: @martnst)

--- lib/test.coffee
+++ lib/test.coffee
@@ -107,6 +107,11 @@

       json = validateJson()
       result = tv4.validateResult json, schema
+      assert.lengthOf result.missing, 0, """
+        Missing/unresolved JSON schema $refs (#{result.missing?.join(', ')}) in schema:
+        #{JSON.stringify(schema, null, 4)}
+        Error
+      """
       assert.ok result.valid, """
         Got unexpected response body: #{result.error?.message}
         #{JSON.stringify(json, null, 4)}